diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index a24c524047..af67bdbe5d 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,6 @@ FROM ghcr.io/commaai/openpilot-base:latest -RUN apt update && apt install -y vim net-tools usbutils htop ripgrep tmux wget mesa-utils xvfb libxtst6 libxv1 libglu1-mesa libegl1-mesa +RUN apt update && apt install -y vim net-tools usbutils htop ripgrep tmux wget mesa-utils xvfb libxtst6 libxv1 libglu1-mesa libegl1-mesa gdb bash-completion RUN pip install ipython jupyter jupyterlab RUN cd /tmp && \ @@ -8,6 +8,8 @@ RUN cd /tmp && \ curl -L -o virtualgl.deb "https://downloads.sourceforge.net/project/virtualgl/3.1/virtualgl_3.1_$ARCH.deb" && \ dpkg -i virtualgl.deb +RUN usermod -aG video batman + USER batman RUN cd $HOME && \ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a7a63658ed..3f5b38d12e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,11 +14,12 @@ "force_color_prompt": "1" }, "runArgs": [ + "--volume=/dev:/dev", "--volume=/tmp/.X11-unix:/tmp/.X11-unix", "--volume=${localWorkspaceFolder}/.devcontainer/.host/.Xauthority:/home/batman/.Xauthority", "--volume=${localEnv:HOME}/.comma:/home/batman/.comma", + "--volume=${localEnv:HOME}/.azure:/home/batman/.azure", "--volume=/tmp/comma_download_cache:/tmp/comma_download_cache", - "--volume=/tmp/devcontainer_scons_cache:/tmp/scons_cache", "--shm-size=1G", "--add-host=host.docker.internal:host-gateway", // required to use host.docker.internal on linux "--publish=0.0.0.0:8070-8079:8070-8079" // body ZMQ services @@ -29,7 +30,9 @@ "installOhMyZsh": false, "upgradePackages": false, "username": "batman" - } + }, + "ghcr.io/devcontainers-contrib/features/gh-cli:1": {}, + "ghcr.io/devcontainers/features/azure-cli:1": {} }, "containerUser": "batman", "remoteUser": "batman", @@ -43,5 +46,8 @@ "lharri73.dbc" ] } - } -} \ No newline at end of file + }, + "mounts": [ + "type=volume,source=scons_cache,target=/tmp/scons_cache" + ] +} diff --git a/.github/ISSUE_TEMPLATE/pc_bug_report.yml b/.github/ISSUE_TEMPLATE/pc_bug_report.yml index db3eb22e5c..761c8b1a0a 100644 --- a/.github/ISSUE_TEMPLATE/pc_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/pc_bug_report.yml @@ -24,7 +24,7 @@ body: id: os-version attributes: label: OS Version - placeholder: Ubuntu 20.04 + placeholder: Ubuntu 24.04 validations: required: true diff --git a/.github/PULL_REQUEST_TEMPLATE/car_port.md b/.github/PULL_REQUEST_TEMPLATE/car_port.md index 690c24c9b0..c7aa2b96c2 100644 --- a/.github/PULL_REQUEST_TEMPLATE/car_port.md +++ b/.github/PULL_REQUEST_TEMPLATE/car_port.md @@ -8,7 +8,7 @@ assignees: '' **Checklist** -- [ ] added entry to CarInfo in selfdrive/car/*/values.py and ran `selfdrive/car/docs.py` to generate new docs +- [ ] added entry to CAR in selfdrive/car/*/values.py and ran `selfdrive/car/docs.py` to generate new docs - [ ] test route added to [routes.py](https://github.com/commaai/openpilot/blob/master/selfdrive/car/tests/routes.py) - [ ] route with openpilot: - [ ] route with stock system: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3e3f42dcbc..2b4a5ed48f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -44,7 +44,7 @@ Explain how you tested this bug fix. **Checklist** -- [ ] added entry to CarInfo in selfdrive/car/*/values.py and ran `selfdrive/car/docs.py` to generate new docs +- [ ] added entry to CAR in selfdrive/car/*/values.py and ran `selfdrive/car/docs.py` to generate new docs - [ ] test route added to [routes.py](https://github.com/commaai/openpilot/blob/master/selfdrive/car/tests/routes.py) - [ ] route with openpilot: - [ ] route with stock system: diff --git a/.github/workflows/auto-cache/action.yaml b/.github/workflows/auto-cache/action.yaml index 173803f7f0..e99c46e3c3 100644 --- a/.github/workflows/auto-cache/action.yaml +++ b/.github/workflows/auto-cache/action.yaml @@ -19,14 +19,14 @@ runs: using: "composite" steps: - name: setup namespace cache - if: ${{ contains(runner.name, 'nsc') }} + if: ${{ contains(runner.name, 'nsc') }} uses: namespacelabs/nscloud-cache-action@v1 with: path: ${{ inputs.path }} - name: setup github cache if: ${{ !contains(runner.name, 'nsc') && inputs.save != 'false' }} - uses: 'actions/cache@v3' + uses: 'actions/cache@v4' with: path: ${{ inputs.path }} key: ${{ inputs.key }} @@ -34,7 +34,7 @@ runs: - name: setup github cache if: ${{ !contains(runner.name, 'nsc') && inputs.save == 'false' }} - uses: 'actions/cache/restore@v3' + uses: 'actions/cache/restore@v4' with: path: ${{ inputs.path }} key: ${{ inputs.key }} @@ -46,4 +46,4 @@ runs: run: | mkdir -p ${{ inputs.path }} sudo chmod -R 777 ${{ inputs.path }} - sudo chown -R $USER ${{ inputs.path }} \ No newline at end of file + sudo chown -R $USER ${{ inputs.path }} diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index abb6c38d6b..447e559c90 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -5,7 +5,7 @@ on: jobs: labeler: - name: apply labels + name: review permissions: contents: read pull-requests: write @@ -14,17 +14,17 @@ jobs: - uses: actions/checkout@v4 with: submodules: false + + # Label PRs - uses: actions/labeler@v5.0.0 with: dot: true configuration-path: .github/labeler.yaml - pr_branch_check: - name: check branch - runs-on: ubuntu-latest - if: github.repository == 'commaai/openpilot' - steps: - - uses: Vankka/pr-target-branch-action@69ab6dd5c221de3548b3b6c4d102c1f4913d3baa + # Check PR target branch + - name: check branch + uses: Vankka/pr-target-branch-action@def32ec9d93514138d6ac0132ee62e120a72aed5 + if: github.repository == 'commaai/openpilot' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -34,174 +34,20 @@ jobs: already-exists-action: close_this already-exists-comment: "Your PR should be made against the `master` branch" - check-pr-template: - runs-on: ubuntu-latest - permissions: - contents: read - issues: write - pull-requests: write - actions: read - if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot' - steps: - - uses: actions/github-script@v7 - with: - script: | - // Comment to add to the PR if no template has been used - const NO_TEMPLATE_MESSAGE = - "It looks like you didn't use one of the Pull Request templates. Please check [the contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md). \ - Also make sure that you didn't modify any of the checkboxes or headings within the template."; - // body data for future requests - const body_data = { - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - }; - - // Utility function to extract all headings - const extractHeadings = (markdown) => { - const headingRegex = /^(#{1,6})\s+(.+)$/gm; - const boldTextRegex = /^(?:\*\*|__)(.+?)(?:\*\*|__)\s*$/gm; - const headings = []; - let headingMatch; - while ((headingMatch = headingRegex.exec(markdown))) { - headings.push(headingMatch[2].trim()); - } - let boldMatch; - while ((boldMatch = boldTextRegex.exec(markdown))) { - headings.push(boldMatch[1].trim()); - } - return headings; - }; - - // Utility function to extract all check box descriptions - const extractCheckBoxTexts = (markdown) => { - const checkboxRegex = /^\s*-\s*\[( |x)\]\s+(.+)$/gm; - const checkboxes = []; - let match; - while ((match = checkboxRegex.exec(markdown))) { - checkboxes.push(match[2].trim()); - } - return checkboxes; - }; - - // Utility function to check if a list is a subset of another list - isSubset = (subset, superset) => { - 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, - repo: context.repo.repo, - path: ".github/PULL_REQUEST_TEMPLATE", - }); - var template_filenames = []; - for (const content of template_contents.data) { - template_filenames.push(content.path); - } - console.debug("Received template filenames: " + template_filenames); - // Retrieve templates - var templates = []; - for (const template_filename of template_filenames) { - const template_response = await github.rest.repos.getContent({ - owner: context.repo.owner, - repo: context.repo.repo, - path: template_filename, - }); - // Convert Base64 content back - const decoded_template = atob(template_response.data.content); - const headings = extractHeadings(decoded_template); - const checkboxes = extractCheckBoxTexts(decoded_template); - if (!headings.length && !checkboxes.length) { - console.warn( - "Invalid template! Contains neither headings nor checkboxes, ignoring it: \n" + - decoded_template - ); - } else { - templates.push({ headings: headings, checkboxes: checkboxes }); - } - } - // Retrieve the PR Body - const pull_request = await github.rest.issues.get({ - ...body_data, - }); - const pull_request_text = pull_request.data.body; - console.debug("Received Pull Request body: \n" + pull_request_text); - - /* Check if the PR Body matches one of the templates - A template is defined by all headings and checkboxes it contains - We extract all Headings and Checkboxes from the PR text and check if any of the templates is a subset of that - */ - const pr_headings = extractHeadings(pull_request_text); - const pr_checkboxes = extractCheckBoxTexts(pull_request_text); - console.debug("Found Headings in PR body:\n" + pr_headings); - console.debug("Found Checkboxes in PR body:\n" + pr_checkboxes); - var template_found = false; - // Iterate over each template to check if it applies - for (const template of templates) { - console.log( - "Checking for headings: [" + - template.headings + - "] and checkboxes: [" + - template.checkboxes + "]" - ); - if ( - isCheckboxSubset(template.checkboxes, pr_checkboxes) && - isSubset(template.headings, pr_headings) - ) { - console.debug("Found matching template!"); - template_found = true; - } - } - - // List comments from previous runs - var existing_comments = []; - const comments = await github.rest.issues.listComments({ - ...body_data, - }); - for (const comment of comments.data) { - if (comment.body === NO_TEMPLATE_MESSAGE) { - existing_comments.push(comment); - } - } - - // Add a comment to the PR that it is not using a the template (but only if this comment does not exist already) - if (!template_found) { - var comment_already_sent = false; - - // Add an 'in-bot-review' label since this PR doesn't have the template - github.rest.issues.addLabels({ - ...body_data, - labels: ["in-bot-review"], - }); - - if (existing_comments.length < 1) { - github.rest.issues.createComment({ - ...body_data, - body: NO_TEMPLATE_MESSAGE, - }); - } - } else { - // If template has been found, delete any old comment about missing template - for (const existing_comment of existing_comments) { - github.rest.issues.deleteComment({ - ...body_data, - comment_id: existing_comment.id, - }); - } - // Remove the 'in-bot-review' label after the review is done and the PR has passed - github.rest.issues.removeLabel({ - ...body_data, - name: "in-bot-review", - }).catch((error) => { - console.log("Label 'in-bot-review' not found, ignoring"); - }); - } - + # Welcome comment + - name: comment + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 + if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot' + with: + message: | + + Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following: + * Convert your PR to a draft unless it's ready to review + * Read the [contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md) + * Before marking as "ready for review", ensure: + * the goal is clearly stated in the description + * all the tests are passing + * the change is [something we merge](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md#what-gets-merged) + * include a route or your device' dongle ID if relevant + comment_tag: run_id + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml index 2f1a7d67c4..cf8bdc7109 100644 --- a/.github/workflows/badges.yaml +++ b/.github/workflows/badges.yaml @@ -12,7 +12,7 @@ env: jobs: badges: name: create badges - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest if: github.repository == 'commaai/openpilot' permissions: contents: write diff --git a/.github/workflows/compile-openpilot/action.yaml b/.github/workflows/compile-openpilot/action.yaml index 2945b67d2e..4015746c0e 100644 --- a/.github/workflows/compile-openpilot/action.yaml +++ b/.github/workflows/compile-openpilot/action.yaml @@ -14,7 +14,7 @@ runs: ${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \ scons -j$(nproc) --cache-populate" - name: Save scons cache - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 if: github.ref == 'refs/heads/master' with: path: .ci_cache/scons_cache diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index c555eafc73..a32989f7d0 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -20,7 +20,7 @@ env: jobs: docs: name: build docs - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest timeout-minutes: 45 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index 990be73f72..d8963ec89f 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -11,7 +11,7 @@ env: jobs: build_prebuilt: name: build prebuilt - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest if: github.repository == 'commaai/openpilot' env: PUSH_IMAGE: true @@ -22,7 +22,7 @@ jobs: steps: - name: Wait for green check mark if: ${{ github.event_name != 'workflow_dispatch' }} - uses: lewagon/wait-on-check-action@595dabb3acf442d47e29c9ec9ba44db0c6bdd18f + uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc with: ref: master wait-interval: 30 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3e3fe46e7a..34e333bb59 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,7 +12,7 @@ jobs: ImageOS: ubuntu20 container: image: ghcr.io/commaai/openpilot-base:latest - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest if: github.repository == 'commaai/openpilot' permissions: checks: read @@ -24,7 +24,7 @@ jobs: sudo apt-get install -y libyaml-dev - name: Wait for green check mark if: ${{ github.event_name != 'workflow_dispatch' }} - uses: lewagon/wait-on-check-action@595dabb3acf442d47e29c9ec9ba44db0c6bdd18f + uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc with: ref: master wait-interval: 30 diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 445a1cf11c..0e6606112b 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -8,7 +8,7 @@ on: jobs: bump_submodules: name: bump_submodules - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest container: image: ghcr.io/commaai/openpilot-base:latest if: github.repository == 'commaai/openpilot' @@ -22,8 +22,9 @@ jobs: git -c submodule."tinygrad".update=none submodule update --remote git add . - name: Create Pull Request - uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 + uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83 with: + author: Vehicle Researcher token: ${{ secrets.ACTIONS_CREATE_PR_PAT }} commit-message: bump submodules title: '[bot] Bump submodules' @@ -34,7 +35,7 @@ jobs: labels: bot package_updates: name: package_updates - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest container: image: ghcr.io/commaai/openpilot-base:latest if: github.repository == 'commaai/openpilot' @@ -49,8 +50,9 @@ jobs: git config --global --add safe.directory '*' pre-commit autoupdate - name: Create Pull Request - uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 + uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83 with: + author: Vehicle Researcher token: ${{ secrets.ACTIONS_CREATE_PR_PAT }} commit-message: Update Python packages and pre-commit hooks title: '[bot] Update Python packages and pre-commit hooks' diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index d1dff147f2..35bab255cb 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -26,7 +26,9 @@ env: jobs: build_release: name: build release - runs-on: ubuntu-20.04 + runs-on: ${{ ((github.repository == 'commaai/openpilot') && + ((github.event_name != 'pull_request') || + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-latest' }} env: STRIPPED_DIR: /tmp/releasepilot steps: @@ -47,13 +49,13 @@ jobs: timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache run: | cd $STRIPPED_DIR - ${{ env.RUN }} "python selfdrive/manager/build.py" + ${{ env.RUN }} "python system/manager/build.py" - name: Run tests timeout-minutes: 3 run: | cd $STRIPPED_DIR ${{ env.RUN }} "release/check-dirty.sh && \ - MAX_EXAMPLES=5 $PYTEST selfdrive/car" + MAX_EXAMPLES=5 $PYTEST -m 'not slow' selfdrive/car" - name: pre-commit timeout-minutes: 3 run: | @@ -62,38 +64,19 @@ jobs: cp pyproject.toml $STRIPPED_DIR cp poetry.lock $STRIPPED_DIR cd $STRIPPED_DIR - ${{ env.RUN }} "unset PYTHONWARNINGS && SKIP=check-added-large-files pre-commit run --all && chmod -R 777 /tmp/pre-commit" + ${{ env.RUN }} "unset PYTHONWARNINGS && SKIP=check-added-large-files,check-hooks-apply,check-useless-excludes pre-commit run --all && chmod -R 777 /tmp/pre-commit" build: strategy: matrix: - arch: ${{ fromJson( - ((github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ (matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8' || 'ubuntu-20.04' }} + arch: ${{ fromJson('["x86_64"]') }} # TODO: Re-add build test for aarch64 once we switched to ubuntu-2404 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - - uses: ./.github/workflows/setup-with-retry - with: - docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} - - uses: ./.github/workflows/compile-openpilot - timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache - - docker_push: - name: docker push - strategy: - matrix: - arch: ${{ fromJson( (github.repository == 'commaai/openpilot') && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ (matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8' || 'ubuntu-20.04' }} - if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Setup to push to repo + - name: Setup docker push + if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' run: | echo "PUSH_IMAGE=true" >> "$GITHUB_ENV" echo "TARGET_ARCHITECTURE=${{ matrix.arch }}" >> "$GITHUB_ENV" @@ -101,62 +84,31 @@ jobs: - uses: ./.github/workflows/setup-with-retry with: docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} - - docker_push_multiarch: - name: docker push multiarch tag - runs-on: ubuntu-20.04 - if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' - needs: [docker_push] - steps: - - uses: actions/checkout@v4 - with: - submodules: false - - name: Setup docker - run: | - $DOCKER_LOGIN - - name: Merge x64 and arm64 tags - run: | - export PUSH_IMAGE=true - scripts/retry.sh selfdrive/test/docker_tag_multiarch.sh base x86_64 aarch64 + - uses: ./.github/workflows/compile-openpilot + timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache static_analysis: name: static analysis runs-on: ${{ ((github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} + ((github.event_name != 'pull_request') || + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/workflows/setup-pre-commit - uses: ./.github/workflows/setup-with-retry + - name: Build openpilot + run: ${{ env.RUN }} "scons -j$(nproc)" - name: pre-commit timeout-minutes: 4 run: ${{ env.RUN }} "unset PYTHONWARNINGS && pre-commit run --all && chmod -R 777 /tmp/pre-commit" - valgrind: - name: valgrind - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - uses: ./.github/workflows/setup-with-retry - - name: Build openpilot - run: ${{ env.RUN }} "scons -j$(nproc)" - - name: Run valgrind - timeout-minutes: 1 - run: | - ${{ env.RUN }} "python selfdrive/test/test_valgrind_replay.py" - - name: Print logs - if: always() - run: cat selfdrive/test/valgrind_logs.txt - unit_tests: name: unit tests runs-on: ${{ ((github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} + ((github.event_name != 'pull_request') || + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 with: @@ -175,9 +127,9 @@ jobs: $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" + pytest ./selfdrive/ui/tests/test_translations.py" - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: name: ${{ github.job }} env: @@ -186,8 +138,8 @@ jobs: process_replay: name: process replay runs-on: ${{ ((github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} + ((github.event_name != 'pull_request') || + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 with: @@ -197,10 +149,10 @@ jobs: docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} - name: Cache test routes id: dependency-cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .ci_cache/comma_download_cache - key: proc-replay-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/ref_commit') }} + key: proc-replay-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/ref_commit', 'selfdrive/test/process_replay/test_regen.py') }} - name: Build openpilot run: | ${{ env.RUN }} "scons -j$(nproc)" @@ -215,7 +167,7 @@ jobs: id: print-diff if: always() run: cat selfdrive/test/process_replay/diff.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() continue-on-error: true with: @@ -225,66 +177,20 @@ jobs: if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} run: | ${{ env.RUN }} "unset PYTHONWARNINGS && AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" - - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v3 - with: - name: ${{ github.job }} - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - regen: - name: regen - runs-on: 'ubuntu-20.04' - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - uses: ./.github/workflows/setup-with-retry - - name: Cache test routes - id: dependency-cache - uses: actions/cache@v3 - with: - path: .ci_cache/comma_download_cache - 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 openpilot - run: | - ${{ env.RUN }} "scons -j$(nproc)" - - name: Run regen - timeout-minutes: 30 - run: | - ${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \ - chmod -R 777 /tmp/comma_download_cache" - - test_modeld: - name: model tests - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - uses: ./.github/workflows/setup-with-retry - - name: Build base Docker image - run: eval "$BUILD" - - name: Build openpilot - run: | - ${{ env.RUN }} "scons -j$(nproc)" # PYTHONWARNINGS triggers a SyntaxError in onnxruntime - name: Run model replay with ONNX timeout-minutes: 4 run: | ${{ 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 + ONNXCPU=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ + coverage combine && coverage xml" + - name: Run regen timeout-minutes: 4 run: | - ${{ env.RUN }} "unset PYTHONWARNINGS && \ - $PYTEST selfdrive/modeld" + ${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \ + chmod -R 777 /tmp/comma_download_cache" - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: name: ${{ github.job }} env: @@ -293,12 +199,12 @@ jobs: test_cars: name: cars runs-on: ${{ ((github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} + ((github.event_name != 'pull_request') || + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-latest' }} strategy: fail-fast: false matrix: - job: [0, 1, 2, 3, 4] + job: [0, 1] steps: - uses: actions/checkout@v4 with: @@ -313,15 +219,15 @@ jobs: - name: Build openpilot run: ${{ env.RUN }} "scons -j$(nproc)" - name: Test car models - timeout-minutes: 10 + timeout-minutes: 20 run: | ${{ env.RUN }} "$PYTEST selfdrive/car/tests/test_models.py && \ chmod -R 777 /tmp/comma_download_cache" env: - NUM_JOBS: 5 + NUM_JOBS: 2 JOB_ID: ${{ matrix.job }} - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: name: ${{ github.job }}-${{ matrix.job }} env: @@ -329,7 +235,7 @@ jobs: car_docs_diff: name: PR comments - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 @@ -340,7 +246,7 @@ jobs: - uses: ./.github/workflows/setup-with-retry - name: Get base car info run: | - ${{ env.RUN }} "scons -j$(nproc) && python selfdrive/debug/dump_car_info.py --path /tmp/openpilot_cache/base_car_info" + ${{ env.RUN }} "scons -j$(nproc) && python selfdrive/debug/dump_car_docs.py --path /tmp/openpilot_cache/base_car_docs" sudo chown -R $USER:$USER ${{ github.workspace }} - uses: actions/checkout@v4 with: @@ -352,19 +258,19 @@ jobs: run: | cd current ${{ env.RUN }} "scons -j$(nproc)" - output=$(${{ env.RUN }} "python selfdrive/debug/print_docs_diff.py --path /tmp/openpilot_cache/base_car_info") + output=$(${{ env.RUN }} "python selfdrive/debug/print_docs_diff.py --path /tmp/openpilot_cache/base_car_docs") output="${output//$'\n'/'%0A'}" echo "::set-output name=diff::$output" - name: Find comment if: ${{ env.AZURE_TOKEN != '' }} - uses: peter-evans/find-comment@1769778a0c5bd330272d749d12c036d65e70d39d + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e id: fc with: issue-number: ${{ github.event.pull_request.number }} body-includes: This PR makes changes to - name: Update comment if: ${{ steps.save_diff.outputs.diff != '' && env.AZURE_TOKEN != '' }} - uses: peter-evans/create-or-update-comment@b95e16d2859ad843a14218d1028da5b2c4cbc4b4 + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} @@ -372,7 +278,7 @@ jobs: edit-mode: replace - name: Delete comment if: ${{ steps.fc.outputs.comment-id != '' && steps.save_diff.outputs.diff == '' && env.AZURE_TOKEN != '' }} - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | github.rest.issues.deleteComment({ @@ -383,7 +289,7 @@ jobs: create_ui_report: name: Create UI Report - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: @@ -395,10 +301,10 @@ jobs: run: > ${{ env.RUN }} "PYTHONWARNINGS=ignore && source selfdrive/test/setup_xvfb.sh && - export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && + export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && python selfdrive/ui/tests/test_ui/run.py" - name: Upload Test Report - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: report - path: selfdrive/ui/tests/test_ui/report \ No newline at end of file + path: selfdrive/ui/tests/test_ui/report diff --git a/.github/workflows/setup-pre-commit/action.yaml b/.github/workflows/setup-pre-commit/action.yaml index 1b3e16e73f..f07a106861 100644 --- a/.github/workflows/setup-pre-commit/action.yaml +++ b/.github/workflows/setup-pre-commit/action.yaml @@ -4,7 +4,7 @@ runs: using: "composite" steps: - uses: ./.github/workflows/auto-cache - with: + with: path: .ci_cache/pre-commit key: pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} restore-keys: | diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 2d60d2f0e9..3c86b8e854 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -5,22 +5,23 @@ on: workflow_dispatch: env: - DAYS_BEFORE_PR_CLOSE: 7 - DAYS_BEFORE_PR_STALE: 30 + DAYS_BEFORE_PR_CLOSE: 3 + DAYS_BEFORE_PR_STALE: 14 jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v8 + - uses: actions/stale@v9 with: exempt-milestones: true # pull request config stale-pr-message: 'This PR has had no activity for ${{ env.DAYS_BEFORE_PR_STALE }} days. It will be automatically closed in ${{ env.DAYS_BEFORE_PR_CLOSE }} days if there is no activity.' close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.' + stale-pr-label: stale delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'commaai/openpilot' }} # only delete branches on the main repo - exempt-pr-labels: "ignore stale,needs testing,car port" # if wip or it needs testing from the community, don't mark as stale + exempt-pr-labels: "ignore stale,needs testing,car port,car" # if wip or it needs testing from the community, don't mark as stale days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE }} days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }} diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index d15ab8cd60..c2f7d86e69 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -20,41 +20,43 @@ env: jobs: - plotjuggler: - name: plotjuggler - runs-on: ubuntu-20.04 - timeout-minutes: 45 + simulator_driving: + name: simulator driving + runs-on: ubuntu-latest + timeout-minutes: 20 steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/workflows/setup-with-retry - name: Build openpilot - timeout-minutes: 5 - run: ${{ env.RUN }} "scons -j$(nproc) cereal/ common/ --minimal" - - name: Test PlotJuggler - timeout-minutes: 2 run: | - ${{ env.RUN }} "pytest tools/plotjuggler/" + ${{ env.RUN }} "scons -j$(nproc)" + - name: Run bridge test + run: | + ${{ env.RUN }} "export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \ + source selfdrive/test/setup_xvfb.sh && \ + source selfdrive/test/setup_vsound.sh && \ + CI=1 pytest tools/sim/tests/test_metadrive_bridge.py" - simulator: - name: simulator - runs-on: ubuntu-20.04 - if: github.repository == 'commaai/openpilot' - timeout-minutes: 45 + test_python311: + name: test python3.11 support + runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 + - name: Installing ubuntu dependencies + run: INSTALL_EXTRA_PACKAGES=no tools/install_ubuntu_dependencies.sh + - name: Installing python + uses: actions/setup-python@v5 with: - submodules: true - - uses: ./.github/workflows/setup-with-retry - - name: Setup to push to repo - if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' - run: | - echo "PUSH_IMAGE=true" >> "$GITHUB_ENV" - $DOCKER_LOGIN - - name: Build and push sim image - run: | - selfdrive/test/docker_build.sh sim + python-version: '3.11.4' + - name: Installing pip + run: pip install pip==24.0 + - name: Installing poetry + run: pip install poetry==1.7.0 + - name: Installing python dependencies + run: poetry install --no-cache --no-root devcontainer: name: devcontainer @@ -70,7 +72,7 @@ jobs: - name: Setup Dev Container CLI run: npm install -g @devcontainers/cli - name: Build dev container image - run: devcontainer build --workspace-folder . + run: ./scripts/retry.sh devcontainer build --workspace-folder . - name: Run dev container run: | mkdir -p /tmp/devcontainer_scons_cache/ @@ -82,21 +84,3 @@ jobs: devcontainer exec --workspace-folder . pip install pip-install-test devcontainer exec --workspace-folder . touch /home/batman/.comma/auth.json devcontainer exec --workspace-folder . sudo touch /root/test.txt - - notebooks: - name: notebooks - runs-on: ubuntu-20.04 - if: github.repository == 'commaai/openpilot' - timeout-minutes: 45 - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - uses: ./.github/workflows/setup-with-retry - - name: Build openpilot - 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: 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 3da75aaea4..f067893c77 100644 --- a/.gitignore +++ b/.gitignore @@ -40,8 +40,9 @@ compile_commands.json compare_runtime*.html persist -board/obj/ -selfdrive/boardd/boardd +selfdrive/pandad/pandad +cereal/services.h +cereal/gen selfdrive/logcatd/logcatd selfdrive/mapd/default_speeds_by_region.json system/proclogd/proclogd @@ -52,16 +53,11 @@ selfdrive/car/tests/cars_dump system/camerad/camerad system/camerad/test/ae_gray_test selfdrive/modeld/_modeld -selfdrive/modeld/_navmodeld selfdrive/modeld/_dmonitoringmodeld /src/ -one notebooks -xx -yy hyperthneed -panda_jungle provisioning .coverage* @@ -87,3 +83,22 @@ build/ poetry.toml Pipfile + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 73f832b1d6..e33902db52 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,9 @@ [submodule "opendbc"] path = opendbc url = ../../commaai/opendbc.git -[submodule "cereal"] - path = cereal - url = ../../commaai/cereal.git +[submodule "msgq"] + path = msgq_repo + url = ../../commaai/msgq.git [submodule "rednose_repo"] path = rednose_repo url = ../../commaai/rednose.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6db68e55bc..61b7823644 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: check-hooks-apply - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-ast exclude: '^(third_party)/' @@ -24,19 +24,19 @@ repos: - --maxkb=120 - --enforce-all - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell - exclude: '^(third_party/)|(body/)|(cereal/)|(panda/)|(opendbc/)|(rednose/)|(rednose_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(selfdrive/ui/translations/.*.ts)|(poetry.lock)' + exclude: '^(third_party/)|(body/)|(msgq/)|(panda/)|(opendbc/)|(rednose/)|(rednose_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(selfdrive/ui/translations/.*.ts)|(poetry.lock)' args: # if you've got a short variable name that's getting flagged, add it here - - -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints + - -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn - --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 + rev: v0.4.8 hooks: - id: ruff - exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)' + exclude: '^(third_party/)|(msgq/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)' - repo: local hooks: - id: mypy @@ -47,7 +47,7 @@ repos: args: - --local-partial-types - --explicit-package-bases - exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(xx/)' + exclude: '^(third_party/)|(body/)|(msgq/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)' - repo: local hooks: - id: cppcheck @@ -55,7 +55,7 @@ repos: entry: cppcheck language: system types: [c++] - exclude: '^(third_party/)|(cereal/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(tools/)|(selfdrive/modeld/thneed/debug/)|(selfdrive/modeld/test/)|(selfdrive/camerad/test/)|(installer/)' + exclude: '^(third_party/)|(msgq/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(tools/)|(selfdrive/modeld/thneed/debug/)|(selfdrive/modeld/test/)|(selfdrive/camerad/test/)|(installer/)' args: - --error-exitcode=1 - --language=c++ @@ -66,7 +66,7 @@ repos: rev: 1.6.1 hooks: - id: cpplint - exclude: '^(third_party/)|(cereal/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(generated/)' + exclude: '^(third_party/)|(msgq/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(generated/)' args: - --quiet - --counting=total @@ -75,10 +75,10 @@ repos: # 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 + rev: v0.16.2 hooks: - id: cython-lint - exclude: '^(third_party/)|(cereal/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(generated/)' + exclude: '^(third_party/)|(msgq/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(generated/)' args: - --max-line-length=240 - --ignore=E111, E302, E305 @@ -86,10 +86,10 @@ repos: hooks: - id: test_translations name: test translations - entry: selfdrive/ui/tests/test_translations.py - language: script + entry: pytest selfdrive/ui/tests/test_translations.py + language: system pass_filenames: false - files: 'selfdrive/ui/translations/*' + files: '^selfdrive/ui/translations/' - repo: https://github.com/python-poetry/poetry rev: '1.8.0' hooks: @@ -98,6 +98,6 @@ repos: args: - --lock - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.0 + rev: 0.28.4 hooks: - id: check-github-workflows diff --git a/.python-version b/.python-version deleted file mode 100644 index 0c7d5f5f5d..0000000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.11.4 diff --git a/.vscode/settings.json b/.vscode/settings.json index daf74ca777..f0731c362d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,16 @@ "**/.git": true, "**/.venv": true, "**/__pycache__": true - } -} \ No newline at end of file + }, + "python.analysis.exclude": [ + "**/.git", + "**/.venv", + "**/__pycache__", + // exclude directories that should be using the symlinked version + "common/**", + "selfdrive/**", + "system/**", + "third_party/**", + "tools/**", + ] +} diff --git a/Dockerfile.openpilot b/Dockerfile.openpilot index 1e15722bb9..5d8f958c48 100644 --- a/Dockerfile.openpilot +++ b/Dockerfile.openpilot @@ -20,6 +20,8 @@ COPY ./release ${OPENPILOT_PATH}/release COPY ./common ${OPENPILOT_PATH}/common COPY ./opendbc ${OPENPILOT_PATH}/opendbc COPY ./cereal ${OPENPILOT_PATH}/cereal +COPY ./msgq_repo ${OPENPILOT_PATH}/msgq_repo +COPY ./msgq ${OPENPILOT_PATH}/msgq COPY ./panda ${OPENPILOT_PATH}/panda COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive COPY ./system ${OPENPILOT_PATH}/system diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index a6c4c71790..45b29bdcb1 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -13,14 +13,10 @@ ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 COPY tools/install_ubuntu_dependencies.sh /tmp/tools/ -RUN cd /tmp && \ - tools/install_ubuntu_dependencies.sh && \ - rm -rf /var/lib/apt/lists/* && \ - rm -rf /tmp/* && \ - # remove unused architectures from gcc for panda +RUN INSTALL_EXTRA_PACKAGES=no /tmp/tools/install_ubuntu_dependencies.sh && \ + rm -rf /var/lib/apt/lists/* /tmp/* && \ cd /usr/lib/gcc/arm-none-eabi/9.2.1 && \ - rm -rf arm/ && \ - rm -rf thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp + rm -rf arm/ 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 \ @@ -68,11 +64,11 @@ RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers USER $USER ENV POETRY_VIRTUALENVS_CREATE=false -ENV PYENV_VERSION=3.11.4 +ENV PYENV_VERSION=3.12.4 ENV PYENV_ROOT="/home/$USER/pyenv" ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH" -COPY --chown=$USER pyproject.toml poetry.lock .python-version /tmp/ +COPY --chown=$USER pyproject.toml poetry.lock /tmp/ COPY --chown=$USER tools/install_python_dependencies.sh /tmp/tools/ RUN cd /tmp && \ @@ -80,7 +76,7 @@ RUN cd /tmp && \ rm -rf /tmp/* && \ rm -rf /home/$USER/.cache && \ find /home/$USER/pyenv -type d -name ".git" | xargs rm -rf && \ - rm -rf /home/$USER/pyenv/versions/3.11.4/lib/python3.11/test + rm -rf /home/$USER/pyenv/versions/3.12.4/lib/python3.12/test USER root -RUN sudo git config --global --add safe.directory /tmp/openpilot \ No newline at end of file +RUN sudo git config --global --add safe.directory /tmp/openpilot diff --git a/Jenkinsfile b/Jenkinsfile index d716510bfe..321237716b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -40,6 +40,7 @@ if [ -f /TICI ]; then rm -rf /tmp/tmp* rm -rf ~/.commacache rm -rf /dev/shm/* + rm -rf /dev/tmp/tmp* if ! systemctl is-active --quiet systemd-resolved; then echo "restarting resolved" @@ -182,7 +183,7 @@ node { deviceStage("onroad", "tici-needs-can", [], [ // TODO: ideally, this test runs in master-ci, but it takes 5+m to build it //["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"], - ["build openpilot", "cd selfdrive/manager && ./build.py"], + ["build openpilot", "cd system/manager && ./build.py"], ["check dirty", "release/check-dirty.sh"], ["onroad tests", "pytest selfdrive/test/test_onroad.py -s"], ["time to onroad", "pytest selfdrive/test/test_time_to_onroad.py"], @@ -190,53 +191,54 @@ node { }, 'HW + Unit Tests': { deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [ - ["build", "cd selfdrive/manager && ./build.py"], - ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"], + ["build", "cd system/manager && ./build.py"], + ["test pandad", "pytest selfdrive/pandad/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"], + ["test pigeond", "pytest system/ubloxd/tests/test_pigeond.py"], + ["test manager", "pytest system/manager/test/test_manager.py"], ]) }, 'loopback': { deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [ - ["build openpilot", "cd selfdrive/manager && ./build.py"], - ["test boardd loopback", "pytest selfdrive/boardd/tests/test_boardd_loopback.py"], + ["build openpilot", "cd system/manager && ./build.py"], + ["test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"], ]) }, 'camerad': { deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [ - ["build", "cd selfdrive/manager && ./build.py"], + ["build", "cd system/manager && ./build.py"], ["test camerad", "pytest system/camerad/test/test_camerad.py"], ["test exposure", "pytest system/camerad/test/test_exposure.py"], ]) deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [ - ["build", "cd selfdrive/manager && ./build.py"], + ["build", "cd system/manager && ./build.py"], ["test camerad", "pytest system/camerad/test/test_camerad.py"], ["test exposure", "pytest system/camerad/test/test_exposure.py"], ]) }, 'sensord': { deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [ - ["build", "cd selfdrive/manager && ./build.py"], + ["build", "cd system/manager && ./build.py"], ["test sensord", "pytest system/sensord/tests/test_sensord.py"], ]) deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [ - ["build", "cd selfdrive/manager && ./build.py"], + ["build", "cd system/manager && ./build.py"], ["test sensord", "pytest system/sensord/tests/test_sensord.py"], ]) }, 'replay': { deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [ - ["build", "cd selfdrive/manager && ./build.py"], + ["build", "cd system/manager && ./build.py"], ["model replay", "selfdrive/test/process_replay/model_replay.py"], ]) }, 'tizi': { 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"], + ["build openpilot", "cd system/manager && ./build.py"], + ["test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"], + ["test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"], + ["test pandad", "pytest selfdrive/pandad/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"], @@ -249,4 +251,4 @@ node { currentBuild.result = 'FAILED' throw e } -} \ No newline at end of file +} diff --git a/RELEASES.md b/RELEASES.md index d0fd530db1..57f6979117 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,7 +1,19 @@ -Version 0.9.7 (2024-XX-XX) +Version 0.9.8 (2024-XX-XX) +======================== +* Added toggle to enable driver monitoring even when openpilot is not engaged + +Version 0.9.7 (2024-06-13) ======================== * New driving model -* Support for many hybrid Ford models + * Inputs the past curvature for smoother and more accurate lateral control + * Simplified neural network architecture in the model's last layers + * Minor fixes to desire augmentation and weight decay +* New driver monitoring model + * Improved end-to-end bit for phone detection +* Adjust driving personality with the follow distance button +* Support for hybrid variants of supported Ford models +* Fingerprinting without the OBD-II port on all cars +* Improved fuzzy fingerprinting for Ford and Volkswagen Version 0.9.6 (2024-02-27) ======================== @@ -11,6 +23,9 @@ Version 0.9.6 (2024-02-27) * Directly outputs curvature for lateral control * New driver monitoring model * Trained on larger dataset +* Model path UI + * Shows where driving model wants to be + * Shows what model is seeing more clearly, but more jittery * AGNOS 9 * comma body streaming and controls over WebRTC * Improved fuzzy fingerprinting for many makes and models @@ -628,7 +643,7 @@ Version 0.5.13 (2019-05-31) * Reduce CPU utilization by 20% and improve stability * Temporarily remove mapd functionalities to improve stability * Add openpilot record-only mode for unsupported cars - * Synchronize controlsd to boardd to reduce latency + * Synchronize controlsd to pandad to reduce latency * Remove panda support for Subaru giraffe Version 0.5.12 (2019-05-16) @@ -964,7 +979,7 @@ Version 0.2.8 (2017-02-27) Version 0.2.7 (2017-02-08) =========================== * Better performance and pictures at night - * Fix ptr alignment issue in boardd + * Fix ptr alignment issue in pandad * Fix brake error light, fix crash if too cold Version 0.2.6 (2017-01-31) @@ -996,7 +1011,7 @@ Version 0.2.2 (2017-01-10) Version 0.2.1 (2016-12-14) =========================== * Performance improvements, removal of more numpy - * Fix boardd process priority + * Fix pandad process priority * Make counter timer reset on use of steering wheel Version 0.2 (2016-12-12) diff --git a/SConstruct b/SConstruct index 4d4f9ad4fe..944d650d74 100644 --- a/SConstruct +++ b/SConstruct @@ -67,7 +67,7 @@ AddOption('--pc-thneed', AddOption('--minimal', action='store_false', dest='extras', - default=os.path.islink(Dir('#rednose/').abspath), # minimal by default on release branch (where rednose is not a link) + default=os.path.exists(File('#.lfsconfig').abspath), # minimal by default on release branch (where there's no LFS) help='the minimum build to run openpilot. no tests, tools, etc.') ## Architecture name breakdown (arch) @@ -193,6 +193,7 @@ env = Environment( "-Wno-c99-designator", "-Wno-reorder-init-list", "-Wno-error=unused-but-set-variable", + "-Wno-vla-cxx-extension", ] + cflags + ccflags, CPPPATH=cpppath + [ @@ -208,6 +209,7 @@ env = Environment( "#third_party/qrcode", "#third_party", "#cereal", + "#msgq", "#opendbc/can", "#third_party/maplibre-native-qt/include", f"#third_party/maplibre-native-qt/{arch}/include" @@ -222,10 +224,9 @@ env = Environment( CFLAGS=["-std=gnu11"] + cflags, CXXFLAGS=["-std=c++1z"] + cxxflags, LIBPATH=libpath + [ - "#cereal", + "#msgq_repo", "#third_party", - "#opendbc/can", - "#selfdrive/boardd", + "#selfdrive/pandad", "#common", "#rednose/helpers", ], @@ -355,8 +356,13 @@ gpucommon = [_gpucommon] Export('common', 'gpucommon') -# Build cereal and messaging +# Build messaging (cereal + msgq + socketmaster + their dependencies) +SConscript(['msgq_repo/SConscript']) SConscript(['cereal/SConscript']) +Import('socketmaster', 'msgq') +messaging = [socketmaster, msgq, 'zmq', 'capnp', 'kj',] +Export('messaging') + # Build other submodules SConscript([ @@ -376,25 +382,22 @@ SConscript([ ]) if arch != "Darwin": SConscript([ - 'system/camerad/SConscript', 'system/sensord/SConscript', 'system/logcatd/SConscript', ]) +if arch == "larch64": + SConscript(['system/camerad/SConscript']) + # Build openpilot SConscript(['third_party/SConscript']) -SConscript(['selfdrive/boardd/SConscript']) -SConscript(['selfdrive/controls/lib/lateral_mpc_lib/SConscript']) -SConscript(['selfdrive/controls/lib/longitudinal_mpc_lib/SConscript']) -SConscript(['selfdrive/locationd/SConscript']) -SConscript(['selfdrive/navd/SConscript']) -SConscript(['selfdrive/modeld/SConscript']) -SConscript(['selfdrive/ui/SConscript']) +SConscript(['selfdrive/SConscript']) -if arch in ['x86_64', 'aarch64', 'Darwin'] and Dir('#tools/cabana/').exists() and GetOption('extras'): +if Dir('#tools/cabana/').exists() and GetOption('extras'): SConscript(['tools/replay/SConscript']) - SConscript(['tools/cabana/SConscript']) + if arch != "larch64": + SConscript(['tools/cabana/SConscript']) external_sconscript = GetOption('external_sconscript') if external_sconscript: diff --git a/body b/body index 61ace31efa..0e74db67ae 160000 --- a/body +++ b/body @@ -1 +1 @@ -Subproject commit 61ace31efad27ae0d6d86888842f82bc92545e72 +Subproject commit 0e74db67ae6aaa7c30054bd4335dcafe69a5aa72 diff --git a/cereal b/cereal deleted file mode 160000 index 2012d9fd16..0000000000 --- a/cereal +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2012d9fd16f62c737951ce2d1eb19d39f0c7615f diff --git a/cereal/README.md b/cereal/README.md new file mode 100644 index 0000000000..ad940facdc --- /dev/null +++ b/cereal/README.md @@ -0,0 +1,51 @@ +# What is cereal? + +cereal is the messaging system for openpilot. It uses [msgq](https://github.com/commaai/msgq) as a pub/sub backend, and [Cap'n proto](https://capnproto.org/capnp-tool.html) for serialization of the structs. + + +## Messaging Spec + +You'll find the message types in [log.capnp](log.capnp). It uses [Cap'n proto](https://capnproto.org/capnp-tool.html) and defines one struct called `Event`. + +All `Events` have a `logMonoTime` and a `valid`. Then a big union defines the packet type. + +### Best Practices + +- **All fields must describe quantities in SI units**, unless otherwise specified in the field name. +- In the context of the message they are in, field names should be completely unambiguous. +- All values should be easy to plot and be human-readable with minimal parsing. + +### Maintaining backwards-compatibility + +When making changes to the messaging spec you want to maintain backwards-compatibility, such that old logs can +be parsed with a new version of cereal. Adding structs and adding members to structs is generally safe, most other +things are not. Read more details [here](https://capnproto.org/language.html). + +### Custom forks + +Forks of [openpilot](https://github.com/commaai/openpilot) might want to add things to the messaging +spec, however this could conflict with future changes made in mainline cereal/openpilot. Rebasing against mainline openpilot +then means breaking backwards-compatibility with all old logs of your fork. So we added reserved events in +[custom.capnp](custom.capnp) that we will leave empty in mainline cereal/openpilot. **If you only modify those, you can ensure your +fork will remain backwards-compatible with all versions of mainline openpilot and your fork.** + +Example +--- +```python +import cereal.messaging as messaging + +# in subscriber +sm = messaging.SubMaster(['sensorEvents']) +while 1: + sm.update() + print(sm['sensorEvents']) + +``` + +```python +# in publisher +pm = messaging.PubMaster(['sensorEvents']) +dat = messaging.new_message('sensorEvents', size=1) +dat.sensorEvents[0] = {"gyro": {"v": [0.1, -0.1, 0.1]}} +pm.send('sensorEvents', dat) +``` diff --git a/cereal/SConscript b/cereal/SConscript new file mode 100644 index 0000000000..be5f161dea --- /dev/null +++ b/cereal/SConscript @@ -0,0 +1,31 @@ +Import('env', 'envCython', 'arch', 'common', 'msgq') + +import shutil + +cereal_dir = Dir('.') +gen_dir = Dir('gen') +other_dir = Dir('#msgq') + +# Build cereal +schema_files = ['log.capnp', 'car.capnp', 'legacy.capnp', 'custom.capnp'] +env.Command(["gen/c/include/c++.capnp.h"], [], "mkdir -p " + gen_dir.path + "/c/include && touch $TARGETS") +env.Command([f'gen/cpp/{s}.c++' for s in schema_files] + [f'gen/cpp/{s}.h' for s in schema_files], + schema_files, + f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/") + +# TODO: remove non shared cereal and messaging +cereal_objects = env.SharedObject([f'gen/cpp/{s}.c++' for s in schema_files]) + +cereal = env.Library('cereal', cereal_objects) +env.SharedLibrary('cereal_shared', cereal_objects) + +# Build messaging + +services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_dir.path + '/services.py > $TARGET') +env.Program('messaging/bridge', ['messaging/bridge.cc'], LIBS=[msgq, 'zmq', common]) + + +socketmaster = env.SharedObject(['messaging/socketmaster.cc']) +socketmaster = env.Library('socketmaster', socketmaster) + +Export('cereal', 'socketmaster') diff --git a/cereal/__init__.py b/cereal/__init__.py new file mode 100644 index 0000000000..89c5cf38e3 --- /dev/null +++ b/cereal/__init__.py @@ -0,0 +1,9 @@ +import os +import capnp + +CEREAL_PATH = os.path.dirname(os.path.abspath(__file__)) +capnp.remove_import_hook() + +log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp")) +car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp")) +custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp")) diff --git a/cereal/car.capnp b/cereal/car.capnp new file mode 100644 index 0000000000..c17e1c063f --- /dev/null +++ b/cereal/car.capnp @@ -0,0 +1,706 @@ +using Cxx = import "./include/c++.capnp"; +$Cxx.namespace("cereal"); + +@0x8e2af1e708af8b8d; + +# ******* events causing controls state machine transition ******* + +struct CarEvent @0x9b1657f34caf3ad3 { + name @0 :EventName; + + # event types + enable @1 :Bool; + noEntry @2 :Bool; + warning @3 :Bool; # alerts presented only when enabled or soft disabling + userDisable @4 :Bool; + softDisable @5 :Bool; + immediateDisable @6 :Bool; + preEnable @7 :Bool; + permanent @8 :Bool; # alerts presented regardless of openpilot state + overrideLateral @10 :Bool; + overrideLongitudinal @9 :Bool; + + enum EventName @0xbaa8c5d505f727de { + canError @0; + steerUnavailable @1; + wrongGear @4; + doorOpen @5; + seatbeltNotLatched @6; + espDisabled @7; + wrongCarMode @8; + steerTempUnavailable @9; + reverseGear @10; + buttonCancel @11; + buttonEnable @12; + pedalPressed @13; # exits active state + preEnableStandstill @73; # added during pre-enable state with brake + gasPressedOverride @108; # added when user is pressing gas with no disengage on gas + steerOverride @114; + cruiseDisabled @14; + speedTooLow @17; + outOfSpace @18; + overheat @19; + calibrationIncomplete @20; + calibrationInvalid @21; + calibrationRecalibrating @117; + controlsMismatch @22; + pcmEnable @23; + pcmDisable @24; + radarFault @26; + brakeHold @28; + parkBrake @29; + manualRestart @30; + lowSpeedLockout @31; + joystickDebug @34; + steerTempUnavailableSilent @35; + resumeRequired @36; + preDriverDistracted @37; + promptDriverDistracted @38; + driverDistracted @39; + preDriverUnresponsive @43; + promptDriverUnresponsive @44; + driverUnresponsive @45; + belowSteerSpeed @46; + lowBattery @48; + accFaulted @51; + sensorDataInvalid @52; + commIssue @53; + commIssueAvgFreq @109; + tooDistracted @54; + posenetInvalid @55; + soundsUnavailable @56; + preLaneChangeLeft @57; + preLaneChangeRight @58; + laneChange @59; + lowMemory @63; + stockAeb @64; + ldw @65; + carUnrecognized @66; + invalidLkasSetting @69; + speedTooHigh @70; + laneChangeBlocked @71; + relayMalfunction @72; + stockFcw @74; + startup @75; + startupNoCar @76; + startupNoControl @77; + startupMaster @78; + startupNoFw @104; + fcw @79; + steerSaturated @80; + belowEngageSpeed @84; + noGps @85; + wrongCruiseMode @87; + modeldLagging @89; + deviceFalling @90; + fanMalfunction @91; + cameraMalfunction @92; + cameraFrameRate @110; + processNotRunning @95; + dashcamMode @96; + controlsInitializing @98; + usbError @99; + roadCameraError @100; + driverCameraError @101; + wideRoadCameraError @102; + highCpuUsage @105; + cruiseMismatch @106; + lkasDisabled @107; + canBusMissing @111; + controlsdLagging @112; + resumeBlocked @113; + steerTimeLimit @115; + vehicleSensorsInvalid @116; + locationdTemporaryError @103; + locationdPermanentError @118; + paramsdTemporaryError @50; + paramsdPermanentError @119; + actuatorsApiUnavailable @120; + + radarCanErrorDEPRECATED @15; + communityFeatureDisallowedDEPRECATED @62; + radarCommIssueDEPRECATED @67; + driverMonitorLowAccDEPRECATED @68; + gasUnavailableDEPRECATED @3; + dataNeededDEPRECATED @16; + modelCommIssueDEPRECATED @27; + ipasOverrideDEPRECATED @33; + geofenceDEPRECATED @40; + driverMonitorOnDEPRECATED @41; + driverMonitorOffDEPRECATED @42; + calibrationProgressDEPRECATED @47; + invalidGiraffeHondaDEPRECATED @49; + invalidGiraffeToyotaDEPRECATED @60; + internetConnectivityNeededDEPRECATED @61; + whitePandaUnsupportedDEPRECATED @81; + commIssueWarningDEPRECATED @83; + focusRecoverActiveDEPRECATED @86; + neosUpdateRequiredDEPRECATED @88; + modelLagWarningDEPRECATED @93; + startupOneplusDEPRECATED @82; + startupFuzzyFingerprintDEPRECATED @97; + noTargetDEPRECATED @25; + brakeUnavailableDEPRECATED @2; + plannerErrorDEPRECATED @32; + gpsMalfunctionDEPRECATED @94; + } +} + +# ******* main car state @ 100hz ******* +# all speeds in m/s + +struct CarState { + events @13 :List(CarEvent); + + # CAN health + canValid @26 :Bool; # invalid counter/checksums + canTimeout @40 :Bool; # CAN bus dropped out + canErrorCounter @48 :UInt32; + + # car speed + vEgo @1 :Float32; # best estimate of speed + aEgo @16 :Float32; # best estimate of acceleration + vEgoRaw @17 :Float32; # unfiltered speed from CAN sensors + vEgoCluster @44 :Float32; # best estimate of speed shown on car's instrument cluster, used for UI + + yawRate @22 :Float32; # best estimate of yaw rate + standstill @18 :Bool; + wheelSpeeds @2 :WheelSpeeds; + + # gas pedal, 0.0-1.0 + gas @3 :Float32; # this is user pedal only + gasPressed @4 :Bool; # this is user pedal only + + engineRpm @46 :Float32; + + # brake pedal, 0.0-1.0 + brake @5 :Float32; # this is user pedal only + brakePressed @6 :Bool; # this is user pedal only + regenBraking @45 :Bool; # this is user pedal only + parkingBrake @39 :Bool; + brakeHoldActive @38 :Bool; + + # steering wheel + steeringAngleDeg @7 :Float32; + steeringAngleOffsetDeg @37 :Float32; # Offset betweens sensors in case there multiple + steeringRateDeg @15 :Float32; + steeringTorque @8 :Float32; # TODO: standardize units + steeringTorqueEps @27 :Float32; # TODO: standardize units + steeringPressed @9 :Bool; # if the user is using the steering wheel + steerFaultTemporary @35 :Bool; # temporary EPS fault + steerFaultPermanent @36 :Bool; # permanent EPS fault + stockAeb @30 :Bool; + stockFcw @31 :Bool; + espDisabled @32 :Bool; + accFaulted @42 :Bool; + carFaultedNonCritical @47 :Bool; # some ECU is faulted, but car remains controllable + + # cruise state + cruiseState @10 :CruiseState; + + # gear + gearShifter @14 :GearShifter; + + # button presses + buttonEvents @11 :List(ButtonEvent); + leftBlinker @20 :Bool; + rightBlinker @21 :Bool; + genericToggle @23 :Bool; + + # lock info + doorOpen @24 :Bool; + seatbeltUnlatched @25 :Bool; + + # clutch (manual transmission only) + clutchPressed @28 :Bool; + + # blindspot sensors + leftBlindspot @33 :Bool; # Is there something blocking the left lane change + rightBlindspot @34 :Bool; # Is there something blocking the right lane change + + fuelGauge @41 :Float32; # battery or fuel tank level from 0.0 to 1.0 + charging @43 :Bool; + + # process meta + cumLagMs @50 :Float32; + + struct WheelSpeeds { + # optional wheel speeds + fl @0 :Float32; + fr @1 :Float32; + rl @2 :Float32; + rr @3 :Float32; + } + + struct CruiseState { + enabled @0 :Bool; + speed @1 :Float32; + speedCluster @6 :Float32; # Set speed as shown on instrument cluster + available @2 :Bool; + speedOffset @3 :Float32; + standstill @4 :Bool; + nonAdaptive @5 :Bool; + } + + enum GearShifter { + unknown @0; + park @1; + drive @2; + neutral @3; + reverse @4; + sport @5; + low @6; + brake @7; + eco @8; + manumatic @9; + } + + # send on change + struct ButtonEvent { + pressed @0 :Bool; + type @1 :Type; + + enum Type { + unknown @0; + leftBlinker @1; + rightBlinker @2; + accelCruise @3; + decelCruise @4; + cancel @5; + altButton1 @6; + altButton2 @7; + altButton3 @8; + setCruise @9; + resumeCruise @10; + gapAdjustCruise @11; + } + } + + # deprecated + errorsDEPRECATED @0 :List(CarEvent.EventName); + brakeLightsDEPRECATED @19 :Bool; + steeringRateLimitedDEPRECATED @29 :Bool; + canMonoTimesDEPRECATED @12: List(UInt64); + canRcvTimeoutDEPRECATED @49 :Bool; +} + +# ******* radar state @ 20hz ******* + +struct RadarData @0x888ad6581cf0aacb { + errors @0 :List(Error); + points @1 :List(RadarPoint); + + enum Error { + canError @0; + fault @1; + wrongConfig @2; + } + + # similar to LiveTracks + # is one timestamp valid for all? I think so + struct RadarPoint { + trackId @0 :UInt64; # no trackId reuse + + # these 3 are the minimum required + dRel @1 :Float32; # m from the front bumper of the car + yRel @2 :Float32; # m + vRel @3 :Float32; # m/s + + # these are optional and valid if they are not NaN + aRel @4 :Float32; # m/s^2 + yvRel @5 :Float32; # m/s + + # some radars flag measurements VS estimates + measured @6 :Bool; + } + + # deprecated + canMonoTimesDEPRECATED @2 :List(UInt64); +} + +# ******* car controls @ 100hz ******* + +struct CarControl { + # must be true for any actuator commands to work + enabled @0 :Bool; + latActive @11: Bool; + longActive @12: Bool; + + # Actuator commands as computed by controlsd + actuators @6 :Actuators; + + # moved to CarOutput + actuatorsOutputDEPRECATED @10 :Actuators; + + leftBlinker @15: Bool; + rightBlinker @16: Bool; + + orientationNED @13 :List(Float32); + angularVelocity @14 :List(Float32); + + cruiseControl @4 :CruiseControl; + hudControl @5 :HUDControl; + + struct Actuators { + # range from 0.0 - 1.0 + gas @0: Float32; + brake @1: Float32; + # range from -1.0 - 1.0 + steer @2: Float32; + # value sent over can to the car + steerOutputCan @8: Float32; + steeringAngleDeg @3: Float32; + + curvature @7: Float32; + + speed @6: Float32; # m/s + accel @4: Float32; # m/s^2 + longControlState @5: LongControlState; + + enum LongControlState @0xe40f3a917d908282{ + off @0; + pid @1; + stopping @2; + starting @3; + } + } + + struct CruiseControl { + cancel @0: Bool; + resume @1: Bool; + override @4: Bool; + speedOverrideDEPRECATED @2: Float32; + accelOverrideDEPRECATED @3: Float32; + } + + struct HUDControl { + speedVisible @0: Bool; + setSpeed @1: Float32; + lanesVisible @2: Bool; + leadVisible @3: Bool; + visualAlert @4: VisualAlert; + audibleAlert @5: AudibleAlert; + rightLaneVisible @6: Bool; + leftLaneVisible @7: Bool; + rightLaneDepart @8: Bool; + leftLaneDepart @9: Bool; + leadDistanceBars @10: Int8; # 1-3: 1 is closest, 3 is farthest. some ports may utilize 2-4 bars instead + + enum VisualAlert { + # these are the choices from the Honda + # map as good as you can for your car + none @0; + fcw @1; + steerRequired @2; + brakePressed @3; + wrongGear @4; + seatbeltUnbuckled @5; + speedTooHigh @6; + ldw @7; + } + + enum AudibleAlert { + none @0; + + engage @1; + disengage @2; + refuse @3; + + warningSoft @4; + warningImmediate @5; + + prompt @6; + promptRepeat @7; + promptDistracted @8; + } + } + + gasDEPRECATED @1 :Float32; + brakeDEPRECATED @2 :Float32; + steeringTorqueDEPRECATED @3 :Float32; + activeDEPRECATED @7 :Bool; + rollDEPRECATED @8 :Float32; + pitchDEPRECATED @9 :Float32; +} + +struct CarOutput { + # Any car specific rate limits or quirks applied by + # the CarController are reflected in actuatorsOutput + # and matches what is sent to the car + actuatorsOutput @0 :CarControl.Actuators; +} + +# ****** car param ****** + +struct CarParams { + carName @0 :Text; + carFingerprint @1 :Text; + fuzzyFingerprint @55 :Bool; + + notCar @66 :Bool; # flag for non-car robotics platforms + + pcmCruise @3 :Bool; # is openpilot's state tied to the PCM's cruise state? + enableDsu @5 :Bool; # driving support unit + enableBsm @56 :Bool; # blind spot monitoring + flags @64 :UInt32; # flags for car specific quirks + experimentalLongitudinalAvailable @71 :Bool; + + minEnableSpeed @7 :Float32; + minSteerSpeed @8 :Float32; + safetyConfigs @62 :List(SafetyConfig); + alternativeExperience @65 :Int16; # panda flag for features like no disengage on gas + + # Car docs fields + maxLateralAccel @68 :Float32; + autoResumeSng @69 :Bool; # describes whether car can resume from a stop automatically + + # things about the car in the manual + mass @17 :Float32; # [kg] curb weight: all fluids no cargo + wheelbase @18 :Float32; # [m] distance from rear axle to front axle + centerToFront @19 :Float32; # [m] distance from center of mass to front axle + steerRatio @20 :Float32; # [] ratio of steering wheel angle to front wheel angle + steerRatioRear @21 :Float32; # [] ratio of steering wheel angle to rear wheel angle (usually 0) + + # things we can derive + rotationalInertia @22 :Float32; # [kg*m2] body rotational inertia + tireStiffnessFactor @72 :Float32; # scaling factor used in calculating tireStiffness[Front,Rear] + tireStiffnessFront @23 :Float32; # [N/rad] front tire coeff of stiff + tireStiffnessRear @24 :Float32; # [N/rad] rear tire coeff of stiff + + longitudinalTuning @25 :LongitudinalPIDTuning; + lateralParams @48 :LateralParams; + lateralTuning :union { + pid @26 :LateralPIDTuning; + indiDEPRECATED @27 :LateralINDITuning; + lqrDEPRECATED @40 :LateralLQRTuning; + torque @67 :LateralTorqueTuning; + } + + steerLimitAlert @28 :Bool; + steerLimitTimer @47 :Float32; # time before steerLimitAlert is issued + + vEgoStopping @29 :Float32; # Speed at which the car goes into stopping state + vEgoStarting @59 :Float32; # Speed at which the car goes into starting state + stoppingControl @31 :Bool; # Does the car allow full control even at lows speeds when stopping + steerControlType @34 :SteerControlType; + radarUnavailable @35 :Bool; # True when radar objects aren't visible on CAN or aren't parsed out + stopAccel @60 :Float32; # Required acceleration to keep vehicle stationary + stoppingDecelRate @52 :Float32; # m/s^2/s while trying to stop + startAccel @32 :Float32; # Required acceleration to get car moving + startingState @70 :Bool; # Does this car make use of special starting state + + steerActuatorDelay @36 :Float32; # Steering wheel actuator delay in seconds + longitudinalActuatorDelay @58 :Float32; # Gas/Brake actuator delay in seconds + openpilotLongitudinalControl @37 :Bool; # is openpilot doing the longitudinal control? + carVin @38 :Text; # VIN number queried during fingerprinting + dashcamOnly @41: Bool; + passive @73: Bool; # is openpilot in control? + transmissionType @43 :TransmissionType; + carFw @44 :List(CarFw); + + radarTimeStep @45: Float32 = 0.05; # time delta between radar updates, 20Hz is very standard + fingerprintSource @49: FingerprintSource; + networkLocation @50 :NetworkLocation; # Where Panda/C2 is integrated into the car's CAN network + + wheelSpeedFactor @63 :Float32; # Multiplier on wheels speeds to computer actual speeds + + struct SafetyConfig { + safetyModel @0 :SafetyModel; + safetyParam @3 :UInt16; + safetyParamDEPRECATED @1 :Int16; + safetyParam2DEPRECATED @2 :UInt32; + } + + struct LateralParams { + torqueBP @0 :List(Int32); + torqueV @1 :List(Int32); + } + + struct LateralPIDTuning { + kpBP @0 :List(Float32); + kpV @1 :List(Float32); + kiBP @2 :List(Float32); + kiV @3 :List(Float32); + kf @4 :Float32; + } + + struct LateralTorqueTuning { + useSteeringAngle @0 :Bool; + kp @1 :Float32; + ki @2 :Float32; + friction @3 :Float32; + kf @4 :Float32; + steeringAngleDeadzoneDeg @5 :Float32; + latAccelFactor @6 :Float32; + latAccelOffset @7 :Float32; + } + + struct LongitudinalPIDTuning { + kpBP @0 :List(Float32); + kpV @1 :List(Float32); + kiBP @2 :List(Float32); + kiV @3 :List(Float32); + kf @6 :Float32; + deadzoneBPDEPRECATED @4 :List(Float32); + deadzoneVDEPRECATED @5 :List(Float32); + } + + struct LateralINDITuning { + outerLoopGainBP @4 :List(Float32); + outerLoopGainV @5 :List(Float32); + innerLoopGainBP @6 :List(Float32); + innerLoopGainV @7 :List(Float32); + timeConstantBP @8 :List(Float32); + timeConstantV @9 :List(Float32); + actuatorEffectivenessBP @10 :List(Float32); + actuatorEffectivenessV @11 :List(Float32); + + outerLoopGainDEPRECATED @0 :Float32; + innerLoopGainDEPRECATED @1 :Float32; + timeConstantDEPRECATED @2 :Float32; + actuatorEffectivenessDEPRECATED @3 :Float32; + } + + struct LateralLQRTuning { + scale @0 :Float32; + ki @1 :Float32; + dcGain @2 :Float32; + + # State space system + a @3 :List(Float32); + b @4 :List(Float32); + c @5 :List(Float32); + + k @6 :List(Float32); # LQR gain + l @7 :List(Float32); # Kalman gain + } + + enum SafetyModel { + silent @0; + hondaNidec @1; + toyota @2; + elm327 @3; + gm @4; + hondaBoschGiraffe @5; + ford @6; + cadillac @7; + hyundai @8; + chrysler @9; + tesla @10; + subaru @11; + gmPassive @12; + mazda @13; + nissan @14; + volkswagen @15; + toyotaIpas @16; + allOutput @17; + gmAscm @18; + noOutput @19; # like silent but without silent CAN TXs + hondaBosch @20; + volkswagenPq @21; + subaruPreglobal @22; # pre-Global platform + hyundaiLegacy @23; + hyundaiCommunity @24; + volkswagenMlb @25; + hongqi @26; + body @27; + hyundaiCanfd @28; + volkswagenMqbEvo @29; + chryslerCusw @30; + psa @31; + } + + enum SteerControlType { + torque @0; + angle @1; + + curvatureDEPRECATED @2; + } + + enum TransmissionType { + unknown @0; + automatic @1; # Traditional auto, including DSG + manual @2; # True "stick shift" only + direct @3; # Electric vehicle or other direct drive + cvt @4; + } + + struct CarFw { + ecu @0 :Ecu; + fwVersion @1 :Data; + address @2 :UInt32; + subAddress @3 :UInt8; + responseAddress @4 :UInt32; + request @5 :List(Data); + brand @6 :Text; + bus @7 :UInt8; + logging @8 :Bool; + obdMultiplexing @9 :Bool; + } + + enum Ecu { + eps @0; + abs @1; + fwdRadar @2; + fwdCamera @3; + engine @4; + unknown @5; + transmission @8; # Transmission Control Module + hybrid @18; # hybrid control unit, e.g. Chrysler's HCP, Honda's IMA Control Unit, Toyota's hybrid control computer + srs @9; # airbag + gateway @10; # can gateway + hud @11; # heads up display + combinationMeter @12; # instrument cluster + electricBrakeBooster @15; + shiftByWire @16; + adas @19; + cornerRadar @21; + hvac @20; + parkingAdas @7; # parking assist system ECU, e.g. Toyota's IPAS, Hyundai's RSPA, etc. + epb @22; # electronic parking brake + telematics @23; + body @24; # body control module + + # Toyota only + dsu @6; + + # Honda only + vsa @13; # Vehicle Stability Assist + programmedFuelInjection @14; + + debug @17; + } + + enum FingerprintSource { + can @0; + fw @1; + fixed @2; + } + + enum NetworkLocation { + fwdCamera @0; # Standard/default integration at LKAS camera + gateway @1; # Integration at vehicle's CAN gateway + } + + enableGasInterceptorDEPRECATED @2 :Bool; + enableCameraDEPRECATED @4 :Bool; + enableApgsDEPRECATED @6 :Bool; + steerRateCostDEPRECATED @33 :Float32; + isPandaBlackDEPRECATED @39 :Bool; + hasStockCameraDEPRECATED @57 :Bool; + safetyParamDEPRECATED @10 :Int16; + safetyModelDEPRECATED @9 :SafetyModel; + safetyModelPassiveDEPRECATED @42 :SafetyModel = silent; + minSpeedCanDEPRECATED @51 :Float32; + communityFeatureDEPRECATED @46: Bool; + startingAccelRateDEPRECATED @53 :Float32; + steerMaxBPDEPRECATED @11 :List(Float32); + steerMaxVDEPRECATED @12 :List(Float32); + gasMaxBPDEPRECATED @13 :List(Float32); + gasMaxVDEPRECATED @14 :List(Float32); + brakeMaxBPDEPRECATED @15 :List(Float32); + brakeMaxVDEPRECATED @16 :List(Float32); + directAccelControlDEPRECATED @30 :Bool; + maxSteeringAngleDegDEPRECATED @54 :Float32; + longitudinalActuatorDelayLowerBoundDEPRECATEDDEPRECATED @61 :Float32; +} diff --git a/cereal/custom.capnp b/cereal/custom.capnp new file mode 100644 index 0000000000..369222add8 --- /dev/null +++ b/cereal/custom.capnp @@ -0,0 +1,39 @@ +using Cxx = import "./include/c++.capnp"; +$Cxx.namespace("cereal"); + +@0xb526ba661d550a59; + +# custom.capnp: a home for empty structs reserved for custom forks +# These structs are guaranteed to remain reserved and empty in mainline +# cereal, so use these if you want custom events in your fork. + +# you can rename the struct, but don't change the identifier +struct CustomReserved0 @0x81c2f05a394cf4af { +} + +struct CustomReserved1 @0xaedffd8f31e7b55d { +} + +struct CustomReserved2 @0xf35cc4560bbf6ec2 { +} + +struct CustomReserved3 @0xda96579883444c35 { +} + +struct CustomReserved4 @0x80ae746ee2596b11 { +} + +struct CustomReserved5 @0xa5cd762cd951a455 { +} + +struct CustomReserved6 @0xf98d843bfd7004a3 { +} + +struct CustomReserved7 @0xb86e6369214c01c8 { +} + +struct CustomReserved8 @0xf416ec09499d9d19 { +} + +struct CustomReserved9 @0xa1680744031fdb2d { +} diff --git a/cereal/include/c++.capnp b/cereal/include/c++.capnp new file mode 100644 index 0000000000..2bda547179 --- /dev/null +++ b/cereal/include/c++.capnp @@ -0,0 +1,26 @@ +# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors +# Licensed under the MIT License: +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +@0xbdf87d7bb8304e81; +$namespace("capnp::annotations"); + +annotation namespace(file): Text; +annotation name(field, enumerant, struct, enum, interface, method, param, group, union): Text; diff --git a/cereal/legacy.capnp b/cereal/legacy.capnp new file mode 100644 index 0000000000..a8fa5e4a1f --- /dev/null +++ b/cereal/legacy.capnp @@ -0,0 +1,574 @@ +using Cxx = import "./include/c++.capnp"; +$Cxx.namespace("cereal"); + +@0x80ef1ec4889c2a63; + +# legacy.capnp: a home for deprecated structs + +struct LogRotate @0x9811e1f38f62f2d1 { + segmentNum @0 :Int32; + path @1 :Text; +} + +struct LiveUI @0xc08240f996aefced { + rearViewCam @0 :Bool; + alertText1 @1 :Text; + alertText2 @2 :Text; + awarenessStatus @3 :Float32; +} + +struct UiLayoutState @0x88dcce08ad29dda0 { + activeApp @0 :App; + sidebarCollapsed @1 :Bool; + mapEnabled @2 :Bool; + mockEngaged @3 :Bool; + + enum App @0x9917470acf94d285 { + home @0; + music @1; + nav @2; + settings @3; + none @4; + } +} + +struct OrbslamCorrection @0x8afd33dc9b35e1aa { + correctionMonoTime @0 :UInt64; + prePositionECEF @1 :List(Float64); + postPositionECEF @2 :List(Float64); + prePoseQuatECEF @3 :List(Float32); + postPoseQuatECEF @4 :List(Float32); + numInliers @5 :UInt32; +} + +struct EthernetPacket @0xa99a9d5b33cf5859 { + pkt @0 :Data; + ts @1 :Float32; +} + +struct CellInfo @0xcff7566681c277ce { + timestamp @0 :UInt64; + repr @1 :Text; # android toString() for now +} + +struct WifiScan @0xd4df5a192382ba0b { + bssid @0 :Text; + ssid @1 :Text; + capabilities @2 :Text; + frequency @3 :Int32; + level @4 :Int32; + timestamp @5 :Int64; + + centerFreq0 @6 :Int32; + centerFreq1 @7 :Int32; + channelWidth @8 :ChannelWidth; + operatorFriendlyName @9 :Text; + venueName @10 :Text; + is80211mcResponder @11 :Bool; + passpoint @12 :Bool; + + distanceCm @13 :Int32; + distanceSdCm @14 :Int32; + + enum ChannelWidth @0xcb6a279f015f6b51 { + w20Mhz @0; + w40Mhz @1; + w80Mhz @2; + w160Mhz @3; + w80Plus80Mhz @4; + } +} + +struct LiveEventData @0x94b7baa90c5c321e { + name @0 :Text; + value @1 :Int32; +} + +struct ModelData @0xb8aad62cffef28a9 { + frameId @0 :UInt32; + frameAge @12 :UInt32; + frameDropPerc @13 :Float32; + timestampEof @9 :UInt64; + modelExecutionTime @14 :Float32; + gpuExecutionTime @16 :Float32; + rawPred @15 :Data; + + path @1 :PathData; + leftLane @2 :PathData; + rightLane @3 :PathData; + lead @4 :LeadData; + freePath @6 :List(Float32); + + settings @5 :ModelSettings; + leadFuture @7 :LeadData; + speed @8 :List(Float32); + meta @10 :MetaData; + longitudinal @11 :LongitudinalData; + + struct PathData @0x8817eeea389e9f08 { + points @0 :List(Float32); + prob @1 :Float32; + std @2 :Float32; + stds @3 :List(Float32); + poly @4 :List(Float32); + validLen @5 :Float32; + } + + struct LeadData @0xd1c9bef96d26fa91 { + dist @0 :Float32; + prob @1 :Float32; + std @2 :Float32; + relVel @3 :Float32; + relVelStd @4 :Float32; + relY @5 :Float32; + relYStd @6 :Float32; + relA @7 :Float32; + relAStd @8 :Float32; + } + + struct ModelSettings @0xa26e3710efd3e914 { + bigBoxX @0 :UInt16; + bigBoxY @1 :UInt16; + bigBoxWidth @2 :UInt16; + bigBoxHeight @3 :UInt16; + boxProjection @4 :List(Float32); + yuvCorrection @5 :List(Float32); + inputTransform @6 :List(Float32); + } + + struct MetaData @0x9744f25fb60f2bf8 { + engagedProb @0 :Float32; + desirePrediction @1 :List(Float32); + brakeDisengageProb @2 :Float32; + gasDisengageProb @3 :Float32; + steerOverrideProb @4 :Float32; + desireState @5 :List(Float32); + } + + struct LongitudinalData @0xf98f999c6a071122 { + distances @2 :List(Float32); + speeds @0 :List(Float32); + accelerations @1 :List(Float32); + } +} + +struct ECEFPoint @0xc25bbbd524983447 { + x @0 :Float64; + y @1 :Float64; + z @2 :Float64; +} + +struct ECEFPointDEPRECATED @0xe10e21168db0c7f7 { + x @0 :Float32; + y @1 :Float32; + z @2 :Float32; +} + +struct GPSPlannerPoints @0xab54c59699f8f9f3 { + curPosDEPRECATED @0 :ECEFPointDEPRECATED; + pointsDEPRECATED @1 :List(ECEFPointDEPRECATED); + curPos @6 :ECEFPoint; + points @7 :List(ECEFPoint); + valid @2 :Bool; + trackName @3 :Text; + speedLimit @4 :Float32; + accelTarget @5 :Float32; +} + +struct GPSPlannerPlan @0xf5ad1d90cdc1dd6b { + valid @0 :Bool; + poly @1 :List(Float32); + trackName @2 :Text; + speed @3 :Float32; + acceleration @4 :Float32; + pointsDEPRECATED @5 :List(ECEFPointDEPRECATED); + points @6 :List(ECEFPoint); + xLookahead @7 :Float32; +} + +struct UiNavigationEvent @0x90c8426c3eaddd3b { + type @0: Type; + status @1: Status; + distanceTo @2: Float32; + endRoadPointDEPRECATED @3: ECEFPointDEPRECATED; + endRoadPoint @4: ECEFPoint; + + enum Type @0xe8db07dcf8fcea05 { + none @0; + laneChangeLeft @1; + laneChangeRight @2; + mergeLeft @3; + mergeRight @4; + turnLeft @5; + turnRight @6; + } + + enum Status @0xb9aa88c75ef99a1f { + none @0; + passive @1; + approaching @2; + active @3; + } +} + +struct LiveLocationData @0xb99b2bc7a57e8128 { + status @0 :UInt8; + + # 3D fix + lat @1 :Float64; + lon @2 :Float64; + alt @3 :Float32; # m + + # speed + speed @4 :Float32; # m/s + + # NED velocity components + vNED @5 :List(Float32); + + # roll, pitch, heading (x,y,z) + roll @6 :Float32; # WRT to center of earth? + pitch @7 :Float32; # WRT to center of earth? + heading @8 :Float32; # WRT to north? + + # what are these? + wanderAngle @9 :Float32; + trackAngle @10 :Float32; + + # car frame -- https://upload.wikimedia.org/wikipedia/commons/f/f5/RPY_angles_of_cars.png + + # gyro, in car frame, deg/s + gyro @11 :List(Float32); + + # accel, in car frame, m/s^2 + accel @12 :List(Float32); + + accuracy @13 :Accuracy; + + source @14 :SensorSource; + # if we are fixing a location in the past + fixMonoTime @15 :UInt64; + + gpsWeek @16 :Int32; + timeOfWeek @17 :Float64; + + positionECEF @18 :List(Float64); + poseQuatECEF @19 :List(Float32); + pitchCalibration @20 :Float32; + yawCalibration @21 :Float32; + imuFrame @22 :List(Float32); + + struct Accuracy @0x943dc4625473b03f { + pNEDError @0 :List(Float32); + vNEDError @1 :List(Float32); + rollError @2 :Float32; + pitchError @3 :Float32; + headingError @4 :Float32; + ellipsoidSemiMajorError @5 :Float32; + ellipsoidSemiMinorError @6 :Float32; + ellipsoidOrientationError @7 :Float32; + } + + enum SensorSource @0xc871d3cc252af657 { + applanix @0; + kalman @1; + orbslam @2; + timing @3; + dummy @4; + } +} + +struct OrbOdometry @0xd7700859ed1f5b76 { + # timing first + startMonoTime @0 :UInt64; + endMonoTime @1 :UInt64; + + # fundamental matrix and error + f @2: List(Float64); + err @3: Float64; + + # number of inlier points + inliers @4: Int32; + + # for debug only + # indexed by endMonoTime features + # value is startMonoTime feature match + # -1 if no match + matches @5: List(Int16); +} + +struct OrbFeatures @0xcd60164a8a0159ef { + timestampEof @0 :UInt64; + # transposed arrays of normalized image coordinates + # len(xs) == len(ys) == len(descriptors) * 32 + xs @1 :List(Float32); + ys @2 :List(Float32); + descriptors @3 :Data; + octaves @4 :List(Int8); + + # match index to last OrbFeatures + # -1 if no match + timestampLastEof @5 :UInt64; + matches @6: List(Int16); +} + +struct OrbFeaturesSummary @0xd500d30c5803fa4f { + timestampEof @0 :UInt64; + timestampLastEof @1 :UInt64; + + featureCount @2 :UInt16; + matchCount @3 :UInt16; + computeNs @4 :UInt64; +} + +struct OrbKeyFrame @0xc8233c0345e27e24 { + # this is a globally unique id for the KeyFrame + id @0: UInt64; + + # this is the location of the KeyFrame + pos @1: ECEFPoint; + + # these are the features in the world + # len(dpos) == len(descriptors) * 32 + dpos @2 :List(ECEFPoint); + descriptors @3 :Data; +} + +struct KalmanOdometry @0x92e21bb7ea38793a { + trans @0 :List(Float32); # m/s in device frame + rot @1 :List(Float32); # rad/s in device frame + transStd @2 :List(Float32); # std m/s in device frame + rotStd @3 :List(Float32); # std rad/s in device frame +} + +struct OrbObservation @0x9b326d4e436afec7 { + observationMonoTime @0 :UInt64; + normalizedCoordinates @1 :List(Float32); + locationECEF @2 :List(Float64); + matchDistance @3: UInt32; +} + +struct CalibrationFeatures @0x8fdfadb254ea867a { + frameId @0 :UInt32; + + p0 @1 :List(Float32); + p1 @2 :List(Float32); + status @3 :List(Int8); +} + +struct NavStatus @0xbd8822120928120c { + isNavigating @0 :Bool; + currentAddress @1 :Address; + + struct Address @0xce7cd672cacc7814 { + title @0 :Text; + lat @1 :Float64; + lng @2 :Float64; + house @3 :Text; + address @4 :Text; + street @5 :Text; + city @6 :Text; + state @7 :Text; + country @8 :Text; + } +} + +struct NavUpdate @0xdb98be6565516acb { + isNavigating @0 :Bool; + curSegment @1 :Int32; + segments @2 :List(Segment); + + struct LatLng @0x9eaef9187cadbb9b { + lat @0 :Float64; + lng @1 :Float64; + } + + struct Segment @0xa5b39b4fc4d7da3f { + from @0 :LatLng; + to @1 :LatLng; + updateTime @2 :Int32; + distance @3 :Int32; + crossTime @4 :Int32; + exitNo @5 :Int32; + instruction @6 :Instruction; + + parts @7 :List(LatLng); + + enum Instruction @0xc5417a637451246f { + turnLeft @0; + turnRight @1; + keepLeft @2; + keepRight @3; + straight @4; + roundaboutExitNumber @5; + roundaboutExit @6; + roundaboutTurnLeft @7; + unkn8 @8; + roundaboutStraight @9; + unkn10 @10; + roundaboutTurnRight @11; + unkn12 @12; + roundaboutUturn @13; + unkn14 @14; + arrive @15; + exitLeft @16; + exitRight @17; + unkn18 @18; + uturn @19; + # ... + } + } +} + +struct TrafficEvent @0xacfa74a094e62626 { + type @0 :Type; + distance @1 :Float32; + action @2 :Action; + resuming @3 :Bool; + + enum Type @0xd85d75253435bf4b { + stopSign @0; + lightRed @1; + lightYellow @2; + lightGreen @3; + stopLight @4; + } + + enum Action @0xa6f6ce72165ccb49 { + none @0; + yield @1; + stop @2; + resumeReady @3; + } + +} + + +struct AndroidGnss @0xdfdf30d03fc485bd { + union { + measurements @0 :Measurements; + navigationMessage @1 :NavigationMessage; + } + + struct Measurements @0xa20710d4f428d6cd { + clock @0 :Clock; + measurements @1 :List(Measurement); + + struct Clock @0xa0e27b453a38f450 { + timeNanos @0 :Int64; + hardwareClockDiscontinuityCount @1 :Int32; + + hasTimeUncertaintyNanos @2 :Bool; + timeUncertaintyNanos @3 :Float64; + + hasLeapSecond @4 :Bool; + leapSecond @5 :Int32; + + hasFullBiasNanos @6 :Bool; + fullBiasNanos @7 :Int64; + + hasBiasNanos @8 :Bool; + biasNanos @9 :Float64; + + hasBiasUncertaintyNanos @10 :Bool; + biasUncertaintyNanos @11 :Float64; + + hasDriftNanosPerSecond @12 :Bool; + driftNanosPerSecond @13 :Float64; + + hasDriftUncertaintyNanosPerSecond @14 :Bool; + driftUncertaintyNanosPerSecond @15 :Float64; + } + + struct Measurement @0xd949bf717d77614d { + svId @0 :Int32; + constellation @1 :Constellation; + + timeOffsetNanos @2 :Float64; + state @3 :Int32; + receivedSvTimeNanos @4 :Int64; + receivedSvTimeUncertaintyNanos @5 :Int64; + cn0DbHz @6 :Float64; + pseudorangeRateMetersPerSecond @7 :Float64; + pseudorangeRateUncertaintyMetersPerSecond @8 :Float64; + accumulatedDeltaRangeState @9 :Int32; + accumulatedDeltaRangeMeters @10 :Float64; + accumulatedDeltaRangeUncertaintyMeters @11 :Float64; + + hasCarrierFrequencyHz @12 :Bool; + carrierFrequencyHz @13 :Float32; + hasCarrierCycles @14 :Bool; + carrierCycles @15 :Int64; + hasCarrierPhase @16 :Bool; + carrierPhase @17 :Float64; + hasCarrierPhaseUncertainty @18 :Bool; + carrierPhaseUncertainty @19 :Float64; + hasSnrInDb @20 :Bool; + snrInDb @21 :Float64; + + multipathIndicator @22 :MultipathIndicator; + + enum Constellation @0x9ef1f3ff0deb5ffb { + unknown @0; + gps @1; + sbas @2; + glonass @3; + qzss @4; + beidou @5; + galileo @6; + } + + enum State @0xcbb9490adce12d72 { + unknown @0; + codeLock @1; + bitSync @2; + subframeSync @3; + towDecoded @4; + msecAmbiguous @5; + symbolSync @6; + gloStringSync @7; + gloTodDecoded @8; + bdsD2BitSync @9; + bdsD2SubframeSync @10; + galE1bcCodeLock @11; + galE1c2ndCodeLock @12; + galE1bPageSync @13; + sbasSync @14; + } + + enum MultipathIndicator @0xc04e7b6231d4caa8 { + unknown @0; + detected @1; + notDetected @2; + } + } + } + + struct NavigationMessage @0xe2517b083095fd4e { + type @0 :Int32; + svId @1 :Int32; + messageId @2 :Int32; + submessageId @3 :Int32; + data @4 :Data; + status @5 :Status; + + enum Status @0xec1ff7996b35366f { + unknown @0; + parityPassed @1; + parityRebuilt @2; + } + } +} + +struct LidarPts @0xe3d6685d4e9d8f7a { + r @0 :List(UInt16); # uint16 m*500.0 + theta @1 :List(UInt16); # uint16 deg*100.0 + reflect @2 :List(UInt8); # uint8 0-255 + + # For storing out of file. + idx @3 :UInt64; + + # For storing in file + pkt @4 :Data; +} + + diff --git a/cereal/log.capnp b/cereal/log.capnp new file mode 100644 index 0000000000..3c720d4526 --- /dev/null +++ b/cereal/log.capnp @@ -0,0 +1,2373 @@ +using Cxx = import "./include/c++.capnp"; +$Cxx.namespace("cereal"); + +using Car = import "car.capnp"; +using Legacy = import "legacy.capnp"; +using Custom = import "custom.capnp"; + +@0xf3b1f17e25a4285b; + +const logVersion :Int32 = 1; + +struct Map(Key, Value) { + entries @0 :List(Entry); + struct Entry { + key @0 :Key; + value @1 :Value; + } +} + +enum LongitudinalPersonality { + aggressive @0; + standard @1; + relaxed @2; +} + +struct InitData { + kernelArgs @0 :List(Text); + kernelVersion @15 :Text; + osVersion @18 :Text; + + dongleId @2 :Text; + bootlogId @22 :Text; + + deviceType @3 :DeviceType; + version @4 :Text; + gitCommit @10 :Text; + gitCommitDate @21 :Text; + gitBranch @11 :Text; + gitRemote @13 :Text; + + androidProperties @16 :Map(Text, Text); + + pandaInfo @8 :PandaInfo; + + dirty @9 :Bool; + passive @12 :Bool; + params @17 :Map(Text, Data); + + commands @19 :Map(Text, Data); + + wallTimeNanos @20 :UInt64; + + enum DeviceType { + unknown @0; + neo @1; + chffrAndroid @2; + chffrIos @3; + tici @4; + pc @5; + tizi @6; + mici @7; + } + + struct PandaInfo { + hasPanda @0 :Bool; + dongleId @1 :Text; + stVersion @2 :Text; + espVersion @3 :Text; + } + + # ***** deprecated stuff ***** + gctxDEPRECATED @1 :Text; + androidBuildInfo @5 :AndroidBuildInfo; + androidSensorsDEPRECATED @6 :List(AndroidSensor); + chffrAndroidExtraDEPRECATED @7 :ChffrAndroidExtra; + iosBuildInfoDEPRECATED @14 :IosBuildInfo; + + struct AndroidBuildInfo { + board @0 :Text; + bootloader @1 :Text; + brand @2 :Text; + device @3 :Text; + display @4 :Text; + fingerprint @5 :Text; + hardware @6 :Text; + host @7 :Text; + id @8 :Text; + manufacturer @9 :Text; + model @10 :Text; + product @11 :Text; + radioVersion @12 :Text; + serial @13 :Text; + supportedAbis @14 :List(Text); + tags @15 :Text; + time @16 :Int64; + type @17 :Text; + user @18 :Text; + + versionCodename @19 :Text; + versionRelease @20 :Text; + versionSdk @21 :Int32; + versionSecurityPatch @22 :Text; + } + + struct AndroidSensor { + id @0 :Int32; + name @1 :Text; + vendor @2 :Text; + version @3 :Int32; + handle @4 :Int32; + type @5 :Int32; + maxRange @6 :Float32; + resolution @7 :Float32; + power @8 :Float32; + minDelay @9 :Int32; + fifoReservedEventCount @10 :UInt32; + fifoMaxEventCount @11 :UInt32; + stringType @12 :Text; + maxDelay @13 :Int32; + } + + struct ChffrAndroidExtra { + allCameraCharacteristics @0 :Map(Text, Text); + } + + struct IosBuildInfo { + appVersion @0 :Text; + appBuild @1 :UInt32; + osVersion @2 :Text; + deviceModel @3 :Text; + } +} + +struct FrameData { + frameId @0 :UInt32; + frameIdSensor @25 :UInt32; + requestId @28 :UInt32; + encodeId @1 :UInt32; + + frameType @7 :FrameType; + + # Timestamps + timestampEof @2 :UInt64; + timestampSof @8 :UInt64; + processingTime @23 :Float32; + + # Exposure + integLines @4 :Int32; + highConversionGain @20 :Bool; + gain @15 :Float32; # This includes highConversionGain if enabled + measuredGreyFraction @21 :Float32; + targetGreyFraction @22 :Float32; + exposureValPercent @27 :Float32; + + transform @10 :List(Float32); + + image @6 :Data; + + temperaturesC @24 :List(Float32); + + enum FrameType { + unknown @0; + neo @1; + chffrAndroid @2; + front @3; + } + + sensor @26 :ImageSensor; + enum ImageSensor { + unknown @0; + ar0231 @1; + ox03c10 @2; + os04c10 @3; + } + + frameLengthDEPRECATED @3 :Int32; + globalGainDEPRECATED @5 :Int32; + androidCaptureResultDEPRECATED @9 :AndroidCaptureResult; + lensPosDEPRECATED @11 :Int32; + lensSagDEPRECATED @12 :Float32; + lensErrDEPRECATED @13 :Float32; + lensTruePosDEPRECATED @14 :Float32; + focusValDEPRECATED @16 :List(Int16); + focusConfDEPRECATED @17 :List(UInt8); + sharpnessScoreDEPRECATED @18 :List(UInt16); + recoverStateDEPRECATED @19 :Int32; + struct AndroidCaptureResult { + sensitivity @0 :Int32; + frameDuration @1 :Int64; + exposureTime @2 :Int64; + rollingShutterSkew @3 :UInt64; + colorCorrectionTransform @4 :List(Int32); + colorCorrectionGains @5 :List(Float32); + displayRotation @6 :Int8; + } +} + +struct Thumbnail { + frameId @0 :UInt32; + timestampEof @1 :UInt64; + thumbnail @2 :Data; + encoding @3 :Encoding; + + enum Encoding { + unknown @0; + jpeg @1; + keyframe @2; + } +} + +struct GPSNMEAData { + timestamp @0 :Int64; + localWallTime @1 :UInt64; + nmea @2 :Text; +} + +# android sensor_event_t +struct SensorEventData { + version @0 :Int32; + sensor @1 :Int32; + type @2 :Int32; + timestamp @3 :Int64; + uncalibratedDEPRECATED @10 :Bool; + + union { + acceleration @4 :SensorVec; + magnetic @5 :SensorVec; + orientation @6 :SensorVec; + gyro @7 :SensorVec; + pressure @9 :SensorVec; + magneticUncalibrated @11 :SensorVec; + gyroUncalibrated @12 :SensorVec; + proximity @13: Float32; + light @14: Float32; + temperature @15: Float32; + } + source @8 :SensorSource; + + struct SensorVec { + v @0 :List(Float32); + status @1 :Int8; + } + + enum SensorSource { + android @0; + iOS @1; + fiber @2; + velodyne @3; # Velodyne IMU + bno055 @4; # Bosch accelerometer + lsm6ds3 @5; # includes LSM6DS3 and LSM6DS3TR, TR = tape reel + bmp280 @6; # barometer + mmc3416x @7; # magnetometer + bmx055 @8; + rpr0521 @9; + lsm6ds3trc @10; + mmc5603nj @11; + } +} + +# android struct GpsLocation +struct GpsLocationData { + # Contains module-specific flags. + flags @0 :UInt16; + + # Represents latitude in degrees. + latitude @1 :Float64; + + # Represents longitude in degrees. + longitude @2 :Float64; + + # Represents altitude in meters above the WGS 84 reference ellipsoid. + altitude @3 :Float64; + + # Represents speed in meters per second. + speed @4 :Float32; + + # Represents heading in degrees. + bearingDeg @5 :Float32; + + # Represents expected horizontal accuracy in meters. + horizontalAccuracy @6 :Float32; + + unixTimestampMillis @7 :Int64; + + source @8 :SensorSource; + + # Represents NED velocity in m/s. + vNED @9 :List(Float32); + + # Represents expected vertical accuracy in meters. (presumably 1 sigma?) + verticalAccuracy @10 :Float32; + + # Represents bearing accuracy in degrees. (presumably 1 sigma?) + bearingAccuracyDeg @11 :Float32; + + # Represents velocity accuracy in m/s. (presumably 1 sigma?) + speedAccuracy @12 :Float32; + + hasFix @13 :Bool; + + enum SensorSource { + android @0; + iOS @1; + car @2; + velodyne @3; # Velodyne IMU + fusion @4; + external @5; + ublox @6; + trimble @7; + qcomdiag @8; + unicore @9; + } +} + +enum Desire { + none @0; + turnLeft @1; + turnRight @2; + laneChangeLeft @3; + laneChangeRight @4; + keepLeft @5; + keepRight @6; +} + +enum LaneChangeState { + off @0; + preLaneChange @1; + laneChangeStarting @2; + laneChangeFinishing @3; +} + +enum LaneChangeDirection { + none @0; + left @1; + right @2; +} + +struct CanData { + address @0 :UInt32; + busTime @1 :UInt16; + dat @2 :Data; + src @3 :UInt8; +} + +struct DeviceState @0xa4d8b5af2aa492eb { + deviceType @45 :InitData.DeviceType; + + networkType @22 :NetworkType; + networkInfo @31 :NetworkInfo; + networkStrength @24 :NetworkStrength; + networkStats @43 :NetworkStats; + networkMetered @41 :Bool; + lastAthenaPingTime @32 :UInt64; + + started @11 :Bool; + startedMonoTime @13 :UInt64; + + # system utilization + freeSpacePercent @7 :Float32; + memoryUsagePercent @19 :Int8; + gpuUsagePercent @33 :Int8; + cpuUsagePercent @34 :List(Int8); # per-core cpu usage + + # power + offroadPowerUsageUwh @23 :UInt32; + carBatteryCapacityUwh @25 :UInt32; + powerDrawW @40 :Float32; + somPowerDrawW @42 :Float32; + + # device thermals + cpuTempC @26 :List(Float32); + gpuTempC @27 :List(Float32); + memoryTempC @28 :Float32; + nvmeTempC @35 :List(Float32); + modemTempC @36 :List(Float32); + pmicTempC @39 :List(Float32); + maxTempC @44 :Float32; # max of other temps, used to control fan + thermalZones @38 :List(ThermalZone); + thermalStatus @14 :ThermalStatus; + + fanSpeedPercentDesired @10 :UInt16; + screenBrightnessPercent @37 :Int8; + + struct ThermalZone { + name @0 :Text; + temp @1 :Float32; + } + + enum ThermalStatus { + green @0; + yellow @1; + red @2; + danger @3; + } + + enum NetworkType { + none @0; + wifi @1; + cell2G @2; + cell3G @3; + cell4G @4; + cell5G @5; + ethernet @6; + } + + enum NetworkStrength { + unknown @0; + poor @1; + moderate @2; + good @3; + great @4; + } + + struct NetworkInfo { + technology @0 :Text; + operator @1 :Text; + band @2 :Text; + channel @3 :UInt16; + extra @4 :Text; + state @5 :Text; + } + + struct NetworkStats { + wwanTx @0 :Int64; + wwanRx @1 :Int64; + } + + # deprecated + cpu0DEPRECATED @0 :UInt16; + cpu1DEPRECATED @1 :UInt16; + cpu2DEPRECATED @2 :UInt16; + cpu3DEPRECATED @3 :UInt16; + memDEPRECATED @4 :UInt16; + gpuDEPRECATED @5 :UInt16; + batDEPRECATED @6 :UInt32; + pa0DEPRECATED @21 :UInt16; + cpuUsagePercentDEPRECATED @20 :Int8; + batteryStatusDEPRECATED @9 :Text; + batteryVoltageDEPRECATED @16 :Int32; + batteryTempCDEPRECATED @29 :Float32; + batteryPercentDEPRECATED @8 :Int16; + batteryCurrentDEPRECATED @15 :Int32; + chargingErrorDEPRECATED @17 :Bool; + chargingDisabledDEPRECATED @18 :Bool; + usbOnlineDEPRECATED @12 :Bool; + ambientTempCDEPRECATED @30 :Float32; +} + +struct PandaState @0xa7649e2575e4591e { + ignitionLine @2 :Bool; + rxBufferOverflow @7 :UInt32; + txBufferOverflow @8 :UInt32; + pandaType @10 :PandaType; + ignitionCan @13 :Bool; + faultStatus @15 :FaultStatus; + powerSaveEnabled @16 :Bool; + uptime @17 :UInt32; + faults @18 :List(FaultType); + heartbeatLost @22 :Bool; + interruptLoad @25 :Float32; + fanPower @28 :UInt8; + fanStallCount @34 :UInt8; + + spiChecksumErrorCount @33 :UInt16; + + harnessStatus @21 :HarnessStatus; + sbu1Voltage @35 :Float32; + sbu2Voltage @36 :Float32; + + # can health + canState0 @29 :PandaCanState; + canState1 @30 :PandaCanState; + canState2 @31 :PandaCanState; + + # safety stuff + controlsAllowed @3 :Bool; + safetyRxInvalid @19 :UInt32; + safetyTxBlocked @24 :UInt32; + safetyModel @14 :Car.CarParams.SafetyModel; + safetyParam @27 :UInt16; + alternativeExperience @23 :Int16; + safetyRxChecksInvalid @32 :Bool; + + voltage @0 :UInt32; + current @1 :UInt32; + + enum FaultStatus { + none @0; + faultTemp @1; + faultPerm @2; + } + + enum FaultType { + relayMalfunction @0; + unusedInterruptHandled @1; + interruptRateCan1 @2; + interruptRateCan2 @3; + interruptRateCan3 @4; + interruptRateTach @5; + interruptRateGmlanDEPRECATED @6; + interruptRateInterrupts @7; + interruptRateSpiDma @8; + interruptRateSpiCs @9; + interruptRateUart1 @10; + interruptRateUart2 @11; + interruptRateUart3 @12; + interruptRateUart5 @13; + interruptRateUartDma @14; + interruptRateUsb @15; + interruptRateTim1 @16; + interruptRateTim3 @17; + registerDivergent @18; + interruptRateKlineInit @19; + interruptRateClockSource @20; + interruptRateTick @21; + interruptRateExti @22; + interruptRateSpi @23; + interruptRateUart7 @24; + sirenMalfunction @25; + heartbeatLoopWatchdog @26; + # Update max fault type in boardd when adding faults + } + + enum PandaType @0x8a58adf93e5b3751 { + unknown @0; + whitePanda @1; + greyPanda @2; + blackPanda @3; + pedal @4; + uno @5; + dos @6; + redPanda @7; + redPandaV2 @8; + tres @9; + cuatro @10; + } + + enum HarnessStatus { + notConnected @0; + normal @1; + flipped @2; + } + + struct PandaCanState { + busOff @0 :Bool; + busOffCnt @1 :UInt32; + errorWarning @2 :Bool; + errorPassive @3 :Bool; + lastError @4 :LecErrorCode; + lastStoredError @5 :LecErrorCode; + lastDataError @6 :LecErrorCode; + lastDataStoredError @7 :LecErrorCode; + receiveErrorCnt @8 :UInt8; + transmitErrorCnt @9 :UInt8; + totalErrorCnt @10 :UInt32; + totalTxLostCnt @11 :UInt32; + totalRxLostCnt @12 :UInt32; + totalTxCnt @13 :UInt32; + totalRxCnt @14 :UInt32; + totalFwdCnt @15 :UInt32; + canSpeed @16 :UInt16; + canDataSpeed @17 :UInt16; + canfdEnabled @18 :Bool; + brsEnabled @19 :Bool; + canfdNonIso @20 :Bool; + irq0CallRate @21 :UInt32; + irq1CallRate @22 :UInt32; + irq2CallRate @23 :UInt32; + canCoreResetCnt @24 :UInt32; + + enum LecErrorCode { + noError @0; + stuffError @1; + formError @2; + ackError @3; + bit1Error @4; + bit0Error @5; + crcError @6; + noChange @7; + } + } + + gasInterceptorDetectedDEPRECATED @4 :Bool; + startedSignalDetectedDEPRECATED @5 :Bool; + hasGpsDEPRECATED @6 :Bool; + gmlanSendErrsDEPRECATED @9 :UInt32; + fanSpeedRpmDEPRECATED @11 :UInt16; + usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED; + safetyParamDEPRECATED @20 :Int16; + safetyParam2DEPRECATED @26 :UInt32; +} + +struct PeripheralState { + pandaType @0 :PandaState.PandaType; + voltage @1 :UInt32; + current @2 :UInt32; + fanSpeedRpm @3 :UInt16; + + usbPowerModeDEPRECATED @4 :UsbPowerModeDEPRECATED; + enum UsbPowerModeDEPRECATED @0xa8883583b32c9877 { + none @0; + client @1; + cdp @2; + dcp @3; + } +} + +struct RadarState @0x9a185389d6fdd05f { + mdMonoTime @6 :UInt64; + carStateMonoTime @11 :UInt64; + radarErrors @12 :List(Car.RadarData.Error); + + leadOne @3 :LeadData; + leadTwo @4 :LeadData; + cumLagMs @5 :Float32; + + struct LeadData { + dRel @0 :Float32; + yRel @1 :Float32; + vRel @2 :Float32; + aRel @3 :Float32; + vLead @4 :Float32; + dPath @6 :Float32; + vLat @7 :Float32; + vLeadK @8 :Float32; + aLeadK @9 :Float32; + fcw @10 :Bool; + status @11 :Bool; + aLeadTau @12 :Float32; + modelProb @13 :Float32; + radar @14 :Bool; + radarTrackId @15 :Int32 = -1; + + aLeadDEPRECATED @5 :Float32; + } + + # deprecated + ftMonoTimeDEPRECATED @7 :UInt64; + warpMatrixDEPRECATED @0 :List(Float32); + angleOffsetDEPRECATED @1 :Float32; + calStatusDEPRECATED @2 :Int8; + calCycleDEPRECATED @8 :Int32; + calPercDEPRECATED @9 :Int8; + canMonoTimesDEPRECATED @10 :List(UInt64); +} + +struct LiveCalibrationData { + calStatus @11 :Status; + calCycle @2 :Int32; + calPerc @3 :Int8; + validBlocks @9 :Int32; + + # view_frame_from_road_frame + # ui's is inversed needs new + extrinsicMatrix @4 :List(Float32); + # the direction of travel vector in device frame + rpyCalib @7 :List(Float32); + rpyCalibSpread @8 :List(Float32); + wideFromDeviceEuler @10 :List(Float32); + height @12 :List(Float32); + + warpMatrixDEPRECATED @0 :List(Float32); + calStatusDEPRECATED @1 :Int8; + warpMatrix2DEPRECATED @5 :List(Float32); + warpMatrixBigDEPRECATED @6 :List(Float32); + + enum Status { + uncalibrated @0; + calibrated @1; + invalid @2; + recalibrating @3; + } +} + +struct LiveTracks { + trackId @0 :Int32; + dRel @1 :Float32; + yRel @2 :Float32; + vRel @3 :Float32; + aRel @4 :Float32; + timeStamp @5 :Float32; + status @6 :Float32; + currentTime @7 :Float32; + stationary @8 :Bool; + oncoming @9 :Bool; +} + +struct ControlsState @0x97ff69c53601abf1 { + startMonoTime @48 :UInt64; + longitudinalPlanMonoTime @28 :UInt64; + lateralPlanMonoTime @50 :UInt64; + + state @31 :OpenpilotState; + enabled @19 :Bool; + active @36 :Bool; + + experimentalMode @64 :Bool; + personality @66 :LongitudinalPersonality; + + longControlState @30 :Car.CarControl.Actuators.LongControlState; + vTargetLead @3 :Float32; + vCruise @22 :Float32; # actual set speed + vCruiseCluster @63 :Float32; # set speed to display in the UI + upAccelCmd @4 :Float32; + uiAccelCmd @5 :Float32; + ufAccelCmd @33 :Float32; + aTarget @35 :Float32; + curvature @37 :Float32; # path curvature from vehicle model + desiredCurvature @61 :Float32; # lag adjusted curvatures used by lateral controllers + forceDecel @51 :Bool; + + # UI alerts + alertText1 @24 :Text; + alertText2 @25 :Text; + alertStatus @38 :AlertStatus; + alertSize @39 :AlertSize; + alertBlinkingRate @42 :Float32; + alertType @44 :Text; + alertSound @56 :Car.CarControl.HUDControl.AudibleAlert; + engageable @41 :Bool; # can OP be engaged? + + cumLagMs @15 :Float32; + + lateralControlState :union { + indiState @52 :LateralINDIState; + pidState @53 :LateralPIDState; + angleState @58 :LateralAngleState; + debugState @59 :LateralDebugState; + torqueState @60 :LateralTorqueState; + + curvatureStateDEPRECATED @65 :LateralCurvatureState; + lqrStateDEPRECATED @55 :LateralLQRState; + } + + enum OpenpilotState @0xdbe58b96d2d1ac61 { + disabled @0; + preEnabled @1; + enabled @2; + softDisabling @3; + overriding @4; # superset of overriding with steering or accelerator + } + + enum AlertStatus { + normal @0; # low priority alert for user's convenience + userPrompt @1; # mid priority alert that might require user intervention + critical @2; # high priority alert that needs immediate user intervention + } + + enum AlertSize { + none @0; # don't display the alert + small @1; # small box + mid @2; # mid screen + full @3; # full screen + } + + struct LateralINDIState { + active @0 :Bool; + steeringAngleDeg @1 :Float32; + steeringRateDeg @2 :Float32; + steeringAccelDeg @3 :Float32; + rateSetPoint @4 :Float32; + accelSetPoint @5 :Float32; + accelError @6 :Float32; + delayedOutput @7 :Float32; + delta @8 :Float32; + output @9 :Float32; + saturated @10 :Bool; + steeringAngleDesiredDeg @11 :Float32; + steeringRateDesiredDeg @12 :Float32; + } + + struct LateralPIDState { + active @0 :Bool; + steeringAngleDeg @1 :Float32; + steeringRateDeg @2 :Float32; + angleError @3 :Float32; + p @4 :Float32; + i @5 :Float32; + f @6 :Float32; + output @7 :Float32; + saturated @8 :Bool; + steeringAngleDesiredDeg @9 :Float32; + } + + struct LateralTorqueState { + active @0 :Bool; + error @1 :Float32; + errorRate @8 :Float32; + p @2 :Float32; + i @3 :Float32; + d @4 :Float32; + f @5 :Float32; + output @6 :Float32; + saturated @7 :Bool; + actualLateralAccel @9 :Float32; + desiredLateralAccel @10 :Float32; + } + + struct LateralLQRState { + active @0 :Bool; + steeringAngleDeg @1 :Float32; + i @2 :Float32; + output @3 :Float32; + lqrOutput @4 :Float32; + saturated @5 :Bool; + steeringAngleDesiredDeg @6 :Float32; + } + + struct LateralAngleState { + active @0 :Bool; + steeringAngleDeg @1 :Float32; + output @2 :Float32; + saturated @3 :Bool; + steeringAngleDesiredDeg @4 :Float32; + } + + struct LateralCurvatureState { + active @0 :Bool; + actualCurvature @1 :Float32; + desiredCurvature @2 :Float32; + error @3 :Float32; + p @4 :Float32; + i @5 :Float32; + f @6 :Float32; + output @7 :Float32; + saturated @8 :Bool; + } + + struct LateralDebugState { + active @0 :Bool; + steeringAngleDeg @1 :Float32; + output @2 :Float32; + saturated @3 :Bool; + } + + # deprecated + vEgoDEPRECATED @0 :Float32; + vEgoRawDEPRECATED @32 :Float32; + aEgoDEPRECATED @1 :Float32; + canMonoTimeDEPRECATED @16 :UInt64; + radarStateMonoTimeDEPRECATED @17 :UInt64; + mdMonoTimeDEPRECATED @18 :UInt64; + yActualDEPRECATED @6 :Float32; + yDesDEPRECATED @7 :Float32; + upSteerDEPRECATED @8 :Float32; + uiSteerDEPRECATED @9 :Float32; + ufSteerDEPRECATED @34 :Float32; + aTargetMinDEPRECATED @10 :Float32; + aTargetMaxDEPRECATED @11 :Float32; + rearViewCamDEPRECATED @23 :Bool; + driverMonitoringOnDEPRECATED @43 :Bool; + hudLeadDEPRECATED @14 :Int32; + alertSoundDEPRECATED @45 :Text; + angleModelBiasDEPRECATED @27 :Float32; + gpsPlannerActiveDEPRECATED @40 :Bool; + decelForTurnDEPRECATED @47 :Bool; + decelForModelDEPRECATED @54 :Bool; + awarenessStatusDEPRECATED @26 :Float32; + angleSteersDEPRECATED @13 :Float32; + vCurvatureDEPRECATED @46 :Float32; + mapValidDEPRECATED @49 :Bool; + jerkFactorDEPRECATED @12 :Float32; + steerOverrideDEPRECATED @20 :Bool; + steeringAngleDesiredDegDEPRECATED @29 :Float32; + canMonoTimesDEPRECATED @21 :List(UInt64); + desiredCurvatureRateDEPRECATED @62 :Float32; + canErrorCounterDEPRECATED @57 :UInt32; + vPidDEPRECATED @2 :Float32; +} + +# All SI units and in device frame +struct XYZTData @0xc3cbae1fd505ae80 { + x @0 :List(Float32); + y @1 :List(Float32); + z @2 :List(Float32); + t @3 :List(Float32); + xStd @4 :List(Float32); + yStd @5 :List(Float32); + zStd @6 :List(Float32); +} + +struct ModelDataV2 { + frameId @0 :UInt32; + frameIdExtra @20 :UInt32; + frameAge @1 :UInt32; + frameDropPerc @2 :Float32; + timestampEof @3 :UInt64; + modelExecutionTime @15 :Float32; + gpuExecutionTime @17 :Float32; + rawPredictions @16 :Data; + + # predicted future position, orientation, etc.. + position @4 :XYZTData; + orientation @5 :XYZTData; + velocity @6 :XYZTData; + orientationRate @7 :XYZTData; + acceleration @19 :XYZTData; + + # prediction lanelines and road edges + laneLines @8 :List(XYZTData); + laneLineProbs @9 :List(Float32); + laneLineStds @13 :List(Float32); + roadEdges @10 :List(XYZTData); + roadEdgeStds @14 :List(Float32); + + # predicted lead cars + leads @11 :List(LeadDataV2); + leadsV3 @18 :List(LeadDataV3); + + meta @12 :MetaData; + confidence @23: ConfidenceClass; + + # Model perceived motion + temporalPose @21 :Pose; + + navEnabledDEPRECATED @22 :Bool; + locationMonoTimeDEPRECATED @24 :UInt64; + + # e2e lateral planner + lateralPlannerSolutionDEPRECATED @25: LateralPlannerSolution; + action @26: Action; + + struct LeadDataV2 { + prob @0 :Float32; # probability that car is your lead at time t + t @1 :Float32; + + # x and y are relative position in device frame + # v is norm relative speed + # a is norm relative acceleration + xyva @2 :List(Float32); + xyvaStd @3 :List(Float32); + } + + struct LeadDataV3 { + prob @0 :Float32; # probability that car is your lead at time t + probTime @1 :Float32; + t @2 :List(Float32); + + # x and y are relative position in device frame + # v absolute norm speed + # a is derivative of v + x @3 :List(Float32); + xStd @4 :List(Float32); + y @5 :List(Float32); + yStd @6 :List(Float32); + v @7 :List(Float32); + vStd @8 :List(Float32); + a @9 :List(Float32); + aStd @10 :List(Float32); + } + + + struct MetaData { + engagedProb @0 :Float32; + desirePrediction @1 :List(Float32); + desireState @5 :List(Float32); + disengagePredictions @6 :DisengagePredictions; + hardBrakePredicted @7 :Bool; + laneChangeState @8 :LaneChangeState; + laneChangeDirection @9 :LaneChangeDirection; + + + # deprecated + brakeDisengageProbDEPRECATED @2 :Float32; + gasDisengageProbDEPRECATED @3 :Float32; + steerOverrideProbDEPRECATED @4 :Float32; + } + + enum ConfidenceClass { + red @0; + yellow @1; + green @2; + } + + struct DisengagePredictions { + t @0 :List(Float32); + brakeDisengageProbs @1 :List(Float32); + gasDisengageProbs @2 :List(Float32); + steerOverrideProbs @3 :List(Float32); + brake3MetersPerSecondSquaredProbs @4 :List(Float32); + brake4MetersPerSecondSquaredProbs @5 :List(Float32); + brake5MetersPerSecondSquaredProbs @6 :List(Float32); + } + + struct Pose { + trans @0 :List(Float32); # m/s in device frame + rot @1 :List(Float32); # rad/s in device frame + transStd @2 :List(Float32); # std m/s in device frame + rotStd @3 :List(Float32); # std rad/s in device frame + } + + struct LateralPlannerSolution { + x @0 :List(Float32); + y @1 :List(Float32); + yaw @2 :List(Float32); + yawRate @3 :List(Float32); + xStd @4 :List(Float32); + yStd @5 :List(Float32); + yawStd @6 :List(Float32); + yawRateStd @7 :List(Float32); + } + + struct Action { + desiredCurvature @0 :Float32; + } +} + +struct EncodeIndex { + # picture from camera + frameId @0 :UInt32; + type @1 :Type; + # index of encoder from start of route + encodeId @2 :UInt32; + # minute long segment this frame is in + segmentNum @3 :Int32; + # index into camera file in segment in presentation order + segmentId @4 :UInt32; + # index into camera file in segment in encode order + segmentIdEncode @5 :UInt32; + timestampSof @6 :UInt64; + timestampEof @7 :UInt64; + + # encoder metadata + flags @8 :UInt32; + len @9 :UInt32; + + enum Type { + bigBoxLossless @0; + fullHEVC @1; + qcameraH264 @6; + livestreamH264 @7; + + # deprecated + bigBoxHEVCDEPRECATED @2; + chffrAndroidH264DEPRECATED @3; + fullLosslessClipDEPRECATED @4; + frontDEPRECATED @5; + + } +} + +struct AndroidLogEntry { + id @0 :UInt8; + ts @1 :UInt64; + priority @2 :UInt8; + pid @3 :Int32; + tid @4 :Int32; + tag @5 :Text; + message @6 :Text; +} + +struct LongitudinalPlan @0xe00b5b3eba12876c { + modelMonoTime @9 :UInt64; + hasLead @7 :Bool; + fcw @8 :Bool; + longitudinalPlanSource @15 :LongitudinalPlanSource; + processingDelay @29 :Float32; + + # desired speed/accel/jerk over next 2.5s + accels @32 :List(Float32); + speeds @33 :List(Float32); + jerks @34 :List(Float32); + aTarget @18 :Float32; + shouldStop @37: Bool; + allowThrottle @38: Bool; + allowBrake @39: Bool; + + + solverExecutionTime @35 :Float32; + + enum LongitudinalPlanSource { + cruise @0; + lead0 @1; + lead1 @2; + lead2 @3; + e2e @4; + } + + # deprecated + vCruiseDEPRECATED @16 :Float32; + aCruiseDEPRECATED @17 :Float32; + vTargetDEPRECATED @3 :Float32; + vTargetFutureDEPRECATED @14 :Float32; + vStartDEPRECATED @26 :Float32; + aStartDEPRECATED @27 :Float32; + vMaxDEPRECATED @20 :Float32; + radarStateMonoTimeDEPRECATED @10 :UInt64; + jerkFactorDEPRECATED @6 :Float32; + hasLeftLaneDEPRECATED @23 :Bool; + hasRightLaneDEPRECATED @24 :Bool; + aTargetMinDEPRECATED @4 :Float32; + aTargetMaxDEPRECATED @5 :Float32; + lateralValidDEPRECATED @0 :Bool; + longitudinalValidDEPRECATED @2 :Bool; + dPolyDEPRECATED @1 :List(Float32); + laneWidthDEPRECATED @11 :Float32; + vCurvatureDEPRECATED @21 :Float32; + decelForTurnDEPRECATED @22 :Bool; + mapValidDEPRECATED @25 :Bool; + radarValidDEPRECATED @28 :Bool; + radarCanErrorDEPRECATED @30 :Bool; + commIssueDEPRECATED @31 :Bool; + eventsDEPRECATED @13 :List(Car.CarEvent); + gpsTrajectoryDEPRECATED @12 :GpsTrajectory; + gpsPlannerActiveDEPRECATED @19 :Bool; + personalityDEPRECATED @36 :LongitudinalPersonality; + + struct GpsTrajectory { + x @0 :List(Float32); + y @1 :List(Float32); + } +} +struct UiPlan { + frameId @2 :UInt32; + position @0 :XYZTData; + accel @1 :List(Float32); +} + +struct LateralPlan @0xe1e9318e2ae8b51e { + modelMonoTime @31 :UInt64; + laneWidthDEPRECATED @0 :Float32; + lProbDEPRECATED @5 :Float32; + rProbDEPRECATED @7 :Float32; + dPathPoints @20 :List(Float32); + dProbDEPRECATED @21 :Float32; + + mpcSolutionValid @9 :Bool; + desire @17 :Desire; + laneChangeState @18 :LaneChangeState; + laneChangeDirection @19 :LaneChangeDirection; + useLaneLines @29 :Bool; + + # desired curvatures over next 2.5s in rad/m + psis @26 :List(Float32); + curvatures @27 :List(Float32); + curvatureRates @28 :List(Float32); + + solverExecutionTime @30 :Float32; + solverCost @32 :Float32; + solverState @33 :SolverState; + + struct SolverState { + x @0 :List(List(Float32)); + u @1 :List(Float32); + } + + # deprecated + curvatureDEPRECATED @22 :Float32; + curvatureRateDEPRECATED @23 :Float32; + rawCurvatureDEPRECATED @24 :Float32; + rawCurvatureRateDEPRECATED @25 :Float32; + cProbDEPRECATED @3 :Float32; + dPolyDEPRECATED @1 :List(Float32); + cPolyDEPRECATED @2 :List(Float32); + lPolyDEPRECATED @4 :List(Float32); + rPolyDEPRECATED @6 :List(Float32); + modelValidDEPRECATED @12 :Bool; + commIssueDEPRECATED @15 :Bool; + posenetValidDEPRECATED @16 :Bool; + sensorValidDEPRECATED @14 :Bool; + paramsValidDEPRECATED @10 :Bool; + steeringAngleDegDEPRECATED @8 :Float32; # deg + steeringRateDegDEPRECATED @13 :Float32; # deg/s + angleOffsetDegDEPRECATED @11 :Float32; +} + +struct LiveLocationKalman { + + # More info on reference frames: + # https://github.com/commaai/openpilot/tree/master/common/transformations + + positionECEF @0 : Measurement; + positionGeodetic @1 : Measurement; + velocityECEF @2 : Measurement; + velocityNED @3 : Measurement; + velocityDevice @4 : Measurement; + accelerationDevice @5: Measurement; + + + # These angles are all eulers and roll, pitch, yaw + # orientationECEF transforms to rot matrix: ecef_from_device + orientationECEF @6 : Measurement; + calibratedOrientationECEF @20 : Measurement; + orientationNED @7 : Measurement; + angularVelocityDevice @8 : Measurement; + + # orientationNEDCalibrated transforms to rot matrix: NED_from_calibrated + calibratedOrientationNED @9 : Measurement; + + # Calibrated frame is simply device frame + # aligned with the vehicle + velocityCalibrated @10 : Measurement; + accelerationCalibrated @11 : Measurement; + angularVelocityCalibrated @12 : Measurement; + + gpsWeek @13 :Int32; + gpsTimeOfWeek @14 :Float64; + status @15 :Status; + unixTimestampMillis @16 :Int64; + inputsOK @17 :Bool = true; + posenetOK @18 :Bool = true; + gpsOK @19 :Bool = true; + sensorsOK @21 :Bool = true; + deviceStable @22 :Bool = true; + timeSinceReset @23 :Float64; + excessiveResets @24 :Bool; + timeToFirstFix @25 :Float32; + + filterState @26 : Measurement; + + enum Status { + uninitialized @0; + uncalibrated @1; + valid @2; + } + + struct Measurement { + value @0 : List(Float64); + std @1 : List(Float64); + valid @2 : Bool; + } +} + +struct ProcLog { + cpuTimes @0 :List(CPUTimes); + mem @1 :Mem; + procs @2 :List(Process); + + struct Process { + pid @0 :Int32; + name @1 :Text; + state @2 :UInt8; + ppid @3 :Int32; + + cpuUser @4 :Float32; + cpuSystem @5 :Float32; + cpuChildrenUser @6 :Float32; + cpuChildrenSystem @7 :Float32; + priority @8 :Int64; + nice @9 :Int32; + numThreads @10 :Int32; + startTime @11 :Float64; + + memVms @12 :UInt64; + memRss @13 :UInt64; + + processor @14 :Int32; + + cmdline @15 :List(Text); + exe @16 :Text; + } + + struct CPUTimes { + cpuNum @0 :Int64; + user @1 :Float32; + nice @2 :Float32; + system @3 :Float32; + idle @4 :Float32; + iowait @5 :Float32; + irq @6 :Float32; + softirq @7 :Float32; + } + + struct Mem { + total @0 :UInt64; + free @1 :UInt64; + available @2 :UInt64; + buffers @3 :UInt64; + cached @4 :UInt64; + active @5 :UInt64; + inactive @6 :UInt64; + shared @7 :UInt64; + } +} + +struct GnssMeasurements { + measTime @0 :UInt64; + gpsWeek @1 :Int16; + gpsTimeOfWeek @2 :Float64; + + correctedMeasurements @3 :List(CorrectedMeasurement); + ephemerisStatuses @9 :List(EphemerisStatus); + + kalmanPositionECEF @4 :LiveLocationKalman.Measurement; + kalmanVelocityECEF @5 :LiveLocationKalman.Measurement; + positionECEF @6 :LiveLocationKalman.Measurement; + velocityECEF @7 :LiveLocationKalman.Measurement; + timeToFirstFix @8 :Float32; + # Todo sync this with timing pulse of ublox + + struct EphemerisStatus { + constellationId @0 :ConstellationId; + svId @1 :UInt8; + type @2 :EphemerisType; + source @3 :EphemerisSource; + gpsWeek @4 : UInt16; + tow @5 :Float64; + } + + struct CorrectedMeasurement { + constellationId @0 :ConstellationId; + svId @1 :UInt8; + # Is 0 when not Glonass constellation. + glonassFrequency @2 :Int8; + pseudorange @3 :Float64; + pseudorangeStd @4 :Float64; + pseudorangeRate @5 :Float64; + pseudorangeRateStd @6 :Float64; + # Satellite position and velocity [x,y,z] + satPos @7 :List(Float64); + satVel @8 :List(Float64); + ephemerisSourceDEPRECATED @9 :EphemerisSourceDEPRECATED; + } + + struct EphemerisSourceDEPRECATED { + type @0 :EphemerisType; + # first epoch in file: + gpsWeek @1 :Int16; # -1 if Nav + gpsTimeOfWeek @2 :Int32; # -1 if Nav. Integer for seconds is good enough for logs. + } + + enum ConstellationId { + # Satellite Constellation using the Ublox gnssid as index + gps @0; + sbas @1; + galileo @2; + beidou @3; + imes @4; + qznss @5; + glonass @6; + } + + enum EphemerisType { + nav @0; + # Different ultra-rapid files: + nasaUltraRapid @1; + glonassIacUltraRapid @2; + qcom @3; + } + + enum EphemerisSource { + gnssChip @0; + internet @1; + cache @2; + unknown @3; + } +} + +struct UbloxGnss { + union { + measurementReport @0 :MeasurementReport; + ephemeris @1 :Ephemeris; + ionoData @2 :IonoData; + hwStatus @3 :HwStatus; + hwStatus2 @4 :HwStatus2; + glonassEphemeris @5 :GlonassEphemeris; + satReport @6 :SatReport; + } + + struct SatReport { + #received time of week in gps time in seconds and gps week + iTow @0 :UInt32; + svs @1 :List(SatInfo); + + struct SatInfo { + svId @0 :UInt8; + gnssId @1 :UInt8; + flagsBitfield @2 :UInt32; + } + } + + struct MeasurementReport { + #received time of week in gps time in seconds and gps week + rcvTow @0 :Float64; + gpsWeek @1 :UInt16; + # leap seconds in seconds + leapSeconds @2 :UInt16; + # receiver status + receiverStatus @3 :ReceiverStatus; + # num of measurements to follow + numMeas @4 :UInt8; + measurements @5 :List(Measurement); + + struct ReceiverStatus { + # leap seconds have been determined + leapSecValid @0 :Bool; + # Clock reset applied + clkReset @1 :Bool; + } + + struct Measurement { + svId @0 :UInt8; + trackingStatus @1 :TrackingStatus; + # pseudorange in meters + pseudorange @2 :Float64; + # carrier phase measurement in cycles + carrierCycles @3 :Float64; + # doppler measurement in Hz + doppler @4 :Float32; + # GNSS id, 0 is gps + gnssId @5 :UInt8; + glonassFrequencyIndex @6 :UInt8; + # carrier phase locktime counter in ms + locktime @7 :UInt16; + # Carrier-to-noise density ratio (signal strength) in dBHz + cno @8 :UInt8; + # pseudorange standard deviation in meters + pseudorangeStdev @9 :Float32; + # carrier phase standard deviation in cycles + carrierPhaseStdev @10 :Float32; + # doppler standard deviation in Hz + dopplerStdev @11 :Float32; + sigId @12 :UInt8; + + struct TrackingStatus { + # pseudorange valid + pseudorangeValid @0 :Bool; + # carrier phase valid + carrierPhaseValid @1 :Bool; + # half cycle valid + halfCycleValid @2 :Bool; + # half cycle subtracted from phase + halfCycleSubtracted @3 :Bool; + } + } + } + + struct Ephemeris { + # This is according to the rinex (2?) format + svId @0 :UInt16; + year @1 :UInt16; + month @2 :UInt16; + day @3 :UInt16; + hour @4 :UInt16; + minute @5 :UInt16; + second @6 :Float32; + af0 @7 :Float64; + af1 @8 :Float64; + af2 @9 :Float64; + + iode @10 :Float64; + crs @11 :Float64; + deltaN @12 :Float64; + m0 @13 :Float64; + + cuc @14 :Float64; + ecc @15 :Float64; + cus @16 :Float64; + a @17 :Float64; # note that this is not the root!! + + toe @18 :Float64; + cic @19 :Float64; + omega0 @20 :Float64; + cis @21 :Float64; + + i0 @22 :Float64; + crc @23 :Float64; + omega @24 :Float64; + omegaDot @25 :Float64; + + iDot @26 :Float64; + codesL2 @27 :Float64; + gpsWeekDEPRECATED @28 :Float64; + l2 @29 :Float64; + + svAcc @30 :Float64; + svHealth @31 :Float64; + tgd @32 :Float64; + iodc @33 :Float64; + + transmissionTime @34 :Float64; + fitInterval @35 :Float64; + + toc @36 :Float64; + + ionoCoeffsValid @37 :Bool; + ionoAlpha @38 :List(Float64); + ionoBeta @39 :List(Float64); + + towCount @40 :UInt32; + toeWeek @41 :UInt16; + tocWeek @42 :UInt16; + } + + struct IonoData { + svHealth @0 :UInt32; + tow @1 :Float64; + gpsWeek @2 :Float64; + + ionoAlpha @3 :List(Float64); + ionoBeta @4 :List(Float64); + + healthValid @5 :Bool; + ionoCoeffsValid @6 :Bool; + } + + struct HwStatus { + noisePerMS @0 :UInt16; + agcCnt @1 :UInt16; + aStatus @2 :AntennaSupervisorState; + aPower @3 :AntennaPowerStatus; + jamInd @4 :UInt8; + flags @5 :UInt8; + + enum AntennaSupervisorState { + init @0; + dontknow @1; + ok @2; + short @3; + open @4; + } + + enum AntennaPowerStatus { + off @0; + on @1; + dontknow @2; + } + } + + struct HwStatus2 { + ofsI @0 :Int8; + magI @1 :UInt8; + ofsQ @2 :Int8; + magQ @3 :UInt8; + cfgSource @4 :ConfigSource; + lowLevCfg @5 :UInt32; + postStatus @6 :UInt32; + + enum ConfigSource { + undefined @0; + rom @1; + otp @2; + configpins @3; + flash @4; + } + } + + struct GlonassEphemeris { + svId @0 :UInt16; + year @1 :UInt16; + dayInYear @2 :UInt16; + hour @3 :UInt16; + minute @4 :UInt16; + second @5 :Float32; + + x @6 :Float64; + xVel @7 :Float64; + xAccel @8 :Float64; + y @9 :Float64; + yVel @10 :Float64; + yAccel @11 :Float64; + z @12 :Float64; + zVel @13 :Float64; + zAccel @14 :Float64; + + svType @15 :UInt8; + svURA @16 :Float32; + age @17 :UInt8; + + svHealth @18 :UInt8; + tkDEPRECATED @19 :UInt16; + tb @20 :UInt16; + + tauN @21 :Float64; + deltaTauN @22 :Float64; + gammaN @23 :Float64; + + p1 @24 :UInt8; + p2 @25 :UInt8; + p3 @26 :UInt8; + p4 @27 :UInt8; + + freqNumDEPRECATED @28 :UInt32; + + n4 @29 :UInt8; + nt @30 :UInt16; + freqNum @31 :Int16; + tkSeconds @32 :UInt32; + } +} + +struct QcomGnss @0xde94674b07ae51c1 { + logTs @0 :UInt64; + union { + measurementReport @1 :MeasurementReport; + clockReport @2 :ClockReport; + drMeasurementReport @3 :DrMeasurementReport; + drSvPoly @4 :DrSvPolyReport; + rawLog @5 :Data; + } + + enum MeasurementSource @0xd71a12b6faada7ee { + gps @0; + glonass @1; + beidou @2; + unknown3 @3; + unknown4 @4; + unknown5 @5; + sbas @6; + } + + enum SVObservationState @0xe81e829a0d6c83e9 { + idle @0; + search @1; + searchVerify @2; + bitEdge @3; + trackVerify @4; + track @5; + restart @6; + dpo @7; + glo10msBe @8; + glo10msAt @9; + } + + struct MeasurementStatus @0xe501010e1bcae83b { + subMillisecondIsValid @0 :Bool; + subBitTimeIsKnown @1 :Bool; + satelliteTimeIsKnown @2 :Bool; + bitEdgeConfirmedFromSignal @3 :Bool; + measuredVelocity @4 :Bool; + fineOrCoarseVelocity @5 :Bool; + lockPointValid @6 :Bool; + lockPointPositive @7 :Bool; + lastUpdateFromDifference @8 :Bool; + lastUpdateFromVelocityDifference @9 :Bool; + strongIndicationOfCrossCorelation @10 :Bool; + tentativeMeasurement @11 :Bool; + measurementNotUsable @12 :Bool; + sirCheckIsNeeded @13 :Bool; + probationMode @14 :Bool; + + glonassMeanderBitEdgeValid @15 :Bool; + glonassTimeMarkValid @16 :Bool; + + gpsRoundRobinRxDiversity @17 :Bool; + gpsRxDiversity @18 :Bool; + gpsLowBandwidthRxDiversityCombined @19 :Bool; + gpsHighBandwidthNu4 @20 :Bool; + gpsHighBandwidthNu8 @21 :Bool; + gpsHighBandwidthUniform @22 :Bool; + multipathIndicator @23 :Bool; + + imdJammingIndicator @24 :Bool; + lteB13TxJammingIndicator @25 :Bool; + freshMeasurementIndicator @26 :Bool; + + multipathEstimateIsValid @27 :Bool; + directionIsValid @28 :Bool; + } + + struct MeasurementReport @0xf580d7d86b7b8692 { + source @0 :MeasurementSource; + + fCount @1 :UInt32; + + gpsWeek @2 :UInt16; + glonassCycleNumber @3 :UInt8; + glonassNumberOfDays @4 :UInt16; + + milliseconds @5 :UInt32; + timeBias @6 :Float32; + clockTimeUncertainty @7 :Float32; + clockFrequencyBias @8 :Float32; + clockFrequencyUncertainty @9 :Float32; + + sv @10 :List(SV); + + struct SV @0xf10c595ae7bb2c27 { + svId @0 :UInt8; + observationState @2 :SVObservationState; + observations @3 :UInt8; + goodObservations @4 :UInt8; + gpsParityErrorCount @5 :UInt16; + glonassFrequencyIndex @1 :Int8; + glonassHemmingErrorCount @6 :UInt8; + filterStages @7 :UInt8; + carrierNoise @8 :UInt16; + latency @9 :Int16; + predetectInterval @10 :UInt8; + postdetections @11 :UInt16; + + unfilteredMeasurementIntegral @12 :UInt32; + unfilteredMeasurementFraction @13 :Float32; + unfilteredTimeUncertainty @14 :Float32; + unfilteredSpeed @15 :Float32; + unfilteredSpeedUncertainty @16 :Float32; + measurementStatus @17 :MeasurementStatus; + multipathEstimate @18 :UInt32; + azimuth @19 :Float32; + elevation @20 :Float32; + carrierPhaseCyclesIntegral @21 :Int32; + carrierPhaseCyclesFraction @22 :UInt16; + fineSpeed @23 :Float32; + fineSpeedUncertainty @24 :Float32; + cycleSlipCount @25 :UInt8; + } + + } + + struct ClockReport @0xca965e4add8f4f0b { + hasFCount @0 :Bool; + fCount @1 :UInt32; + + hasGpsWeek @2 :Bool; + gpsWeek @3 :UInt16; + hasGpsMilliseconds @4 :Bool; + gpsMilliseconds @5 :UInt32; + gpsTimeBias @6 :Float32; + gpsClockTimeUncertainty @7 :Float32; + gpsClockSource @8 :UInt8; + + hasGlonassYear @9 :Bool; + glonassYear @10 :UInt8; + hasGlonassDay @11 :Bool; + glonassDay @12 :UInt16; + hasGlonassMilliseconds @13 :Bool; + glonassMilliseconds @14 :UInt32; + glonassTimeBias @15 :Float32; + glonassClockTimeUncertainty @16 :Float32; + glonassClockSource @17 :UInt8; + + bdsWeek @18 :UInt16; + bdsMilliseconds @19 :UInt32; + bdsTimeBias @20 :Float32; + bdsClockTimeUncertainty @21 :Float32; + bdsClockSource @22 :UInt8; + + galWeek @23 :UInt16; + galMilliseconds @24 :UInt32; + galTimeBias @25 :Float32; + galClockTimeUncertainty @26 :Float32; + galClockSource @27 :UInt8; + + clockFrequencyBias @28 :Float32; + clockFrequencyUncertainty @29 :Float32; + frequencySource @30 :UInt8; + gpsLeapSeconds @31 :UInt8; + gpsLeapSecondsUncertainty @32 :UInt8; + gpsLeapSecondsSource @33 :UInt8; + + gpsToGlonassTimeBiasMilliseconds @34 :Float32; + gpsToGlonassTimeBiasMillisecondsUncertainty @35 :Float32; + gpsToBdsTimeBiasMilliseconds @36 :Float32; + gpsToBdsTimeBiasMillisecondsUncertainty @37 :Float32; + bdsToGloTimeBiasMilliseconds @38 :Float32; + bdsToGloTimeBiasMillisecondsUncertainty @39 :Float32; + gpsToGalTimeBiasMilliseconds @40 :Float32; + gpsToGalTimeBiasMillisecondsUncertainty @41 :Float32; + galToGloTimeBiasMilliseconds @42 :Float32; + galToGloTimeBiasMillisecondsUncertainty @43 :Float32; + galToBdsTimeBiasMilliseconds @44 :Float32; + galToBdsTimeBiasMillisecondsUncertainty @45 :Float32; + + hasRtcTime @46 :Bool; + systemRtcTime @47 :UInt32; + fCountOffset @48 :UInt32; + lpmRtcCount @49 :UInt32; + clockResets @50 :UInt32; + } + + struct DrMeasurementReport @0x8053c39445c6c75c { + + reason @0 :UInt8; + seqNum @1 :UInt8; + seqMax @2 :UInt8; + rfLoss @3 :UInt16; + + systemRtcValid @4 :Bool; + fCount @5 :UInt32; + clockResets @6 :UInt32; + systemRtcTime @7 :UInt64; + + gpsLeapSeconds @8 :UInt8; + gpsLeapSecondsUncertainty @9 :UInt8; + gpsToGlonassTimeBiasMilliseconds @10 :Float32; + gpsToGlonassTimeBiasMillisecondsUncertainty @11 :Float32; + + gpsWeek @12 :UInt16; + gpsMilliseconds @13 :UInt32; + gpsTimeBiasMs @14 :UInt32; + gpsClockTimeUncertaintyMs @15 :UInt32; + gpsClockSource @16 :UInt8; + + glonassClockSource @17 :UInt8; + glonassYear @18 :UInt8; + glonassDay @19 :UInt16; + glonassMilliseconds @20 :UInt32; + glonassTimeBias @21 :Float32; + glonassClockTimeUncertainty @22 :Float32; + + clockFrequencyBias @23 :Float32; + clockFrequencyUncertainty @24 :Float32; + frequencySource @25 :UInt8; + + source @26 :MeasurementSource; + + sv @27 :List(SV); + + struct SV @0xf08b81df8cbf459c { + svId @0 :UInt8; + glonassFrequencyIndex @1 :Int8; + observationState @2 :SVObservationState; + observations @3 :UInt8; + goodObservations @4 :UInt8; + filterStages @5 :UInt8; + predetectInterval @6 :UInt8; + cycleSlipCount @7 :UInt8; + postdetections @8 :UInt16; + + measurementStatus @9 :MeasurementStatus; + + carrierNoise @10 :UInt16; + rfLoss @11 :UInt16; + latency @12 :Int16; + + filteredMeasurementFraction @13 :Float32; + filteredMeasurementIntegral @14 :UInt32; + filteredTimeUncertainty @15 :Float32; + filteredSpeed @16 :Float32; + filteredSpeedUncertainty @17 :Float32; + + unfilteredMeasurementFraction @18 :Float32; + unfilteredMeasurementIntegral @19 :UInt32; + unfilteredTimeUncertainty @20 :Float32; + unfilteredSpeed @21 :Float32; + unfilteredSpeedUncertainty @22 :Float32; + + multipathEstimate @23 :UInt32; + azimuth @24 :Float32; + elevation @25 :Float32; + dopplerAcceleration @26 :Float32; + fineSpeed @27 :Float32; + fineSpeedUncertainty @28 :Float32; + + carrierPhase @29 :Float64; + fCount @30 :UInt32; + + parityErrorCount @31 :UInt16; + goodParity @32 :Bool; + } + } + + struct DrSvPolyReport @0xb1fb80811a673270 { + svId @0 :UInt16; + frequencyIndex @1 :Int8; + + hasPosition @2 :Bool; + hasIono @3 :Bool; + hasTropo @4 :Bool; + hasElevation @5 :Bool; + polyFromXtra @6 :Bool; + hasSbasIono @7 :Bool; + + iode @8 :UInt16; + t0 @9 :Float64; + xyz0 @10 :List(Float64); + xyzN @11 :List(Float64); + other @12 :List(Float32); + + positionUncertainty @13 :Float32; + ionoDelay @14 :Float32; + ionoDot @15 :Float32; + sbasIonoDelay @16 :Float32; + sbasIonoDot @17 :Float32; + tropoDelay @18 :Float32; + elevation @19 :Float32; + elevationDot @20 :Float32; + elevationUncertainty @21 :Float32; + velocityCoeff @22 :List(Float64); + + gpsWeek @23 :UInt16; + gpsTow @24 :Float64; + } +} + +struct Clocks { + wallTimeNanos @3 :UInt64; # unix epoch time + + bootTimeNanosDEPRECATED @0 :UInt64; + monotonicNanosDEPRECATED @1 :UInt64; + monotonicRawNanosDEPRECATD @2 :UInt64; + modemUptimeMillisDEPRECATED @4 :UInt64; +} + +struct LiveMpcData { + x @0 :List(Float32); + y @1 :List(Float32); + psi @2 :List(Float32); + curvature @3 :List(Float32); + qpIterations @4 :UInt32; + calculationTime @5 :UInt64; + cost @6 :Float64; +} + +struct LiveLongitudinalMpcData { + xEgo @0 :List(Float32); + vEgo @1 :List(Float32); + aEgo @2 :List(Float32); + xLead @3 :List(Float32); + vLead @4 :List(Float32); + aLead @5 :List(Float32); + aLeadTau @6 :Float32; # lead accel time constant + qpIterations @7 :UInt32; + mpcId @8 :UInt32; + calculationTime @9 :UInt64; + cost @10 :Float64; +} + +struct Joystick { + # convenient for debug and live tuning + axes @0: List(Float32); + buttons @1: List(Bool); +} + +struct DriverStateV2 { + frameId @0 :UInt32; + modelExecutionTime @1 :Float32; + dspExecutionTime @2 :Float32; + rawPredictions @3 :Data; + + poorVisionProb @4 :Float32; + wheelOnRightProb @5 :Float32; + + leftDriverData @6 :DriverData; + rightDriverData @7 :DriverData; + + struct DriverData { + faceOrientation @0 :List(Float32); + faceOrientationStd @1 :List(Float32); + facePosition @2 :List(Float32); + facePositionStd @3 :List(Float32); + faceProb @4 :Float32; + leftEyeProb @5 :Float32; + rightEyeProb @6 :Float32; + leftBlinkProb @7 :Float32; + rightBlinkProb @8 :Float32; + sunglassesProb @9 :Float32; + occludedProb @10 :Float32; + readyProb @11 :List(Float32); + notReadyProb @12 :List(Float32); + } +} + +struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 { + frameId @0 :UInt32; + modelExecutionTime @14 :Float32; + dspExecutionTime @16 :Float32; + rawPredictions @15 :Data; + + faceOrientation @3 :List(Float32); + facePosition @4 :List(Float32); + faceProb @5 :Float32; + leftEyeProb @6 :Float32; + rightEyeProb @7 :Float32; + leftBlinkProb @8 :Float32; + rightBlinkProb @9 :Float32; + faceOrientationStd @11 :List(Float32); + facePositionStd @12 :List(Float32); + sunglassesProb @13 :Float32; + poorVision @17 :Float32; + partialFace @18 :Float32; + distractedPose @19 :Float32; + distractedEyes @20 :Float32; + eyesOnRoad @21 :Float32; + phoneUse @22 :Float32; + occludedProb @23 :Float32; + + readyProb @24 :List(Float32); + notReadyProb @25 :List(Float32); + + irPwrDEPRECATED @10 :Float32; + descriptorDEPRECATED @1 :List(Float32); + stdDEPRECATED @2 :Float32; +} + +struct DriverMonitoringState @0xb83cda094a1da284 { + events @0 :List(Car.CarEvent); + faceDetected @1 :Bool; + isDistracted @2 :Bool; + distractedType @17 :UInt32; + awarenessStatus @3 :Float32; + posePitchOffset @6 :Float32; + posePitchValidCount @7 :UInt32; + poseYawOffset @8 :Float32; + poseYawValidCount @9 :UInt32; + stepChange @10 :Float32; + awarenessActive @11 :Float32; + awarenessPassive @12 :Float32; + isLowStd @13 :Bool; + hiStdCount @14 :UInt32; + isActiveMode @16 :Bool; + isRHD @4 :Bool; + + isPreviewDEPRECATED @15 :Bool; + rhdCheckedDEPRECATED @5 :Bool; +} + +struct Boot { + wallTimeNanos @0 :UInt64; + pstore @4 :Map(Text, Data); + commands @5 :Map(Text, Data); + launchLog @3 :Text; + + lastKmsgDEPRECATED @1 :Data; + lastPmsgDEPRECATED @2 :Data; +} + +struct LiveParametersData { + valid @0 :Bool; + gyroBias @1 :Float32; + angleOffsetDeg @2 :Float32; + angleOffsetAverageDeg @3 :Float32; + stiffnessFactor @4 :Float32; + steerRatio @5 :Float32; + sensorValid @6 :Bool; + posenetSpeed @8 :Float32; + posenetValid @9 :Bool; + angleOffsetFastStd @10 :Float32; + angleOffsetAverageStd @11 :Float32; + stiffnessFactorStd @12 :Float32; + steerRatioStd @13 :Float32; + roll @14 :Float32; + filterState @15 :LiveLocationKalman.Measurement; + + yawRateDEPRECATED @7 :Float32; +} + +struct LiveTorqueParametersData { + liveValid @0 :Bool; + latAccelFactorRaw @1 :Float32; + latAccelOffsetRaw @2 :Float32; + frictionCoefficientRaw @3 :Float32; + latAccelFactorFiltered @4 :Float32; + latAccelOffsetFiltered @5 :Float32; + frictionCoefficientFiltered @6 :Float32; + totalBucketPoints @7 :Float32; + decay @8 :Float32; + maxResets @9 :Float32; + points @10 :List(List(Float32)); + version @11 :Int32; + useParams @12 :Bool; +} + +struct LiveMapDataDEPRECATED { + speedLimitValid @0 :Bool; + speedLimit @1 :Float32; + speedAdvisoryValid @12 :Bool; + speedAdvisory @13 :Float32; + speedLimitAheadValid @14 :Bool; + speedLimitAhead @15 :Float32; + speedLimitAheadDistance @16 :Float32; + curvatureValid @2 :Bool; + curvature @3 :Float32; + wayId @4 :UInt64; + roadX @5 :List(Float32); + roadY @6 :List(Float32); + lastGps @7: GpsLocationData; + roadCurvatureX @8 :List(Float32); + roadCurvature @9 :List(Float32); + distToTurn @10 :Float32; + mapValid @11 :Bool; +} + +struct CameraOdometry { + frameId @4 :UInt32; + timestampEof @5 :UInt64; + trans @0 :List(Float32); # m/s in device frame + rot @1 :List(Float32); # rad/s in device frame + transStd @2 :List(Float32); # std m/s in device frame + rotStd @3 :List(Float32); # std rad/s in device frame + wideFromDeviceEuler @6 :List(Float32); + wideFromDeviceEulerStd @7 :List(Float32); + roadTransformTrans @8 :List(Float32); + roadTransformTransStd @9 :List(Float32); +} + +struct Sentinel { + enum SentinelType { + endOfSegment @0; + endOfRoute @1; + startOfSegment @2; + startOfRoute @3; + } + type @0 :SentinelType; + signal @1 :Int32; +} + +struct UIDebug { + drawTimeMillis @0 :Float32; +} + +struct ManagerState { + processes @0 :List(ProcessState); + + struct ProcessState { + name @0 :Text; + pid @1 :Int32; + running @2 :Bool; + shouldBeRunning @4 :Bool; + exitCode @3 :Int32; + } +} + +struct UploaderState { + immediateQueueSize @0 :UInt32; + immediateQueueCount @1 :UInt32; + rawQueueSize @2 :UInt32; + rawQueueCount @3 :UInt32; + + # stats for last successfully uploaded file + lastTime @4 :Float32; # s + lastSpeed @5 :Float32; # MB/s + lastFilename @6 :Text; +} + +struct NavInstruction { + maneuverPrimaryText @0 :Text; + maneuverSecondaryText @1 :Text; + maneuverDistance @2 :Float32; # m + maneuverType @3 :Text; # TODO: Make Enum + maneuverModifier @4 :Text; # TODO: Make Enum + + distanceRemaining @5 :Float32; # m + timeRemaining @6 :Float32; # s + timeRemainingTypical @7 :Float32; # s + + lanes @8 :List(Lane); + showFull @9 :Bool; + + speedLimit @10 :Float32; # m/s + speedLimitSign @11 :SpeedLimitSign; + + allManeuvers @12 :List(Maneuver); + + struct Lane { + directions @0 :List(Direction); + active @1 :Bool; + activeDirection @2 :Direction; + } + + enum Direction { + none @0; + left @1; + right @2; + straight @3; + slightLeft @4; + slightRight @5; + } + + enum SpeedLimitSign { + mutcd @0; # US Style + vienna @1; # EU Style + } + + struct Maneuver { + distance @0 :Float32; + type @1 :Text; + modifier @2 :Text; + } +} + +struct NavRoute { + coordinates @0 :List(Coordinate); + + struct Coordinate { + latitude @0 :Float32; + longitude @1 :Float32; + } +} + +struct MapRenderState { + locationMonoTime @0 :UInt64; + renderTime @1 :Float32; + frameId @2: UInt32; +} + +struct NavModelData { + frameId @0 :UInt32; + locationMonoTime @6 :UInt64; + modelExecutionTime @1 :Float32; + dspExecutionTime @2 :Float32; + features @3 :List(Float32); + # predicted future position + position @4 :XYData; + desirePrediction @5 :List(Float32); + + # All SI units and in device frame + struct XYData { + x @0 :List(Float32); + y @1 :List(Float32); + xStd @2 :List(Float32); + yStd @3 :List(Float32); + } +} + +struct EncodeData { + idx @0 :EncodeIndex; + data @1 :Data; + header @2 :Data; + unixTimestampNanos @3 :UInt64; + width @4 :UInt32; + height @5 :UInt32; +} + +struct UserFlag { +} + +struct Microphone { + soundPressure @0 :Float32; + + # uncalibrated, A-weighted + soundPressureWeighted @3 :Float32; + soundPressureWeightedDb @1 :Float32; + filteredSoundPressureWeightedDb @2 :Float32; +} + +struct Event { + logMonoTime @0 :UInt64; # nanoseconds + valid @67 :Bool = true; + + union { + # *********** log metadata *********** + initData @1 :InitData; + sentinel @73 :Sentinel; + + # *********** bootlog *********** + boot @60 :Boot; + + # ********** openpilot daemon msgs ********** + gpsNMEA @3 :GPSNMEAData; + can @5 :List(CanData); + controlsState @7 :ControlsState; + gyroscope @99 :SensorEventData; + gyroscope2 @100 :SensorEventData; + accelerometer @98 :SensorEventData; + accelerometer2 @101 :SensorEventData; + magnetometer @95 :SensorEventData; + lightSensor @96 :SensorEventData; + temperatureSensor @97 :SensorEventData; + temperatureSensor2 @123 :SensorEventData; + pandaStates @81 :List(PandaState); + peripheralState @80 :PeripheralState; + radarState @13 :RadarState; + liveTracks @16 :List(LiveTracks); + sendcan @17 :List(CanData); + liveCalibration @19 :LiveCalibrationData; + carState @22 :Car.CarState; + carControl @23 :Car.CarControl; + carOutput @127 :Car.CarOutput; + longitudinalPlan @24 :LongitudinalPlan; + ubloxGnss @34 :UbloxGnss; + ubloxRaw @39 :Data; + qcomGnss @31 :QcomGnss; + gpsLocationExternal @48 :GpsLocationData; + gpsLocation @21 :GpsLocationData; + gnssMeasurements @91 :GnssMeasurements; + liveParameters @61 :LiveParametersData; + liveTorqueParameters @94 :LiveTorqueParametersData; + cameraOdometry @63 :CameraOdometry; + thumbnail @66: Thumbnail; + onroadEvents @68: List(Car.CarEvent); + carParams @69: Car.CarParams; + driverMonitoringState @71: DriverMonitoringState; + liveLocationKalman @72 :LiveLocationKalman; + modelV2 @75 :ModelDataV2; + driverStateV2 @92 :DriverStateV2; + + # camera stuff, each camera state has a matching encode idx + roadCameraState @2 :FrameData; + driverCameraState @70: FrameData; + wideRoadCameraState @74: FrameData; + roadEncodeIdx @15 :EncodeIndex; + driverEncodeIdx @76 :EncodeIndex; + wideRoadEncodeIdx @77 :EncodeIndex; + qRoadEncodeIdx @90 :EncodeIndex; + + livestreamRoadEncodeIdx @117 :EncodeIndex; + livestreamWideRoadEncodeIdx @118 :EncodeIndex; + livestreamDriverEncodeIdx @119 :EncodeIndex; + + # microphone data + microphone @103 :Microphone; + + # systems stuff + androidLog @20 :AndroidLogEntry; + managerState @78 :ManagerState; + uploaderState @79 :UploaderState; + procLog @33 :ProcLog; + clocks @35 :Clocks; + deviceState @6 :DeviceState; + logMessage @18 :Text; + errorLogMessage @85 :Text; + + # navigation + navInstruction @82 :NavInstruction; + navRoute @83 :NavRoute; + navThumbnail @84: Thumbnail; + mapRenderState @105: MapRenderState; + + # UI services + userFlag @93 :UserFlag; + uiDebug @102 :UIDebug; + + # *********** debug *********** + testJoystick @52 :Joystick; + roadEncodeData @86 :EncodeData; + driverEncodeData @87 :EncodeData; + wideRoadEncodeData @88 :EncodeData; + qRoadEncodeData @89 :EncodeData; + + livestreamRoadEncodeData @120 :EncodeData; + livestreamWideRoadEncodeData @121 :EncodeData; + livestreamDriverEncodeData @122 :EncodeData; + + customReservedRawData0 @124 :Data; + customReservedRawData1 @125 :Data; + customReservedRawData2 @126 :Data; + + # *********** Custom: reserved for forks *********** + customReserved0 @107 :Custom.CustomReserved0; + customReserved1 @108 :Custom.CustomReserved1; + customReserved2 @109 :Custom.CustomReserved2; + customReserved3 @110 :Custom.CustomReserved3; + customReserved4 @111 :Custom.CustomReserved4; + customReserved5 @112 :Custom.CustomReserved5; + customReserved6 @113 :Custom.CustomReserved6; + customReserved7 @114 :Custom.CustomReserved7; + customReserved8 @115 :Custom.CustomReserved8; + customReserved9 @116 :Custom.CustomReserved9; + + # *********** legacy + deprecated *********** + model @9 :Legacy.ModelData; # TODO: rename modelV2 and mark this as deprecated + liveMpcDEPRECATED @36 :LiveMpcData; + liveLongitudinalMpcDEPRECATED @37 :LiveLongitudinalMpcData; + liveLocationKalmanDEPRECATED @51 :Legacy.LiveLocationData; + orbslamCorrectionDEPRECATED @45 :Legacy.OrbslamCorrection; + liveUIDEPRECATED @14 :Legacy.LiveUI; + sensorEventDEPRECATED @4 :SensorEventData; + liveEventDEPRECATED @8 :List(Legacy.LiveEventData); + liveLocationDEPRECATED @25 :Legacy.LiveLocationData; + ethernetDataDEPRECATED @26 :List(Legacy.EthernetPacket); + cellInfoDEPRECATED @28 :List(Legacy.CellInfo); + wifiScanDEPRECATED @29 :List(Legacy.WifiScan); + uiNavigationEventDEPRECATED @50 :Legacy.UiNavigationEvent; + liveMapDataDEPRECATED @62 :LiveMapDataDEPRECATED; + gpsPlannerPointsDEPRECATED @40 :Legacy.GPSPlannerPoints; + gpsPlannerPlanDEPRECATED @41 :Legacy.GPSPlannerPlan; + applanixRawDEPRECATED @42 :Data; + androidGnssDEPRECATED @30 :Legacy.AndroidGnss; + lidarPtsDEPRECATED @32 :Legacy.LidarPts; + navStatusDEPRECATED @38 :Legacy.NavStatus; + trafficEventsDEPRECATED @43 :List(Legacy.TrafficEvent); + liveLocationTimingDEPRECATED @44 :Legacy.LiveLocationData; + liveLocationCorrectedDEPRECATED @46 :Legacy.LiveLocationData; + navUpdateDEPRECATED @27 :Legacy.NavUpdate; + orbObservationDEPRECATED @47 :List(Legacy.OrbObservation); + locationDEPRECATED @49 :Legacy.LiveLocationData; + orbOdometryDEPRECATED @53 :Legacy.OrbOdometry; + orbFeaturesDEPRECATED @54 :Legacy.OrbFeatures; + applanixLocationDEPRECATED @55 :Legacy.LiveLocationData; + orbKeyFrameDEPRECATED @56 :Legacy.OrbKeyFrame; + orbFeaturesSummaryDEPRECATED @58 :Legacy.OrbFeaturesSummary; + featuresDEPRECATED @10 :Legacy.CalibrationFeatures; + kalmanOdometryDEPRECATED @65 :Legacy.KalmanOdometry; + uiLayoutStateDEPRECATED @57 :Legacy.UiLayoutState; + pandaStateDEPRECATED @12 :PandaState; + driverStateDEPRECATED @59 :DriverStateDEPRECATED; + sensorEventsDEPRECATED @11 :List(SensorEventData); + lateralPlanDEPRECATED @64 :LateralPlan; + navModelDEPRECATED @104 :NavModelData; + uiPlanDEPRECATED @106 :UiPlan; + } +} diff --git a/cereal/maptile.capnp b/cereal/maptile.capnp new file mode 100644 index 0000000000..c8a23a1822 --- /dev/null +++ b/cereal/maptile.capnp @@ -0,0 +1,49 @@ +using Cxx = import "./include/c++.capnp"; +$Cxx.namespace("cereal"); + +@0xa086df597ef5d7a0; + +# Geometry +struct Point { + x @0: Float64; + y @1: Float64; + z @2: Float64; +} + +struct PolyLine { + points @0: List(Point); +} + +# Map features +struct Lane { + id @0 :Text; + + leftBoundary @1 :LaneBoundary; + rightBoundary @2 :LaneBoundary; + + leftAdjacentId @3 :Text; + rightAdjacentId @4 :Text; + + inboundIds @5 :List(Text); + outboundIds @6 :List(Text); + + struct LaneBoundary { + polyLine @0 :PolyLine; + startHeading @1 :Float32; # WRT north + } +} + +# Map tiles +struct TileSummary { + version @0 :Text; + updatedAt @1 :UInt64; # Millis since epoch + + level @2 :UInt8; + x @3 :UInt16; + y @4 :UInt16; +} + +struct MapTile { + summary @0 :TileSummary; + lanes @1 :List(Lane); +} diff --git a/cereal/messaging/.gitignore b/cereal/messaging/.gitignore new file mode 100644 index 0000000000..dbbe8e22ae --- /dev/null +++ b/cereal/messaging/.gitignore @@ -0,0 +1,10 @@ +demo +bridge +test_runner +*.o +*.os +*.d +*.a +*.so +messaging_pyx.cpp +build/ diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py new file mode 100644 index 0000000000..4ba55cf7b9 --- /dev/null +++ b/cereal/messaging/__init__.py @@ -0,0 +1,252 @@ +# must be built with scons +from msgq.ipc_pyx import Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \ + set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event +from msgq.ipc_pyx import MultiplePublishersError, IpcError +from msgq import fake_event_handle, pub_sock, sub_sock, drain_sock_raw, context + +import os +import capnp +import time + +from typing import Optional, List, Union, Dict, Deque +from collections import deque + +from cereal import log +from cereal.services import SERVICE_LIST + +NO_TRAVERSAL_LIMIT = 2**64-1 + + +def log_from_bytes(dat: bytes) -> capnp.lib.capnp._DynamicStructReader: + with log.Event.from_bytes(dat, traversal_limit_in_words=NO_TRAVERSAL_LIMIT) as msg: + return msg + + +def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder: + args = { + 'valid': False, + 'logMonoTime': int(time.monotonic() * 1e9), + **kwargs + } + dat = log.Event.new_message(**args) + if service is not None: + if size is None: + dat.init(service) + else: + dat.init(service, size) + return dat + + +def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> List[capnp.lib.capnp._DynamicStructReader]: + """Receive all message currently available on the queue""" + msgs = drain_sock_raw(sock, wait_for_one=wait_for_one) + return [log_from_bytes(m) for m in msgs] + + +# TODO: print when we drop packets? +def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._DynamicStructReader]: + """Same as drain sock, but only returns latest message. Consider using conflate instead.""" + dat = None + + while 1: + if wait and dat is None: + recv = sock.receive() + else: + recv = sock.receive(non_blocking=True) + + if recv is None: # Timeout hit + break + + dat = recv + + if dat is not None: + dat = log_from_bytes(dat) + + return dat + + +def recv_one(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]: + dat = sock.receive() + if dat is not None: + dat = log_from_bytes(dat) + return dat + + +def recv_one_or_none(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]: + dat = sock.receive(non_blocking=True) + if dat is not None: + dat = log_from_bytes(dat) + return dat + + +def recv_one_retry(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader: + """Keep receiving until we get a message""" + while True: + dat = sock.receive() + if dat is not None: + return log_from_bytes(dat) + + +class SubMaster: + def __init__(self, services: List[str], poll: Optional[str] = None, + ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None, + ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None): + self.frame = -1 + self.seen = {s: False for s in services} + self.updated = {s: False for s in services} + self.recv_time = {s: 0. for s in services} + self.recv_frame = {s: 0 for s in services} + self.alive = {s: False for s in services} + self.freq_ok = {s: False for s in services} + self.recv_dts: Dict[str, Deque[float]] = {} + self.sock = {} + self.data = {} + self.valid = {} + self.logMonoTime = {} + + self.max_freq = {} + self.min_freq = {} + + self.poller = Poller() + polled_services = set([poll, ] if poll is not None else services) + self.non_polled_services = set(services) - polled_services + + self.ignore_average_freq = [] if ignore_avg_freq is None else ignore_avg_freq + self.ignore_alive = [] if ignore_alive is None else ignore_alive + self.ignore_valid = [] if ignore_valid is None else ignore_valid + + self.simulation = bool(int(os.getenv("SIMULATION", "0"))) + + # if freq and poll aren't specified, assume the max to be conservative + assert frequency is None or poll is None, "Do not specify 'frequency' - frequency of the polled service will be used." + self.update_freq = frequency or max([SERVICE_LIST[s].frequency for s in polled_services]) + + for s in services: + p = self.poller if s not in self.non_polled_services else None + self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=True) + + try: + data = new_message(s) + except capnp.lib.capnp.KjException: + data = new_message(s, 0) # lists + + self.data[s] = getattr(data.as_reader(), s) + self.logMonoTime[s] = 0 + self.valid[s] = True # FIXME: this should default to False + + freq = max(min([SERVICE_LIST[s].frequency, self.update_freq]), 1.) + if s == poll: + max_freq = freq + min_freq = freq + else: + max_freq = min(freq, self.update_freq) + if SERVICE_LIST[s].frequency >= 2*self.update_freq: + min_freq = self.update_freq + elif self.update_freq >= 2*SERVICE_LIST[s].frequency: + min_freq = freq + else: + min_freq = min(freq, freq / 2.) + self.max_freq[s] = max_freq*1.2 + self.min_freq[s] = min_freq*0.8 + self.recv_dts[s] = deque(maxlen=int(10*freq)) + + def __getitem__(self, s: str) -> capnp.lib.capnp._DynamicStructReader: + return self.data[s] + + def _check_avg_freq(self, s: str) -> bool: + return SERVICE_LIST[s].frequency > 0.99 and (s not in self.ignore_average_freq) and (s not in self.ignore_alive) + + def update(self, timeout: int = 100) -> None: + msgs = [] + for sock in self.poller.poll(timeout): + msgs.append(recv_one_or_none(sock)) + + # non-blocking receive for non-polled sockets + for s in self.non_polled_services: + msgs.append(recv_one_or_none(self.sock[s])) + self.update_msgs(time.monotonic(), msgs) + + def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None: + self.frame += 1 + self.updated = dict.fromkeys(self.updated, False) + for msg in msgs: + if msg is None: + continue + + s = msg.which() + self.seen[s] = True + self.updated[s] = True + + if self.recv_time[s] > 1e-5: + self.recv_dts[s].append(cur_time - self.recv_time[s]) + self.recv_time[s] = cur_time + self.recv_frame[s] = self.frame + self.data[s] = getattr(msg, s) + self.logMonoTime[s] = msg.logMonoTime + self.valid[s] = msg.valid + + for s in self.data: + if SERVICE_LIST[s].frequency > 1e-5 and not self.simulation: + # alive if delay is within 10x the expected frequency + self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) + + # check average frequency; slow to fall, quick to recover + dts = self.recv_dts[s] + assert dts.maxlen is not None + recent_dts = list(dts)[-int(dts.maxlen / 10):] + try: + avg_freq = 1 / (sum(dts) / len(dts)) + avg_freq_recent = 1 / (sum(recent_dts) / len(recent_dts)) + except ZeroDivisionError: + avg_freq = 0 + avg_freq_recent = 0 + + avg_freq_ok = self.min_freq[s] <= avg_freq <= self.max_freq[s] + recent_freq_ok = self.min_freq[s] <= avg_freq_recent <= self.max_freq[s] + self.freq_ok[s] = avg_freq_ok or recent_freq_ok + else: + self.freq_ok[s] = True + if self.simulation: + self.alive[s] = self.seen[s] # alive is defined as seen when simulation flag set + else: + self.alive[s] = True + + def all_alive(self, service_list: Optional[List[str]] = None) -> bool: + if service_list is None: + service_list = list(self.sock.keys()) + return all(self.alive[s] for s in service_list if s not in self.ignore_alive) + + def all_freq_ok(self, service_list: Optional[List[str]] = None) -> bool: + if service_list is None: + service_list = list(self.sock.keys()) + return all(self.freq_ok[s] for s in service_list if self._check_avg_freq(s)) + + def all_valid(self, service_list: Optional[List[str]] = None) -> bool: + if service_list is None: + service_list = list(self.sock.keys()) + return all(self.valid[s] for s in service_list if s not in self.ignore_valid) + + def all_checks(self, service_list: Optional[List[str]] = None) -> bool: + return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list) + + +class PubMaster: + def __init__(self, services: List[str]): + self.sock = {} + for s in services: + self.sock[s] = pub_sock(s) + + def send(self, s: str, dat: Union[bytes, capnp.lib.capnp._DynamicStructBuilder]) -> None: + if not isinstance(dat, bytes): + dat = dat.to_bytes() + self.sock[s].send(dat) + + def wait_for_readers_to_update(self, s: str, timeout: int, dt: float = 0.05) -> bool: + for _ in range(int(timeout*(1./dt))): + if self.sock[s].all_readers_updated(): + return True + time.sleep(dt) + return False + + def all_readers_updated(self, s: str) -> bool: + return self.sock[s].all_readers_updated() # type: ignore diff --git a/cereal/messaging/bridge.cc b/cereal/messaging/bridge.cc new file mode 100644 index 0000000000..8619c1e226 --- /dev/null +++ b/cereal/messaging/bridge.cc @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include + +typedef void (*sighandler_t)(int sig); + +#include "cereal/services.h" +#include "msgq/impl_msgq.h" +#include "msgq/impl_zmq.h" + +std::atomic do_exit = false; +static void set_do_exit(int sig) { + do_exit = true; +} + +void sigpipe_handler(int sig) { + assert(sig == SIGPIPE); + std::cout << "SIGPIPE received" << std::endl; +} + +static std::vector get_services(std::string whitelist_str, bool zmq_to_msgq) { + std::vector service_list; + for (const auto& it : services) { + std::string name = it.second.name; + bool in_whitelist = whitelist_str.find(name) != std::string::npos; + if (name == "plusFrame" || name == "uiLayoutState" || (zmq_to_msgq && !in_whitelist)) { + continue; + } + service_list.push_back(name); + } + return service_list; +} + +int main(int argc, char** argv) { + signal(SIGPIPE, (sighandler_t)sigpipe_handler); + signal(SIGINT, (sighandler_t)set_do_exit); + signal(SIGTERM, (sighandler_t)set_do_exit); + + bool zmq_to_msgq = argc > 2; + std::string ip = zmq_to_msgq ? argv[1] : "127.0.0.1"; + std::string whitelist_str = zmq_to_msgq ? std::string(argv[2]) : ""; + + Poller *poller; + Context *pub_context; + Context *sub_context; + if (zmq_to_msgq) { // republishes zmq debugging messages as msgq + poller = new ZMQPoller(); + pub_context = new MSGQContext(); + sub_context = new ZMQContext(); + } else { + poller = new MSGQPoller(); + pub_context = new ZMQContext(); + sub_context = new MSGQContext(); + } + + std::map sub2pub; + for (auto endpoint : get_services(whitelist_str, zmq_to_msgq)) { + PubSocket * pub_sock; + SubSocket * sub_sock; + if (zmq_to_msgq) { + pub_sock = new MSGQPubSocket(); + sub_sock = new ZMQSubSocket(); + } else { + pub_sock = new ZMQPubSocket(); + sub_sock = new MSGQSubSocket(); + } + pub_sock->connect(pub_context, endpoint); + sub_sock->connect(sub_context, endpoint, ip, false); + + poller->registerSocket(sub_sock); + sub2pub[sub_sock] = pub_sock; + } + + while (!do_exit) { + for (auto sub_sock : poller->poll(100)) { + Message * msg = sub_sock->receive(); + if (msg == NULL) continue; + int ret; + do { + ret = sub2pub[sub_sock]->sendMessage(msg); + } while (ret == -1 && errno == EINTR && !do_exit); + assert(ret >= 0 || do_exit); + delete msg; + + if (do_exit) break; + } + } + return 0; +} diff --git a/cereal/messaging/messaging.h b/cereal/messaging/messaging.h new file mode 100644 index 0000000000..f3850130e6 --- /dev/null +++ b/cereal/messaging/messaging.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "cereal/gen/cpp/log.capnp.h" +#include "msgq/ipc.h" + +#ifdef __APPLE__ +#define CLOCK_BOOTTIME CLOCK_MONOTONIC +#endif + +#define MSG_MULTIPLE_PUBLISHERS 100 + + +class SubMaster { +public: + SubMaster(const std::vector &service_list, const std::vector &poll = {}, + const char *address = nullptr, const std::vector &ignore_alive = {}); + void update(int timeout = 1000); + void update_msgs(uint64_t current_time, const std::vector> &messages); + inline bool allAlive(const std::vector &service_list = {}) { return all_(service_list, false, true); } + inline bool allValid(const std::vector &service_list = {}) { return all_(service_list, true, false); } + inline bool allAliveAndValid(const std::vector &service_list = {}) { return all_(service_list, true, true); } + void drain(); + ~SubMaster(); + + uint64_t frame = 0; + bool updated(const char *name) const; + bool alive(const char *name) const; + bool valid(const char *name) const; + uint64_t rcv_frame(const char *name) const; + uint64_t rcv_time(const char *name) const; + cereal::Event::Reader &operator[](const char *name) const; + +private: + bool all_(const std::vector &service_list, bool valid, bool alive); + Poller *poller_ = nullptr; + struct SubMessage; + std::map messages_; + std::map services_; +}; + +class MessageBuilder : public capnp::MallocMessageBuilder { +public: + MessageBuilder() = default; + + cereal::Event::Builder initEvent(bool valid = true) { + cereal::Event::Builder event = initRoot(); + struct timespec t; + clock_gettime(CLOCK_BOOTTIME, &t); + uint64_t current_time = t.tv_sec * 1000000000ULL + t.tv_nsec; + event.setLogMonoTime(current_time); + event.setValid(valid); + return event; + } + + kj::ArrayPtr toBytes() { + heapArray_ = capnp::messageToFlatArray(*this); + return heapArray_.asBytes(); + } + + size_t getSerializedSize() { + return capnp::computeSerializedSizeInWords(*this) * sizeof(capnp::word); + } + + int serializeToBuffer(unsigned char *buffer, size_t buffer_size) { + size_t serialized_size = getSerializedSize(); + if (serialized_size > buffer_size) { return -1; } + kj::ArrayOutputStream out(kj::ArrayPtr(buffer, buffer_size)); + capnp::writeMessage(out, *this); + return serialized_size; + } + +private: + kj::Array heapArray_; +}; + +class PubMaster { +public: + PubMaster(const std::vector &service_list); + inline int send(const char *name, capnp::byte *data, size_t size) { return sockets_.at(name)->send((char *)data, size); } + int send(const char *name, MessageBuilder &msg); + ~PubMaster(); + +private: + std::map sockets_; +}; + +class AlignedBuffer { +public: + kj::ArrayPtr align(const char *data, const size_t size) { + words_size = size / sizeof(capnp::word) + 1; + if (aligned_buf.size() < words_size) { + aligned_buf = kj::heapArray(words_size < 512 ? 512 : words_size); + } + memcpy(aligned_buf.begin(), data, size); + return aligned_buf.slice(0, words_size); + } + inline kj::ArrayPtr align(Message *m) { + return align(m->getData(), m->getSize()); + } +private: + kj::Array aligned_buf; + size_t words_size; +}; diff --git a/cereal/messaging/socketmaster.cc b/cereal/messaging/socketmaster.cc new file mode 100644 index 0000000000..cd697b1f51 --- /dev/null +++ b/cereal/messaging/socketmaster.cc @@ -0,0 +1,211 @@ +#include +#include +#include +#include +#include + +#include "cereal/services.h" +#include "cereal/messaging/messaging.h" + + +const bool SIMULATION = (getenv("SIMULATION") != nullptr) && (std::string(getenv("SIMULATION")) == "1"); + +static inline uint64_t nanos_since_boot() { + struct timespec t; + clock_gettime(CLOCK_BOOTTIME, &t); + return t.tv_sec * 1000000000ULL + t.tv_nsec; +} + +static inline bool inList(const std::vector &list, const char *value) { + for (auto &v : list) { + if (strcmp(value, v) == 0) return true; + } + return false; +} + +class MessageContext { +public: + MessageContext() : ctx_(nullptr) {} + ~MessageContext() { delete ctx_; } + inline Context *context() { + std::call_once(init_flag, [=]() { ctx_ = Context::create(); }); + return ctx_; + } +private: + Context *ctx_; + std::once_flag init_flag; +}; + +MessageContext message_context; + +struct SubMaster::SubMessage { + std::string name; + SubSocket *socket = nullptr; + int freq = 0; + bool updated = false, alive = false, valid = true, ignore_alive; + uint64_t rcv_time = 0, rcv_frame = 0; + void *allocated_msg_reader = nullptr; + bool is_polled = false; + capnp::FlatArrayMessageReader *msg_reader = nullptr; + AlignedBuffer aligned_buf; + cereal::Event::Reader event; +}; + +SubMaster::SubMaster(const std::vector &service_list, const std::vector &poll, + const char *address, const std::vector &ignore_alive) { + poller_ = Poller::create(); + for (auto name : service_list) { + assert(services.count(std::string(name)) > 0); + + service serv = services.at(std::string(name)); + SubSocket *socket = SubSocket::create(message_context.context(), name, address ? address : "127.0.0.1", true); + assert(socket != 0); + bool is_polled = inList(poll, name) || poll.empty(); + if (is_polled) poller_->registerSocket(socket); + SubMessage *m = new SubMessage{ + .name = name, + .socket = socket, + .freq = serv.frequency, + .ignore_alive = inList(ignore_alive, name), + .allocated_msg_reader = malloc(sizeof(capnp::FlatArrayMessageReader)), + .is_polled = is_polled}; + m->msg_reader = new (m->allocated_msg_reader) capnp::FlatArrayMessageReader({}); + messages_[socket] = m; + services_[name] = m; + } +} + +void SubMaster::update(int timeout) { + for (auto &kv : messages_) kv.second->updated = false; + + auto sockets = poller_->poll(timeout); + + // add non-polled sockets for non-blocking receive + for (auto &kv : messages_) { + SubMessage *m = kv.second; + SubSocket *s = kv.first; + if (!m->is_polled) sockets.push_back(s); + } + + uint64_t current_time = nanos_since_boot(); + + std::vector> messages; + + for (auto s : sockets) { + Message *msg = s->receive(true); + if (msg == nullptr) continue; + + SubMessage *m = messages_.at(s); + + m->msg_reader->~FlatArrayMessageReader(); + capnp::ReaderOptions options; + options.traversalLimitInWords = kj::maxValue; // Don't limit + m->msg_reader = new (m->allocated_msg_reader) capnp::FlatArrayMessageReader(m->aligned_buf.align(msg), options); + delete msg; + messages.push_back({m->name, m->msg_reader->getRoot()}); + } + + update_msgs(current_time, messages); +} + +void SubMaster::update_msgs(uint64_t current_time, const std::vector> &messages){ + if (++frame == UINT64_MAX) frame = 1; + + for (auto &kv : messages) { + auto m_find = services_.find(kv.first); + if (m_find == services_.end()){ + continue; + } + SubMessage *m = m_find->second; + m->event = kv.second; + m->updated = true; + m->rcv_time = current_time; + m->rcv_frame = frame; + m->valid = m->event.getValid(); + if (SIMULATION) m->alive = true; + } + + if (!SIMULATION) { + for (auto &kv : messages_) { + SubMessage *m = kv.second; + m->alive = (m->freq <= (1e-5) || ((current_time - m->rcv_time) * (1e-9)) < (10.0 / m->freq)); + } + } +} + +bool SubMaster::all_(const std::vector &service_list, bool valid, bool alive) { + int found = 0; + for (auto &kv : messages_) { + SubMessage *m = kv.second; + if (service_list.size() == 0 || inList(service_list, m->name.c_str())) { + found += (!valid || m->valid) && (!alive || (m->alive || m->ignore_alive)); + } + } + return service_list.size() == 0 ? found == messages_.size() : found == service_list.size(); +} + +void SubMaster::drain() { + while (true) { + auto polls = poller_->poll(0); + if (polls.size() == 0) + break; + + for (auto sock : polls) { + Message *msg = sock->receive(true); + delete msg; + } + } +} + +bool SubMaster::updated(const char *name) const { + return services_.at(name)->updated; +} + +bool SubMaster::alive(const char *name) const { + return services_.at(name)->alive; +} + +bool SubMaster::valid(const char *name) const { + return services_.at(name)->valid; +} + +uint64_t SubMaster::rcv_frame(const char *name) const { + return services_.at(name)->rcv_frame; +} + +uint64_t SubMaster::rcv_time(const char *name) const { + return services_.at(name)->rcv_time; +} + +cereal::Event::Reader &SubMaster::operator[](const char *name) const { + return services_.at(name)->event; +} + +SubMaster::~SubMaster() { + delete poller_; + for (auto &kv : messages_) { + SubMessage *m = kv.second; + m->msg_reader->~FlatArrayMessageReader(); + free(m->allocated_msg_reader); + delete m->socket; + delete m; + } +} + +PubMaster::PubMaster(const std::vector &service_list) { + for (auto name : service_list) { + assert(services.count(name) > 0); + PubSocket *socket = PubSocket::create(message_context.context(), name); + assert(socket); + sockets_[name] = socket; + } +} + +int PubMaster::send(const char *name, MessageBuilder &msg) { + auto bytes = msg.toBytes(); + return send(name, bytes.begin(), bytes.size()); +} + +PubMaster::~PubMaster() { + for (auto s : sockets_) delete s.second; +} diff --git a/selfdrive/athena/__init__.py b/cereal/messaging/tests/__init__.py similarity index 100% rename from selfdrive/athena/__init__.py rename to cereal/messaging/tests/__init__.py diff --git a/cereal/messaging/tests/test_messaging.py b/cereal/messaging/tests/test_messaging.py new file mode 100755 index 0000000000..429c2d3c53 --- /dev/null +++ b/cereal/messaging/tests/test_messaging.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +import os +import capnp +import multiprocessing +import numbers +import random +import threading +import time +import unittest +from parameterized import parameterized + +from cereal import log, car +import cereal.messaging as messaging +from cereal.services import SERVICE_LIST + +events = [evt for evt in log.Event.schema.union_fields if evt in SERVICE_LIST.keys()] + +def random_sock(): + return random.choice(events) + +def random_socks(num_socks=10): + return list({random_sock() for _ in range(num_socks)}) + +def random_bytes(length=1000): + return bytes([random.randrange(0xFF) for _ in range(length)]) + +def zmq_sleep(t=1): + if "ZMQ" in os.environ: + time.sleep(t) + +def zmq_expected_failure(func): + if "ZMQ" in os.environ: + return unittest.expectedFailure(func) + else: + return func + + +# TODO: this should take any capnp struct and returrn a msg with random populated data +def random_carstate(): + fields = ["vEgo", "aEgo", "gas", "steeringAngleDeg"] + msg = messaging.new_message("carState") + cs = msg.carState + for f in fields: + setattr(cs, f, random.random() * 10) + return msg + +# TODO: this should compare any capnp structs +def assert_carstate(cs1, cs2): + for f in car.CarState.schema.non_union_fields: + # TODO: check all types + val1, val2 = getattr(cs1, f), getattr(cs2, f) + if isinstance(val1, numbers.Number): + assert val1 == val2, f"{f}: sent '{val1}' vs recvd '{val2}'" + +def delayed_send(delay, sock, dat): + def send_func(): + sock.send(dat) + threading.Timer(delay, send_func).start() + + +class TestMessaging(unittest.TestCase): + def setUp(self): + # TODO: ZMQ tests are too slow; all sleeps will need to be + # replaced with logic to block on the necessary condition + if "ZMQ" in os.environ: + raise unittest.SkipTest + + # ZMQ pub socket takes too long to die + # sleep to prevent multiple publishers error between tests + zmq_sleep() + + @parameterized.expand(events) + def test_new_message(self, evt): + try: + msg = messaging.new_message(evt) + except capnp.lib.capnp.KjException: + msg = messaging.new_message(evt, random.randrange(200)) + self.assertLess(time.monotonic() - msg.logMonoTime, 0.1) + self.assertFalse(msg.valid) + self.assertEqual(evt, msg.which()) + + @parameterized.expand(events) + def test_pub_sock(self, evt): + messaging.pub_sock(evt) + + @parameterized.expand(events) + def test_sub_sock(self, evt): + messaging.sub_sock(evt) + + @parameterized.expand([ + (messaging.drain_sock, capnp._DynamicStructReader), + (messaging.drain_sock_raw, bytes), + ]) + def test_drain_sock(self, func, expected_type): + sock = "carState" + pub_sock = messaging.pub_sock(sock) + sub_sock = messaging.sub_sock(sock, timeout=1000) + zmq_sleep() + + # no wait and no msgs in queue + msgs = func(sub_sock) + self.assertIsInstance(msgs, list) + self.assertEqual(len(msgs), 0) + + # no wait but msgs are queued up + num_msgs = random.randrange(3, 10) + for _ in range(num_msgs): + pub_sock.send(messaging.new_message(sock).to_bytes()) + time.sleep(0.1) + msgs = func(sub_sock) + self.assertIsInstance(msgs, list) + self.assertTrue(all(isinstance(msg, expected_type) for msg in msgs)) + self.assertEqual(len(msgs), num_msgs) + + def test_recv_sock(self): + sock = "carState" + pub_sock = messaging.pub_sock(sock) + sub_sock = messaging.sub_sock(sock, timeout=100) + zmq_sleep() + + # no wait and no msg in queue, socket should timeout + recvd = messaging.recv_sock(sub_sock) + self.assertTrue(recvd is None) + + # no wait and one msg in queue + msg = random_carstate() + pub_sock.send(msg.to_bytes()) + time.sleep(0.01) + recvd = messaging.recv_sock(sub_sock) + self.assertIsInstance(recvd, capnp._DynamicStructReader) + # https://github.com/python/mypy/issues/13038 + assert_carstate(msg.carState, recvd.carState) + + def test_recv_one(self): + sock = "carState" + pub_sock = messaging.pub_sock(sock) + sub_sock = messaging.sub_sock(sock, timeout=1000) + zmq_sleep() + + # no msg in queue, socket should timeout + recvd = messaging.recv_one(sub_sock) + self.assertTrue(recvd is None) + + # one msg in queue + msg = random_carstate() + pub_sock.send(msg.to_bytes()) + recvd = messaging.recv_one(sub_sock) + self.assertIsInstance(recvd, capnp._DynamicStructReader) + assert_carstate(msg.carState, recvd.carState) + + @zmq_expected_failure + def test_recv_one_or_none(self): + sock = "carState" + pub_sock = messaging.pub_sock(sock) + sub_sock = messaging.sub_sock(sock) + zmq_sleep() + + # no msg in queue, socket shouldn't block + recvd = messaging.recv_one_or_none(sub_sock) + self.assertTrue(recvd is None) + + # one msg in queue + msg = random_carstate() + pub_sock.send(msg.to_bytes()) + recvd = messaging.recv_one_or_none(sub_sock) + self.assertIsInstance(recvd, capnp._DynamicStructReader) + assert_carstate(msg.carState, recvd.carState) + + def test_recv_one_retry(self): + sock = "carState" + sock_timeout = 0.1 + pub_sock = messaging.pub_sock(sock) + sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000)) + zmq_sleep() + + # this test doesn't work with ZMQ since multiprocessing interrupts it + if "ZMQ" not in os.environ: + # wait 15 socket timeouts and make sure it's still retrying + p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,)) + p.start() + time.sleep(sock_timeout*15) + self.assertTrue(p.is_alive()) + p.terminate() + + # wait 15 socket timeouts before sending + msg = random_carstate() + delayed_send(sock_timeout*15, pub_sock, msg.to_bytes()) + start_time = time.monotonic() + recvd = messaging.recv_one_retry(sub_sock) + self.assertGreaterEqual(time.monotonic() - start_time, sock_timeout*15) + self.assertIsInstance(recvd, capnp._DynamicStructReader) + assert_carstate(msg.carState, recvd.carState) + +if __name__ == "__main__": + unittest.main() diff --git a/cereal/messaging/tests/test_pub_sub_master.py b/cereal/messaging/tests/test_pub_sub_master.py new file mode 100755 index 0000000000..81a1cf2d57 --- /dev/null +++ b/cereal/messaging/tests/test_pub_sub_master.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +import random +import time +from typing import Sized, cast +import unittest + +import cereal.messaging as messaging +from cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \ + random_bytes, random_carstate, assert_carstate, \ + zmq_sleep + + +class TestSubMaster(unittest.TestCase): + + def setUp(self): + # ZMQ pub socket takes too long to die + # sleep to prevent multiple publishers error between tests + zmq_sleep(3) + + def test_init(self): + sm = messaging.SubMaster(events) + for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive, + sm.sock, sm.data, sm.logMonoTime, sm.valid]: + self.assertEqual(len(cast(Sized, p)), len(events)) + + def test_init_state(self): + socks = random_socks() + sm = messaging.SubMaster(socks) + self.assertEqual(sm.frame, -1) + self.assertFalse(any(sm.updated.values())) + self.assertFalse(any(sm.alive.values())) + self.assertTrue(all(t == 0. for t in sm.recv_time.values())) + self.assertTrue(all(f == 0 for f in sm.recv_frame.values())) + self.assertTrue(all(t == 0 for t in sm.logMonoTime.values())) + + for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive, + sm.sock, sm.data, sm.logMonoTime, sm.valid]: + self.assertEqual(len(cast(Sized, p)), len(socks)) + + def test_getitem(self): + sock = "carState" + pub_sock = messaging.pub_sock(sock) + sm = messaging.SubMaster([sock,]) + zmq_sleep() + + msg = random_carstate() + pub_sock.send(msg.to_bytes()) + sm.update(1000) + assert_carstate(msg.carState, sm[sock]) + + # TODO: break this test up to individually test SubMaster.update and SubMaster.update_msgs + def test_update(self): + sock = "carState" + pub_sock = messaging.pub_sock(sock) + sm = messaging.SubMaster([sock,]) + zmq_sleep() + + for i in range(10): + msg = messaging.new_message(sock) + pub_sock.send(msg.to_bytes()) + sm.update(1000) + self.assertEqual(sm.frame, i) + self.assertTrue(all(sm.updated.values())) + + def test_update_timeout(self): + sock = random_sock() + sm = messaging.SubMaster([sock,]) + for _ in range(5): + timeout = random.randrange(1000, 5000) + start_time = time.monotonic() + sm.update(timeout) + t = time.monotonic() - start_time + self.assertGreaterEqual(t, timeout/1000.) + self.assertLess(t, 5) + self.assertFalse(any(sm.updated.values())) + + def test_avg_frequency_checks(self): + for poll in (True, False): + sm = messaging.SubMaster(["modelV2", "carParams", "carState", "cameraOdometry", "liveCalibration"], + poll=("modelV2" if poll else None), + frequency=(20. if not poll else None)) + + checks = { + "carState": (20, 20), + "modelV2": (20, 20 if poll else 10), + "cameraOdometry": (20, 10), + "liveCalibration": (4, 4), + "carParams": (None, None), + } + + for service, (max_freq, min_freq) in checks.items(): + if max_freq is not None: + assert sm._check_avg_freq(service) + assert sm.max_freq[service] == max_freq*1.2 + assert sm.min_freq[service] == min_freq*0.8 + else: + assert not sm._check_avg_freq(service) + + def test_alive(self): + pass + + def test_ignore_alive(self): + pass + + def test_valid(self): + pass + + # SubMaster should always conflate + def test_conflate(self): + sock = "carState" + pub_sock = messaging.pub_sock(sock) + sm = messaging.SubMaster([sock,]) + + n = 10 + for i in range(n+1): + msg = messaging.new_message(sock) + msg.carState.vEgo = i + pub_sock.send(msg.to_bytes()) + time.sleep(0.01) + sm.update(1000) + self.assertEqual(sm[sock].vEgo, n) + + +class TestPubMaster(unittest.TestCase): + + def setUp(self): + # ZMQ pub socket takes too long to die + # sleep to prevent multiple publishers error between tests + zmq_sleep(3) + + def test_init(self): + messaging.PubMaster(events) + + def test_send(self): + socks = random_socks() + pm = messaging.PubMaster(socks) + sub_socks = {s: messaging.sub_sock(s, conflate=True, timeout=1000) for s in socks} + zmq_sleep() + + # PubMaster accepts either a capnp msg builder or bytes + for capnp in [True, False]: + for i in range(100): + sock = socks[i % len(socks)] + + if capnp: + try: + msg = messaging.new_message(sock) + except Exception: + msg = messaging.new_message(sock, random.randrange(50)) + else: + msg = random_bytes() + + pm.send(sock, msg) + recvd = sub_socks[sock].receive() + + if capnp: + msg.clear_write_flag() + msg = msg.to_bytes() + self.assertEqual(msg, recvd, i) + + +if __name__ == "__main__": + unittest.main() diff --git a/cereal/messaging/tests/test_services.py b/cereal/messaging/tests/test_services.py new file mode 100755 index 0000000000..0fac379e43 --- /dev/null +++ b/cereal/messaging/tests/test_services.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import os +import tempfile +from typing import Dict +import unittest +from parameterized import parameterized + +import cereal.services as services +from cereal.services import SERVICE_LIST + + +class TestServices(unittest.TestCase): + + @parameterized.expand(SERVICE_LIST.keys()) + def test_services(self, s): + service = SERVICE_LIST[s] + self.assertTrue(service.frequency <= 104) + + def test_generated_header(self): + with tempfile.NamedTemporaryFile(suffix=".h") as f: + ret = os.system(f"python3 {services.__file__} > {f.name} && clang++ {f.name}") + self.assertEqual(ret, 0, "generated services header is not valid C") + +if __name__ == "__main__": + unittest.main() diff --git a/cereal/services.py b/cereal/services.py new file mode 100755 index 0000000000..ca9139c431 --- /dev/null +++ b/cereal/services.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +from typing import Optional + + +class Service: + def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] = None): + self.should_log = should_log + self.frequency = frequency + self.decimation = decimation + + +_services: dict[str, tuple] = { + # service: (should_log, frequency, qlog decimation (optional)) + # note: the "EncodeIdx" packets will still be in the log + "gyroscope": (True, 104., 104), + "gyroscope2": (True, 100., 100), + "accelerometer": (True, 104., 104), + "accelerometer2": (True, 100., 100), + "magnetometer": (True, 25., 25), + "lightSensor": (True, 100., 100), + "temperatureSensor": (True, 2., 200), + "temperatureSensor2": (True, 2., 200), + "gpsNMEA": (True, 9.), + "deviceState": (True, 2., 1), + "can": (True, 100., 1223), # decimation gives ~5 msgs in a full segment + "controlsState": (True, 100., 10), + "pandaStates": (True, 10., 1), + "peripheralState": (True, 2., 1), + "radarState": (True, 20., 5), + "roadEncodeIdx": (False, 20., 1), + "liveTracks": (True, 20.), + "sendcan": (True, 100., 139), + "logMessage": (True, 0.), + "errorLogMessage": (True, 0., 1), + "liveCalibration": (True, 4., 4), + "liveTorqueParameters": (True, 4., 1), + "androidLog": (True, 0.), + "carState": (True, 100., 10), + "carControl": (True, 100., 10), + "carOutput": (True, 100., 10), + "longitudinalPlan": (True, 20., 5), + "procLog": (True, 0.5, 15), + "gpsLocationExternal": (True, 10., 10), + "gpsLocation": (True, 1., 1), + "ubloxGnss": (True, 10.), + "qcomGnss": (True, 2.), + "gnssMeasurements": (True, 10., 10), + "clocks": (True, 0.1, 1), + "ubloxRaw": (True, 20.), + "liveLocationKalman": (True, 20., 5), + "liveParameters": (True, 20., 5), + "cameraOdometry": (True, 20., 5), + "thumbnail": (True, 0.2, 1), + "onroadEvents": (True, 1., 1), + "carParams": (True, 0.02, 1), + "roadCameraState": (True, 20., 20), + "driverCameraState": (True, 20., 20), + "driverEncodeIdx": (False, 20., 1), + "driverStateV2": (True, 20., 10), + "driverMonitoringState": (True, 20., 10), + "wideRoadEncodeIdx": (False, 20., 1), + "wideRoadCameraState": (True, 20., 20), + "modelV2": (True, 20., 40), + "managerState": (True, 2., 1), + "uploaderState": (True, 0., 1), + "navInstruction": (True, 1., 10), + "navRoute": (True, 0.), + "navThumbnail": (True, 0.), + "qRoadEncodeIdx": (False, 20.), + "userFlag": (True, 0., 1), + "microphone": (True, 10., 10), + + # debug + "uiDebug": (True, 0., 1), + "testJoystick": (True, 0.), + "roadEncodeData": (False, 20.), + "driverEncodeData": (False, 20.), + "wideRoadEncodeData": (False, 20.), + "qRoadEncodeData": (False, 20.), + "livestreamWideRoadEncodeIdx": (False, 20.), + "livestreamRoadEncodeIdx": (False, 20.), + "livestreamDriverEncodeIdx": (False, 20.), + "livestreamWideRoadEncodeData": (False, 20.), + "livestreamRoadEncodeData": (False, 20.), + "livestreamDriverEncodeData": (False, 20.), + "customReservedRawData0": (True, 0.), + "customReservedRawData1": (True, 0.), + "customReservedRawData2": (True, 0.), +} +SERVICE_LIST = {name: Service(*vals) for + idx, (name, vals) in enumerate(_services.items())} + + +def build_header(): + h = "" + h += "/* THIS IS AN AUTOGENERATED FILE, PLEASE EDIT services.py */\n" + h += "#ifndef __SERVICES_H\n" + h += "#define __SERVICES_H\n" + + h += "#include \n" + h += "#include \n" + + h += "struct service { std::string name; bool should_log; int frequency; int decimation; };\n" + h += "static std::map services = {\n" + for k, v in SERVICE_LIST.items(): + should_log = "true" if v.should_log else "false" + decimation = -1 if v.decimation is None else v.decimation + h += ' { "%s", {"%s", %s, %d, %d}},\n' % \ + (k, k, should_log, v.frequency, decimation) + h += "};\n" + + h += "#endif\n" + return h + + +if __name__ == "__main__": + print(build_header()) diff --git a/common/api/__init__.py b/common/api/__init__.py index 79875023a2..ac231400a4 100644 --- a/common/api/__init__.py +++ b/common/api/__init__.py @@ -1,13 +1,13 @@ import jwt import os import requests -from datetime import datetime, timedelta +from datetime import datetime, timedelta, UTC from openpilot.system.hardware.hw import Paths from openpilot.system.version import get_version API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') -class Api(): +class Api: def __init__(self, dongle_id): self.dongle_id = dongle_id with open(Paths.persist_root()+'/comma/id_rsa') as f: @@ -23,7 +23,7 @@ class Api(): return api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params) def get_token(self, expiry_hours=1): - now = datetime.utcnow() + now = datetime.now(UTC).replace(tzinfo=None) payload = { 'identity': self.dongle_id, 'nbf': now, diff --git a/common/git.py b/common/git.py new file mode 100644 index 0000000000..4406bf96b1 --- /dev/null +++ b/common/git.py @@ -0,0 +1,42 @@ +from functools import cache +import subprocess +from openpilot.common.run import run_cmd, run_cmd_default + + +@cache +def get_commit(cwd: str = None, branch: str = "HEAD") -> str: + return run_cmd_default(["git", "rev-parse", branch], cwd=cwd) + + +@cache +def get_commit_date(cwd: str = None, commit: str = "HEAD") -> str: + return run_cmd_default(["git", "show", "--no-patch", "--format='%ct %ci'", commit], cwd=cwd) + + +@cache +def get_short_branch(cwd: str = None) -> str: + return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd) + + +@cache +def get_branch(cwd: str = None) -> str: + return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd=cwd) + + +@cache +def get_origin(cwd: str = None) -> str: + try: + local_branch = run_cmd(["git", "name-rev", "--name-only", "HEAD"], cwd=cwd) + tracking_remote = run_cmd(["git", "config", "branch." + local_branch + ".remote"], cwd=cwd) + return run_cmd(["git", "config", "remote." + tracking_remote + ".url"], cwd=cwd) + except subprocess.CalledProcessError: # Not on a branch, fallback + return run_cmd_default(["git", "config", "--get", "remote.origin.url"], cwd=cwd) + + +@cache +def get_normalized_origin(cwd: str = None) -> str: + return get_origin(cwd) \ + .replace("git@", "", 1) \ + .replace(".git", "", 1) \ + .replace("https://", "", 1) \ + .replace(":", "/", 1) diff --git a/common/gpio.py b/common/gpio.py index 68932cb87a..66b2adf438 100644 --- a/common/gpio.py +++ b/common/gpio.py @@ -1,5 +1,5 @@ import os -from functools import lru_cache +from functools import cache def gpio_init(pin: int, output: bool) -> None: try: @@ -35,7 +35,7 @@ def gpio_export(pin: int) -> None: except Exception: print(f"Failed to export gpio {pin}") -@lru_cache(maxsize=None) +@cache def get_irq_action(irq: int) -> list[str]: try: with open(f"/sys/kernel/irq/{irq}/actions") as f: diff --git a/common/markdown.py b/common/markdown.py new file mode 100644 index 0000000000..f0f056d963 --- /dev/null +++ b/common/markdown.py @@ -0,0 +1,45 @@ +HTML_REPLACEMENTS = [ + (r'&', r'&'), + (r'"', r'"'), +] + +def parse_markdown(text: str, tab_length: int = 2) -> str: + lines = text.split("\n") + output: list[str] = [] + list_level = 0 + + def end_outstanding_lists(level: int, end_level: int) -> int: + while level > end_level: + level -= 1 + output.append("") + if level > 0: + output.append("") + return end_level + + for i, line in enumerate(lines): + if i + 1 < len(lines) and lines[i + 1].startswith("==="): # heading + output.append(f"

{line}

") + elif line.startswith("==="): + pass + elif line.lstrip().startswith("* "): # list + line_level = 1 + line.count(" " * tab_length, 0, line.index("*")) + if list_level >= line_level: + list_level = end_outstanding_lists(list_level, line_level) + else: + list_level += 1 + if list_level > 1: + output[-1] = output[-1].replace("", "") + output.append("
    ") + output.append(f"
  • {line.replace('*', '', 1).lstrip()}
  • ") + else: + list_level = end_outstanding_lists(list_level, 0) + if len(line) > 0: + output.append(line) + + end_outstanding_lists(list_level, 0) + output_str = "\n".join(output) + "\n" + + for (fr, to) in HTML_REPLACEMENTS: + output_str = output_str.replace(fr, to) + + return output_str diff --git a/common/params.cc b/common/params.cc index aef2cbdecd..2330160173 100644 --- a/common/params.cc +++ b/common/params.cc @@ -89,6 +89,7 @@ private: std::unordered_map keys = { {"AccessToken", CLEAR_ON_MANAGER_START | DONT_LOG}, + {"AlwaysOnDM", PERSISTENT}, {"ApiCache_Device", PERSISTENT}, {"ApiCache_NavDestinations", PERSISTENT}, {"AssistNowToken", PERSISTENT}, @@ -171,7 +172,6 @@ std::unordered_map keys = { {"Offroad_CarUnrecognized", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"Offroad_ConnectivityNeeded", CLEAR_ON_MANAGER_START}, {"Offroad_ConnectivityNeededPrompt", CLEAR_ON_MANAGER_START}, - {"Offroad_InvalidTime", CLEAR_ON_MANAGER_START}, {"Offroad_IsTakingSnapshot", CLEAR_ON_MANAGER_START}, {"Offroad_NeosUpdate", CLEAR_ON_MANAGER_START}, {"Offroad_NoFirmware", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, @@ -188,6 +188,7 @@ std::unordered_map keys = { {"RecordFront", PERSISTENT}, {"RecordFrontLock", PERSISTENT}, // for the internal fleet {"ReplayControlsState", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, + {"RouteCount", PERSISTENT}, {"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"SshEnabled", PERSISTENT}, {"TermsVersion", PERSISTENT}, @@ -206,7 +207,6 @@ std::unordered_map keys = { {"UpdaterTargetBranch", CLEAR_ON_MANAGER_START}, {"UpdaterLastFetchTime", PERSISTENT}, {"Version", PERSISTENT}, - {"VisionRadarToggle", PERSISTENT}, }; } // namespace @@ -273,7 +273,9 @@ int Params::put(const char* key, const char* value, size_t value_size) { } while (false); close(tmp_fd); - ::unlink(tmp_path.c_str()); + if (result != 0) { + ::unlink(tmp_path.c_str()); + } return result; } diff --git a/common/prefix.h b/common/prefix.h index f5abe14b2b..11d0bc1066 100644 --- a/common/prefix.h +++ b/common/prefix.h @@ -5,6 +5,7 @@ #include "common/params.h" #include "common/util.h" +#include "system/hardware/hw.h" class OpenpilotPrefix { public: @@ -25,6 +26,10 @@ public: system(util::string_format("rm %s -rf", real_path.c_str()).c_str()); unlink(param_path.c_str()); } + if (getenv("COMMA_CACHE") == nullptr) { + system(util::string_format("rm %s -rf", Path::download_cache_root().c_str()).c_str()); + } + system(util::string_format("rm %s -rf", Path::comma_home().c_str()).c_str()); system(util::string_format("rm %s -rf", msgq_path.c_str()).c_str()); unsetenv("OPENPILOT_PREFIX"); } diff --git a/common/prefix.py b/common/prefix.py index 4059ac09e2..3292acae86 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -4,6 +4,7 @@ import uuid from openpilot.common.params import Params +from openpilot.system.hardware import PC from openpilot.system.hardware.hw import Paths from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT @@ -45,7 +46,8 @@ class OpenpilotPrefix: shutil.rmtree(os.path.realpath(symlink_path), ignore_errors=True) os.remove(symlink_path) shutil.rmtree(self.msgq_path, ignore_errors=True) - shutil.rmtree(Paths.log_root(), ignore_errors=True) + if PC: + shutil.rmtree(Paths.log_root(), ignore_errors=True) if not os.environ.get("COMMA_CACHE", False): shutil.rmtree(Paths.download_cache_root(), ignore_errors=True) shutil.rmtree(Paths.comma_home(), ignore_errors=True) diff --git a/common/ratekeeper.h b/common/ratekeeper.h index b6e8ac66a6..e7323c6ec3 100644 --- a/common/ratekeeper.h +++ b/common/ratekeeper.h @@ -9,7 +9,7 @@ public: ~RateKeeper() {} bool keepTime(); bool monitorTime(); - inline double frame() const { return frame_; } + inline uint64_t frame() const { return frame_; } inline double remaining() const { return remaining_; } private: diff --git a/common/realtime.py b/common/realtime.py index 6b8587ff06..e7265406ac 100644 --- a/common/realtime.py +++ b/common/realtime.py @@ -4,7 +4,7 @@ import os import time from collections import deque -from setproctitle import getproctitle +from openpilot.common.threadname import getthreadname from openpilot.system.hardware import PC @@ -12,7 +12,7 @@ from openpilot.system.hardware import PC # time step for each process DT_CTRL = 0.01 # controlsd DT_MDL = 0.05 # model -DT_TRML = 0.5 # thermald and manager +DT_HW = 0.5 # hardwared and manager DT_DMON = 0.05 # driver monitoring @@ -23,7 +23,7 @@ class Priority: CTRL_LOW = 51 # plannerd & radard # CORE 3 - # - boardd = 55 + # - pandad = 55 CTRL_HIGH = 53 @@ -52,7 +52,7 @@ class Ratekeeper: self._print_delay_threshold = print_delay_threshold self._frame = 0 self._remaining = 0.0 - self._process_name = getproctitle() + self._thread_name = getthreadname() self._dts = deque([self._interval], maxlen=100) self._last_monitor_time = time.monotonic() @@ -87,7 +87,7 @@ class Ratekeeper: remaining = self._next_frame_time - time.monotonic() self._next_frame_time += self._interval if self._print_delay_threshold is not None and remaining < -self._print_delay_threshold: - print(f"{self._process_name} lagging by {-remaining * 1000:.2f} ms") + print(f"{self._thread_name} lagging by {-remaining * 1000:.2f} ms") lagged = True self._frame += 1 self._remaining = remaining diff --git a/common/run.py b/common/run.py new file mode 100644 index 0000000000..06deb6388d --- /dev/null +++ b/common/run.py @@ -0,0 +1,13 @@ +import subprocess + + +def run_cmd(cmd: list[str], cwd=None, env=None) -> str: + return subprocess.check_output(cmd, encoding='utf8', cwd=cwd, env=env).strip() + + +def run_cmd_default(cmd: list[str], default: str = "", cwd=None, env=None) -> str: + try: + return run_cmd(cmd, cwd=cwd, env=env) + except subprocess.CalledProcessError: + return default + diff --git a/common/spinner.py b/common/spinner.py index 43d4bb2cc2..dcf22641c4 100644 --- a/common/spinner.py +++ b/common/spinner.py @@ -3,7 +3,7 @@ import subprocess from openpilot.common.basedir import BASEDIR -class Spinner(): +class Spinner: def __init__(self): try: self.spinner_proc = subprocess.Popen(["./spinner"], diff --git a/common/stat_live.py b/common/stat_live.py index a91c1819bb..3901c448d8 100644 --- a/common/stat_live.py +++ b/common/stat_live.py @@ -1,6 +1,6 @@ import numpy as np -class RunningStat(): +class RunningStat: # tracks realtime mean and standard deviation without storing any data def __init__(self, priors=None, max_trackable=-1): self.max_trackable = max_trackable @@ -51,7 +51,7 @@ class RunningStat(): def params_to_save(self): return [self.M, self.S, self.n] -class RunningStatFilter(): +class RunningStatFilter: def __init__(self, raw_priors=None, filtered_priors=None, max_trackable=-1): self.raw_stat = RunningStat(raw_priors, -1) self.filtered_stat = RunningStat(filtered_priors, max_trackable) diff --git a/common/tests/test_file_helpers.py b/common/tests/test_file_helpers.py index 1817f77cd2..a9977c2362 100644 --- a/common/tests/test_file_helpers.py +++ b/common/tests/test_file_helpers.py @@ -1,11 +1,10 @@ import os -import unittest from uuid import uuid4 from openpilot.common.file_helpers import atomic_write_in_dir -class TestFileHelpers(unittest.TestCase): +class TestFileHelpers: def run_atomic_write_func(self, atomic_write_func): path = f"/tmp/tmp{uuid4()}" with atomic_write_func(path) as f: @@ -13,12 +12,8 @@ class TestFileHelpers(unittest.TestCase): assert not os.path.exists(path) with open(path) as f: - self.assertEqual(f.read(), "test") + assert f.read() == "test" os.remove(path) def test_atomic_write_in_dir(self): self.run_atomic_write_func(atomic_write_in_dir) - - -if __name__ == "__main__": - unittest.main() diff --git a/common/tests/test_markdown.py b/common/tests/test_markdown.py new file mode 100644 index 0000000000..d3c7e02c69 --- /dev/null +++ b/common/tests/test_markdown.py @@ -0,0 +1,15 @@ +import os + +from openpilot.common.basedir import BASEDIR +from openpilot.common.markdown import parse_markdown + + +class TestMarkdown: + def test_all_release_notes(self): + with open(os.path.join(BASEDIR, "RELEASES.md")) as f: + release_notes = f.read().split("\n\n") + assert len(release_notes) > 10 + + for rn in release_notes: + md = parse_markdown(rn) + assert len(md) > 0 diff --git a/common/tests/test_numpy_fast.py b/common/tests/test_numpy_fast.py index de7bb972e7..aa53851db0 100644 --- a/common/tests/test_numpy_fast.py +++ b/common/tests/test_numpy_fast.py @@ -1,10 +1,9 @@ import numpy as np -import unittest from openpilot.common.numpy_fast import interp -class InterpTest(unittest.TestCase): +class TestInterp: def test_correctness_controls(self): _A_CRUISE_MIN_BP = np.asarray([0., 5., 10., 20., 40.]) _A_CRUISE_MIN_V = np.asarray([-1.0, -.8, -.67, -.5, -.30]) @@ -20,7 +19,3 @@ class InterpTest(unittest.TestCase): expected = np.interp(v_ego, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V) actual = interp(v_ego, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V) np.testing.assert_equal(actual, expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/common/tests/test_params.py b/common/tests/test_params.py index 490ee122be..16cbc45295 100644 --- a/common/tests/test_params.py +++ b/common/tests/test_params.py @@ -1,13 +1,13 @@ +import pytest import os import threading import time import uuid -import unittest from openpilot.common.params import Params, ParamKeyType, UnknownKeyName -class TestParams(unittest.TestCase): - def setUp(self): +class TestParams: + def setup_method(self): self.params = Params() def test_params_put_and_get(self): @@ -49,16 +49,16 @@ class TestParams(unittest.TestCase): assert self.params.get("CarParams", True) == b"test" def test_params_unknown_key_fails(self): - with self.assertRaises(UnknownKeyName): + with pytest.raises(UnknownKeyName): self.params.get("swag") - with self.assertRaises(UnknownKeyName): + with pytest.raises(UnknownKeyName): self.params.get_bool("swag") - with self.assertRaises(UnknownKeyName): + with pytest.raises(UnknownKeyName): self.params.put("swag", "abc") - with self.assertRaises(UnknownKeyName): + with pytest.raises(UnknownKeyName): self.params.put_bool("swag", True) def test_remove_not_there(self): @@ -68,19 +68,19 @@ class TestParams(unittest.TestCase): def test_get_bool(self): self.params.remove("IsMetric") - self.assertFalse(self.params.get_bool("IsMetric")) + assert not self.params.get_bool("IsMetric") self.params.put_bool("IsMetric", True) - self.assertTrue(self.params.get_bool("IsMetric")) + assert self.params.get_bool("IsMetric") self.params.put_bool("IsMetric", False) - self.assertFalse(self.params.get_bool("IsMetric")) + assert not self.params.get_bool("IsMetric") self.params.put("IsMetric", "1") - self.assertTrue(self.params.get_bool("IsMetric")) + assert self.params.get_bool("IsMetric") self.params.put("IsMetric", "0") - self.assertFalse(self.params.get_bool("IsMetric")) + assert not self.params.get_bool("IsMetric") def test_put_non_blocking_with_get_block(self): q = Params() @@ -107,7 +107,3 @@ class TestParams(unittest.TestCase): assert len(keys) > 20 assert len(keys) == len(set(keys)) assert b"CarParams" in keys - - -if __name__ == "__main__": - unittest.main() diff --git a/common/tests/test_simple_kalman.py b/common/tests/test_simple_kalman.py index f641cd19e6..f4a967e58a 100644 --- a/common/tests/test_simple_kalman.py +++ b/common/tests/test_simple_kalman.py @@ -1,10 +1,8 @@ -import unittest - from openpilot.common.simple_kalman import KF1D -class TestSimpleKalman(unittest.TestCase): - def setUp(self): +class TestSimpleKalman: + def setup_method(self): dt = 0.01 x0_0 = 0.0 x1_0 = 0.0 @@ -24,12 +22,8 @@ class TestSimpleKalman(unittest.TestCase): def test_getter_setter(self): self.kf.set_x([[1.0], [1.0]]) - self.assertEqual(self.kf.x, [[1.0], [1.0]]) + assert self.kf.x == [[1.0], [1.0]] def update_returns_state(self): x = self.kf.update(100) - self.assertEqual(x, self.kf.x) - - -if __name__ == "__main__": - unittest.main() + assert x == self.kf.x diff --git a/common/tests/test_threadname.py b/common/tests/test_threadname.py new file mode 100644 index 0000000000..38e5e1d498 --- /dev/null +++ b/common/tests/test_threadname.py @@ -0,0 +1,8 @@ +from openpilot.common.threadname import setthreadname, getthreadname, LINUX + +class TestThreadName: + def test_set_get_threadname(self): + if LINUX: + name = 'TESTING' + setthreadname(name) + assert name == getthreadname() diff --git a/common/threadname.py b/common/threadname.py new file mode 100644 index 0000000000..7c415721f4 --- /dev/null +++ b/common/threadname.py @@ -0,0 +1,19 @@ +import ctypes +import os + +LINUX = os.name == 'posix' and os.uname().sysname == 'Linux' + +if LINUX: + libc = ctypes.CDLL('libc.so.6') + +def setthreadname(name: str) -> None: + if LINUX: + name = name[-15:] + '\0' + libc.prctl(15, str.encode(name), 0, 0, 0) + +def getthreadname() -> str: + if LINUX: + name = ctypes.create_string_buffer(16) + libc.prctl(16, name) + return name.value.decode('utf-8') + return "" diff --git a/common/time.py b/common/time.py index f2e49eb546..5379faf22a 100644 --- a/common/time.py +++ b/common/time.py @@ -1,6 +1,15 @@ import datetime +from pathlib import Path -MIN_DATE = datetime.datetime(year=2024, month=1, day=28) +_MIN_DATE = datetime.datetime(year=2024, month=3, day=30) + +def min_date(): + # on systemd systems, the default time is the systemd build time + systemd_path = Path("/lib/systemd/systemd") + if systemd_path.exists(): + d = datetime.datetime.fromtimestamp(systemd_path.stat().st_mtime) + return d + datetime.timedelta(days=1) + return _MIN_DATE def system_time_valid(): - return datetime.datetime.now() > MIN_DATE + return datetime.datetime.now() > min_date() diff --git a/common/transformations/camera.py b/common/transformations/camera.py index c643cb5702..dc3ca5f388 100644 --- a/common/transformations/camera.py +++ b/common/transformations/camera.py @@ -1,55 +1,74 @@ +import itertools import numpy as np +from dataclasses import dataclass import openpilot.common.transformations.orientation as orient ## -- hardcoded hardware params -- -eon_f_focal_length = 910.0 -eon_d_focal_length = 650.0 -tici_f_focal_length = 2648.0 -tici_e_focal_length = tici_d_focal_length = 567.0 # probably wrong? magnification is not consistent across frame +@dataclass(frozen=True) +class CameraConfig: + width: int + height: int + focal_length: float -eon_f_frame_size = (1164, 874) -eon_d_frame_size = (816, 612) -tici_f_frame_size = tici_e_frame_size = tici_d_frame_size = (1928, 1208) + @property + def size(self): + return (self.width, self.height) -# aka 'K' aka camera_frame_from_view_frame -eon_fcam_intrinsics = np.array([ - [eon_f_focal_length, 0.0, float(eon_f_frame_size[0])/2], - [0.0, eon_f_focal_length, float(eon_f_frame_size[1])/2], - [0.0, 0.0, 1.0]]) -eon_intrinsics = eon_fcam_intrinsics # xx + @property + def intrinsics(self): + # aka 'K' aka camera_frame_from_view_frame + return np.array([ + [self.focal_length, 0.0, float(self.width)/2], + [0.0, self.focal_length, float(self.height)/2], + [0.0, 0.0, 1.0] + ]) -eon_dcam_intrinsics = np.array([ - [eon_d_focal_length, 0.0, float(eon_d_frame_size[0])/2], - [0.0, eon_d_focal_length, float(eon_d_frame_size[1])/2], - [0.0, 0.0, 1.0]]) + @property + def intrinsics_inv(self): + # aka 'K_inv' aka view_frame_from_camera_frame + return np.linalg.inv(self.intrinsics) -tici_fcam_intrinsics = np.array([ - [tici_f_focal_length, 0.0, float(tici_f_frame_size[0])/2], - [0.0, tici_f_focal_length, float(tici_f_frame_size[1])/2], - [0.0, 0.0, 1.0]]) +@dataclass(frozen=True) +class _NoneCameraConfig(CameraConfig): + width: int = 0 + height: int = 0 + focal_length: float = 0 -tici_dcam_intrinsics = np.array([ - [tici_d_focal_length, 0.0, float(tici_d_frame_size[0])/2], - [0.0, tici_d_focal_length, float(tici_d_frame_size[1])/2], - [0.0, 0.0, 1.0]]) +@dataclass(frozen=True) +class DeviceCameraConfig: + fcam: CameraConfig + dcam: CameraConfig + ecam: CameraConfig -tici_ecam_intrinsics = tici_dcam_intrinsics + def all_cams(self): + for cam in ['fcam', 'dcam', 'ecam']: + if not isinstance(getattr(self, cam), _NoneCameraConfig): + yield cam, getattr(self, cam) -# aka 'K_inv' aka view_frame_from_camera_frame -eon_fcam_intrinsics_inv = np.linalg.inv(eon_fcam_intrinsics) -eon_intrinsics_inv = eon_fcam_intrinsics_inv # xx +_ar_ox_fisheye = CameraConfig(1928, 1208, 567.0) # focal length probably wrong? magnification is not consistent across frame +_os_fisheye = CameraConfig(2688, 1520, 567.0 / 2 * 3) +_ar_ox_config = DeviceCameraConfig(CameraConfig(1928, 1208, 2648.0), _ar_ox_fisheye, _ar_ox_fisheye) +_os_config = DeviceCameraConfig(CameraConfig(2688, 1520, 2648.0 * 2 / 3), _os_fisheye, _os_fisheye) +_neo_config = DeviceCameraConfig(CameraConfig(1164, 874, 910.0), CameraConfig(816, 612, 650.0), _NoneCameraConfig()) -tici_fcam_intrinsics_inv = np.linalg.inv(tici_fcam_intrinsics) -tici_ecam_intrinsics_inv = np.linalg.inv(tici_ecam_intrinsics) +DEVICE_CAMERAS = { + # A "device camera" is defined by a device type and sensor + # sensor type was never set on eon/neo/two + ("neo", "unknown"): _neo_config, + # unknown here is AR0231, field was added with OX03C10 support + ("tici", "unknown"): _ar_ox_config, -FULL_FRAME_SIZE = tici_f_frame_size -FOCAL = tici_f_focal_length -fcam_intrinsics = tici_fcam_intrinsics - -W, H = FULL_FRAME_SIZE[0], FULL_FRAME_SIZE[1] + # before deviceState.deviceType was set, assume tici AR config + ("unknown", "ar0231"): _ar_ox_config, + ("unknown", "ox03c10"): _ar_ox_config, + # simulator (emulates a tici) + ("pc", "unknown"): _ar_ox_config, +} +prods = itertools.product(('tici', 'tizi', 'mici'), (('ar0231', _ar_ox_config), ('ox03c10', _ar_ox_config), ('os04c10', _os_config))) +DEVICE_CAMERAS.update({(d, c[0]): c[1] for d, c in prods}) # device/mesh : x->forward, y-> right, z->down # view : x->right, y->down, z->forward @@ -93,7 +112,7 @@ def roll_from_ke(m): -(m[0, 0] - m[0, 1] * m[2, 0] / m[2, 1])) -def normalize(img_pts, intrinsics=fcam_intrinsics): +def normalize(img_pts, intrinsics): # normalizes image coordinates # accepts single pt or array of pts intrinsics_inv = np.linalg.inv(intrinsics) @@ -106,7 +125,7 @@ def normalize(img_pts, intrinsics=fcam_intrinsics): return img_pts_normalized[:, :2].reshape(input_shape) -def denormalize(img_pts, intrinsics=fcam_intrinsics, width=np.inf, height=np.inf): +def denormalize(img_pts, intrinsics, width=np.inf, height=np.inf): # denormalizes image coordinates # accepts single pt or array of pts img_pts = np.array(img_pts) @@ -123,7 +142,7 @@ def denormalize(img_pts, intrinsics=fcam_intrinsics, width=np.inf, height=np.inf return img_pts_denormalized[:, :2].reshape(input_shape) -def get_calib_from_vp(vp, intrinsics=fcam_intrinsics): +def get_calib_from_vp(vp, intrinsics): vp_norm = normalize(vp, intrinsics) yaw_calib = np.arctan(vp_norm[0]) pitch_calib = -np.arctan(vp_norm[1]*np.cos(yaw_calib)) diff --git a/common/transformations/model.py b/common/transformations/model.py index 7e40767f63..aaa12d776a 100644 --- a/common/transformations/model.py +++ b/common/transformations/model.py @@ -1,19 +1,11 @@ import numpy as np from openpilot.common.transformations.orientation import rot_from_euler -from openpilot.common.transformations.camera import ( - FULL_FRAME_SIZE, get_view_frame_from_calib_frame, view_frame_from_device_frame, - eon_fcam_intrinsics, tici_ecam_intrinsics, tici_fcam_intrinsics) +from openpilot.common.transformations.camera import get_view_frame_from_calib_frame, view_frame_from_device_frame # segnet SEGNET_SIZE = (512, 384) -def get_segnet_frame_from_camera_frame(segnet_size=SEGNET_SIZE, full_frame_size=FULL_FRAME_SIZE): - return np.array([[float(segnet_size[0]) / full_frame_size[0], 0.0], - [0.0, float(segnet_size[1]) / full_frame_size[1]]]) -segnet_frame_from_camera_frame = get_segnet_frame_from_camera_frame() # xx - - # MED model MEDMODEL_INPUT_SIZE = (512, 256) MEDMODEL_YUV_SIZE = (MEDMODEL_INPUT_SIZE[0], MEDMODEL_INPUT_SIZE[1] * 3 // 2) @@ -63,16 +55,9 @@ calib_from_medmodel = np.linalg.inv(medmodel_frame_from_calib_frame[:, :3]) calib_from_sbigmodel = np.linalg.inv(sbigmodel_frame_from_calib_frame[:, :3]) # This function is verified to give similar results to xx.uncommon.utils.transform_img -def get_warp_matrix(device_from_calib_euler: np.ndarray, wide_camera: bool = False, bigmodel_frame: bool = False, tici: bool = True) -> np.ndarray: - if tici and wide_camera: - cam_intrinsics = tici_ecam_intrinsics - elif tici: - cam_intrinsics = tici_fcam_intrinsics - else: - cam_intrinsics = eon_fcam_intrinsics - +def get_warp_matrix(device_from_calib_euler: np.ndarray, intrinsics: np.ndarray, bigmodel_frame: bool = False) -> np.ndarray: calib_from_model = calib_from_sbigmodel if bigmodel_frame else calib_from_medmodel device_from_calib = rot_from_euler(device_from_calib_euler) - camera_from_calib = cam_intrinsics @ view_frame_from_device_frame @ device_from_calib + camera_from_calib = intrinsics @ view_frame_from_device_frame @ device_from_calib warp_matrix: np.ndarray = camera_from_calib @ calib_from_model return warp_matrix diff --git a/common/transformations/tests/test_coordinates.py b/common/transformations/tests/test_coordinates.py old mode 100755 new mode 100644 index 7ae79403bd..11a6bf70ee --- a/common/transformations/tests/test_coordinates.py +++ b/common/transformations/tests/test_coordinates.py @@ -1,7 +1,4 @@ -#!/usr/bin/env python3 - import numpy as np -import unittest import openpilot.common.transformations.coordinates as coord @@ -44,7 +41,7 @@ ned_offsets_batch = np.array([[ 53.88103168, 43.83445935, -46.27488057], [ 78.56272609, 18.53100158, -43.25290759]]) -class TestNED(unittest.TestCase): +class TestNED: def test_small_distances(self): start_geodetic = np.array([33.8042184, -117.888593, 0.0]) local_coord = coord.LocalCoord.from_geodetic(start_geodetic) @@ -54,13 +51,13 @@ class TestNED(unittest.TestCase): west_geodetic = start_geodetic + [0, -0.0005, 0] west_ned = local_coord.geodetic2ned(west_geodetic) - self.assertLess(np.abs(west_ned[0]), 1e-3) - self.assertLess(west_ned[1], 0) + assert np.abs(west_ned[0]) < 1e-3 + assert west_ned[1] < 0 southwest_geodetic = start_geodetic + [-0.0005, -0.002, 0] southwest_ned = local_coord.geodetic2ned(southwest_geodetic) - self.assertLess(southwest_ned[0], 0) - self.assertLess(southwest_ned[1], 0) + assert southwest_ned[0] < 0 + assert southwest_ned[1] < 0 def test_ecef_geodetic(self): # testing single @@ -105,5 +102,3 @@ class TestNED(unittest.TestCase): np.testing.assert_allclose(converter.ned2ecef(ned_offsets_batch), ecef_positions_offset_batch, rtol=1e-9, atol=1e-7) -if __name__ == "__main__": - unittest.main() diff --git a/common/transformations/tests/test_orientation.py b/common/transformations/tests/test_orientation.py old mode 100755 new mode 100644 index f77827d2f9..55fbc6581e --- a/common/transformations/tests/test_orientation.py +++ b/common/transformations/tests/test_orientation.py @@ -1,7 +1,4 @@ -#!/usr/bin/env python3 - import numpy as np -import unittest from openpilot.common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \ rot2quat, quat2rot, \ @@ -32,7 +29,7 @@ ned_eulers = np.array([[ 0.46806039, -0.4881889 , 1.65697808], [ 2.50450101, 0.36304151, 0.33136365]]) -class TestOrientation(unittest.TestCase): +class TestOrientation: def test_quat_euler(self): for i, eul in enumerate(eulers): np.testing.assert_allclose(quats[i], euler2quat(eul), rtol=1e-7) @@ -62,7 +59,3 @@ class TestOrientation(unittest.TestCase): np.testing.assert_allclose(ned_eulers[i], ned_euler_from_ecef(ecef_positions[i], eulers[i]), rtol=1e-7) #np.testing.assert_allclose(eulers[i], ecef_euler_from_ned(ecef_positions[i], ned_eulers[i]), rtol=1e-7) # np.testing.assert_allclose(ned_eulers, ned_euler_from_ecef(ecef_positions, eulers), rtol=1e-7) - - -if __name__ == "__main__": - unittest.main() diff --git a/common/util.cc b/common/util.cc index 8af9f5884c..5ffd6e4099 100644 --- a/common/util.cc +++ b/common/util.cc @@ -246,18 +246,13 @@ std::string random_string(std::string::size_type length) { return s; } -std::string dir_name(std::string const &path) { - size_t pos = path.find_last_of("/"); - if (pos == std::string::npos) return ""; - return path.substr(0, pos); -} - bool starts_with(const std::string &s1, const std::string &s2) { return strncmp(s1.c_str(), s2.c_str(), s2.size()) == 0; } -bool ends_with(const std::string &s1, const std::string &s2) { - return strcmp(s1.c_str() + (s1.size() - s2.size()), s2.c_str()) == 0; +bool ends_with(const std::string& s, const std::string& suffix) { + return s.size() >= suffix.size() && + strcmp(s.c_str() + (s.size() - suffix.size()), suffix.c_str()) == 0; } std::string check_output(const std::string& command) { @@ -276,20 +271,4 @@ std::string check_output(const std::string& command) { return result; } -struct tm get_time() { - time_t rawtime; - time(&rawtime); - - struct tm sys_time; - gmtime_r(&rawtime, &sys_time); - - return sys_time; -} - -bool time_valid(struct tm sys_time) { - int year = 1900 + sys_time.tm_year; - int month = 1 + sys_time.tm_mon; - return (year > 2023) || (year == 2023 && month >= 6); -} - } // namespace util diff --git a/common/util.h b/common/util.h index 0cbad5bd31..186873ac21 100644 --- a/common/util.h +++ b/common/util.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -45,10 +44,6 @@ int set_realtime_priority(int level); int set_core_affinity(std::vector cores); int set_file_descriptor_limit(uint64_t limit); -// ***** Time helpers ***** -struct tm get_time(); -bool time_valid(struct tm sys_time); - // ***** math helpers ***** // map x from [a1, a2] to [b1, b2] @@ -75,9 +70,8 @@ int getenv(const char* key, int default_val); float getenv(const char* key, float default_val); std::string hexdump(const uint8_t* in, const size_t size); -std::string dir_name(std::string const& path); bool starts_with(const std::string &s1, const std::string &s2); -bool ends_with(const std::string &s1, const std::string &s2); +bool ends_with(const std::string &s, const std::string &suffix); // ***** random helpers ***** int random_int(int min, int max); @@ -179,3 +173,10 @@ void update_max_atomic(std::atomic& max, T const& value) { T prev = max; while (prev < value && !max.compare_exchange_weak(prev, value)) {} } + +typedef struct Rect { + int x; + int y; + int w; + int h; +} Rect; diff --git a/common/utils.py b/common/utils.py new file mode 100644 index 0000000000..b9de020ee6 --- /dev/null +++ b/common/utils.py @@ -0,0 +1,11 @@ +class Freezable: + _frozen: bool = False + + def freeze(self): + if not self._frozen: + self._frozen = True + + def __setattr__(self, *args, **kwargs): + if self._frozen: + raise Exception("cannot modify frozen object") + super().__setattr__(*args, **kwargs) diff --git a/common/version.h b/common/version.h index 177882e31d..1f651fb392 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.9.7" +#define COMMA_VERSION "0.9.8" diff --git a/conftest.py b/conftest.py index 0d2a4a8fc4..fc4931fb54 100644 --- a/conftest.py +++ b/conftest.py @@ -5,7 +5,7 @@ import pytest import random from openpilot.common.prefix import OpenpilotPrefix -from openpilot.selfdrive.manager import manager +from openpilot.system.manager import manager from openpilot.system.hardware import TICI, HARDWARE diff --git a/docs/BOUNTIES.md b/docs/BOUNTIES.md index 5ff5dd939f..31e19e1206 100644 --- a/docs/BOUNTIES.md +++ b/docs/BOUNTIES.md @@ -9,8 +9,10 @@ Get paid to improve openpilot! * once you open a PR, the bounty is locked to you until you stop working on it * open a ticket at [comma.ai/support](https://comma.ai/support/shop-order) with links to your PRs to claim * get an extra 20% if you redeem your bounty in [comma shop](https://comma.ai/shop) credit (including refunds on previous orders) +* for bounties >$100, the first PR gets a lock, which times out after a week of no progress -New bounties can be proposed in the [**#contributing**](https://discord.com/channels/469524606043160576/1183173332531687454) channel in Discord. +We put up each bounty with the intention that it'll get merged, but occasionally the right resolution is to close the bounty, which only becomes clear once some effort is put in. +This is still valuable work, so we'll pay out $100 for getting any bounty closed with a good explanation. ## Issue bounties @@ -20,6 +22,8 @@ We've tagged bounty-eligible issues across openpilot and the rest of our repos; * **$500** - a few days of work for an experienced openpilot developer * **$1k+** - a week or two of work (could be less for the right person) +New bounties can be proposed in the [**#contributing**](https://discord.com/channels/469524606043160576/1183173332531687454) channel in Discord. + ## Car bounties The car bounties only apply to cars that have a path to ship in openpilot release, which excludes unsupportable cars (e.g. Fords with a steering lockout) or cars that require extra hardware (Honda Accord with serial steering). @@ -53,10 +57,6 @@ The control doesn't have to be perfect, but it should generally do what it's sup Get a Rivian driving with openpilot. Requires a merged port with lateral control and at least a POC of longitudinal control. -#### Toyota SecOc - $5000 - -We're contributing $5k to the [community-organized bounty](https://github.com/commaai/openpilot/discussions/19932). - #### Chevy Bolt with SuperCruise - $2500 The Bolt is already supported on the trim with standard ACC. Get openpilot working on the trim with SuperCruise. It must be a normal install: no extra pandas or other hardware, no ECU reflashes, etc. The full bounty is for a port with lateral and longitudinal control. $1500 of the bounty can be claimed with a lateral-only port. diff --git a/docs/CARS.md b/docs/CARS.md index ded4a244c9..43bd8a8b30 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,29 +4,24 @@ 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. -# 289 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| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 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
    || |Acura|RDX 2016-18|AcuraWatch Plus|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
    || |Acura|RDX 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || -|Audi|A3 2014-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
    || -|Audi|A3 Sportback e-tron 2017-18|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
    || -|Audi|Q2 2018|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
    || -|Audi|Q3 2019-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
    || -|Audi|RS3 2018|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
    || -|Audi|S3 2015-17|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
    || -|Buick|LaCrosse 2017-19[4](#footnotes)|Driver Confidence Package 2|openpilot|18 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
    || -|Cadillac|Escalade 2017[4](#footnotes)|Driver Assist Package|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
    || -|Cadillac|Escalade ESV 2016[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
    || -|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
    || +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || |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
    || |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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 2019-20|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
    || |Chrysler|Pacifica 2021-23|All|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
    || @@ -34,8 +29,9 @@ 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|| +|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || |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|Bronco Sport 2021-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 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
    || @@ -47,32 +43,31 @@ A supported vehicle is one that just works when you install a comma device. All |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 2023-24|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|G70 2024|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai L 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
    || +|Ford|Maverick Hybrid 2023-24|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|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 2019-21|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|G70 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai L 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-empty.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
    || |Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai H 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|G90 2017-18|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
    || -|Genesis|GV60 (Advanced Trim) 2023[6](#footnotes)|All|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
    || -|Genesis|GV60 (Performance Trim) 2023[6](#footnotes)|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
    || -|Genesis|GV70 (2.5T Trim) 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai L 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|GV70 (3.5T Trim) 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai M 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|GV80 2023[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai M 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
    || -|GMC|Acadia 2018[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
    || +|Genesis|G90 2017-20|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
    || +|Genesis|GV60 (Advanced Trim) 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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
    || +|Genesis|GV60 (Performance Trim) 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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
    || +|Genesis|GV70 (2.5T Trim) 2022-23[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai L 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|GV70 (3.5T Trim) 2022-23[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai M 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|GV80 2023[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai M 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
    || |GMC|Sierra 1500 2020-21|Driver Alert 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
    || |Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || |Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || |Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.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|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || |Honda|Civic 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || |Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || |Honda|Civic Hatchback 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || |Honda|CR-V 2015-16|Touring Trim|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|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || -|Honda|CR-V Hybrid 2017-20|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || +|Honda|CR-V Hybrid 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || |Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Honda Bosch 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
    || |Honda|Fit 2018-20|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|Freed 2020|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
    || @@ -88,30 +83,30 @@ A supported vehicle is one that just works when you install a comma device. All |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
    || |Hyundai|Custin 2023|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|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 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
    || -|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 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
    || +|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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
    || +|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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
    || |Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai E 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|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|Genesis 2015-16|Smart Cruise Control (SCC)|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
    || +|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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
    || |Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai E 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|Ioniq 5 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai Q 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|Ioniq 5 (with HDA II) 2022-23[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai Q 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|Ioniq 5 (without HDA II) 2022-23[6](#footnotes)|Highway Driving Assist|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|Ioniq 6 (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai P 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|Ioniq 5 (Non-US only) 2022-24[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai Q 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|Ioniq 5 (with HDA II) 2022-24[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai Q 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|Ioniq 5 (without HDA II) 2022-24[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|Ioniq 6 (with HDA II) 2023-24[5](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai P 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|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 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
    || |Hyundai|Ioniq Electric 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 H 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|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 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
    || |Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai H 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|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai H 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|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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
    || +|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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
    || |Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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
    || |Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai O 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|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai R 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|Kona Electric (with HDA II, Korea only) 2023[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai R 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|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai I 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|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai H 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|Santa Cruz 2022-23[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 N 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|Santa Cruz 2022-24[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai N 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|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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
    || |Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai L 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|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai L 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
    || @@ -119,49 +114,51 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai E 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|Sonata 2020-23|All|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
    || |Hyundai|Sonata Hybrid 2020-23|All|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
    || -|Hyundai|Staria 2023[6](#footnotes)|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|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai L 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|Tucson 2022[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 N 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|Tucson 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai N 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|Staria 2023[5](#footnotes)|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|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 Hyundai L 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|Tucson 2022[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai N 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|Tucson 2023-24[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai N 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|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai L 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|Tucson Hybrid 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai N 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|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai E 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|Tucson Hybrid 2022-24[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai N 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|Tucson Plug-in Hybrid 2024[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai N 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|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 Hyundai E 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
    || |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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
    || |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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
    || -|Kia|Carnival 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|Stock|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|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|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
    || +|Kia|Carnival 2022-24[5](#footnotes)|Smart Cruise Control (SCC)|Stock|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|Carnival (China only) 2023[5](#footnotes)|Smart Cruise Control (SCC)|Stock|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
    || |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai E 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|EV6 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai P 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|EV6 (with HDA II) 2022-23[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai P 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|EV6 (without HDA II) 2022-23[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai L 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|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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
    || -|Kia|Forte 2023|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 E 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|EV6 (Non-US only) 2022-24[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai P 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|EV6 (with HDA II) 2022-24[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai P 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|EV6 (without HDA II) 2022-24[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai L 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|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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
    || +|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai E 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|K5 2021-24|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|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|K8 Hybrid (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai Q 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|K8 Hybrid (with HDA II) 2023[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai Q 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 EV 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 H 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 EV 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
    || |Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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 EV 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 H 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 EV 2023[6](#footnotes)|All|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 EV 2023[5](#footnotes)|All|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 Hybrid 2018|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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 Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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 Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|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|Niro Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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-empty.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|Smart Cruise Control (SCC)|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|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 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|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 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
    || |Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai H 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|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai E 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|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai E 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|Sorento 2021-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|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
    || -|Kia|Sorento Hybrid 2021-23[6](#footnotes)|All|Stock|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|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|All|Stock|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|Sportage 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 N 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|Sportage 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 N 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|Sorento 2021-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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
    || +|Kia|Sorento Hybrid 2021-23[5](#footnotes)|All|Stock|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|Sorento Plug-in Hybrid 2022-23[5](#footnotes)|All|Stock|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|Sportage 2023-24[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai N 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|Sportage Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai N 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|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|Stinger 2022-23|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
    || |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 Hyundai H 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
    || @@ -188,8 +185,8 @@ A supported vehicle is one that just works when you install a comma device. All |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-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
    || +|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || |Mazda|CX-9 2021-23|All|Stock|0 mph|28 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
    || |Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 Nissan B connector
    - 1 RJ45 cable (7 ft)
    - 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
    || @@ -197,26 +194,27 @@ A supported vehicle is one that just works when you install a comma device. All |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 Nissan A connector
    - 1 RJ45 cable (7 ft)
    - 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
    || |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 Nissan A connector
    - 1 RJ45 cable (7 ft)
    - 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
    || |Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Ram connector
    - 1 comma 3X
    - 1 comma power v2
    - 1 harness box
    - 1 mount
    - 1 right angle OBD-C cable (1.5 ft)
    Buy Here
    || -|SEAT|Ateca 2018|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
    || -|SEAT|Leon 2014-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
    || -|Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || -|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || -|Subaru|Forester 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || -|Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || -|Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || -|Subaru|Legacy 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru B connector
    - 1 comma 3X
    - 1 comma power v2
    - 1 harness box
    - 1 mount
    - 1 right angle OBD-C cable (1.5 ft)
    Buy Here
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || -|Subaru|Outback 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru B connector
    - 1 comma 3X
    - 1 comma power v2
    - 1 harness box
    - 1 mount
    - 1 right angle OBD-C cable (1.5 ft)
    Buy Here
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || -|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || -|Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || -|Škoda|Fabia 2022-23[12](#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
    [14](#footnotes)|| -|Škoda|Kamiq 2021-23[10,12](#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
    [14](#footnotes)|| -|Škoda|Karoq 2019-23[12](#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
    || -|Škoda|Kodiaq 2017-23[12](#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
    || -|Škoda|Octavia 2015-19[12](#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
    || -|Škoda|Octavia RS 2016[12](#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
    || -|Škoda|Scala 2020-23[12](#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
    [14](#footnotes)|| -|Škoda|Superb 2015-22[12](#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
    || +|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|Subaru|Ascent 2019-21|All[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || +|Subaru|Forester 2019-21|All[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || +|Subaru|Impreza 2017-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || +|Subaru|Impreza 2020-22|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || +|Subaru|Legacy 2020-22|All[6](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru B connector
    - 1 comma 3X
    - 1 comma power v2
    - 1 harness box
    - 1 mount
    - 1 right angle OBD-C cable (1.5 ft)
    Buy Here
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || +|Subaru|Outback 2020-22|All[6](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru B connector
    - 1 comma 3X
    - 1 comma power v2
    - 1 harness box
    - 1 mount
    - 1 right angle OBD-C cable (1.5 ft)
    Buy Here
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || +|Subaru|XV 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || +|Subaru|XV 2020-21|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    Parts- 1 RJ45 cable (7 ft)
    - 1 Subaru 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
    Tools- 1 Pry Tool
    - 1 Socket Wrench 8mm or 5/16" (deep)
    || +|Škoda|Fabia 2022-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    [13](#footnotes)|| +|Škoda|Kamiq 2021-23[9,11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    [13](#footnotes)|| +|Škoda|Karoq 2019-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|Škoda|Kodiaq 2017-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|Škoda|Octavia 2015-19[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|Škoda|Octavia RS 2016[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|Škoda|Octavia Scout 2017-19[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || +|Škoda|Scala 2020-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    [13](#footnotes)|| +|Škoda|Superb 2015-22[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || |Toyota|Alphard 2019-20|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|Alphard Hybrid 2021|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|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    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
    || @@ -229,20 +227,20 @@ A supported vehicle is one that just works when you install a comma device. All |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|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 2018-20|All|Stock|0 mph[8](#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[8](#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
    || |Toyota|Camry Hybrid 2021-24|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|Corolla 2017-19|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|Corolla 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
    || -|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 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|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 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|Corolla Hatchback 2019-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
    || |Toyota|Corolla 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
    || -|Toyota|Corolla Hybrid (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|Highlander 2017-19|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|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 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|Highlander 2017-19|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|Highlander 2020-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
    || -|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|Highlander Hybrid 2017-19|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|Highlander Hybrid 2020-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
    || |Toyota|Mirai 2021|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|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    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
    || @@ -250,69 +248,69 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|Prius 2021-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
    || |Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
    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|Prius Prime 2021-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
    || -|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
    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|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-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 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|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 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 Hybrid 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 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|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
    || -|Volkswagen|Arteon R 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
    || -|Volkswagen|Atlas 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|Atlas Cross Sport 2020-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|California 2021-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|Caravelle 2020|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|CC 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|Crafter 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
    || -|Volkswagen|e-Crafter 2018-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|e-Golf 2014-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|Golf 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-empty.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 Alltrack 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-empty.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 GTD 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|Golf GTE 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|Golf GTI 2015-21|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-empty.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 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-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
    || -|Volkswagen|Polo 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
    [14](#footnotes)|| -|Volkswagen|Polo GTI 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
    [14](#footnotes)|| -|Volkswagen|T-Cross 2021|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
    [14](#footnotes)|| -|Volkswagen|T-Roc 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
    [14](#footnotes)|| -|Volkswagen|Taos 2022-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|Teramont 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|Teramont Cross Sport 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|Teramont X 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|Tiguan 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|Tiguan eHybrid 2021-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|Touran 2016-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
    || +|Toyota|Sienna 2018-20|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
    || +|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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,12](#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 R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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 Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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 Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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 GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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 GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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 GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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 R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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,12](#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-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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,12](#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[10](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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,12](#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,12](#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|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    [13](#footnotes)|| +|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    [13](#footnotes)|| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    [13](#footnotes)|| +|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#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
    || ### Footnotes 1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
    2By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
    3Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
    -4Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
    -52019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
    -6Requires a CAN FD panda kit if not using comma 3X for this CAN FD car.
    -7In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
    -8Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.
    -9openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
    -10Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
    -11Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
    -12Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma 3X functionality.
    -13Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
    -14Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
    +42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
    +5Requires a CAN FD panda kit if not using comma 3X for this CAN FD car.
    +6In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
    +7Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.
    +8openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
    +9Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
    +10Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
    +11Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma 3X functionality.
    +12Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
    +13Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
    ## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). @@ -353,7 +351,9 @@ openpilot does not yet support these Toyota models due to a new message authenti * Toyota Venza 2021+ * Toyota Sequoia 2023+ * Toyota Tundra 2022+ +* Toyota Highlander 2024+ * Toyota Corolla Cross 2022+ (only US model) +* Toyota Camry 2025+ * Lexus NX 2022+ * Toyota bZ4x 2023+ * Subaru Solterra 2023+ diff --git a/docs/c_docs.rst b/docs/c_docs.rst index 6e8808ec20..3b89fe9874 100644 --- a/docs/c_docs.rst +++ b/docs/c_docs.rst @@ -14,12 +14,12 @@ cereal messaging ^^^^^^^^^ .. autodoxygenindex:: - :project: cereal_messaging + :project: msgq_repo_msgq visionipc ^^^^^^^^^ .. autodoxygenindex:: - :project: cereal_visionipc + :project: msgq_repo_msgq_visionipc selfdrive @@ -77,10 +77,10 @@ sensorsd .. autodoxygenindex:: :project: system_sensord_sensors -boardd +pandad ^^^^^^ .. autodoxygenindex:: - :project: selfdrive_boardd + :project: selfdrive_pandad rednose diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index a7f61411c8..9256f463af 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -82,7 +82,7 @@ function launch { tmux capture-pane -pq -S-1000 > /tmp/launch_log # start manager - cd selfdrive/manager + cd system/manager if [ ! -f $DIR/prebuilt ]; then ./build.py fi diff --git a/launch_env.sh b/launch_env.sh index 6859afb0d4..81578aff01 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.7" + export AGNOS_VERSION="10.1" fi export STAGING_ROOT="/data/safe_staging" diff --git a/msgq b/msgq new file mode 120000 index 0000000000..df09146f62 --- /dev/null +++ b/msgq @@ -0,0 +1 @@ +msgq_repo/msgq \ No newline at end of file diff --git a/msgq_repo b/msgq_repo new file mode 160000 index 0000000000..74074d650f --- /dev/null +++ b/msgq_repo @@ -0,0 +1 @@ +Subproject commit 74074d650f5d516a33962c1681a2a15b1d603537 diff --git a/opendbc b/opendbc index 0ac21652f2..5956b7b24a 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 0ac21652f2e643e29aa471ad6b238bf74b22e356 +Subproject commit 5956b7b24a42bf5b880a92240612f017c66577aa diff --git a/panda b/panda index 0c7d5f11d7..8e2d4f8db9 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 0c7d5f11d7187904022ea49b6a76b54d7b280345 +Subproject commit 8e2d4f8db980d725a268266ac8dd037e9832c4d3 diff --git a/poetry.lock b/poetry.lock index 588186d138..2467d6be2d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,88 +1,88 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. [[package]] name = "aiohttp" -version = "3.9.3" +version = "3.9.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {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"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, + {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, + {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, + {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, + {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, + {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, + {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [package.dependencies] @@ -112,39 +112,42 @@ ifaddr = ">=0.2.0" [[package]] name = "aiortc" -version = "1.7.0" +version = "1.9.0" description = "An implementation of WebRTC and ORTC" optional = false python-versions = ">=3.8" files = [ - {file = "aiortc-1.7.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aba47eac61ee7fb7e89876f40e4132aa66a13ed2a730dff003342e57219f34c0"}, - {file = "aiortc-1.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:169abaaa0c11a1695942b3eeea9d9032ea4992c6e84958c1b31c6ba22fcf4b0e"}, - {file = "aiortc-1.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5323a347d02d53989e61944eead19e550d930afbb872dd0fb51b3d065aaa833"}, - {file = "aiortc-1.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71c18762ebfeb239352705672e598c57f0e56e5c1b7955dba27651c801c56ea2"}, - {file = "aiortc-1.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:817526c2e429a1ef1226ca9cdb4ff3c5d03857eb31de0f5a00433dc4cb5569f3"}, - {file = "aiortc-1.7.0-cp38-abi3-win32.whl", hash = "sha256:a63c4da5c4a9d96ef6e3948c1f4675e02b0b908605eff4cea8b5e2fa5a34da4e"}, - {file = "aiortc-1.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:e60f19f810a552690bf6e969429c624df39af2b5079ee0d95fb75d110b978e20"}, - {file = "aiortc-1.7.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7407d7065cbc649adf866927c007d84f94eeb3fcaa65f44eb94def8c2c5bbca"}, - {file = "aiortc-1.7.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35e1dce2697762841dd3cc5a6e23ef8a0d96207e3fd33b834b5a8686748f6143"}, - {file = "aiortc-1.7.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f77cc2d757d7c3c37c6157cce36fe13fc161c5cb5aea62759c8b0d3e6d7f45f9"}, - {file = "aiortc-1.7.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80465daa66f89e4fd22b6afd3a6ae71ffd506343cf30025dbc36eb5453f95330"}, - {file = "aiortc-1.7.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8fcf352df6fabad32fd337dc51b629060de80d06987a8544c3c842ecc04254f8"}, - {file = "aiortc-1.7.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b1427673e3ef8889dbd1c4f05d3d2aa7895cbfdc985532d54892ad6f96fc08c"}, - {file = "aiortc-1.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec2d018bd01c7532188be5842e11de252a1346156880ff3387d4f879c9f163d2"}, - {file = "aiortc-1.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f21e0024c0c7b07fec87e1ffcc30b6dbd57d1b822324d9c0128731388a82f08"}, - {file = "aiortc-1.7.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd267c338bd6578b46fb8f664418f9a48ad5d1582895eb029b4c5087e105fc89"}, - {file = "aiortc-1.7.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:919b6edbc1e462cb00e313d44368798066c3ebe81525ec6fb6008e0cad572c97"}, - {file = "aiortc-1.7.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:573314501d4aa2ef5e8826abe7e675742c92d25908f5f6b48ea2f5fababdfb4b"}, - {file = "aiortc-1.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:791f525577033572ee937853159cff2c63121d1e30d9c93df4ef3a4c94eec5ec"}, - {file = "aiortc-1.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52e945b2ba536e4e78c106e6c923f73b1838bf0c35592d729ea9b3ba6791b108"}, - {file = "aiortc-1.7.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89a8aef4067c68ccbc97872d506b9b80d5ed39e0cc41a46d641b66c518e00240"}, - {file = "aiortc-1.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0b7248716706c52e3bc1f21a89a1a9ca205e95a74c0b4aa4b2863d2162fd2552"}, - {file = "aiortc-1.7.0.tar.gz", hash = "sha256:4fd900797b419a9189443b7c95a2ce4bf5aa0d9542d8d19edfabf30aa5fbc296"}, + {file = "aiortc-1.9.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e3e67c1970c2cffacac53c8f161df264efc62b22721c64a621940935028ee087"}, + {file = "aiortc-1.9.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d893cb3d4ffa0ff4f9bb03a88f0a700cdbcd4c0dc060a46c59a27ccd1c890663"}, + {file = "aiortc-1.9.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:176b4eb38d833667f87cf719a7a3e105e25a35b138b30893294418c1c96e38db"}, + {file = "aiortc-1.9.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44b610f36b8d17123855dfbe915fa6874201765b8a2c7fd9cf72d14cf417740"}, + {file = "aiortc-1.9.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55505adb31d56cba19a1ef8ad6aa9b727ccdba2a83bfbfb4aa79ef3c472026a6"}, + {file = "aiortc-1.9.0-cp38-abi3-win32.whl", hash = "sha256:680b703e35870e301535c930bfe32e7d012224a91ce51531aba45a3124ef07cc"}, + {file = "aiortc-1.9.0-cp38-abi3-win_amd64.whl", hash = "sha256:de5e7020cfc2d2d9fb95690926ff2e3b3c30cd4f5f5bc68d5b6756a8eebb686e"}, + {file = "aiortc-1.9.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:34c516ae4e70e8f64494305057af09311444325722fe6938ec38dd1e111adca9"}, + {file = "aiortc-1.9.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:40e61c1b84914d6f4c2968ff49353a22eed9419de74b151237cdb71af431209c"}, + {file = "aiortc-1.9.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1924e130a441507b1315956aff05c504a274f1a09802def225d0f3a3d1870320"}, + {file = "aiortc-1.9.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb62950e396c311e398925149fa76bc90b8d6525b4eccf28cba704e7ded8bf5"}, + {file = "aiortc-1.9.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5234177e8d3126a0190ed9b6f8d0288daedcc0158c45cc279b4e6ac7d97f43f8"}, + {file = "aiortc-1.9.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0e31575eb050aa68e0ea4c519aef101770b2297954f49e64a5c3d73ef27702ea"}, + {file = "aiortc-1.9.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:33018ce36186142e8a5eccca53189ff30c6d48d3d4d0c3df952fbf80f59f17d7"}, + {file = "aiortc-1.9.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d8cec50d8dcb1ea089378f885cb780357842ac39b7cb0e9361e5143c0126ecc"}, + {file = "aiortc-1.9.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8f73ee2d738208c10bb9be8bd2dc0d7da7621de1bbd5ab709216dfa681f4585"}, + {file = "aiortc-1.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84f16cb2a654d4e5018c8aba45e77882c75bf50c6af4eee3048ddb35af7f0c8f"}, + {file = "aiortc-1.9.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23df191d42b2937214263471a796502fcb8f86980e6b676aedbd188107e2a11d"}, + {file = "aiortc-1.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:9e7bf1c892f7b0a474e85f7e2c462775dbb5a79d6bd6450cf0d0784804629020"}, + {file = "aiortc-1.9.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bc9692a1b67f4eea8d3f22785b6ac9bc9c3d98478fd2326f99247ebf432b05d1"}, + {file = "aiortc-1.9.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4d0321824d24e3db918d2e494d44f2bc4467c8abd70646bbd57a5a5a49b72583"}, + {file = "aiortc-1.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7eeabe29472c9e3bb73fb58d5ad3b017269046d43189532e6ec64359c5697b8"}, + {file = "aiortc-1.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c51f030e175569962df436d61953162da7a7c5e3d691adec660b5eee72dbba56"}, + {file = "aiortc-1.9.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:928f136647e609eca0028f7aaf3812fff4e3a4e0fe78abdc19dd0786e65ffac8"}, + {file = "aiortc-1.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1107acead0fa97e2f039d3cf00179a8b12b35822372b922f6b9f007145b171d3"}, + {file = "aiortc-1.9.0.tar.gz", hash = "sha256:03faa76d76ef0e5989ac10386898b029369756102217230e2fcd4b029c50b303"}, ] [package.dependencies] aioice = ">=0.9.0,<1.0.0" -av = ">=9.0.0,<12.0.0" +av = ">=9.0.0,<13.0.0" cffi = ">=1.0.0" cryptography = ">=42.0.0" google-crc32c = ">=1.1" @@ -201,67 +204,71 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "av" -version = "11.0.0" +version = "12.1.0" description = "Pythonic bindings for FFmpeg's libraries." optional = false python-versions = ">=3.8" files = [ - {file = "av-11.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a01f13b37eb6d181e03bbbbda29093fe2d68f10755795188220acdc89560ec27"}, - {file = "av-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b2236faee1b5d71dff3cdef81ef6eec22cc8b71dbfb45eb037e6437fe80f24e7"}, - {file = "av-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40543a08e5c84aecd2bc84da5d43548743201897f0ba21bf5ae3a4dcddefca2b"}, - {file = "av-11.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2907376884d956376aaf3bc1905fa4e0dcb9ba4e0d183e519392a19d89317d1b"}, - {file = "av-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8d5581dcdc81cd601e3ce036809f14da82c46ff187bcefe981ec819390e0ab0"}, - {file = "av-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:150490f2a62cfa470f3cb60f3a0060ff93afd807e2b7b3b0eeeb5a992eb8d67b"}, - {file = "av-11.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d9bac0de62f09e2cb4e2132b5a46a89bc31c898189aa285b484c17351d991afe"}, - {file = "av-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2122ff8bdace4ce50207920f37de472517921e2ca1f0503464f748fdb8e20506"}, - {file = "av-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:527d840697fee6ad4cf47eba987eaf30cd76bd96b2d20eaa907e166b9b8065c8"}, - {file = "av-11.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abeaedddfca9101886eb6fc47318c5f5ece8480d330d73aacf6917d7421981a2"}, - {file = "av-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13790fbb889b955baf885fe3761e923e85537ef414173465ec293177cedb7b99"}, - {file = "av-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc27e27f52480287f44226ad4ae3eb53346bf027959d0f00a9154530bd98b371"}, - {file = "av-11.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:892583e2c6b8c2500e5d24310f499caefcdaa2e48c8f7169ad41041aaaf4da11"}, - {file = "av-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6943679d70a9f4de974049e7ae2cf0b20afe0d7ddab650526c02a6cf9adcd08f"}, - {file = "av-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6d73b038ccf1df5c16bc643eee5c694fb7732e09375e2f4903c1f4ce90dfb72"}, - {file = "av-11.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c83422db3333e97b9680700df5185139352fc3a568b14179da3bdcbeb2f0e91b"}, - {file = "av-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8413900f6a3639e0088c018a3a516a1656d4d16799e7aa759a16ddf3bd268e2b"}, - {file = "av-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:908e49ee336223801d8f2f7dca5a1deb64e9d8256138b8e7a79013b682a6ebb5"}, - {file = "av-11.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:82411ae4a562da07b76028d2f349fb0e6a86aa78ad2b18d2d7bf5b06b17fba14"}, - {file = "av-11.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:621104bd63e38fa4eca554da3722b1aac329619de39152f27eec8999acc72342"}, - {file = "av-11.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:442878990c094455a16c10127edcc54bc4e78d355e6a13ad2a27608b0ecda38f"}, - {file = "av-11.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:658199c92987dc72511f5ee8ade62faef6234b7a04c8b5788de99e366be5e073"}, - {file = "av-11.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4b381665c49267b46f87297573898b85e5c41384750fee2e70267fbc4ba318"}, - {file = "av-11.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:60de14f71293e36ca4e297cc8a8460f0cf74f38a201694f3c6fc7f40301582f2"}, - {file = "av-11.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a90f04af96374dab94028a7471597bdfcf03083338b9be2eb8ca4805a8ec7ab5"}, - {file = "av-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8821ab2d23e4cb5c8abea6b08d2b1bfceca6af2d88fab1d1dc1b3ec7b34933c7"}, - {file = "av-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a92342ed307eeaf9509a6b0f3bafd4337c4880c851b50acc18df48c625b63b6"}, - {file = "av-11.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe3502975bc844f5d432c1f24d331bf6ef3e05532ebf06f7ed08b60719b8ea5"}, - {file = "av-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c278b3a4fd111b4c9190abe6b1a5ca358d5f91e851d470b62577b957e0187b09"}, - {file = "av-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:478aa1d54fbc3058ea65ff41086b6adbe1326b456a027d2f3b59dbe60b4ac2ca"}, - {file = "av-11.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e8df10bb2d56a981d02a8a0b41491912b76dad06305d174a2575ef55ad451100"}, - {file = "av-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30c51e597785a89241bd61865faff2dbd3327856a8285a1e120dbf60e18348b"}, - {file = "av-11.0.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8b8bd92edb096699b306e7b090ad096925ca3bdae6f89656f023fa2a2da627d"}, - {file = "av-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9383af733abfc44f6fc29307a6c922fbf671ee343dc97b78b74eac6a2346a46d"}, - {file = "av-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a9df4a60579198b560f641cdfe4c2139948a70193ddc096b275f2cf6d94e3e04"}, - {file = "av-11.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8ae5f7ae0a7093fb813686d4aa4c554531f80a28480427f5c155da51b747eff0"}, - {file = "av-11.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50fb7d606f8236891d773c701d5650b93af8dbf78eeaac36fc7e1f7f64a9d664"}, - {file = "av-11.0.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:543e0f9bf6ff02dedbe66d906fbc89c8907c80a8ea7413fc3fed68ce4a6e9b44"}, - {file = "av-11.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:daa279c884457ab194ce78bdd89c0aa391af733da95fb3258d4c6eb8c258299a"}, - {file = "av-11.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1aacc21f4cf96447117a61edfb776afb73186750a5e08a21484ddfc3599aefb5"}, - {file = "av-11.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2568b38eef777b916a5d02e42b8f67f92e12023531239ddd32e1ca4f3cdf8c5b"}, - {file = "av-11.0.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747c6d347e27c59cc2e78c9c505d23cd88eceff0cc9386be73693ae9009a577c"}, - {file = "av-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bbd8f4941b9d3450eff40003b9b9d904667aec7ab085fa31f0f9bca32d755e0"}, - {file = "av-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f39c1244ba0cf185b2722aeec116b8a98a2ee5728ce687cec0bda60ee0360dfc"}, - {file = "av-11.0.0.tar.gz", hash = "sha256:48223f000a252070f8e700ff634bb7fb3aa1b7bc7e450373029fbdd6f369ac31"}, + {file = "av-12.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0df2ad330ccf63ed8192d637306f13123cdf1c06717168d1de8b9a084d62f70"}, + {file = "av-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e66ad48dc3f618cf4a75cc14dd7e119d1151ff3c13b9b064014c79bad20df85"}, + {file = "av-12.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0e8fbbe3cffd04dcbfaf7f9e0469c8c9d3ae962728487aae0dbbac9ebb62567"}, + {file = "av-12.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c24d21b116e3af45e2f4b3a7ff1c96ae9a266bcde33a689ace0c52888e74d9"}, + {file = "av-12.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eff59d1eb0ba263e9efe8e460ca239c6ee2285f1b92c6b3c64f002c1b2ffd56"}, + {file = "av-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:09f8bd1fd124e389a266c770d209b5b4333f69c4b5a66b9aa2d09a561b0b54ab"}, + {file = "av-12.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e4c409639699d75e85a5b4b9fbb0538388bb009c8b426f7976b218731815e645"}, + {file = "av-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f624a61d8062bb7128a4b0af018ef5c7642acff2af7cea1bb6cc5aa663954b77"}, + {file = "av-12.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73c61635e959dd50857f1ae3ad28984ce813688262672a5188376686dd293333"}, + {file = "av-12.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f8dcf20ecdfed62cb8b31790d3f394c76f05d5d58d5cc516f7b37c8608b78e2"}, + {file = "av-12.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebb11aba1ef2acb945713be5f4f7a359439230dc566243c354dddb2b06361367"}, + {file = "av-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a309994db77f632b606fe22c5bac03302e3dbe48d53c195abc435ccc56192746"}, + {file = "av-12.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:08401e59a9e33a42511d28cf1fdc570c31d3416426a2d73f4f4aaaaca5945c54"}, + {file = "av-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:efd45e3aa1e478ccbaafd84baf7d95d660b9cef30d850816129fd37d76813589"}, + {file = "av-12.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab553ce72c631477181d6c08c6e710afa44fa3452e61b82d9a75be07b1b2fef"}, + {file = "av-12.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:555f3240306ff02169ff209b152f97b071b57957868c3004c65e25c28130d593"}, + {file = "av-12.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07706499489f2047b54a4675dd04e2cf88322caef904b7b6eb03f480e682cf15"}, + {file = "av-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f669f5fb2515e9a4c9ee05b24ffbe3168d33c241bda93c84c8e384ca682a5cde"}, + {file = "av-12.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:876302ee793a457a03c4faa8281012671bb52dec843062bec59d6f0ae3735ba6"}, + {file = "av-12.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e6ad88e1e61e65c69d92ff1db8826686f913f147b427c99aa3202b027e766128"}, + {file = "av-12.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49a8f88b26d3d25140633a8ec48328a9467bbe001d01c54472394484cdb60b10"}, + {file = "av-12.1.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97873f344344b9b6aef786b22b57fb42c6eaa4ea0798d2020c5ed061f29ab3d6"}, + {file = "av-12.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdf4c54354580abbea9390e23a471a346e9a4b4ca19c6929ad11a59d525e2ad3"}, + {file = "av-12.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:dc1a82e7d43495be6d34b50fd917989a72de7c3a7434d8ec72af0952c1ad4ea3"}, + {file = "av-12.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41d13494401bd3968255f7f9af2af203c30b684efc5a7ed92ebe9ec37f9f9264"}, + {file = "av-12.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc36f7b74e88db8e73fa69dc869331da74abc4f034ecd55f85f6232fcdddca60"}, + {file = "av-12.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ff7a43ce921f2cc3c794810b148c4fa2cfd7ff10f4404072c94cf57b39b13d"}, + {file = "av-12.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce02915698d605c19c372314b7894033a451e838300d0a45c2708a550044e2d1"}, + {file = "av-12.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eadd5c7c374c9ff889a9116802cdda7ef9d574b623338f4045effc0f3f3c2cbc"}, + {file = "av-12.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:f32893849fe34300f3cec51c4ae71c45b0acac448d36336d3452a5bb4f7e11bf"}, + {file = "av-12.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a0a2a8693fdaa3bbb00255cda388f110f7a0b00817470a8cd8f1aa5c8dcbc3c9"}, + {file = "av-12.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:615f440856cbc5b96b8ae52c75ba722f082b898c3ab837eae024a06a0914e8a6"}, + {file = "av-12.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257fe519b0ffb4e900b737515137fb9ae0490edca7d70818b6c71c3cd79994ca"}, + {file = "av-12.1.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04afe8f9005bb42f95717bcfbb22a8950b4b942a862444edb1f0bab71ea702e9"}, + {file = "av-12.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63cbeaedc0184094b7d36bd4267cd61e6c69c18cb3464cc726ce6a8a438ac87a"}, + {file = "av-12.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0a0e056baa87037f932d12de3d3f258cbc4284d18d85099ccd845b333ac1bb91"}, + {file = "av-12.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7d549c2e6e9035022ea2280b781150a8c81acc4a03c69bde20b2f53262041a88"}, + {file = "av-12.1.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b1e02715cbb985b0efe6b6aaf134f9d1fee760822a07fd19e995a8e461909f4"}, + {file = "av-12.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b348264ba26152d7b06f2aaf0b2a11c90b13c628a447f6daa2a6770b9443fb0"}, + {file = "av-12.1.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6a3b3e4138cd1977f14e3d16c5f89979de8efa251d7558e2dc10a51cfcc0100"}, + {file = "av-12.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:105b017958eb5b6a128a5399200a4ec2b1040c2047e0b5f5e3714cd64fe7046e"}, + {file = "av-12.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:00596e53db3082193142e32fbdf47349724221de117645b0ed8fcaaec508adf4"}, + {file = "av-12.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed7c48d2d79961d70ea59f44fcff453bb2444a152793f80d2ceaa17af4331b9c"}, + {file = "av-12.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2c486adf83fc5b8e444efcc32f3eef27eefd6d0966ef68607d41205adcd8ec0"}, + {file = "av-12.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abe9475dd2c8bea47338d5e90d6a45a28930d0fe3820ed2d3d09dfbb3316d476"}, + {file = "av-12.1.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0130a8391aa258eee60da3c09d69eb5c9480f14a9f1b1b5312336bac879edd2a"}, + {file = "av-12.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669f206cfdd5696d0edf2c81c5d220acc40b4153b71cf6662618c376e00b6d3a"}, + {file = "av-12.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e322533f585c2e8df07aa708c594fcb67f5f27a2f8b4107a7e6a6f90606190c7"}, + {file = "av-12.1.0.tar.gz", hash = "sha256:67adab9fdabcb8a86bd542787196580e38ed4132331ee9e82234b23cea9546b3"}, ] [[package]] name = "azure-core" -version = "1.30.0" +version = "1.30.2" description = "Microsoft Azure Core Library for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "azure-core-1.30.0.tar.gz", hash = "sha256:6f3a7883ef184722f6bd997262eddaf80cfe7e5b3e0caaaf8db1695695893d35"}, - {file = "azure_core-1.30.0-py3-none-any.whl", hash = "sha256:3dae7962aad109610e68c9a7abb31d79720e1d982ddf61363038d175a5025e89"}, + {file = "azure-core-1.30.2.tar.gz", hash = "sha256:a14dc210efcd608821aa472d9fb8e8d035d29b68993819147bc290a8ac224472"}, + {file = "azure_core-1.30.2-py3-none-any.whl", hash = "sha256:cf019c1ca832e96274ae85abd3d9f752397194d9fea3b41487290562ac8abe4a"}, ] [package.dependencies] @@ -274,50 +281,50 @@ aio = ["aiohttp (>=3.0)"] [[package]] name = "azure-identity" -version = "1.15.0" +version = "1.16.1" description = "Microsoft Azure Identity Library for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "azure-identity-1.15.0.tar.gz", hash = "sha256:4c28fc246b7f9265610eb5261d65931183d019a23d4b0e99357facb2e6c227c8"}, - {file = "azure_identity-1.15.0-py3-none-any.whl", hash = "sha256:a14b1f01c7036f11f148f22cd8c16e05035293d714458d6b44ddf534d93eb912"}, + {file = "azure-identity-1.16.1.tar.gz", hash = "sha256:6d93f04468f240d59246d8afde3091494a5040d4f141cad0f49fc0c399d0d91e"}, + {file = "azure_identity-1.16.1-py3-none-any.whl", hash = "sha256:8fb07c25642cd4ac422559a8b50d3e77f73dcc2bbfaba419d06d6c9d7cff6726"}, ] [package.dependencies] -azure-core = ">=1.23.0,<2.0.0" +azure-core = ">=1.23.0" cryptography = ">=2.5" -msal = ">=1.24.0,<2.0.0" -msal-extensions = ">=0.3.0,<2.0.0" +msal = ">=1.24.0" +msal-extensions = ">=0.3.0" [[package]] name = "azure-storage-blob" -version = "12.19.0" +version = "12.20.0" description = "Microsoft Azure Blob Storage Client Library for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "azure-storage-blob-12.19.0.tar.gz", hash = "sha256:26c0a4320a34a3c2a1b74528ba6812ebcb632a04cd67b1c7377232c4b01a5897"}, - {file = "azure_storage_blob-12.19.0-py3-none-any.whl", hash = "sha256:7bbc2c9c16678f7a420367fef6b172ba8730a7e66df7f4d7a55d5b3c8216615b"}, + {file = "azure-storage-blob-12.20.0.tar.gz", hash = "sha256:eeb91256e41d4b5b9bad6a87fd0a8ade07dd58aa52344e2c8d2746e27a017d3b"}, + {file = "azure_storage_blob-12.20.0-py3-none-any.whl", hash = "sha256:de6b3bf3a90e9341a6bcb96a2ebe981dffff993e9045818f6549afea827a52a9"}, ] [package.dependencies] -azure-core = ">=1.28.0,<2.0.0" +azure-core = ">=1.28.0" cryptography = ">=2.1.4" isodate = ">=0.6.1" -typing-extensions = ">=4.3.0" +typing-extensions = ">=4.6.0" [package.extras] -aio = ["azure-core[aio] (>=1.28.0,<2.0.0)"] +aio = ["azure-core[aio] (>=1.28.0)"] [[package]] name = "babel" -version = "2.14.0" +version = "2.15.0" description = "Internationalization utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, - {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, ] [package.extras] @@ -340,54 +347,59 @@ Sphinx = ">=4.0,<5.0.0 || >5.0.0" [[package]] name = "casadi" -version = "3.6.3" +version = "3.6.5" description = "CasADi -- framework for algorithmic differentiation and numeric optimization" optional = false python-versions = "*" files = [ - {file = "casadi-3.6.3-cp27-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:884c07617fbbedbd047900d6f2ab86e933064efc517b973fa4139fc60543e498"}, - {file = "casadi-3.6.3-cp27-none-manylinux1_i686.whl", hash = "sha256:6f9c1fadfb1eb729f8906f01cd2b45f542846a386fb63d59eb1872451dda8de3"}, - {file = "casadi-3.6.3-cp27-none-manylinux2010_x86_64.whl", hash = "sha256:041639615a866a7244e88905c40c15ef8f84bdedf0b4f92f72e4c5b56f8fe2dc"}, - {file = "casadi-3.6.3-cp27-none-win_amd64.whl", hash = "sha256:418d345e47cbc957a49e28c7765a7f6e37f5eb73ab969e6c6cfcd204189c559c"}, - {file = "casadi-3.6.3-cp310-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:9c8d360ee80dd65c0c7625e3dccfabdd6e78629a754fb28f6fd77e3d4893dc80"}, - {file = "casadi-3.6.3-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:a74cf436d42adf69d5ea16ba13d95f40585e148e856d00553f96740704fe2a03"}, - {file = "casadi-3.6.3-cp310-none-manylinux2014_aarch64.whl", hash = "sha256:1995029b4f11d492cb5277645534bdd377340eaabe51dba3c6d9ed3a25f83f4c"}, - {file = "casadi-3.6.3-cp310-none-manylinux2014_i686.whl", hash = "sha256:501ac40357dae0d224499f1b41b7d409703137ac7c8c1bcbb26444cff9d00de3"}, - {file = "casadi-3.6.3-cp310-none-manylinux2014_x86_64.whl", hash = "sha256:d86511c92f09fa464937098bb47b5cdfd07952a0a2b236938b9370537e4532d6"}, - {file = "casadi-3.6.3-cp310-none-win_amd64.whl", hash = "sha256:4d41d07a9a4c1cd9aa55a43ffe8921721dc5937b124075e903b4c0948e5dcb9b"}, - {file = "casadi-3.6.3-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:9a2aab9799e8a597b737c8ebbde6fce9cad6177d51fef8f73310d429e4b76cb4"}, - {file = "casadi-3.6.3-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:55cff88af39c486895d5d6ec3c1862050dbd51ca879df81be1b1c86cb97e9115"}, - {file = "casadi-3.6.3-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:387e4832a6b98ecf71d7c6ceb129ca7bfe120dd6705162aecee70df13bd8cb62"}, - {file = "casadi-3.6.3-cp311-none-manylinux2014_i686.whl", hash = "sha256:8357cfae956d1c4d2c8e765e93fcfad537f83e8278d2bd7c9d4f66213fdc6f4e"}, - {file = "casadi-3.6.3-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:de87a486a4578f609bb93327004ee0a4598723cff5c0118828e1de31f01ea65b"}, - {file = "casadi-3.6.3-cp311-none-win_amd64.whl", hash = "sha256:d5b5d49b3749aa075678206645b106fea104485285441f771ba5ef347d4bc394"}, - {file = "casadi-3.6.3-cp35-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:9ba455b1afc4b70249810098458951bd37cb346350ed00e913ec93ddb3f15251"}, - {file = "casadi-3.6.3-cp35-none-manylinux1_i686.whl", hash = "sha256:397b3754a0560e9c4ebb3ce9220bfe5194f6ef63dca6caa6695b7e43a170895b"}, - {file = "casadi-3.6.3-cp35-none-manylinux2010_x86_64.whl", hash = "sha256:406423816ef79ba82e579bd999ba9aaa16ffcb071261d8ff32e3c404ee1e816b"}, - {file = "casadi-3.6.3-cp35-none-win_amd64.whl", hash = "sha256:62fcabf79dfc1f3b7cf06eca1408556bc74a59a9de9dde4b779eff20ac243006"}, - {file = "casadi-3.6.3-cp36-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:7972b476b20557592fd1193140e6d1067a91b98cbc96767b3ec2f68e500c5342"}, - {file = "casadi-3.6.3-cp36-none-manylinux2014_aarch64.whl", hash = "sha256:692c284d838d11052c08fc8b38357858607f701ea6495bf9e22a0f2033811325"}, - {file = "casadi-3.6.3-cp36-none-manylinux2014_i686.whl", hash = "sha256:e5bb5ae64de87568c5c3895ca649b209283e2ad61ddeaef1685c7e856e4044e0"}, - {file = "casadi-3.6.3-cp36-none-manylinux2014_x86_64.whl", hash = "sha256:6b15e0bf452f7b3dface72e7d75ef2fe8566574abec51313540a6a251243e099"}, - {file = "casadi-3.6.3-cp36-none-win_amd64.whl", hash = "sha256:b7148bc8b107b1bae4f66fe5b815edb69aca69de8ead2ee25e1b059781654550"}, - {file = "casadi-3.6.3-cp37-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:3a184a4930a046ee788923a129032372f043027df7a2e08d25fd1fe72d1da8bd"}, - {file = "casadi-3.6.3-cp37-none-manylinux2014_aarch64.whl", hash = "sha256:44ceea824512a594b286fff7646470d7ac58e363bcb40ce23a6ec0577e6af3f9"}, - {file = "casadi-3.6.3-cp37-none-manylinux2014_i686.whl", hash = "sha256:ce42fb3df4e795bbb2178c4f9929bd8d0d359890ae1225c64a0c2795374fda25"}, - {file = "casadi-3.6.3-cp37-none-manylinux2014_x86_64.whl", hash = "sha256:39a73fa7383862abae27ebe4158d23d588286b22d74505af8569f4b91acc51d7"}, - {file = "casadi-3.6.3-cp37-none-win_amd64.whl", hash = "sha256:bede02743ac570b6289e8496563aa326dd4fdecc8c3e4f817b909cf6bc1b42cc"}, - {file = "casadi-3.6.3-cp38-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:7e80e8eee3bb174f941d592dbc440f57c76c5d81bad59f3a361dbf47e6175a43"}, - {file = "casadi-3.6.3-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:c0bcd64e8bb0db6e6dcc24d1ee10ef7f2305ed2e9d2786c1dcae43e7fcd05943"}, - {file = "casadi-3.6.3-cp38-none-manylinux2014_aarch64.whl", hash = "sha256:012970fec19a88326e010b8e8ef4d569e32246dabb8cad86c485263e3961f3b1"}, - {file = "casadi-3.6.3-cp38-none-manylinux2014_i686.whl", hash = "sha256:4c718672247d12b437f5b8b2f62eae5c6541bd9125f233ae4977fbf3b2ff2de7"}, - {file = "casadi-3.6.3-cp38-none-manylinux2014_x86_64.whl", hash = "sha256:3a3685525a278f5b450b8be9ab14bb367f2d0fc97f8400021ba406f63fab7795"}, - {file = "casadi-3.6.3-cp38-none-win_amd64.whl", hash = "sha256:0e6f2ae61abb3969ccf9503e2ee576a4f5b437ba40a082e26660a5d9513c839c"}, - {file = "casadi-3.6.3-cp39-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:7093cb7182db81b6184148121d61a68876ff9be55fb73c456927a344ff5983a2"}, - {file = "casadi-3.6.3-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:0a5383b295a499312657eb5c99a0e8f22e8bb06da774663bae69b322ec34dd6d"}, - {file = "casadi-3.6.3-cp39-none-manylinux2014_aarch64.whl", hash = "sha256:6632b30197aa0d491afb7f6d30980636342eaf8a88fe7680c5eb3a37d4013c01"}, - {file = "casadi-3.6.3-cp39-none-manylinux2014_i686.whl", hash = "sha256:5990366a5266dda537d45c6df583001a1519b86db00e1725e2d17304500e511d"}, - {file = "casadi-3.6.3-cp39-none-manylinux2014_x86_64.whl", hash = "sha256:5177b2592f4c147fec882fad92b7282262979bdf8ad5a337f6531464eee19226"}, - {file = "casadi-3.6.3-cp39-none-win_amd64.whl", hash = "sha256:d5b917443733123c634dbde7e5f971ffb87994af01c0d0d528f6d10ac78d97f7"}, - {file = "casadi-3.6.3.tar.gz", hash = "sha256:2a953bd001327c9ae79018a1efa455852c8a4b4f47f5bdda5f0a07ec820d1880"}, + {file = "casadi-3.6.5-cp27-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:6039081fdd1daf4ef7fa2b52814a954d75bfc03eb0dc62414e02af5d25746e8f"}, + {file = "casadi-3.6.5-cp27-none-manylinux1_i686.whl", hash = "sha256:b5192dfabf6f5266b168b984d124dd3086c1c5a408c0743ff3a82290a8ccf3b5"}, + {file = "casadi-3.6.5-cp27-none-manylinux2010_x86_64.whl", hash = "sha256:35b2ff6098e386a4d5e8bc681744e52bcd2f2f15cfa44c09814a8979b51a6794"}, + {file = "casadi-3.6.5-cp27-none-win_amd64.whl", hash = "sha256:caf395d1e36bfb215b154e8df61583d534a07ddabb18cbe50f259b7692a41ac8"}, + {file = "casadi-3.6.5-cp310-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:314886ef44bd01f1a98579e7784a3bed6e0584e88f9465cf9596af2523efb0dd"}, + {file = "casadi-3.6.5-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:c6789c8060a99b329bb584d97c1eab6a5e4f3e2d2db391e6c2001c6323774990"}, + {file = "casadi-3.6.5-cp310-none-manylinux2014_aarch64.whl", hash = "sha256:e40afb3c062817dd6ce2497cd001f00f107ee1ea41ec4d6ee9f9a5056d219e83"}, + {file = "casadi-3.6.5-cp310-none-manylinux2014_i686.whl", hash = "sha256:ee5a4ed50d2becd0bd6d203c7a60ffad27c14a3e0ae357480de11c846a8dd928"}, + {file = "casadi-3.6.5-cp310-none-manylinux2014_x86_64.whl", hash = "sha256:1ddb6e4afdd1da95d7d9d652ed973c1b7f50ef1454965a9170b657e223a2c73e"}, + {file = "casadi-3.6.5-cp310-none-win_amd64.whl", hash = "sha256:e96ca81b00b9621007d45db1254fcf232d518ebcc802f42853f57b4df977c567"}, + {file = "casadi-3.6.5-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:bebd3909db24ba711e094aacc0a2329b9903d422d73f61be851873731244b7d1"}, + {file = "casadi-3.6.5-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:ccb962ea02b7d6d245d5cd40fb52c29e812040a45273c6eed32cb8fcff673dda"}, + {file = "casadi-3.6.5-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:1ce199a4ea1d376edbe5399cd622a4564040c83f50c50114fe50a69a8ea81d92"}, + {file = "casadi-3.6.5-cp311-none-manylinux2014_i686.whl", hash = "sha256:d12b67d467a5b2b0a909378ef7231fbc9af0da923baa13b1d5464d8471601ac3"}, + {file = "casadi-3.6.5-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:3a3fb8af868f83d4a4f26d878c49f4acc4ed7ee92e731c73e650e5893418a634"}, + {file = "casadi-3.6.5-cp311-none-win_amd64.whl", hash = "sha256:3bdd645151beda013af5fd019fb554756e7dac37541b9f120cdfba90405b2671"}, + {file = "casadi-3.6.5-cp312-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:33afd1a4da0c86b4316953fe541635a8a7dc51703282e24a870ada13a46adb53"}, + {file = "casadi-3.6.5-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:0d6ee0558b4ecdd8aa4aa70fd31528b135801f1086c28a9cb78d8e8242b7aedd"}, + {file = "casadi-3.6.5-cp312-none-manylinux2014_i686.whl", hash = "sha256:be40e9897d80fb72a97e750b2143c32f63f8800cfb78f9b396d8ce7a913fca39"}, + {file = "casadi-3.6.5-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:0118637823e292a9270133e02c9c6d3f3c7f75e8c91a6f6dc5275ade82dd1d9d"}, + {file = "casadi-3.6.5-cp312-none-win_amd64.whl", hash = "sha256:fe2b64d777e36cc3f101220dd1e219a0e11c3e4ee2b5e708b30fea9a27107e41"}, + {file = "casadi-3.6.5-cp35-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:a1ae36449adec534125d4af5be912b6fb9dafe74d1fee39f6c82263695e21ca5"}, + {file = "casadi-3.6.5-cp35-none-manylinux1_i686.whl", hash = "sha256:32644c47fbfb643d5cf9769c7bbc94c6bdb9a40ea9c12c54af5e2754599c3186"}, + {file = "casadi-3.6.5-cp35-none-manylinux2010_x86_64.whl", hash = "sha256:601b76b7afcb27b11563999f6ad1d9d2a2510ab3d00a6f4ce86a0bee97c9d17a"}, + {file = "casadi-3.6.5-cp35-none-win_amd64.whl", hash = "sha256:febc645bcc0aed6d7a2bdb6e58b9a89cb8f74b19bc028c41cc807d75a5d54058"}, + {file = "casadi-3.6.5-cp36-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:c98e68023c9e5905d9d6b99ae1fbbfe4b85ba9846b3685408bb498b20509f99a"}, + {file = "casadi-3.6.5-cp36-none-manylinux2014_aarch64.whl", hash = "sha256:eb311088dca5359acc05aa4d8895bf99afaa16c7c04b27bf640ce4c2361b8cde"}, + {file = "casadi-3.6.5-cp36-none-manylinux2014_i686.whl", hash = "sha256:bceb69bf9f04fded8a564eb64e298d19e945eaf4734f7145a5ee61cf9ac693e7"}, + {file = "casadi-3.6.5-cp36-none-manylinux2014_x86_64.whl", hash = "sha256:c951031e26d987986dbc334492b2e6ef108077f11c00e178ff4007e4a9bf91d8"}, + {file = "casadi-3.6.5-cp36-none-win_amd64.whl", hash = "sha256:e44af450ce944649932f9ef63ff00d2d21f642b506444418b4b20e69dba3adaf"}, + {file = "casadi-3.6.5-cp37-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:c661fe88a93b7cc7ea42802aac76a674135cd65e3e564a6f08570dd3bea05201"}, + {file = "casadi-3.6.5-cp37-none-manylinux2014_aarch64.whl", hash = "sha256:5266fc82e39352e26cb1a4e0a5c3deb32d09e6333be637bd78c273fa50f9012b"}, + {file = "casadi-3.6.5-cp37-none-manylinux2014_i686.whl", hash = "sha256:02d6fb63c460abd99a450e861034d97568a8aec621fc0a4fed22f7494989c682"}, + {file = "casadi-3.6.5-cp37-none-manylinux2014_x86_64.whl", hash = "sha256:5e8adffe2015cde370fc545b2d0fe731e96e583e4ea4c5f3044e818fea975cfc"}, + {file = "casadi-3.6.5-cp37-none-win_amd64.whl", hash = "sha256:7ea8545579872b6f5412985dafec26b906b67bd4639a6c718b7e07f802af4e42"}, + {file = "casadi-3.6.5-cp38-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:0a38bf808bf51368607c64307dd77a7363fbe8e5c910cd5c605546be60edfaff"}, + {file = "casadi-3.6.5-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:f62f779481b30e5ea88392bdb8225e9545a21c4460dc3e96c2b782405b938d04"}, + {file = "casadi-3.6.5-cp38-none-manylinux2014_aarch64.whl", hash = "sha256:deb2cb2bee8aba0c2cad03c832965b51ca305d0f8eb15de8b857ba86a76f0db0"}, + {file = "casadi-3.6.5-cp38-none-manylinux2014_i686.whl", hash = "sha256:f6e10b66d6ae8216dab01532f7ad75cc9d66a95125d421b33d078a51ea0fc2a0"}, + {file = "casadi-3.6.5-cp38-none-manylinux2014_x86_64.whl", hash = "sha256:f9e82658c910e3317535d769334260e0a24d97bbce68cadb72f592e9fcbafd61"}, + {file = "casadi-3.6.5-cp38-none-win_amd64.whl", hash = "sha256:092e448e05feaed8958d684e896d909e756d199b84d3b9d0182da38cd3deebf6"}, + {file = "casadi-3.6.5-cp39-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:f9c1de9a798767c00f89c27677b74059df4c9601d69270967b06d7fcff204b4d"}, + {file = "casadi-3.6.5-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:83e3404de4449cb7382e49d811eec79cd370e64b97b5c94b155c604d7c523a40"}, + {file = "casadi-3.6.5-cp39-none-manylinux2014_aarch64.whl", hash = "sha256:af95de5aa5942d627d43312834791623384c2ad6ba87928bf0e3cacc8a6698e8"}, + {file = "casadi-3.6.5-cp39-none-manylinux2014_i686.whl", hash = "sha256:dbeb50726603454a1f85323cba7caf72524cd43ca0aeb1f286d07005a967ece9"}, + {file = "casadi-3.6.5-cp39-none-manylinux2014_x86_64.whl", hash = "sha256:8bbfb2eb8cb6b9e2384814d6427e48bcf6df049bf7ed05b0a58bb311a1fbf18c"}, + {file = "casadi-3.6.5-cp39-none-win_amd64.whl", hash = "sha256:0e4a4ec2e26ebeb22b0c129f2db3cf90f730cf9fbe98adb9a12720ff6ca1834a"}, + {file = "casadi-3.6.5.tar.gz", hash = "sha256:409a5f6725eadea40fddfb8ba2321139b5252edac8bc115a72f68e648631d56a"}, ] [package.dependencies] @@ -395,13 +407,13 @@ numpy = "*" [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] [[package]] @@ -667,82 +679,82 @@ cron = ["capturer (>=2.4)"] [[package]] name = "contourpy" -version = "1.2.0" +version = "1.2.1" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.9" files = [ - {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, - {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, - {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, - {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, - {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, - {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, - {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, - {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, - {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, - {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, - {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, + {file = "contourpy-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040"}, + {file = "contourpy-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b"}, + {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd"}, + {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619"}, + {file = "contourpy-1.2.1-cp310-cp310-win32.whl", hash = "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8"}, + {file = "contourpy-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9"}, + {file = "contourpy-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5"}, + {file = "contourpy-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df"}, + {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205"}, + {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8"}, + {file = "contourpy-1.2.1-cp311-cp311-win32.whl", hash = "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec"}, + {file = "contourpy-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922"}, + {file = "contourpy-1.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc"}, + {file = "contourpy-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b"}, + {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce"}, + {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4"}, + {file = "contourpy-1.2.1-cp312-cp312-win32.whl", hash = "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f"}, + {file = "contourpy-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce"}, + {file = "contourpy-1.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b"}, + {file = "contourpy-1.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445"}, + {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02"}, + {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083"}, + {file = "contourpy-1.2.1-cp39-cp39-win32.whl", hash = "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba"}, + {file = "contourpy-1.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f"}, + {file = "contourpy-1.2.1.tar.gz", hash = "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c"}, ] [package.dependencies] -numpy = ">=1.20,<2.0" +numpy = ">=1.20" [package.extras] bokeh = ["bokeh", "selenium"] docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.8.0)", "types-Pillow"] test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] [[package]] name = "control" -version = "0.9.4" +version = "0.10.0" description = "Python Control Systems Library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" files = [ - {file = "control-0.9.4-py3-none-any.whl", hash = "sha256:ab68980abd8d35ae5015ffa090865cbbd926deea7e66d0b9a41cfd12577e63ff"}, - {file = "control-0.9.4.tar.gz", hash = "sha256:0fa57d2216b7ac4e9339c09eab6827660318a641779335864feee940bd19c9ce"}, + {file = "control-0.10.0-py3-none-any.whl", hash = "sha256:ed1e0eb73f1e2945fc9af9d7b9121ef328fe980e7903b2a14b149d4f1855a808"}, + {file = "control-0.10.0.tar.gz", hash = "sha256:2c18b767537f45c7fd07b2e4afe8fbe5964019499b5f52f888edb5d8560bab53"}, ] [package.dependencies] -matplotlib = "*" -numpy = "*" -scipy = ">=1.3" +matplotlib = ">=3.6" +numpy = ">=1.23" +scipy = ">=1.8" [package.extras] cvxopt = ["cvxopt (>=1.2.0)"] @@ -751,63 +763,63 @@ test = ["pytest", "pytest-timeout"] [[package]] name = "coverage" -version = "7.4.3" +version = "7.5.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {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"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, + {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, + {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, + {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, + {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, + {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, + {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, + {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, + {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, + {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, + {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, + {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, + {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, ] [package.extras] @@ -825,43 +837,43 @@ files = [ [[package]] name = "cryptography" -version = "42.0.5" +version = "42.0.8" 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.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"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, + {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, + {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, + {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, + {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, + {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, + {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, ] [package.dependencies] @@ -894,69 +906,69 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "cython" -version = "3.0.8" +version = "3.0.10" description = "The Cython compiler for writing C extensions in the Python language." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ - {file = "Cython-3.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a846e0a38e2b24e9a5c5dc74b0e54c6e29420d88d1dafabc99e0fc0f3e338636"}, - {file = "Cython-3.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45523fdc2b78d79b32834cc1cc12dc2ca8967af87e22a3ee1bff20e77c7f5520"}, - {file = "Cython-3.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa0b7f3f841fe087410cab66778e2d3fb20ae2d2078a2be3dffe66c6574be39"}, - {file = "Cython-3.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87294e33e40c289c77a135f491cd721bd089f193f956f7b8ed5aa2d0b8c558f"}, - {file = "Cython-3.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a1df7a129344b1215c20096d33c00193437df1a8fcca25b71f17c23b1a44f782"}, - {file = "Cython-3.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:13c2a5e57a0358da467d97667297bf820b62a1a87ae47c5f87938b9bb593acbd"}, - {file = "Cython-3.0.8-cp310-cp310-win32.whl", hash = "sha256:96b028f044f5880e3cb18ecdcfc6c8d3ce9d0af28418d5ab464509f26d8adf12"}, - {file = "Cython-3.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:8140597a8b5cc4f119a1190f5a2228a84f5ca6d8d9ec386cfce24663f48b2539"}, - {file = "Cython-3.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aae26f9663e50caf9657148403d9874eea41770ecdd6caf381d177c2b1bb82ba"}, - {file = "Cython-3.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:547eb3cdb2f8c6f48e6865d5a741d9dd051c25b3ce076fbca571727977b28ac3"}, - {file = "Cython-3.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a567d4b9ba70b26db89d75b243529de9e649a2f56384287533cf91512705bee"}, - {file = "Cython-3.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d1426263b0e82fb22bda8ea60dc77a428581cc19e97741011b938445d383f1"}, - {file = "Cython-3.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c26daaeccda072459b48d211415fd1e5507c06bcd976fa0d5b8b9f1063467d7b"}, - {file = "Cython-3.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:289ce7838208211cd166e975865fd73b0649bf118170b6cebaedfbdaf4a37795"}, - {file = "Cython-3.0.8-cp311-cp311-win32.whl", hash = "sha256:c8aa05f5e17f8042a3be052c24f2edc013fb8af874b0bf76907d16c51b4e7871"}, - {file = "Cython-3.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:000dc9e135d0eec6ecb2b40a5b02d0868a2f8d2e027a41b0fe16a908a9e6de02"}, - {file = "Cython-3.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90d3fe31db55685d8cb97d43b0ec39ef614fcf660f83c77ed06aa670cb0e164f"}, - {file = "Cython-3.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e24791ddae2324e88e3c902a765595c738f19ae34ee66bfb1a6dac54b1833419"}, - {file = "Cython-3.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f020fa1c0552052e0660790b8153b79e3fc9a15dbd8f1d0b841fe5d204a6ae6"}, - {file = "Cython-3.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18bfa387d7a7f77d7b2526af69a65dbd0b731b8d941aaff5becff8e21f6d7717"}, - {file = "Cython-3.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fe81b339cffd87c0069c6049b4d33e28bdd1874625ee515785bf42c9fdff3658"}, - {file = "Cython-3.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80fd94c076e1e1b1ee40a309be03080b75f413e8997cddcf401a118879863388"}, - {file = "Cython-3.0.8-cp312-cp312-win32.whl", hash = "sha256:85077915a93e359a9b920280d214dc0cf8a62773e1f3d7d30fab8ea4daed670c"}, - {file = "Cython-3.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:0cb2dcc565c7851f75d496f724a384a790fab12d1b82461b663e66605bec429a"}, - {file = "Cython-3.0.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:870d2a0a7e3cbd5efa65aecdb38d715ea337a904ea7bb22324036e78fb7068e7"}, - {file = "Cython-3.0.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e8f2454128974905258d86534f4fd4f91d2f1343605657ecab779d80c9d6d5e"}, - {file = "Cython-3.0.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1949d6aa7bc792554bee2b67a9fe41008acbfe22f4f8df7b6ec7b799613a4b3"}, - {file = "Cython-3.0.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9f2c6e1b8f3bcd6cb230bac1843f85114780bb8be8614855b1628b36bb510e0"}, - {file = "Cython-3.0.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:05d7eddc668ae7993643f32c7661f25544e791edb745758672ea5b1a82ecffa6"}, - {file = "Cython-3.0.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bfabe115deef4ada5d23c87bddb11289123336dcc14347011832c07db616dd93"}, - {file = "Cython-3.0.8-cp36-cp36m-win32.whl", hash = "sha256:0c38c9f0bcce2df0c3347285863621be904ac6b64c5792d871130569d893efd7"}, - {file = "Cython-3.0.8-cp36-cp36m-win_amd64.whl", hash = "sha256:6c46939c3983217d140999de7c238c3141f56b1ea349e47ca49cae899969aa2c"}, - {file = "Cython-3.0.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:115f0a50f752da6c99941b103b5cb090da63eb206abbc7c2ad33856ffc73f064"}, - {file = "Cython-3.0.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c0f29246734561c90f36e70ed0506b61aa3d044e4cc4cba559065a2a741fae"}, - {file = "Cython-3.0.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ab75242869ff71e5665fe5c96f3378e79e792fa3c11762641b6c5afbbbbe026"}, - {file = "Cython-3.0.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6717c06e9cfc6c1df18543cd31a21f5d8e378a40f70c851fa2d34f0597037abc"}, - {file = "Cython-3.0.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9d3f74388db378a3c6fd06e79a809ed98df3f56484d317b81ee762dbf3c263e0"}, - {file = "Cython-3.0.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae7ac561fd8253a9ae96311e91d12af5f701383564edc11d6338a7b60b285a6f"}, - {file = "Cython-3.0.8-cp37-cp37m-win32.whl", hash = "sha256:97b2a45845b993304f1799664fa88da676ee19442b15fdcaa31f9da7e1acc434"}, - {file = "Cython-3.0.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9e2be2b340fea46fb849d378f9b80d3c08ff2e81e2bfbcdb656e2e3cd8c6b2dc"}, - {file = "Cython-3.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2cde23c555470db3f149ede78b518e8274853745289c956a0e06ad8d982e4db9"}, - {file = "Cython-3.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7990ca127e1f1beedaf8fc8bf66541d066ef4723ad7d8d47a7cbf842e0f47580"}, - {file = "Cython-3.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b983c8e6803f016146c26854d9150ddad5662960c804ea7f0c752c9266752f0"}, - {file = "Cython-3.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a973268d7ca1a2bdf78575e459a94a78e1a0a9bb62a7db0c50041949a73b02ff"}, - {file = "Cython-3.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:61a237bc9dd23c7faef0fcfce88c11c65d0c9bb73c74ccfa408b3a012073c20e"}, - {file = "Cython-3.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3a3d67f079598af49e90ff9655bf85bd358f093d727eb21ca2708f467c489cae"}, - {file = "Cython-3.0.8-cp38-cp38-win32.whl", hash = "sha256:17a642bb01a693e34c914106566f59844b4461665066613913463a719e0dd15d"}, - {file = "Cython-3.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:2cdfc32252f3b6dc7c94032ab744dcedb45286733443c294d8f909a4854e7f83"}, - {file = "Cython-3.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa97893d99385386925d00074654aeae3a98867f298d1e12ceaf38a9054a9bae"}, - {file = "Cython-3.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05c0bf9d085c031df8f583f0d506aa3be1692023de18c45d0aaf78685bbb944"}, - {file = "Cython-3.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de892422582f5758bd8de187e98ac829330ec1007bc42c661f687792999988a7"}, - {file = "Cython-3.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:314f2355a1f1d06e3c431eaad4708cf10037b5e91e4b231d89c913989d0bdafd"}, - {file = "Cython-3.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:78825a3774211e7d5089730f00cdf7f473042acc9ceb8b9eeebe13ed3a5541de"}, - {file = "Cython-3.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:df8093deabc55f37028190cf5e575c26aad23fc673f34b85d5f45076bc37ce39"}, - {file = "Cython-3.0.8-cp39-cp39-win32.whl", hash = "sha256:1aca1b97e0095b3a9a6c33eada3f661a4ed0d499067d121239b193e5ba3bb4f0"}, - {file = "Cython-3.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:16873d78be63bd38ffb759da7ab82814b36f56c769ee02b1d5859560e4c3ac3c"}, - {file = "Cython-3.0.8-py2.py3-none-any.whl", hash = "sha256:171b27051253d3f9108e9759e504ba59ff06e7f7ba944457f94deaf9c21bf0b6"}, - {file = "Cython-3.0.8.tar.gz", hash = "sha256:8333423d8fd5765e7cceea3a9985dd1e0a5dfeb2734629e1a2ed2d6233d39de6"}, + {file = "Cython-3.0.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e876272548d73583e90babda94c1299537006cad7a34e515a06c51b41f8657aa"}, + {file = "Cython-3.0.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adc377aa33c3309191e617bf675fdbb51ca727acb9dc1aa23fc698d8121f7e23"}, + {file = "Cython-3.0.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:401aba1869a57aba2922ccb656a6320447e55ace42709b504c2f8e8b166f46e1"}, + {file = "Cython-3.0.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:541fbe725d6534a90b93f8c577eb70924d664b227a4631b90a6e0506d1469591"}, + {file = "Cython-3.0.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:86998b01f6a6d48398df8467292c7637e57f7e3a2ca68655367f13f66fed7734"}, + {file = "Cython-3.0.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d092c0ddba7e9e530a5c5be4ac06db8360258acc27675d1fc86294a5dc8994c5"}, + {file = "Cython-3.0.10-cp310-cp310-win32.whl", hash = "sha256:3cffb666e649dba23810732497442fb339ee67ba4e0be1f0579991e83fcc2436"}, + {file = "Cython-3.0.10-cp310-cp310-win_amd64.whl", hash = "sha256:9ea31184c7b3a728ef1f81fccb161d8948c05aa86c79f63b74fb6f3ddec860ec"}, + {file = "Cython-3.0.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:051069638abfb076900b0c2bcb6facf545655b3f429e80dd14365192074af5a4"}, + {file = "Cython-3.0.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712760879600907189c7d0d346851525545484e13cd8b787e94bfd293da8ccf0"}, + {file = "Cython-3.0.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38d40fa1324ac47c04483d151f5e092406a147eac88a18aec789cf01c089c3f2"}, + {file = "Cython-3.0.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bd49a3a9fdff65446a3e1c2bfc0ec85c6ce4c3cad27cd4ad7ba150a62b7fb59"}, + {file = "Cython-3.0.10-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e8df79b596633b8295eaa48b1157d796775c2bb078f32267d32f3001b687f2fd"}, + {file = "Cython-3.0.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bcc9795990e525c192bc5c0775e441d7d56d7a7d02210451e9e13c0448dba51b"}, + {file = "Cython-3.0.10-cp311-cp311-win32.whl", hash = "sha256:09f2000041db482cad3bfce94e1fa3a4c82b0e57390a164c02566cbbda8c4f12"}, + {file = "Cython-3.0.10-cp311-cp311-win_amd64.whl", hash = "sha256:3919a55ec9b6c7db6f68a004c21c05ed540c40dbe459ced5d801d5a1f326a053"}, + {file = "Cython-3.0.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8f2864ab5fcd27a346f0b50f901ebeb8f60b25a60a575ccfd982e7f3e9674914"}, + {file = "Cython-3.0.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:407840c56385b9c085826fe300213e0e76ba15d1d47daf4b58569078ecb94446"}, + {file = "Cython-3.0.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a036d00caa73550a3a976432ef21c1e3fa12637e1616aab32caded35331ae96"}, + {file = "Cython-3.0.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc6a0e7e23a96dec3f3c9d39690d4281beabd5297855140d0d30855f950275e"}, + {file = "Cython-3.0.10-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a5e14a8c6a8157d2b0cdc2e8e3444905d20a0e78e19d2a097e89fb8b04b51f6b"}, + {file = "Cython-3.0.10-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f8a2b8fa0fd8358bccb5f3304be563c4750aae175100463d212d5ea0ec74cbe0"}, + {file = "Cython-3.0.10-cp312-cp312-win32.whl", hash = "sha256:2d29e617fd23cf4b83afe8f93f2966566c9f565918ad1e86a4502fe825cc0a79"}, + {file = "Cython-3.0.10-cp312-cp312-win_amd64.whl", hash = "sha256:6c5af936940a38c300977b81598d9c0901158f220a58c177820e17e1774f1cf1"}, + {file = "Cython-3.0.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:5f465443917d5c0f69825fca3b52b64c74ac3de0143b1fff6db8ba5b48c9fb4a"}, + {file = "Cython-3.0.10-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fadb84193c25641973666e583df8df4e27c52cdc05ddce7c6f6510d690ba34a"}, + {file = "Cython-3.0.10-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fa9e7786083b6aa61594c16979d621b62e61fcd9c2edd4761641b95c7fb34b2"}, + {file = "Cython-3.0.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4780d0f98ce28191c4d841c4358b5d5e79d96520650910cd59904123821c52d"}, + {file = "Cython-3.0.10-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:32fbad02d1189be75eb96456d9c73f5548078e5338d8fa153ecb0115b6ee279f"}, + {file = "Cython-3.0.10-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:90e2f514fc753b55245351305a399463103ec18666150bb1c36779b9862388e9"}, + {file = "Cython-3.0.10-cp36-cp36m-win32.whl", hash = "sha256:a9c976e9ec429539a4367cb4b24d15a1e46b925976f4341143f49f5f161171f5"}, + {file = "Cython-3.0.10-cp36-cp36m-win_amd64.whl", hash = "sha256:a9bb402674788a7f4061aeef8057632ec440123e74ed0fb425308a59afdfa10e"}, + {file = "Cython-3.0.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:206e803598010ecc3813db8748ed685f7beeca6c413f982df9f8a505fce56563"}, + {file = "Cython-3.0.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15b6d397f4ee5ad54e373589522af37935a32863f1b23fa8c6922adf833e28e2"}, + {file = "Cython-3.0.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a181144c2f893ed8e6a994d43d0b96300bc99873f21e3b7334ca26c61c37b680"}, + {file = "Cython-3.0.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74b700d6a793113d03fb54b63bdbadba6365379424bac7c0470605672769260"}, + {file = "Cython-3.0.10-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:076e9fd4e0ca33c5fa00a7479180dbfb62f17fe928e2909f82da814536e96d2b"}, + {file = "Cython-3.0.10-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:269f06e6961e8591d56e30b46e1a51b6ccb42cab04c29fa3b30d3e8723485fb4"}, + {file = "Cython-3.0.10-cp37-cp37m-win32.whl", hash = "sha256:d4e83a8ceff7af60064da4ccfce0ac82372544dd5392f1b350c34f1b04d0fae6"}, + {file = "Cython-3.0.10-cp37-cp37m-win_amd64.whl", hash = "sha256:40fac59c3a7fbcd9c25aea64c342c890a5e2270ce64a1525e840807800167799"}, + {file = "Cython-3.0.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f43a58bf2434870d2fc42ac2e9ff8138c9e00c6251468de279d93fa279e9ba3b"}, + {file = "Cython-3.0.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e9a885ec63d3955a08cefc4eec39fefa9fe14989c6e5e2382bd4aeb6bdb9bc3"}, + {file = "Cython-3.0.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acfbe0fff364d54906058fc61f2393f38cd7fa07d344d80923937b87e339adcf"}, + {file = "Cython-3.0.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8adcde00a8a88fab27509b558cd8c2959ab0c70c65d3814cfea8c68b83fa6dcd"}, + {file = "Cython-3.0.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2c9c1e3e78909488f3b16fabae02308423fa6369ed96ab1e250807d344cfffd7"}, + {file = "Cython-3.0.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fc6e0faf5b57523b073f0cdefadcaef3a51235d519a0594865925cadb3aeadf0"}, + {file = "Cython-3.0.10-cp38-cp38-win32.whl", hash = "sha256:35f6ede7c74024ed1982832ae61c9fad7cf60cc3f5b8c6a63bb34e38bc291936"}, + {file = "Cython-3.0.10-cp38-cp38-win_amd64.whl", hash = "sha256:950c0c7b770d2a7cec74fb6f5ccc321d0b51d151f48c075c0d0db635a60ba1b5"}, + {file = "Cython-3.0.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:077b61ee789e48700e25d4a16daa4258b8e65167136e457174df400cf9b4feab"}, + {file = "Cython-3.0.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f1f8bba9d8f37c0cffc934792b4ac7c42d0891077127c11deebe9fa0a0f7e4"}, + {file = "Cython-3.0.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:651a15a8534ebfb9b58cb0b87c269c70984b6f9c88bfe65e4f635f0e3f07dfcd"}, + {file = "Cython-3.0.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d10fc9aa82e5e53a0b7fd118f9771199cddac8feb4a6d8350b7d4109085aa775"}, + {file = "Cython-3.0.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4f610964ab252a83e573a427e28b103e2f1dd3c23bee54f32319f9e73c3c5499"}, + {file = "Cython-3.0.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c9c4c4f3ab8f8c02817b0e16e8fa7b8cc880f76e9b63fe9c010e60c1a6c2b13"}, + {file = "Cython-3.0.10-cp39-cp39-win32.whl", hash = "sha256:0bac3ccdd4e03924028220c62ae3529e17efa8ca7e9df9330de95de02f582b26"}, + {file = "Cython-3.0.10-cp39-cp39-win_amd64.whl", hash = "sha256:81f356c1c8c0885b8435bfc468025f545c5d764aa9c75ab662616dd1193c331e"}, + {file = "Cython-3.0.10-py2.py3-none-any.whl", hash = "sha256:fcbb679c0b43514d591577fd0d20021c55c240ca9ccafbdb82d3fb95e5edfee2"}, + {file = "Cython-3.0.10.tar.gz", hash = "sha256:dcc96739331fb854dcf503f94607576cfe8488066c61ca50dfd55836f132de99"}, ] [[package]] @@ -1018,15 +1030,32 @@ files = [ {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] +[[package]] +name = "ewmhlib" +version = "0.2" +description = "Extended Window Manager Hints implementation in Python 3" +optional = false +python-versions = "*" +files = [ + {file = "EWMHlib-0.2-py3-none-any.whl", hash = "sha256:f5b07d8cfd4c7734462ee744c32d490f2f3233fa7ab354240069344208d2f6f5"}, +] + +[package.dependencies] +python-xlib = {version = ">=0.21", markers = "sys_platform == \"linux\""} +typing-extensions = ">=4.4.0" + +[package.extras] +dev = ["mypy (>=0.990)", "types-python-xlib (>=0.32)", "types-setuptools (>=65.5)"] + [[package]] name = "execnet" -version = "2.0.2" +version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, - {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, + {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, + {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, ] [package.extras] @@ -1045,51 +1074,51 @@ files = [ [[package]] name = "filelock" -version = "3.13.1" +version = "3.15.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, + {file = "filelock-3.15.1-py3-none-any.whl", hash = "sha256:71b3102950e91dfc1bb4209b64be4dc8854f40e5f534428d8684f953ac847fac"}, + {file = "filelock-3.15.1.tar.gz", hash = "sha256:58a2549afdf9e02e10720eaa4d4470f56386d7a6f72edd7d0596337af8ed7ad8"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] name = "fiona" -version = "1.9.5" +version = "1.9.6" description = "Fiona reads and writes spatial data files" optional = false python-versions = ">=3.7" files = [ - {file = "fiona-1.9.5-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:5f40a40529ecfca5294260316cf987a0420c77a2f0cf0849f529d1afbccd093e"}, - {file = "fiona-1.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:374efe749143ecb5cfdd79b585d83917d2bf8ecfbfc6953c819586b336ce9c63"}, - {file = "fiona-1.9.5-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:35dae4b0308eb44617cdc4461ceb91f891d944fdebbcba5479efe524ec5db8de"}, - {file = "fiona-1.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:5b4c6a3df53bee8f85bb46685562b21b43346be1fe96419f18f70fa1ab8c561c"}, - {file = "fiona-1.9.5-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:6ad04c1877b9fd742871b11965606c6a52f40706f56a48d66a87cc3073943828"}, - {file = "fiona-1.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fb9a24a8046c724787719e20557141b33049466145fc3e665764ac7caf5748c"}, - {file = "fiona-1.9.5-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:d722d7f01a66f4ab6cd08d156df3fdb92f0669cf5f8708ddcb209352f416f241"}, - {file = "fiona-1.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:7ede8ddc798f3d447536080c6db9a5fb73733ad8bdb190cb65eed4e289dd4c50"}, - {file = "fiona-1.9.5-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:8b098054a27c12afac4f819f98cb4d4bf2db9853f70b0c588d7d97d26e128c39"}, - {file = "fiona-1.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d9f29e9bcbb33232ff7fa98b4a3c2234db910c1dc6c4147fc36c0b8b930f2e0"}, - {file = "fiona-1.9.5-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:f1af08da4ecea5036cb81c9131946be4404245d1b434b5b24fd3871a1d4030d9"}, - {file = "fiona-1.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:c521e1135c78dec0d7774303e5a1b4c62e0efb0e602bb8f167550ef95e0a2691"}, - {file = "fiona-1.9.5-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:fce4b1dd98810cabccdaa1828430c7402d283295c2ae31bea4f34188ea9e88d7"}, - {file = "fiona-1.9.5-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:3ea04ec2d8c57b5f81a31200fb352cb3242aa106fc3e328963f30ffbdf0ff7c8"}, - {file = "fiona-1.9.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4877cc745d9e82b12b3eafce3719db75759c27bd8a695521202135b36b58c2e7"}, - {file = "fiona-1.9.5-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:ac2c250f509ec19fad7959d75b531984776517ef3c1222d1cc5b4f962825880b"}, - {file = "fiona-1.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4df21906235928faad856c288cfea0298e9647f09c9a69a230535cbc8eadfa21"}, - {file = "fiona-1.9.5-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:81d502369493687746cb8d3cd77e5ada4447fb71d513721c9a1826e4fb32b23a"}, - {file = "fiona-1.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:ce3b29230ef70947ead4e701f3f82be81082b7f37fd4899009b1445cc8fc276a"}, - {file = "fiona-1.9.5-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:8b53ce8de773fcd5e2e102e833c8c58479edd8796a522f3d83ef9e08b62bfeea"}, - {file = "fiona-1.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bd2355e859a1cd24a3e485c6dc5003129f27a2051629def70036535ffa7e16a4"}, - {file = "fiona-1.9.5-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:9a2da52f865db1aff0eaf41cdd4c87a7c079b3996514e8e7a1ca38457309e825"}, - {file = "fiona-1.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:cfef6db5b779d463298b1113b50daa6c5b55f26f834dc9e37752116fa17277c1"}, - {file = "fiona-1.9.5.tar.gz", hash = "sha256:99e2604332caa7692855c2ae6ed91e1fffdf9b59449aa8032dd18e070e59a2f7"}, + {file = "fiona-1.9.6-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:63e528b5ea3d8b1038d788e7c65117835c787ba7fdc94b1b42f09c2cbc0aaff2"}, + {file = "fiona-1.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:918bd27d8625416672e834593970f96dff63215108f81efb876fe5c0bc58a3b4"}, + {file = "fiona-1.9.6-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:e313210b30d09ed8f829bf625599e248dadd78622728030221f6526580ff26c5"}, + {file = "fiona-1.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:89095c2d542325ee45894b8837e8048cdbb2f22274934e1be3b673ca628010d7"}, + {file = "fiona-1.9.6-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:98cea6f435843b2119731c6b0470e5b7386aa16b6aa7edabbf1ed93aefe029c3"}, + {file = "fiona-1.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f4230eccbd896a79d1ebfa551d84bf90f512f7bcbe1ca61e3f82231321f1a532"}, + {file = "fiona-1.9.6-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:48b6218224e96de5e36b5eb259f37160092260e5de0dcd82ca200b1887aa9884"}, + {file = "fiona-1.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:c1dd5fbc29b7303bb87eb683455e8451e1a53bb8faf20ef97fdcd843c9e4a7f6"}, + {file = "fiona-1.9.6-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:42d8a0e5570948d3821c493b6141866d9a4d7a64edad2be4ecbb89f81904baac"}, + {file = "fiona-1.9.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39819fb8f5ec6d9971cb01b912b4431615a3d3f50c83798565d8ce41917930db"}, + {file = "fiona-1.9.6-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:9b53034efdf93ada9295b081e6a8280af7c75496a20df82d4c2ca46d65b85905"}, + {file = "fiona-1.9.6-cp312-cp312-win_amd64.whl", hash = "sha256:1dcd6eca7524535baf2a39d7981b4a46d33ae28c313934a7c3eae62eecf9dfa5"}, + {file = "fiona-1.9.6-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e5404ed08c711489abcb3a50a184816825b8af06eb73ad2a99e18b8e7b47c96a"}, + {file = "fiona-1.9.6-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:53bedd2989e255df1bf3378ae9c06d6d241ec273c280c544bb44ffffebb97fb0"}, + {file = "fiona-1.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:77653a08564a44e634c44cd74a068d2f55d1d4029edd16d1c8aadcc4d8cc1d2c"}, + {file = "fiona-1.9.6-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:e7617563b36d2be99f048f0d0054b4d765f4aae454398f88f19de9c2c324b7f8"}, + {file = "fiona-1.9.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:50037c3b7a5f6f434b562b5b1a5b664f1caa7a4383b00af23cdb59bfc6ba852c"}, + {file = "fiona-1.9.6-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:bf51846ad602757bf27876f458c5c9f14b09421fac612f64273cc4e3fcabc441"}, + {file = "fiona-1.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:11af1afc1255642a7787fe112c29d01f968f1053e4d4700fc6f3bb879c1622e0"}, + {file = "fiona-1.9.6-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:52e8fec650b72fc5253d8f86b63859acc687182281c29bfacd3930496cf982d1"}, + {file = "fiona-1.9.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9b92aa1badb2773e7cac19bef3064d73e9d80c67c42f0928db2520a04be6f2f"}, + {file = "fiona-1.9.6-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:0eaffbf3bfae9960484c0c08ea461b0c40e111497f04e9475ebf15ac7a22d9dc"}, + {file = "fiona-1.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:f1b49d51a744874608b689f029766aa1e078dd72e94b44cf8eeef6d7bd2e9051"}, + {file = "fiona-1.9.6.tar.gz", hash = "sha256:791b3494f8b218c06ea56f892bd6ba893dfa23525347761d066fb7738acda3b1"}, ] [package.dependencies] @@ -1098,86 +1127,85 @@ certifi = "*" click = ">=8.0,<9.0" click-plugins = ">=1.0" cligj = ">=0.5" -setuptools = "*" six = "*" [package.extras] -all = ["Fiona[calc,s3,test]"] +all = ["fiona[calc,s3,test]"] calc = ["shapely"] s3 = ["boto3 (>=1.3.1)"] -test = ["Fiona[s3]", "pytest (>=7)", "pytest-cov", "pytz"] +test = ["fiona[s3]", "pytest (>=7)", "pytest-cov", "pytz"] [[package]] name = "flaky" -version = "3.7.0" -description = "Plugin for nose or pytest that automatically reruns flaky tests." +version = "3.8.1" +description = "Plugin for pytest that automatically reruns flaky tests." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" files = [ - {file = "flaky-3.7.0-py2.py3-none-any.whl", hash = "sha256:d6eda73cab5ae7364504b7c44670f70abed9e75f77dd116352f662817592ec9c"}, - {file = "flaky-3.7.0.tar.gz", hash = "sha256:3ad100780721a1911f57a165809b7ea265a7863305acb66708220820caf8aa0d"}, + {file = "flaky-3.8.1-py2.py3-none-any.whl", hash = "sha256:194ccf4f0d3a22b2de7130f4b62e45e977ac1b5ccad74d4d48f3005dcc38815e"}, + {file = "flaky-3.8.1.tar.gz", hash = "sha256:47204a81ec905f3d5acfbd61daeabcada8f9d4031616d9bcb0618461729699f5"}, ] [[package]] name = "flatbuffers" -version = "23.5.26" +version = "24.3.25" description = "The FlatBuffers serialization format for Python" optional = false python-versions = "*" files = [ - {file = "flatbuffers-23.5.26-py2.py3-none-any.whl", hash = "sha256:c0ff356da363087b915fde4b8b45bdda73432fc17cddb3c8157472eab1422ad1"}, - {file = "flatbuffers-23.5.26.tar.gz", hash = "sha256:9ea1144cac05ce5d86e2859f431c6cd5e66cd9c78c558317c7955fb8d4c78d89"}, + {file = "flatbuffers-24.3.25-py2.py3-none-any.whl", hash = "sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812"}, + {file = "flatbuffers-24.3.25.tar.gz", hash = "sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4"}, ] [[package]] name = "fonttools" -version = "4.49.0" +version = "4.53.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {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"}, + {file = "fonttools-4.53.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:52a6e0a7a0bf611c19bc8ec8f7592bdae79c8296c70eb05917fd831354699b20"}, + {file = "fonttools-4.53.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:099634631b9dd271d4a835d2b2a9e042ccc94ecdf7e2dd9f7f34f7daf333358d"}, + {file = "fonttools-4.53.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e40013572bfb843d6794a3ce076c29ef4efd15937ab833f520117f8eccc84fd6"}, + {file = "fonttools-4.53.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715b41c3e231f7334cbe79dfc698213dcb7211520ec7a3bc2ba20c8515e8a3b5"}, + {file = "fonttools-4.53.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74ae2441731a05b44d5988d3ac2cf784d3ee0a535dbed257cbfff4be8bb49eb9"}, + {file = "fonttools-4.53.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:95db0c6581a54b47c30860d013977b8a14febc206c8b5ff562f9fe32738a8aca"}, + {file = "fonttools-4.53.0-cp310-cp310-win32.whl", hash = "sha256:9cd7a6beec6495d1dffb1033d50a3f82dfece23e9eb3c20cd3c2444d27514068"}, + {file = "fonttools-4.53.0-cp310-cp310-win_amd64.whl", hash = "sha256:daaef7390e632283051e3cf3e16aff2b68b247e99aea916f64e578c0449c9c68"}, + {file = "fonttools-4.53.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a209d2e624ba492df4f3bfad5996d1f76f03069c6133c60cd04f9a9e715595ec"}, + {file = "fonttools-4.53.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f520d9ac5b938e6494f58a25c77564beca7d0199ecf726e1bd3d56872c59749"}, + {file = "fonttools-4.53.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eceef49f457253000e6a2d0f7bd08ff4e9fe96ec4ffce2dbcb32e34d9c1b8161"}, + {file = "fonttools-4.53.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1f3e34373aa16045484b4d9d352d4c6b5f9f77ac77a178252ccbc851e8b2ee"}, + {file = "fonttools-4.53.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:28d072169fe8275fb1a0d35e3233f6df36a7e8474e56cb790a7258ad822b6fd6"}, + {file = "fonttools-4.53.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a2a6ba400d386e904fd05db81f73bee0008af37799a7586deaa4aef8cd5971e"}, + {file = "fonttools-4.53.0-cp311-cp311-win32.whl", hash = "sha256:bb7273789f69b565d88e97e9e1da602b4ee7ba733caf35a6c2affd4334d4f005"}, + {file = "fonttools-4.53.0-cp311-cp311-win_amd64.whl", hash = "sha256:9fe9096a60113e1d755e9e6bda15ef7e03391ee0554d22829aa506cdf946f796"}, + {file = "fonttools-4.53.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d8f191a17369bd53a5557a5ee4bab91d5330ca3aefcdf17fab9a497b0e7cff7a"}, + {file = "fonttools-4.53.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93156dd7f90ae0a1b0e8871032a07ef3178f553f0c70c386025a808f3a63b1f4"}, + {file = "fonttools-4.53.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bff98816cb144fb7b85e4b5ba3888a33b56ecef075b0e95b95bcd0a5fbf20f06"}, + {file = "fonttools-4.53.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:973d030180eca8255b1bce6ffc09ef38a05dcec0e8320cc9b7bcaa65346f341d"}, + {file = "fonttools-4.53.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4ee5a24e281fbd8261c6ab29faa7fd9a87a12e8c0eed485b705236c65999109"}, + {file = "fonttools-4.53.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5bc124fae781a4422f61b98d1d7faa47985f663a64770b78f13d2c072410c2"}, + {file = "fonttools-4.53.0-cp312-cp312-win32.whl", hash = "sha256:a239afa1126b6a619130909c8404070e2b473dd2b7fc4aacacd2e763f8597fea"}, + {file = "fonttools-4.53.0-cp312-cp312-win_amd64.whl", hash = "sha256:45b4afb069039f0366a43a5d454bc54eea942bfb66b3fc3e9a2c07ef4d617380"}, + {file = "fonttools-4.53.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:93bc9e5aaa06ff928d751dc6be889ff3e7d2aa393ab873bc7f6396a99f6fbb12"}, + {file = "fonttools-4.53.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2367d47816cc9783a28645bc1dac07f8ffc93e0f015e8c9fc674a5b76a6da6e4"}, + {file = "fonttools-4.53.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:907fa0b662dd8fc1d7c661b90782ce81afb510fc4b7aa6ae7304d6c094b27bce"}, + {file = "fonttools-4.53.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e0ad3c6ea4bd6a289d958a1eb922767233f00982cf0fe42b177657c86c80a8f"}, + {file = "fonttools-4.53.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:73121a9b7ff93ada888aaee3985a88495489cc027894458cb1a736660bdfb206"}, + {file = "fonttools-4.53.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ee595d7ba9bba130b2bec555a40aafa60c26ce68ed0cf509983e0f12d88674fd"}, + {file = "fonttools-4.53.0-cp38-cp38-win32.whl", hash = "sha256:fca66d9ff2ac89b03f5aa17e0b21a97c21f3491c46b583bb131eb32c7bab33af"}, + {file = "fonttools-4.53.0-cp38-cp38-win_amd64.whl", hash = "sha256:31f0e3147375002aae30696dd1dc596636abbd22fca09d2e730ecde0baad1d6b"}, + {file = "fonttools-4.53.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d6166192dcd925c78a91d599b48960e0a46fe565391c79fe6de481ac44d20ac"}, + {file = "fonttools-4.53.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef50ec31649fbc3acf6afd261ed89d09eb909b97cc289d80476166df8438524d"}, + {file = "fonttools-4.53.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f193f060391a455920d61684a70017ef5284ccbe6023bb056e15e5ac3de11d1"}, + {file = "fonttools-4.53.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba9f09ff17f947392a855e3455a846f9855f6cf6bec33e9a427d3c1d254c712f"}, + {file = "fonttools-4.53.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c555e039d268445172b909b1b6bdcba42ada1cf4a60e367d68702e3f87e5f64"}, + {file = "fonttools-4.53.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a4788036201c908079e89ae3f5399b33bf45b9ea4514913f4dbbe4fac08efe0"}, + {file = "fonttools-4.53.0-cp39-cp39-win32.whl", hash = "sha256:d1a24f51a3305362b94681120c508758a88f207fa0a681c16b5a4172e9e6c7a9"}, + {file = "fonttools-4.53.0-cp39-cp39-win_amd64.whl", hash = "sha256:1e677bfb2b4bd0e5e99e0f7283e65e47a9814b0486cb64a41adf9ef110e078f2"}, + {file = "fonttools-4.53.0-py3-none-any.whl", hash = "sha256:6b4f04b1fbc01a3569d63359f2227c89ab294550de277fd09d8fca6185669fa4"}, + {file = "fonttools-4.53.0.tar.gz", hash = "sha256:c93ed66d32de1559b6fc348838c7572d5c0ac1e4a258e76763a5caddd8944002"}, ] [package.extras] @@ -1280,50 +1308,6 @@ files = [ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] -[[package]] -name = "ft4222" -version = "1.10.0" -description = "Python wrapper around libFT4222." -optional = false -python-versions = "*" -files = [ - {file = "ft4222-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd003a91ecaf20bc4d4ce8f0aea4a5ecfbc17a3840d41b0ce27410e027530cd0"}, - {file = "ft4222-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1cedabcd6c554e78f3479350153c5a347355ccc977ceeb24726edc37d3e7edb7"}, - {file = "ft4222-1.10.0-cp310-cp310-win32.whl", hash = "sha256:a039d4230926fa9b600baa12903843c53d6b238eb93e99ae68eeeb450d6f4bdc"}, - {file = "ft4222-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b05e499fd3f1baef225671f3afd25bf93e1f287fa17326c90c1f8db70abb3e89"}, - {file = "ft4222-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df9da4d5575d51511ba58e73ee434061dc709ce69491656ec109d995ce304167"}, - {file = "ft4222-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20f2cb89e63ea755d69a4d41c2dcba94d2f720b6e736ea51bdf681f1e809763c"}, - {file = "ft4222-1.10.0-cp311-cp311-win32.whl", hash = "sha256:806d4117d0814c390c7f280345aa99168ec989ca49b20259ce593ea79bcd246f"}, - {file = "ft4222-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:ad626507e9b1520f2f3b6f98ddca0d257c93b844824a1ab7cbb58759b0fbca84"}, - {file = "ft4222-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ecaae4e5ec3bedcdbe04a5792bf08119040669cf58c02df947395e6a96bd18c"}, - {file = "ft4222-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8c2655fe7192eb80ef25ab750123139f591e0f079fe2a5e6f31b7cd6f015a9f"}, - {file = "ft4222-1.10.0-cp312-cp312-win32.whl", hash = "sha256:fe668caa43bcacfbf3287cff8b4b11bb586a71d4722307e574f60180bb4a351e"}, - {file = "ft4222-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:4d7a49febc2b96ebed4b843262ab1f00843d158d6f4877c8ee19219f1c41f0da"}, - {file = "ft4222-1.10.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a6f99e31c767b6ccb7e2f0e7f6cc51d15c9b685e2b81fd12ab08cac0924289"}, - {file = "ft4222-1.10.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83efcb50649a7ab4c7e045467357029f16ef0be064d46ffe7ea1e06eb9ad85f8"}, - {file = "ft4222-1.10.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e051c89cb45f964640e355506efa74b41c424e9ea9046367534180075be0a95c"}, - {file = "ft4222-1.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cae991a917602635238c144395bb59a516b7684b6599da251a88cfe37a5ed8f"}, - {file = "ft4222-1.10.0-cp37-cp37m-win32.whl", hash = "sha256:713ed85fbb8f11755ae7ed932302192a34d2aa8834ffa45a7c345d3a5ba8f67b"}, - {file = "ft4222-1.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6f2458e6c62d251b6219cfa0fbcfb9ccfa22854c5d7463b862f11b268019ec66"}, - {file = "ft4222-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4b489cd434b92748e45695c1313716db4e00bdda4f70b6bcf99cdc042a34d8"}, - {file = "ft4222-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0bd0632063b0e4bb2852ccde3fb95252ea7f0a0bfd3e4c3ee677f4ec63c2520"}, - {file = "ft4222-1.10.0-cp38-cp38-win32.whl", hash = "sha256:057c88eaa2db14a05163b90a7ba2938c0aa6e3870fec31c70051fb791cfbbc22"}, - {file = "ft4222-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:648a739bb5c0ca53b5cb0698e0ebb54ddaea090878a02cbe9039b3d80cf818ba"}, - {file = "ft4222-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88170a0aaa321ae8c974a664b2316fab00f86dd8249ff5927ea4d58a2ed7fa23"}, - {file = "ft4222-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69fa55d8b5bc7da5a20254d480d4764dbcaa1ca74cd2d10bbc0ce39d39891f2a"}, - {file = "ft4222-1.10.0-cp39-cp39-win32.whl", hash = "sha256:9eb40e86fd43ab8d12b33d092369419e275292a6d57f1ef7de30e63cd8dc9bd5"}, - {file = "ft4222-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:875478f2c8c0fa128037c389969375ede546dde8cf7a5ae796421832aa6dd634"}, - {file = "ft4222-1.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:834b9e549d66f3cc0bbe75983b32e756258523b70363bb67970a7be21c9e3d8f"}, - {file = "ft4222-1.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7af52e4d61e2bcaa10b802e6f1af29823da12a27f520c799823490e6fd9ebb19"}, - {file = "ft4222-1.10.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca8c5aff74e213d8b658e7cdd8df3012e55023a2d379b5074628f6bb8947359a"}, - {file = "ft4222-1.10.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:755a70ab2c5d3fd94030e51fd82c332bc2463c6c455adf093d12e2f3803ea207"}, - {file = "ft4222-1.10.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:035ea7fc9ad627b44be57c31e87bc8d445ebd7cf6ac991d750fedc999683d64e"}, - {file = "ft4222-1.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2df65d174200c1451f5646a109e683aada9d7a076bf5f7ce682145d10725445a"}, - {file = "ft4222-1.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99ef3c412017a85ef9f90d8fc884ba66dae71e85433c8890da6aec38eb0288ab"}, - {file = "ft4222-1.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d00b36bdac3960c377f3a625fc352eca20e36825ddff178feaae97c2d9a8846c"}, - {file = "ft4222-1.10.0.tar.gz", hash = "sha256:74eab32dfc5c012d11952c8645b72157f8828685972669ef3c22025491d96001"}, -] - [[package]] name = "future-fstrings" version = "1.2.0" @@ -1340,17 +1324,18 @@ rewrite = ["tokenize-rt (>=3)"] [[package]] name = "geopandas" -version = "0.14.3" +version = "0.14.4" description = "Geographic pandas extensions" optional = false python-versions = ">=3.9" files = [ - {file = "geopandas-0.14.3-py3-none-any.whl", hash = "sha256:41b31ad39e21bc9e8c4254f78f8dc4ce3d33d144e22e630a00bb336c83160204"}, - {file = "geopandas-0.14.3.tar.gz", hash = "sha256:748af035d4a068a4ae00cab384acb61d387685c833b0022e0729aa45216b23ac"}, + {file = "geopandas-0.14.4-py3-none-any.whl", hash = "sha256:3bb6473cb59d51e1a7fe2dbc24a1a063fb0ebdeddf3ce08ddbf8c7ddc99689aa"}, + {file = "geopandas-0.14.4.tar.gz", hash = "sha256:56765be9d58e2c743078085db3bd07dc6be7719f0dbe1dfdc1d705cb80be7c25"}, ] [package.dependencies] fiona = ">=1.8.21" +numpy = ">=1.22" packaging = "*" pandas = ">=1.4.0" pyproj = ">=3.3.0" @@ -1438,80 +1423,81 @@ testing = ["pytest"] [[package]] name = "gymnasium" -version = "0.28.1" +version = "0.29.1" description = "A standard API for reinforcement learning and a diverse set of reference environments (formerly Gym)." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "gymnasium-0.28.1-py3-none-any.whl", hash = "sha256:7bc9a5bce1022f997d1dbc152fc91d1ac977bad9cc7794cdc25437010867cabf"}, - {file = "gymnasium-0.28.1.tar.gz", hash = "sha256:4c2c745808792c8f45c6e88ad0a5504774394e0c126f6e3db555e720d3da6f24"}, + {file = "gymnasium-0.29.1-py3-none-any.whl", hash = "sha256:61c3384b5575985bb7f85e43213bcb40f36fcdff388cae6bc229304c71f2843e"}, + {file = "gymnasium-0.29.1.tar.gz", hash = "sha256:1a532752efcb7590478b1cc7aa04f608eb7a2fdad5570cd217b66b6a35274bb1"}, ] [package.dependencies] cloudpickle = ">=1.2.0" farama-notifications = ">=0.0.1" -jax-jumpy = ">=1.0.0" numpy = ">=1.21.0" typing-extensions = ">=4.3.0" [package.extras] accept-rom-license = ["autorom[accept-rom-license] (>=0.4.2,<0.5.0)"] -all = ["box2d-py (==2.3.5)", "imageio (>=2.14.1)", "jax (==0.3.24)", "jaxlib (==0.3.24)", "lz4 (>=3.1.0)", "matplotlib (>=3.0)", "moviepy (>=1.0.0)", "mujoco (>=2.3.2)", "mujoco-py (>=2.1,<2.2)", "opencv-python (>=3.0)", "pygame (==2.1.3)", "shimmy[atari] (>=0.1.0,<1.0)", "swig (==4.*)", "torch (>=1.0.0)"] +all = ["box2d-py (==2.3.5)", "cython (<3)", "imageio (>=2.14.1)", "jax (>=0.4.0)", "jaxlib (>=0.4.0)", "lz4 (>=3.1.0)", "matplotlib (>=3.0)", "moviepy (>=1.0.0)", "mujoco (>=2.3.3)", "mujoco-py (>=2.1,<2.2)", "opencv-python (>=3.0)", "pygame (>=2.1.3)", "shimmy[atari] (>=0.1.0,<1.0)", "swig (==4.*)", "torch (>=1.0.0)"] atari = ["shimmy[atari] (>=0.1.0,<1.0)"] -box2d = ["box2d-py (==2.3.5)", "pygame (==2.1.3)", "swig (==4.*)"] -classic-control = ["pygame (==2.1.3)", "pygame (==2.1.3)"] -jax = ["jax (==0.3.24)", "jaxlib (==0.3.24)"] -mujoco = ["imageio (>=2.14.1)", "mujoco (>=2.3.2)"] -mujoco-py = ["mujoco-py (>=2.1,<2.2)", "mujoco-py (>=2.1,<2.2)"] +box2d = ["box2d-py (==2.3.5)", "pygame (>=2.1.3)", "swig (==4.*)"] +classic-control = ["pygame (>=2.1.3)", "pygame (>=2.1.3)"] +jax = ["jax (>=0.4.0)", "jaxlib (>=0.4.0)"] +mujoco = ["imageio (>=2.14.1)", "mujoco (>=2.3.3)"] +mujoco-py = ["cython (<3)", "cython (<3)", "mujoco-py (>=2.1,<2.2)", "mujoco-py (>=2.1,<2.2)"] other = ["lz4 (>=3.1.0)", "matplotlib (>=3.0)", "moviepy (>=1.0.0)", "opencv-python (>=3.0)", "torch (>=1.0.0)"] -testing = ["pytest (==7.1.3)", "scipy (==1.7.3)"] -toy-text = ["pygame (==2.1.3)", "pygame (==2.1.3)"] +testing = ["pytest (==7.1.3)", "scipy (>=1.7.3)"] +toy-text = ["pygame (>=2.1.3)", "pygame (>=2.1.3)"] [[package]] name = "h3" -version = "3.7.6" +version = "3.7.7" description = "Hierarchical hexagonal geospatial indexing system" optional = false python-versions = "*" files = [ - {file = "h3-3.7.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:cd4a5103a86a7d98cffa3be77eb82080aa2e9d676bbd1661f3db9ecad7a4ef2b"}, - {file = "h3-3.7.6-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:231959dceb4cc4ae86fe4fe2c385b176ed81712549e787b889dfa66f583676df"}, - {file = "h3-3.7.6-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:b9de9da755c90bbc90d6c041396a20c91816cd86a0bafa3b8899681cfdc2c4c6"}, - {file = "h3-3.7.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cda9a427b0de0d4069115ec765118888f180d0db915b5bc0dba52f5ae957b789"}, - {file = "h3-3.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bf1e080b9a47774754834e7f10155f3d2e3542bf895488a0519b2ae7d5b15db"}, - {file = "h3-3.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d3b93e3f38eb6c8fc5051d1b289b74614fb5f2415d272fea18085dea260d6b0"}, - {file = "h3-3.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783b2ca4448360c5a184fd43b84fc5554e3a8fd02738ff31349506189c5b4b49"}, - {file = "h3-3.7.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3bae8b95f21f20f04141a35f15c8caa74f2046eb01ef49e35fc45e6a8edfc8df"}, - {file = "h3-3.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:6ca9dd410e250d37f24a87c4ecb0615bb6d44a3f90eb5dbbf1b5e3d4489b8703"}, - {file = "h3-3.7.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:991ee991f2ae41f629feb1cd32fa677b8512c72696eb0ad94fcf359d61184b2e"}, - {file = "h3-3.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcbfff87d223279f8e38bbee3ebf52b1b96ae280e9e7de24674d3c284373d946"}, - {file = "h3-3.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eddf10d1d2139b3ea3ad1618c2074e1c47d3d36bddb5359e4955f5fd0b089d93"}, - {file = "h3-3.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76abc02f14a8df42fb5d80e6045023fb756c49d3cb08d69a8ceb9362b95d4bec"}, - {file = "h3-3.7.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc8030968586a7810aa192397ad9a4f7d7a963f57c9b3e210fc38de0aa5c2533"}, - {file = "h3-3.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:1bdc790d91138e781973dcaade5231db7fe8a876330939e0903f602acc4fb64c"}, - {file = "h3-3.7.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:198718ab20a06ebe52f0aaafc02469e4a51964e5ba7990c3cc1d2fc32e7a54d9"}, - {file = "h3-3.7.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02faa911f2d8425c641a1f7c08f3dc9c10a5a3d81408832afa40748534b999c8"}, - {file = "h3-3.7.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b4db92ceaeb9a51cc875c302cdc5e1aa27eed776d95943ee55c941bc2f219a3"}, - {file = "h3-3.7.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4810ddb3d91411a6cbf87a28108fe31712f618ef223c349e1f6675602af2c473"}, - {file = "h3-3.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:211ef3317dcf7863e2d01a97ab6da319b8451d78cd1633dd28faaf69e66bc321"}, - {file = "h3-3.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:99b81620608378fc9910a5c630b0f17eb939156fa13e1adc55229d31f8c3f5ca"}, - {file = "h3-3.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f3175bd3ea3ee528dbf49308e7215a58351ce425e1c3a9838ae22526663311"}, - {file = "h3-3.7.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d7b69015f5bab2525914fad370b96dc386437e19a14cfed3eb13868589263db"}, - {file = "h3-3.7.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d0c2890fa10fa8695c020569c8d55da79e2c552a39533de4ae6991c7acb122e1"}, - {file = "h3-3.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:1cd4f07c49721023c5fef401a4de03c47000705dfd116579dc6b61cad821305d"}, - {file = "h3-3.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f8d1db3dcd8f6ce7f54e061e6c9fbecbb5c3978b9e54e44af05a53787c4f99b3"}, - {file = "h3-3.7.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:495e37b1dee0ec46ccd88b278e571234b0d0d30648f161799d65a8d7f390b3f2"}, - {file = "h3-3.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e2c4808b7691b176c89ebf23c173b3b23dd4ce42f8f494b32c6e31ceee49af"}, - {file = "h3-3.7.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58b1298bf1068704c6d9426749c8ae6021b53d982d5153cc4161c7042ecd810"}, - {file = "h3-3.7.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a2872f695168c4700c73edd6eab9c6181387d7ea177de13b130ae61e613ff7de"}, - {file = "h3-3.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:98c3951c3b67ca3de06ef70aa74a9752640a3eca9b4d68e0d5d8e4fc6fa72337"}, - {file = "h3-3.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0724d4645c1da59e02b3305050c52b93ce1d8971d1d139433d464fcc103249a6"}, - {file = "h3-3.7.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e1f6ec0f2246381ce3a7f72da1ce825a5474eb7c8fb25a2ea1f16c6606ce34a7"}, - {file = "h3-3.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1471ff4d3875b25b521eeec5c2b72abe27b8e6af10ab99b7da5c0de545b0e832"}, - {file = "h3-3.7.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb96f2caae519d0ed17acde625af528476dca121b0336d3eb776429d40284ef6"}, - {file = "h3-3.7.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b1b1bce0dee05175f8d422e50ffa1afacb9a7e78ae0963059aebfbef50e10175"}, - {file = "h3-3.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:36ea935833c37fdfd7ffbfc000d7cd20addcdf67f30b26a6b9bccb9210b03704"}, - {file = "h3-3.7.6.tar.gz", hash = "sha256:9bbd3dbac99532fa521d7d2e288ff55877bea3223b070f659ed7b5f8f1f213eb"}, + {file = "h3-3.7.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:951ecc9da0bcd5091670b13636928747bc98bc76891da0fa725524ec017cd9de"}, + {file = "h3-3.7.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26b9dd605541223ef927cc913deccb236cee024b16032f4a3e4387e2791479f2"}, + {file = "h3-3.7.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:996ebb32dc26dd607af7493149f94ce316117be6f42971f7b33bbd326ec695d2"}, + {file = "h3-3.7.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa2a4aa888cd9476788b874b4e11e178293f5b86e8461c36596bf183c242d417"}, + {file = "h3-3.7.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0256e42687470c6f0044ca78fe375fe32a654be8b5a8313b4a68f52f513389c6"}, + {file = "h3-3.7.7-cp310-cp310-win_amd64.whl", hash = "sha256:a3e2bc125490f900e0513c30480722f129bab1415f23040b6cd3a3f8d5a39336"}, + {file = "h3-3.7.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7d59018a50cd3b6d0ff0b18a54fdfcbaf2f79c13c831842f54fd2780c4b561ea"}, + {file = "h3-3.7.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e74526d941c1656fe162cc63b459b61aa83a15e257e9477b1570f26c544b51a"}, + {file = "h3-3.7.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c7398dbab685fcf3fe92f7c4c5901ab258bc66f7fa05fd1da8693375a10a549"}, + {file = "h3-3.7.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d22ea488ab5fe01c94070e9a6b3222916905a4d3f7a9d33cb2298c93fa0ffd3"}, + {file = "h3-3.7.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c94836155e8169be393980fc059f06481a14dd1913bd9cba609f6f1e8864c171"}, + {file = "h3-3.7.7-cp311-cp311-win_amd64.whl", hash = "sha256:836e74313ff55324485cd7e07783bc67df3191ec08a318035d7cd8ee0b0badab"}, + {file = "h3-3.7.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:51c2f63ef5a57e4b18ebc9c0eb56656433e280ec45ab487a514127bb6e7d6a1f"}, + {file = "h3-3.7.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d6e38dea47c220d9802af8e8bebc806f9f39358aee07b736191ff21e2c9921d"}, + {file = "h3-3.7.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e408342e94f558802a97bfcbe1baae2af8b1fd926ad9041d970ff9dbd0502099"}, + {file = "h3-3.7.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:644c3c84585aa4df62e81bc54fd305c4d6686324731de230b0ddbd7036ed172c"}, + {file = "h3-3.7.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bb4a3d5e82d0c89512dc71b4eac17976a29be29da250ba76bc94bc5b9e824f0e"}, + {file = "h3-3.7.7-cp312-cp312-win_amd64.whl", hash = "sha256:2ccff5f02589e80202597ed0b9f61ebd114e262e7dd0fe88059298602898192f"}, + {file = "h3-3.7.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ef2e71b619f984e71c4bd9d128152e2c7e3e788e2d2ec571b32cef1d295ddf38"}, + {file = "h3-3.7.7-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cb13f0213ed6da80e739355e5b62cfc81b7b1469af997be3384a6cbc3a1a750"}, + {file = "h3-3.7.7-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:701f72f703d892fb17e66b9fd7b6b2ad125e135b091eb7dd0ec11858b84d84d2"}, + {file = "h3-3.7.7-cp36-cp36m-win_amd64.whl", hash = "sha256:796622be7cb052690404c0ac03768183e51ae22505ce4a424b4537b2b7609fba"}, + {file = "h3-3.7.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bcd88a72d6aa97d0f3b3b87b7bfd9725a8909501e6cb9d0057d5b690b6bb37b0"}, + {file = "h3-3.7.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7358ba3f91193a2551c4a8d7ad7fd348e567b3a3581c9c161630029dfb23e07"}, + {file = "h3-3.7.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f34b204edc2e8f7d99a6db4ed1b5d202b7ea3ec6817d373ec432dee14efe04"}, + {file = "h3-3.7.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aa0f8ce89b5e694815ee7a5172a782d58f2652267329de7008354b110b53955"}, + {file = "h3-3.7.7-cp37-cp37m-win_amd64.whl", hash = "sha256:4c851baa1c2d4f29b01157ce2a4cdb1f3879fff5c36ff7861dad1526963a17a7"}, + {file = "h3-3.7.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6f3a9da5472820b0a4add342f96fe52f65fbb8f46984383885738517b38af69e"}, + {file = "h3-3.7.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1c57da776a3c1a01e2986b1f6a31d497ee0be8fcdbaaf9b23bb90f5a90eb8f0b"}, + {file = "h3-3.7.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a5c0c0ddd9c57694ecc3b9ba99cbef2842882f8943d6edc676a365e139dbc6d"}, + {file = "h3-3.7.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c1b5a0a652719b645387231bf6d7d4dd85150e4440a4ce72a804a10e86592ae"}, + {file = "h3-3.7.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:64f76dc827fef94e9f43f95a1daea2e11f2ad2e8c55deac072f3d59bd62412d4"}, + {file = "h3-3.7.7-cp38-cp38-win_amd64.whl", hash = "sha256:c993a36120d7f5607f24ba9e39caf715eaf9cd9d44f5d5660fd85e3f4e0c6bf7"}, + {file = "h3-3.7.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eb154d2af699870b888e10476e327c895078009d2d2a6ef2d053d7dcf0e2c270"}, + {file = "h3-3.7.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c96ad74e246bb7638d413efa8199dd4c58ee929424a4dcaadb16365195f77f87"}, + {file = "h3-3.7.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52901f14f8b6e2c82075fd52c0e70176b868f621d47b5dc93f468c510e963722"}, + {file = "h3-3.7.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9d82a0fcc647e7bab36ab2e7a7392d141edc95d113ccf972e0fb7b0ddf80a0"}, + {file = "h3-3.7.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f4417d09acb36f0452346052f576923d6e4334bff3459f217d6278d40397424"}, + {file = "h3-3.7.7-cp39-cp39-win_amd64.whl", hash = "sha256:7ae774cd43b057f68dc10c99e4522fa40ed6b32ab90b2df0025595ffa15e77a0"}, + {file = "h3-3.7.7.tar.gz", hash = "sha256:33d141c3cef0725a881771fd8cb80c06a0db84a6e4ca5c647ce095ae07c61e94"}, ] [package.extras] @@ -1563,13 +1549,13 @@ zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.1)"] [[package]] name = "identify" -version = "2.5.35" +version = "2.5.36" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [package.extras] @@ -1577,13 +1563,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -1610,22 +1596,22 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.0.1" +version = "7.1.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, - {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "iniconfig" @@ -1663,33 +1649,15 @@ files = [ [package.dependencies] six = "*" -[[package]] -name = "jax-jumpy" -version = "1.0.0" -description = "Common backend for Jax or Numpy." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jax-jumpy-1.0.0.tar.gz", hash = "sha256:195fb955cc4c2b7f0b1453e3cb1fb1c414a51a407ffac7a51e69a73cb30d59ad"}, - {file = "jax_jumpy-1.0.0-py3-none-any.whl", hash = "sha256:ab7e01454bba462de3c4d098e3e585c302a8f06bc36d9182ab4e7e4aa7067c5e"}, -] - -[package.dependencies] -numpy = ">=1.18.0" - -[package.extras] -jax = ["jax (>=0.3.24)", "jaxlib (>=0.3.24)"] -testing = ["pytest (==7.1.3)"] - [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -1930,96 +1898,161 @@ test = ["pytest"] [[package]] name = "lxml" -version = "5.1.0" +version = "5.2.2" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.6" files = [ - {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, - {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, - {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, - {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, - {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, - {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, - {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, - {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, - {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, - {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, - {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, - {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, - {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, - {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, - {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, - {file = "lxml-5.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:24ef5a4631c0b6cceaf2dbca21687e29725b7c4e171f33a8f8ce23c12558ded1"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d2900b7f5318bc7ad8631d3d40190b95ef2aa8cc59473b73b294e4a55e9f30f"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:601f4a75797d7a770daed8b42b97cd1bb1ba18bd51a9382077a6a247a12aa38d"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4b68c961b5cc402cbd99cca5eb2547e46ce77260eb705f4d117fd9c3f932b95"}, - {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:afd825e30f8d1f521713a5669b63657bcfe5980a916c95855060048b88e1adb7"}, - {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:262bc5f512a66b527d026518507e78c2f9c2bd9eb5c8aeeb9f0eb43fcb69dc67"}, - {file = "lxml-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:e856c1c7255c739434489ec9c8aa9cdf5179785d10ff20add308b5d673bed5cd"}, - {file = "lxml-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c7257171bb8d4432fe9d6fdde4d55fdbe663a63636a17f7f9aaba9bcb3153ad7"}, - {file = "lxml-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9e240ae0ba96477682aa87899d94ddec1cc7926f9df29b1dd57b39e797d5ab5"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96f02ba1bcd330807fc060ed91d1f7a20853da6dd449e5da4b09bfcc08fdcf5"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3898ae2b58eeafedfe99e542a17859017d72d7f6a63de0f04f99c2cb125936"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c5a7edbd7c695e54fca029ceb351fc45cd8860119a0f83e48be44e1c464862"}, - {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3aeca824b38ca78d9ee2ab82bd9883083d0492d9d17df065ba3b94e88e4d7ee6"}, - {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, - {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, - {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, - {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98f3f020a2b736566c707c8e034945c02aa94e124c24f77ca097c446f81b01f1"}, - {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, - {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, - {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8b0c78e7aac24979ef09b7f50da871c2de2def043d468c4b41f512d831e912"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcf86dfc8ff3e992fed847c077bd875d9e0ba2fa25d859c3a0f0f76f07f0c8d"}, - {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:49a9b4af45e8b925e1cd6f3b15bbba2c81e7dba6dce170c677c9cda547411e14"}, - {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:280f3edf15c2a967d923bcfb1f8f15337ad36f93525828b40a0f9d6c2ad24890"}, - {file = "lxml-5.1.0-cp39-cp39-win32.whl", hash = "sha256:ed7326563024b6e91fef6b6c7a1a2ff0a71b97793ac33dbbcf38f6005e51ff6e"}, - {file = "lxml-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d7b4beebb178e9183138f552238f7e6613162a42164233e2bda00cb3afac58f"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, - {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632"}, + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526"}, + {file = "lxml-5.2.2-cp310-cp310-win32.whl", hash = "sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30"}, + {file = "lxml-5.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b"}, + {file = "lxml-5.2.2-cp311-cp311-win32.whl", hash = "sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438"}, + {file = "lxml-5.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836"}, + {file = "lxml-5.2.2-cp312-cp312-win32.whl", hash = "sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a"}, + {file = "lxml-5.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48"}, + {file = "lxml-5.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264"}, + {file = "lxml-5.2.2-cp36-cp36m-win32.whl", hash = "sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3"}, + {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, + {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, + {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, + {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, + {file = "lxml-5.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1"}, + {file = "lxml-5.2.2-cp38-cp38-win32.whl", hash = "sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30"}, + {file = "lxml-5.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9"}, + {file = "lxml-5.2.2-cp39-cp39-win32.whl", hash = "sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf"}, + {file = "lxml-5.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324"}, + {file = "lxml-5.2.2.tar.gz", hash = "sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.7)"] +source = ["Cython (>=3.0.10)"] [[package]] name = "markdown-it-py" @@ -2116,39 +2149,40 @@ files = [ [[package]] name = "matplotlib" -version = "3.8.3" +version = "3.9.0" description = "Python plotting package" optional = false python-versions = ">=3.9" files = [ - {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"}, + {file = "matplotlib-3.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2bcee1dffaf60fe7656183ac2190bd630842ff87b3153afb3e384d966b57fe56"}, + {file = "matplotlib-3.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f988bafb0fa39d1074ddd5bacd958c853e11def40800c5824556eb630f94d3b"}, + {file = "matplotlib-3.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe428e191ea016bb278758c8ee82a8129c51d81d8c4bc0846c09e7e8e9057241"}, + {file = "matplotlib-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaf3978060a106fab40c328778b148f590e27f6fa3cd15a19d6892575bce387d"}, + {file = "matplotlib-3.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2e7f03e5cbbfacdd48c8ea394d365d91ee8f3cae7e6ec611409927b5ed997ee4"}, + {file = "matplotlib-3.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:13beb4840317d45ffd4183a778685e215939be7b08616f431c7795276e067463"}, + {file = "matplotlib-3.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:063af8587fceeac13b0936c42a2b6c732c2ab1c98d38abc3337e430e1ff75e38"}, + {file = "matplotlib-3.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a2fa6d899e17ddca6d6526cf6e7ba677738bf2a6a9590d702c277204a7c6152"}, + {file = "matplotlib-3.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550cdda3adbd596078cca7d13ed50b77879104e2e46392dcd7c75259d8f00e85"}, + {file = "matplotlib-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cce0f31b351e3551d1f3779420cf8f6ec0d4a8cf9c0237a3b549fd28eb4abb"}, + {file = "matplotlib-3.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c53aeb514ccbbcbab55a27f912d79ea30ab21ee0531ee2c09f13800efb272674"}, + {file = "matplotlib-3.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5be985db2596d761cdf0c2eaf52396f26e6a64ab46bd8cd810c48972349d1be"}, + {file = "matplotlib-3.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c79f3a585f1368da6049318bdf1f85568d8d04b2e89fc24b7e02cc9b62017382"}, + {file = "matplotlib-3.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bdd1ecbe268eb3e7653e04f451635f0fb0f77f07fd070242b44c076c9106da84"}, + {file = "matplotlib-3.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e85a1a6d732f645f1403ce5e6727fd9418cd4574521d5803d3d94911038e5"}, + {file = "matplotlib-3.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a490715b3b9984fa609116481b22178348c1a220a4499cda79132000a79b4db"}, + {file = "matplotlib-3.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8146ce83cbc5dc71c223a74a1996d446cd35cfb6a04b683e1446b7e6c73603b7"}, + {file = "matplotlib-3.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:d91a4ffc587bacf5c4ce4ecfe4bcd23a4b675e76315f2866e588686cc97fccdf"}, + {file = "matplotlib-3.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:616fabf4981a3b3c5a15cd95eba359c8489c4e20e03717aea42866d8d0465956"}, + {file = "matplotlib-3.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cd53c79fd02f1c1808d2cfc87dd3cf4dbc63c5244a58ee7944497107469c8d8a"}, + {file = "matplotlib-3.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06a478f0d67636554fa78558cfbcd7b9dba85b51f5c3b5a0c9be49010cf5f321"}, + {file = "matplotlib-3.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81c40af649d19c85f8073e25e5806926986806fa6d54be506fbf02aef47d5a89"}, + {file = "matplotlib-3.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52146fc3bd7813cc784562cb93a15788be0b2875c4655e2cc6ea646bfa30344b"}, + {file = "matplotlib-3.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:0fc51eaa5262553868461c083d9adadb11a6017315f3a757fc45ec6ec5f02888"}, + {file = "matplotlib-3.9.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bd4f2831168afac55b881db82a7730992aa41c4f007f1913465fb182d6fb20c0"}, + {file = "matplotlib-3.9.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:290d304e59be2b33ef5c2d768d0237f5bd132986bdcc66f80bc9bcc300066a03"}, + {file = "matplotlib-3.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff2e239c26be4f24bfa45860c20ffccd118d270c5b5d081fa4ea409b5469fcd"}, + {file = "matplotlib-3.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:af4001b7cae70f7eaacfb063db605280058246de590fa7874f00f62259f2df7e"}, + {file = "matplotlib-3.9.0.tar.gz", hash = "sha256:e6d29ea6c19e34b30fb7d88b7081f869a03014f66fe06d62cc77d5a6ea88ed7a"}, ] [package.dependencies] @@ -2156,21 +2190,24 @@ contourpy = ">=1.0.1" cycler = ">=0.10" fonttools = ">=4.22.0" kiwisolver = ">=1.3.1" -numpy = ">=1.21,<2" +numpy = ">=1.23" packaging = ">=20.0" pillow = ">=8" pyparsing = ">=2.3.1" python-dateutil = ">=2.7" +[package.extras] +dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] + [[package]] name = "mdit-py-plugins" -version = "0.4.0" +version = "0.4.1" description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.8" files = [ - {file = "mdit_py_plugins-0.4.0-py3-none-any.whl", hash = "sha256:b51b3bb70691f57f974e257e367107857a93b36f322a9e6d44ca5bf28ec2def9"}, - {file = "mdit_py_plugins-0.4.0.tar.gz", hash = "sha256:d8ab27e9aed6c38aa716819fedfde15ca275715955f8a185a8e1cf90fb1d2c1b"}, + {file = "mdit_py_plugins-0.4.1-py3-none-any.whl", hash = "sha256:1020dfe4e6bfc2c79fb49ae4e3f5b297f5ccd20f010187acc52af2921e27dc6a"}, + {file = "mdit_py_plugins-0.4.1.tar.gz", hash = "sha256:834b8ac23d1cd60cec703646ffd22ae97b7955a6d596eb1d304be1e251ae499c"}, ] [package.dependencies] @@ -2197,21 +2234,19 @@ name = "metadrive-simulator" 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.3.tar.gz", hash = "sha256:bcda7d07146128161b0bc2cc337e01612b9222202706370043b52f7936a8a277"}, - {file = "metadrive_simulator-0.4.2.3-py3-none-any.whl", hash = "sha256:f6fff20b931bb956c55e0e81bf1f6b641169a84a4c853435a8b26bd51c8e9876"}, -] +python-versions = ">=3.6, <4" +files = [] +develop = false [package.dependencies] filelock = "*" geopandas = "*" -gymnasium = ">=0.28,<0.29" +gymnasium = ">=0.28" lxml = "*" matplotlib = "*" -numpy = ">=1.21.6,<=1.24.2" +numpy = ">=1.21.6" opencv-python = "*" -panda3d = "1.10.13" +panda3d = "1.10.14" panda3d-gltf = "0.13" pandas = "*" pillow = "*" @@ -2232,6 +2267,12 @@ cuda = ["PyOpenGL (==3.1.6)", "PyOpenGL-accelerate (==3.1.6)", "cuda-python (==1 gym = ["gym (>=0.19.0,<=0.26.0)"] ros = ["zmq"] +[package.source] +type = "git" +url = "https://github.com/commaai/metadrive.git" +reference = "python3.12" +resolved_reference = "0823c7491a1ab870bcd53d10a41e985a3ad1b968" + [[package]] name = "mouseinfo" version = "0.1.3" @@ -2281,22 +2322,22 @@ tests = ["pytest (>=4.6)"] [[package]] name = "msal" -version = "1.27.0" +version = "1.28.1" 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" +python-versions = ">=3.7" files = [ - {file = "msal-1.27.0-py2.py3-none-any.whl", hash = "sha256:572d07149b83e7343a85a3bcef8e581167b4ac76befcbbb6eef0c0e19643cdc0"}, - {file = "msal-1.27.0.tar.gz", hash = "sha256:3109503c038ba6b307152b0e8d34f98113f2e7a78986e28d0baf5b5303afda52"}, + {file = "msal-1.28.1-py3-none-any.whl", hash = "sha256:563c2d70de77a2ca9786aab84cb4e133a38a6897e6676774edc23d610bfc9e7b"}, + {file = "msal-1.28.1.tar.gz", hash = "sha256:d72bbfe2d5c2f2555f4bc6205be4450ddfd12976610dd9a16a9ab0f05c68b64d"}, ] [package.dependencies] -cryptography = ">=0.6,<45" +cryptography = ">=2.5,<45" PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} requests = ">=2.0.0,<3" [package.extras] -broker = ["pymsalruntime (>=0.13.2,<0.15)"] +broker = ["pymsalruntime (>=0.13.2,<0.17)"] [[package]] name = "msal-extensions" @@ -2418,38 +2459,38 @@ files = [ [[package]] name = "mypy" -version = "1.8.0" +version = "1.10.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, - {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, - {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, - {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, - {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, - {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, - {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, - {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, - {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, - {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, - {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, - {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, - {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, - {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, - {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, - {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, - {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, - {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, - {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, + {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, + {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, + {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, + {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, + {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, + {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, + {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, + {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, + {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, + {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, + {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, + {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, + {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, + {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, + {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, + {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, + {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, ] [package.dependencies] @@ -2475,17 +2516,17 @@ files = [ [[package]] name = "myst-parser" -version = "2.0.0" +version = "3.0.1" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," optional = false python-versions = ">=3.8" files = [ - {file = "myst_parser-2.0.0-py3-none-any.whl", hash = "sha256:7c36344ae39c8e740dad7fdabf5aa6fc4897a813083c6cc9990044eb93656b14"}, - {file = "myst_parser-2.0.0.tar.gz", hash = "sha256:ea929a67a6a0b1683cdbe19b8d2e724cd7643f8aa3e7bb18dd65beac3483bead"}, + {file = "myst_parser-3.0.1-py3-none-any.whl", hash = "sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1"}, + {file = "myst_parser-3.0.1.tar.gz", hash = "sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87"}, ] [package.dependencies] -docutils = ">=0.16,<0.21" +docutils = ">=0.18,<0.22" jinja2 = "*" markdown-it-py = ">=3.0,<4.0" mdit-py-plugins = ">=0.4,<1.0" @@ -2495,9 +2536,9 @@ sphinx = ">=6,<8" [package.extras] code-style = ["pre-commit (>=3.0,<4.0)"] linkify = ["linkify-it-py (>=2.0,<3.0)"] -rtd = ["ipython", "pydata-sphinx-theme (==v0.13.0rc4)", "sphinx-autodoc2 (>=0.4.2,<0.5.0)", "sphinx-book-theme (==1.0.0rc2)", "sphinx-copybutton", "sphinx-design2", "sphinx-pyscript", "sphinx-tippy (>=0.3.1)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.8.2,<0.9.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] -testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=7,<8)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx-pytest"] -testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4,<0.4.0)"] +rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] +testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] +testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] [[package]] name = "natsort" @@ -2516,91 +2557,97 @@ icu = ["PyICU (>=1.0.0)"] [[package]] name = "nodeenv" -version = "1.8.0" +version = "1.9.1" description = "Node.js virtual environment builder" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] -[package.dependencies] -setuptools = "*" - [[package]] name = "numpy" -version = "1.24.2" +version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "numpy-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef70b4fc1e872ebddc38cddacc87c19a3709c0e3e5d20bf3954c147b1dd941d"}, - {file = "numpy-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d2859428712785e8a8b7d2b3ef0a1d1565892367b32f915c4a4df44d0e64f5"}, - {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6524630f71631be2dabe0c541e7675db82651eb998496bbe16bc4f77f0772253"}, - {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a51725a815a6188c662fb66fb32077709a9ca38053f0274640293a14fdd22978"}, - {file = "numpy-1.24.2-cp310-cp310-win32.whl", hash = "sha256:2620e8592136e073bd12ee4536149380695fbe9ebeae845b81237f986479ffc9"}, - {file = "numpy-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:97cf27e51fa078078c649a51d7ade3c92d9e709ba2bfb97493007103c741f1d0"}, - {file = "numpy-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7de8fdde0003f4294655aa5d5f0a89c26b9f22c0a58790c38fae1ed392d44a5a"}, - {file = "numpy-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4173bde9fa2a005c2c6e2ea8ac1618e2ed2c1c6ec8a7657237854d42094123a0"}, - {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cecaed30dc14123020f77b03601559fff3e6cd0c048f8b5289f4eeabb0eb281"}, - {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a23f8440561a633204a67fb44617ce2a299beecf3295f0d13c495518908e910"}, - {file = "numpy-1.24.2-cp311-cp311-win32.whl", hash = "sha256:e428c4fbfa085f947b536706a2fc349245d7baa8334f0c5723c56a10595f9b95"}, - {file = "numpy-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:557d42778a6869c2162deb40ad82612645e21d79e11c1dc62c6e82a2220ffb04"}, - {file = "numpy-1.24.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d0a2db9d20117bf523dde15858398e7c0858aadca7c0f088ac0d6edd360e9ad2"}, - {file = "numpy-1.24.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c72a6b2f4af1adfe193f7beb91ddf708ff867a3f977ef2ec53c0ffb8283ab9f5"}, - {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29e6bd0ec49a44d7690ecb623a8eac5ab8a923bce0bea6293953992edf3a76a"}, - {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eabd64ddb96a1239791da78fa5f4e1693ae2dadc82a76bc76a14cbb2b966e96"}, - {file = "numpy-1.24.2-cp38-cp38-win32.whl", hash = "sha256:e3ab5d32784e843fc0dd3ab6dcafc67ef806e6b6828dc6af2f689be0eb4d781d"}, - {file = "numpy-1.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756"}, - {file = "numpy-1.24.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4199e7cfc307a778f72d293372736223e39ec9ac096ff0a2e64853b866a8e18a"}, - {file = "numpy-1.24.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adbdce121896fd3a17a77ab0b0b5eedf05a9834a18699db6829a64e1dfccca7f"}, - {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889b2cc88b837d86eda1b17008ebeb679d82875022200c6e8e4ce6cf549b7acb"}, - {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f64bb98ac59b3ea3bf74b02f13836eb2e24e48e0ab0145bbda646295769bd780"}, - {file = "numpy-1.24.2-cp39-cp39-win32.whl", hash = "sha256:63e45511ee4d9d976637d11e6c9864eae50e12dc9598f531c035265991910468"}, - {file = "numpy-1.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:a77d3e1163a7770164404607b7ba3967fb49b24782a6ef85d9b5f54126cc39e5"}, - {file = "numpy-1.24.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:92011118955724465fb6853def593cf397b4a1367495e0b59a7e69d40c4eb71d"}, - {file = "numpy-1.24.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9006288bcf4895917d02583cf3411f98631275bc67cce355a7f39f8c14338fa"}, - {file = "numpy-1.24.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:150947adbdfeceec4e5926d956a06865c1c690f2fd902efede4ca6fe2e657c3f"}, - {file = "numpy-1.24.2.tar.gz", hash = "sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] [[package]] name = "onnx" -version = "1.15.0" +version = "1.16.1" description = "Open Neural Network Exchange" optional = false python-versions = ">=3.8" files = [ - {file = "onnx-1.15.0-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:51cacb6aafba308aaf462252ced562111f6991cdc7bc57a6c554c3519453a8ff"}, - {file = "onnx-1.15.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:0aee26b6f7f7da7e840de75ad9195a77a147d0662c94eaa6483be13ba468ffc1"}, - {file = "onnx-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baf6ef6c93b3b843edb97a8d5b3d229a1301984f3f8dee859c29634d2083e6f9"}, - {file = "onnx-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ed899fe6000edc05bb2828863d3841cfddd5a7cf04c1a771f112e94de75d9f"}, - {file = "onnx-1.15.0-cp310-cp310-win32.whl", hash = "sha256:f1ad3d77fc2f4b4296f0ac2c8cadd8c1dcf765fc586b737462d3a0fe8f7c696a"}, - {file = "onnx-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:ca4ebc4f47109bfb12c8c9e83dd99ec5c9f07d2e5f05976356c6ccdce3552010"}, - {file = "onnx-1.15.0-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:233ffdb5ca8cc2d960b10965a763910c0830b64b450376da59207f454701f343"}, - {file = "onnx-1.15.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:51fa79c9ea9af033638ec51f9177b8e76c55fad65bb83ea96ee88fafade18ee7"}, - {file = "onnx-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f277d4861729f5253a51fa41ce91bfec1c4574ee41b5637056b43500917295ce"}, - {file = "onnx-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8a7c94d2ebead8f739fdb70d1ce5a71726f4e17b3e5b8ad64455ea1b2801a85"}, - {file = "onnx-1.15.0-cp311-cp311-win32.whl", hash = "sha256:17dcfb86a8c6bdc3971443c29b023dd9c90ff1d15d8baecee0747a6b7f74e650"}, - {file = "onnx-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:60a3e28747e305cd2e766e6a53a0a6d952cf9e72005ec6023ce5e07666676a4e"}, - {file = "onnx-1.15.0-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:6b5c798d9e0907eaf319e3d3e7c89a2ed9a854bcb83da5fefb6d4c12d5e90721"}, - {file = "onnx-1.15.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:a4f774ff50092fe19bd8f46b2c9b27b1d30fbd700c22abde48a478142d464322"}, - {file = "onnx-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2b0e7f3938f2d994c34616bfb8b4b1cebbc4a0398483344fe5e9f2fe95175e6"}, - {file = "onnx-1.15.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49cebebd0020a4b12c1dd0909d426631212ef28606d7e4d49463d36abe7639ad"}, - {file = "onnx-1.15.0-cp38-cp38-win32.whl", hash = "sha256:1fdf8a3ff75abc2b32c83bf27fb7c18d6b976c9c537263fadd82b9560fe186fa"}, - {file = "onnx-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:763e55c26e8de3a2dce008d55ae81b27fa8fb4acbb01a29b9f3c01f200c4d676"}, - {file = "onnx-1.15.0-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:b2d5e802837629fc9c86f19448d19dd04d206578328bce202aeb3d4bedab43c4"}, - {file = "onnx-1.15.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:9a9cfbb5e5d5d88f89d0dfc9df5fb858899db874e1d5ed21e76c481f3cafc90d"}, - {file = "onnx-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f472bbe5cb670a0a4a4db08f41fde69b187a009d0cb628f964840d3f83524e9"}, - {file = "onnx-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf2de9bef64792e5b8080c678023ac7d2b9e05d79a3e17e92cf6a4a624831d2"}, - {file = "onnx-1.15.0-cp39-cp39-win32.whl", hash = "sha256:ef4d9eb44b111e69e4534f3233fc2c13d1e26920d24ae4359d513bd54694bc6d"}, - {file = "onnx-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d7a3e2d79d371e272e39ae3f7547e0b116d0c7f774a4004e97febe6c93507f"}, - {file = "onnx-1.15.0.tar.gz", hash = "sha256:b18461a7d38f286618ca2a6e78062a2a9c634ce498e631e708a8041b00094825"}, + {file = "onnx-1.16.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:bb2d392e5b7060082c2fb38eb5c44f67eb34ff5f0681bd6f45beff9abc6f7094"}, + {file = "onnx-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15abf94a7868eed6db15a8b5024ba570c891cae77ca4d0e7258dabdad76980df"}, + {file = "onnx-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6251910e554f811fdd070164b0bc76d76b067b95576cb9dad4d52ae64fe014b5"}, + {file = "onnx-1.16.1-cp310-cp310-win32.whl", hash = "sha256:c11e3b15eee46cd20767e505cc3ba97457ef5ac93c3e459cdfb77943ff8fe9a7"}, + {file = "onnx-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:b3d10405706807ec2ef493b2a78519fa0264cf190363e89478585aac1179b596"}, + {file = "onnx-1.16.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:006ba5059c85ce43e89a1486cc0276d0f1a8ec9c6efd1a9334fd3fa0f6e33b64"}, + {file = "onnx-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1521ea7cd3497ecaf57d3b5e72d637ca5ebca632122a0806a9df99bedbeecdf8"}, + {file = "onnx-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cf20421aeac03872bea5fd6ebf92abe15c4d1461a2572eb839add5059e2a09"}, + {file = "onnx-1.16.1-cp311-cp311-win32.whl", hash = "sha256:f98e275b4f46a617a9c527e60c02531eae03cf67a04c26db8a1c20acee539533"}, + {file = "onnx-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:95aa20aa65a9035d7543e81713e8b0f611e213fc02171959ef4ee09311d1bf28"}, + {file = "onnx-1.16.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:32e11d39bee04f927fab09f74c46cf76584094462311bab1aca9ccdae6ed3366"}, + {file = "onnx-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8884bf53b552873c0c9b072cb8625e7d4e8f3cc0529191632d24e3de58a3b93a"}, + {file = "onnx-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595b2830093f81361961295f7b0ebb6000423bcd04123d516d081c306002e387"}, + {file = "onnx-1.16.1-cp312-cp312-win32.whl", hash = "sha256:2fde4dd5bc278b3fc8148f460bce8807b2874c66f48529df9444cdbc9ecf456b"}, + {file = "onnx-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:e69ad8c110d8c37d759cad019d498fdf3fd24e0bfaeb960e52fed0469a5d2974"}, + {file = "onnx-1.16.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:0fc189195a40b5862fb77d97410c89823197fe19c1088ce150444eec72f200c1"}, + {file = "onnx-1.16.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:496ba17b16a74711081772e1b03f3207959972e351298e51abdc600051027a22"}, + {file = "onnx-1.16.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3faf239b48418b3ea6fe73bd4d86807b903d0b2ebd20b8b8c84f83741b0f18"}, + {file = "onnx-1.16.1-cp38-cp38-win32.whl", hash = "sha256:18b22143836838591f6551b089196e69f60c47fabce52b4b72b4cb37522645aa"}, + {file = "onnx-1.16.1-cp38-cp38-win_amd64.whl", hash = "sha256:8c2b70d602acfb90056fbdc60ef26f4658f964591212a4e9dbbda922ff43061b"}, + {file = "onnx-1.16.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:2bed6fe05905b073206cabbb4463c58050cf8d544192303c09927b229f93ac14"}, + {file = "onnx-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5798414332534a41404a7ff83677d49ced01d70160e1541484cce647f2295051"}, + {file = "onnx-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa7518d6d27f357261a4014079dec364cad6fef827d0b3fe1d3ff59939a68394"}, + {file = "onnx-1.16.1-cp39-cp39-win32.whl", hash = "sha256:67f372db4fe8fe61e00b762af5b0833aa72b5baa37e7e2f47d8668964ebff411"}, + {file = "onnx-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c059fea6229c44d2d39c8f6e2f2f0d676d587c97f4c854c86f3e7bc97e0b31c"}, + {file = "onnx-1.16.1.tar.gz", hash = "sha256:8299193f0f2a3849bfc069641aa8e4f93696602da8d165632af8ee48ec7556b6"}, ] [package.dependencies] -numpy = "*" +numpy = ">=1.20" protobuf = ">=3.20.2" [package.extras] @@ -2608,36 +2655,36 @@ reference = ["Pillow", "google-re2"] [[package]] name = "onnxruntime" -version = "1.17.1" +version = "1.18.0" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {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"}, + {file = "onnxruntime-1.18.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:5a3b7993a5ecf4a90f35542a4757e29b2d653da3efe06cdd3164b91167bbe10d"}, + {file = "onnxruntime-1.18.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15b944623b2cdfe7f7945690bfb71c10a4531b51997c8320b84e7b0bb59af902"}, + {file = "onnxruntime-1.18.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e61ce5005118064b1a0ed73ebe936bc773a102f067db34108ea6c64dd62a179"}, + {file = "onnxruntime-1.18.0-cp310-cp310-win32.whl", hash = "sha256:a4fc8a2a526eb442317d280610936a9f73deece06c7d5a91e51570860802b93f"}, + {file = "onnxruntime-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:71ed219b768cab004e5cd83e702590734f968679bf93aa488c1a7ffbe6e220c3"}, + {file = "onnxruntime-1.18.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:3d24bd623872a72a7fe2f51c103e20fcca2acfa35d48f2accd6be1ec8633d960"}, + {file = "onnxruntime-1.18.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f15e41ca9b307a12550bfd2ec93f88905d9fba12bab7e578f05138ad0ae10d7b"}, + {file = "onnxruntime-1.18.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f45ca2887f62a7b847d526965686b2923efa72538c89b7703c7b3fe970afd59"}, + {file = "onnxruntime-1.18.0-cp311-cp311-win32.whl", hash = "sha256:9e24d9ecc8781323d9e2eeda019b4b24babc4d624e7d53f61b1fe1a929b0511a"}, + {file = "onnxruntime-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:f8608398976ed18aef450d83777ff6f77d0b64eced1ed07a985e1a7db8ea3771"}, + {file = "onnxruntime-1.18.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f1d79941f15fc40b1ee67738b2ca26b23e0181bf0070b5fb2984f0988734698f"}, + {file = "onnxruntime-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e8caf3a8565c853a22d323a3eebc2a81e3de7591981f085a4f74f7a60aab2d"}, + {file = "onnxruntime-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:498d2b8380635f5e6ebc50ec1b45f181588927280f32390fb910301d234f97b8"}, + {file = "onnxruntime-1.18.0-cp312-cp312-win32.whl", hash = "sha256:ba7cc0ce2798a386c082aaa6289ff7e9bedc3dee622eef10e74830cff200a72e"}, + {file = "onnxruntime-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:1fa175bd43f610465d5787ae06050c81f7ce09da2bf3e914eb282cb8eab363ef"}, + {file = "onnxruntime-1.18.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:0284c579c20ec8b1b472dd190290a040cc68b6caec790edb960f065d15cf164a"}, + {file = "onnxruntime-1.18.0-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d47353d036d8c380558a5643ea5f7964d9d259d31c86865bad9162c3e916d1f6"}, + {file = "onnxruntime-1.18.0-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:885509d2b9ba4b01f08f7fa28d31ee54b6477953451c7ccf124a84625f07c803"}, + {file = "onnxruntime-1.18.0-cp38-cp38-win32.whl", hash = "sha256:8614733de3695656411d71fc2f39333170df5da6c7efd6072a59962c0bc7055c"}, + {file = "onnxruntime-1.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:47af3f803752fce23ea790fd8d130a47b2b940629f03193f780818622e856e7a"}, + {file = "onnxruntime-1.18.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:9153eb2b4d5bbab764d0aea17adadffcfc18d89b957ad191b1c3650b9930c59f"}, + {file = "onnxruntime-1.18.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c7fd86eca727c989bb8d9c5104f3c45f7ee45f445cc75579ebe55d6b99dfd7c"}, + {file = "onnxruntime-1.18.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac67a4de9c1326c4d87bcbfb652c923039b8a2446bb28516219236bec3b494f5"}, + {file = "onnxruntime-1.18.0-cp39-cp39-win32.whl", hash = "sha256:6ffb445816d06497df7a6dd424b20e0b2c39639e01e7fe210e247b82d15a23b9"}, + {file = "onnxruntime-1.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:46de6031cb6745f33f7eca9e51ab73e8c66037fb7a3b6b4560887c5b55ab5d5d"}, ] [package.dependencies] @@ -2650,21 +2697,21 @@ sympy = "*" [[package]] name = "onnxruntime-gpu" -version = "1.17.1" +version = "1.18.0" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {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"}, + {file = "onnxruntime_gpu-1.18.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ebee59886d4a63d54cec673c73bdcc58b5496f9946e3c539af51550c6f222e88"}, + {file = "onnxruntime_gpu-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:c33a2bfd100bd2b82542c72deaaed40d3de858ac9624dc5730e548869dac2f2f"}, + {file = "onnxruntime_gpu-1.18.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:aa673c044f450b21163265cca2e35eb1ded03d48fb01dec6be1c412811e9b2b0"}, + {file = "onnxruntime_gpu-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:1dfb1172de0043f7bdc32724619f5bc151dbdc5bb8e50e806271d2efa8d72715"}, + {file = "onnxruntime_gpu-1.18.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:40ce4aec8499352b96f8c67d1f2fe4ac170dbc9281dd8ebe36339d9e1c0bcdb7"}, + {file = "onnxruntime_gpu-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:97df451777322a534dbedff49d43a4f6f74bd5258ccf31b2161e1af606bc724d"}, + {file = "onnxruntime_gpu-1.18.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:05b5f17bc78586741715903b5ec7fcef4c64d136aee94d6583dd17035190d550"}, + {file = "onnxruntime_gpu-1.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:40fab33312d02fdaa93b8cc14ffb1fc3aceaef5db50383a579fd8b7ad5a03b17"}, + {file = "onnxruntime_gpu-1.18.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:e0d9e200afe57c69c6407c90189c7f16a55ce326d1ac45864353e13bfe574b3c"}, + {file = "onnxruntime_gpu-1.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:a7d13f0983eec46857acab8bdace7e3178428d17ad3c6d5b84b5b211c98e5acd"}, ] [package.dependencies] @@ -2677,136 +2724,134 @@ sympy = "*" [[package]] name = "opencv-python" -version = "4.9.0.80" +version = "4.10.0.82" description = "Wrapper package for OpenCV python bindings." optional = false python-versions = ">=3.6" files = [ - {file = "opencv-python-4.9.0.80.tar.gz", hash = "sha256:1a9f0e6267de3a1a1db0c54213d022c7c8b5b9ca4b580e80bdc58516c922c9e1"}, - {file = "opencv_python-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:7e5f7aa4486651a6ebfa8ed4b594b65bd2d2f41beeb4241a3e4b1b85acbbbadb"}, - {file = "opencv_python-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71dfb9555ccccdd77305fc3dcca5897fbf0cf28b297c51ee55e079c065d812a3"}, - {file = "opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b34a52e9da36dda8c151c6394aed602e4b17fa041df0b9f5b93ae10b0fcca2a"}, - {file = "opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4088cab82b66a3b37ffc452976b14a3c599269c247895ae9ceb4066d8188a57"}, - {file = "opencv_python-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:dcf000c36dd1651118a2462257e3a9e76db789a78432e1f303c7bac54f63ef6c"}, - {file = "opencv_python-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:3f16f08e02b2a2da44259c7cc712e779eff1dd8b55fdb0323e8cab09548086c0"}, + {file = "opencv-python-4.10.0.82.tar.gz", hash = "sha256:dbc021eaa310c4145c47cd648cb973db69bb5780d6e635386cd53d3ea76bd2d5"}, + {file = "opencv_python-4.10.0.82-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:5f78652339957ec24b80a782becfb32f822d2008a865512121fad8c3ce233e9a"}, + {file = "opencv_python-4.10.0.82-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:e6be19a0615aa8c4e0d34e0c7b133e26e386f4b7e9b557b69479104ab2c133ec"}, + {file = "opencv_python-4.10.0.82-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b49e530f7fd86f671514b39ffacdf5d14ceb073bc79d0de46bbb6b0cad78eaf"}, + {file = "opencv_python-4.10.0.82-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955c5ce8ac90c9e4636ad7f5c0d9c75b80abbe347182cfd09b0e3eec6e50472c"}, + {file = "opencv_python-4.10.0.82-cp37-abi3-win32.whl", hash = "sha256:ff54adc9e4daaf438e669664af08bec4a268c7b7356079338b8e4fae03810f2c"}, + {file = "opencv_python-4.10.0.82-cp37-abi3-win_amd64.whl", hash = "sha256:2e3c2557b176f1e528417520a52c0600a92c1bb1c359f3df8e6411ab4293f065"}, ] [package.dependencies] -numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} +numpy = [ + {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] [[package]] name = "opencv-python-headless" -version = "4.9.0.80" +version = "4.10.0.82" description = "Wrapper package for OpenCV python bindings." optional = false python-versions = ">=3.6" files = [ - {file = "opencv-python-headless-4.9.0.80.tar.gz", hash = "sha256:71a4cd8cf7c37122901d8e81295db7fb188730e33a0e40039a4e59c1030b0958"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:2ea8a2edc4db87841991b2fbab55fc07b97ecb602e0f47d5d485bd75cee17c1a"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0ee54e27be493e8f7850847edae3128e18b540dac1d7b2e4001b8944e11e1c6"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57ce2865e8fec431c6f97a81e9faaf23fa5be61011d0a75ccf47a3c0d65fa73d"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:976656362d68d9f40a5c66f83901430538002465f7db59142784f3893918f3df"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:11e3849d83e6651d4e7699aadda9ec7ed7c38957cbbcb99db074f2a2d2de9670"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:a8056c2cb37cd65dfcdf4153ca16f7362afcf3a50d600d6bb69c660fc61ee29c"}, + {file = "opencv-python-headless-4.10.0.82.tar.gz", hash = "sha256:de9e742c1b9540816fbd115b0b03841d41ed0c65566b0d7a5371f98b131b7e6d"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a09ed50ba21cc5bf5d436cb0e784ad09c692d6b1d1454252772f6c8f2c7b4088"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:977a5fd21e1fe0d3d2134887db4441f8725abeae95150126302f31fcd9f548fa"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db4ec6755838b0be12510bfc9ffb014779c612418f11f4f7e6f505c36124a3aa"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a37fa5276967ecf6eb297295b16b28b7a2eb3b568ca0ee469fb1a5954de298"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-win32.whl", hash = "sha256:94736e9b322d13db4768fd35588ad5e8995e78e207263076bfbee18aac835ad5"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-win_amd64.whl", hash = "sha256:c1822fa23d1641c0249ed5eb906f4c385f7959ff1bd601a776d56b0c18914af4"}, ] [package.dependencies] -numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} +numpy = [ + {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] [[package]] name = "packaging" -version = "23.2" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "panda3d" -version = "1.10.13" +version = "1.10.14" description = "Panda3D is a framework for 3D rendering and game development for Python and C++ programs." optional = false python-versions = "*" files = [ - {file = "panda3d-1.10.13-cp27-cp27m-macosx_10_6_i386.whl", hash = "sha256:697555595a01c3f5468d49a1ee1f05f57df31e63a251a9ba47a8f534faf0ba15"}, - {file = "panda3d-1.10.13-cp27-cp27m-macosx_10_6_x86_64.whl", hash = "sha256:ccfa288a3575c2996ad7f409f14e8ceb8541a9b1b39bb0162e30f73b1fa0884b"}, - {file = "panda3d-1.10.13-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b2d9870c044e8ac0f6a65e96b6814d122a560adfe045b6ad500bc107aa361726"}, - {file = "panda3d-1.10.13-cp27-cp27m-win32.whl", hash = "sha256:1c35d8676caa1a2c11f91592a6abd8cf18c3ac9496437082e79b4b0671603857"}, - {file = "panda3d-1.10.13-cp27-cp27m-win_amd64.whl", hash = "sha256:f7423bf86d3369f5f7578ec70199f6674ba9f81f80f3eea83d0b27ec7d2522fc"}, - {file = "panda3d-1.10.13-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:017c2a9d40c024de3d68359926dfbb0fef51e219bd2bc6d021464051bba9c80e"}, - {file = "panda3d-1.10.13-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8028fd67c0fc524eae3b7dbfba0b433e3d811e386ba51adbccf1f95a79ef32cb"}, - {file = "panda3d-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50427b08d6dcbe91e95c719b3eb32abe5e806fe265a2cfa66f5b704d22776409"}, - {file = "panda3d-1.10.13-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:597332377b09cb97a1699fed2514c99bcc4241924ce7c21e7577685164576afc"}, - {file = "panda3d-1.10.13-cp310-cp310-manylinux2010_i686.whl", hash = "sha256:fd64c7995fc9d3f07100d4fe225498106cf3851f77bd54be3131193965768041"}, - {file = "panda3d-1.10.13-cp310-cp310-manylinux2010_x86_64.whl", hash = "sha256:acbcdf204ff75abe75b66af5c025b2f42e7777c2acb6cb17375cf7c66751a062"}, - {file = "panda3d-1.10.13-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:73905d348b486675706ac53e454b34138a3ab12faa1058720b6bf40110973be1"}, - {file = "panda3d-1.10.13-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:870da8fb7654f60d04ff5a85c75e80f89f14406c7f8812f4916f5781d1caeb25"}, - {file = "panda3d-1.10.13-cp310-cp310-win32.whl", hash = "sha256:5f75c18323ba3d36236e4756a98cba68268b0dd4ac6e4b889d87904a65b4fbbc"}, - {file = "panda3d-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:7d8a28b10167990d55ac696275b08b21b06fb35e7f5f64d482c9b76410373e96"}, - {file = "panda3d-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52f4b9014235ef5abbd6b5714389dc887ad45828316c42c83af53937179360d8"}, - {file = "panda3d-1.10.13-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d4cbd179bf442fab572bd9a486f866147ce38579e78dd5c01e1f59887bea64b8"}, - {file = "panda3d-1.10.13-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b4419faa2ca1747fccca8e76ca37700c1e011a38a8bad5be104fc703d1216bc3"}, - {file = "panda3d-1.10.13-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:68e9e215c4192ca14ab1304be79f86657f43d6d663b3dbe3af209caf8eb9793c"}, - {file = "panda3d-1.10.13-cp311-cp311-win32.whl", hash = "sha256:3a4df33d18fad4bfbc70907e6f7d6ef27f20ef4d0f3dfb3c17d7e4d46abd78f5"}, - {file = "panda3d-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:55efa8ae9a04260358257e94f29c506e338af0197c5b75dae29b0112dfdd67ac"}, - {file = "panda3d-1.10.13-cp34-cp34m-macosx_10_6_i386.whl", hash = "sha256:f6f016870c22ba0a250958fb09c49843eb17d5620dd1bfa86f38dfd200af9401"}, - {file = "panda3d-1.10.13-cp34-cp34m-macosx_10_6_x86_64.whl", hash = "sha256:fd901d3a822824a9b288392ba6ade77143c8024e208d3ce73a554e364f03cadb"}, - {file = "panda3d-1.10.13-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:4e46ad41febad38f57e4948912f9642838f5bff196c7c336194904e13165172d"}, - {file = "panda3d-1.10.13-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:dc7cd2ee0bd8ca9f7e957b2672f2d8ee1132cd6ddc2f8287d9579efa682b8b01"}, - {file = "panda3d-1.10.13-cp34-cp34m-win32.whl", hash = "sha256:190bd5c58184cae94cf158900106215f20d579f84a1b7ce1474bfbcb797b4a0b"}, - {file = "panda3d-1.10.13-cp34-cp34m-win_amd64.whl", hash = "sha256:372ac3770ada07f1c92848f36f13359c2c00f3eefa525b0b3cad4804ec64c6ca"}, - {file = "panda3d-1.10.13-cp35-cp35m-macosx_10_6_i386.whl", hash = "sha256:496147561f15a738a9a212a69ce3dda340ff1d8ae8bfd0501507945a94153bd8"}, - {file = "panda3d-1.10.13-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:b78acc42a2ee7006dd9618d52ea2657e03d6e39fe4d8c9b4c48342a1d866bbfd"}, - {file = "panda3d-1.10.13-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:35bb1a847a9012e218b1fa768632e4e52644e87ac8daf08fbd4c49feff0672b4"}, - {file = "panda3d-1.10.13-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:fdd564b7f154a78310d6d08920a9a6f405d9e7e9f74fd4b53c9eb73a007d2dea"}, - {file = "panda3d-1.10.13-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:730e5c6b8fb20448478ac1af117cc4aadd8e9dfa2558b5f9e89a69f83ce5f284"}, - {file = "panda3d-1.10.13-cp35-cp35m-win32.whl", hash = "sha256:4da16ba0a2c9615c9d68afdee47681454ece21bf252039078b4662dca856b8f0"}, - {file = "panda3d-1.10.13-cp35-cp35m-win_amd64.whl", hash = "sha256:526a1db71fcf1a2b4eaafb36642c710eb5d2425c9ec96d1afb7fadde4b1af619"}, - {file = "panda3d-1.10.13-cp36-cp36m-macosx_10_6_i386.whl", hash = "sha256:3298dcbfc8a9e87565d35129e3942ba0d076c6c7a504b32f656ccea39b876eb4"}, - {file = "panda3d-1.10.13-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:36c55f8aeb1b4eb305c3f76e0704678bdad80e37d46db71d3f723cbe4aa1eecf"}, - {file = "panda3d-1.10.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e4a7549e3583cd009a1a3ce33f84ed68f9ffd5b3f34f67606af6e8d6dafa32d5"}, - {file = "panda3d-1.10.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1711c7edc7620a8ca2f4fee2e6e448522726678cc5318d59de00d0d1e3f35ea6"}, - {file = "panda3d-1.10.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4c9da46324813e23b1108c0c3906460aa73a052c2934643a0e4a406a1a93dc03"}, - {file = "panda3d-1.10.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c1c77315e2a998f9173fe86d3af63b0affc1e7399a03226881d39ff758cc6b2d"}, - {file = "panda3d-1.10.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:017fdcb16c1a786f12b45a39bd33666423689a94060d1fc6135539d826146eb3"}, - {file = "panda3d-1.10.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b112c8d0ecdc85b14a93f6bae57dbb31043a3bbf1cd3ce3905b143e76ffbfc84"}, - {file = "panda3d-1.10.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:1e18194c78b9b519da885620ddcc7f6709e139645f665f08309b7eff58a5a0ee"}, - {file = "panda3d-1.10.13-cp36-cp36m-win32.whl", hash = "sha256:8eda3bd2402d75fb52962051c157f9495742f1f85c49c38b9bc47091fc0639b0"}, - {file = "panda3d-1.10.13-cp36-cp36m-win_amd64.whl", hash = "sha256:39652709ae0b8167aaf253edacc78898109cce96e872d9e7be33257dd5f98485"}, - {file = "panda3d-1.10.13-cp37-cp37m-macosx_10_6_i386.whl", hash = "sha256:d8c9668f257da744f301bf83ff44b2cdc8bd635e732fadf2f9b09200655cd3e4"}, - {file = "panda3d-1.10.13-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:ece8eb268a0be1fa45b6ef1e6cfa1ae89f25ee926e866893baf8bf391708c02d"}, - {file = "panda3d-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:508df5128cb63163eeee8f3cb6cb2984aefe473ee7e8e328f25a8bf205d54de2"}, - {file = "panda3d-1.10.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:d8a7beb7b88f1fc4ec0061be28018f6da1511783eb6a9911442d2a3435e50f7d"}, - {file = "panda3d-1.10.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a1cae74294394aa4afe93414c799798be77777e5bba77a45368bb1973ca65d2e"}, - {file = "panda3d-1.10.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:94d59874924de7b6765d427ea833ffea38fbb1b72a35679f81ef76c34ddf584d"}, - {file = "panda3d-1.10.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:23806bb1b289ad31f1ec1c206ddbdddd484ecdf8fdb09a4f89bc1c61af6b19ba"}, - {file = "panda3d-1.10.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:6013bf0049bb6b30b0ff17ca5dc6bde621b553f0d1815481ced186ba710aa3b6"}, - {file = "panda3d-1.10.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:e208d1ac385acb48e24f557badfdb6244b0122bc25511c512b893c55ccb1bed3"}, - {file = "panda3d-1.10.13-cp37-cp37m-win32.whl", hash = "sha256:1dea56806643077e863abd8735e7056019ffd2b935990670607b44f996ef8456"}, - {file = "panda3d-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:08f7729e3b13d7417a07b14a3532ee93e4c50293de45392d9783038dcc24cfd4"}, - {file = "panda3d-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8099b2bc211a98ff1d26a91c26d126d538896a59ad29d4de50dea19ccaf31b1b"}, - {file = "panda3d-1.10.13-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:bc41bd97e3a2dd04d0f664ae0ea1a7677ce7c6a718ab4d3b3ff79ef06f0a22c9"}, - {file = "panda3d-1.10.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2027d6d8ac86db0989fbcbafa9f4f1e1a4de79a94f087325e43ccf9e65da12a9"}, - {file = "panda3d-1.10.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:dbfccaf3ff52b4e5de66d33bf06aa489af43cb139367b77fa6bc79ac774d23dc"}, - {file = "panda3d-1.10.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c0785bfa3ad3e87f837433737679dce5e0d1a33accbfcb0dcb0393ce3a7a68f2"}, - {file = "panda3d-1.10.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:e384ed11490d479095cbad2f27ad61f536ca75aeb702557ce83b67381d4448bd"}, - {file = "panda3d-1.10.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8cd5e47e18f56dd6dab7ed1acb21c07f356b7220db72b820f02363050ee3bdfd"}, - {file = "panda3d-1.10.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:094ccf20cdfb2a2f44cde56a1ab9a1287a09cfd33dd4fe094cb255aaacfb678f"}, - {file = "panda3d-1.10.13-cp38-cp38-win32.whl", hash = "sha256:83c39308ee95951616dd5ab1e2ee730853fca9b4331a7c9ea9d92c9af3321aa9"}, - {file = "panda3d-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:2ae1bd477a8882c80bdb6d63d6738ae2c4c335f0d88af592a051a9705434c75d"}, - {file = "panda3d-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:df904a0ca374fd7667727a3d3743c386e913e6e80b8efe67fe0fcd01f904e4c3"}, - {file = "panda3d-1.10.13-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:db8cc33163112722cdf313d06939ff30b260c7a5e2a404b38bddc7558e6e8a26"}, - {file = "panda3d-1.10.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:029d7c698114da30d0d5ecd57e0c92423d319848e4197e6a86ac54b9ae43c424"}, - {file = "panda3d-1.10.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4927f23e8c1469e6a17c3f05a81ac0c6fa7c23b2d39bffee5277ecabfb2da10f"}, - {file = "panda3d-1.10.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2683fc8ad1e98a98ae692dbd82896c3f055743351e7a61a8ecd22557ddef643e"}, - {file = "panda3d-1.10.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:809881853087d36211f380245499778a4838f2f1748a4f8d90546debed294f8b"}, - {file = "panda3d-1.10.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:05be45eeddc81fb4879b0e6b206422b810e2de0f6639ab04522f660270eb202a"}, - {file = "panda3d-1.10.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:a00823d1ae2490dba0643109f10e7347b15e6c2554de0b1bed0a085950fa6ed0"}, - {file = "panda3d-1.10.13-cp39-cp39-win32.whl", hash = "sha256:2d5d3e2160d8fa667ef548171695be84e7de25f51817ae31c61cf2e869a2f6c6"}, - {file = "panda3d-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:839d8b2724a149d56a31f621c6397a1fed597a7dacee478922c711426160990e"}, + {file = "panda3d-1.10.14-cp27-cp27m-macosx_10_6_i386.whl", hash = "sha256:cf47bcbe5d6270107e80f5f18b88186cc40de83e4e90c86e7e516d2289a68dd1"}, + {file = "panda3d-1.10.14-cp27-cp27m-macosx_10_6_x86_64.whl", hash = "sha256:c371a3df183b2e382cb6dbf07efb744b05bdb0d89101457f350ea9a7a9a481f1"}, + {file = "panda3d-1.10.14-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ba49c6f213a5f0287e4cbdb66714cdf3b4aa5bdbe4e1a55034e74d0dda9f0250"}, + {file = "panda3d-1.10.14-cp27-cp27m-win32.whl", hash = "sha256:adb54216a9234f3736693d49032783d7fb8952a15510f1397f992a5c94cdf897"}, + {file = "panda3d-1.10.14-cp27-cp27m-win_amd64.whl", hash = "sha256:f94dd5f557166b6571969883bd2ffd3a555e8ac720f0f3e347d2081222bc0997"}, + {file = "panda3d-1.10.14-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c98d2143755e52d4be3b474b932207c830e473aa0e10052831bc82784f1dd17c"}, + {file = "panda3d-1.10.14-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:b7f8c6524e1381bd28a15e5e2ed7aaba8ed3b6fa29d4ea5a4b215c4639b49438"}, + {file = "panda3d-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b0e2aacb148b7033c08929bbf89bb497c2bed1811aa9f7e5efc167dbf57c90ae"}, + {file = "panda3d-1.10.14-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:2c60139139654a51dab213ce1c9f67abd73153fc9e0a0514c0758741b2d2bd96"}, + {file = "panda3d-1.10.14-cp310-cp310-manylinux2010_i686.whl", hash = "sha256:f5da02fb5735294193eb1794ecbe619f20f87712f1b2c5d6e4d1a269f652ebeb"}, + {file = "panda3d-1.10.14-cp310-cp310-manylinux2010_x86_64.whl", hash = "sha256:d6a65b1930e144d5901cdd20d2779dc157897a730f0c9674692d61ad50e31213"}, + {file = "panda3d-1.10.14-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:182a309537cd76c514b9f4d0dfd54bc9cb35d06a32234084bab6f3f0b0db3985"}, + {file = "panda3d-1.10.14-cp310-cp310-win32.whl", hash = "sha256:2a2b872a180ed84ff337147d6d46c14aceccbcfd1149135ba57e15136350d87f"}, + {file = "panda3d-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:385d12a1f0924e91a39ebd6053104712a1acd91e089638d292b533e36f402c48"}, + {file = "panda3d-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54b8ef9fe3684960a2c7d47b0d63c0354c17bc516795e59db6c1e5bda8c16c1c"}, + {file = "panda3d-1.10.14-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:93414675894b18eea8d27edc1bbd1dc719eae207d45ec263d47195504bc4705f"}, + {file = "panda3d-1.10.14-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:d1bc0d926f90c8fa14a1587fa9dbe5f89a4eda8c9684fa183a8eaa35fc8e891a"}, + {file = "panda3d-1.10.14-cp311-cp311-win32.whl", hash = "sha256:1039340a4a7965fe4c3e904edb4fff691584df435a154fecccf534587cd07a34"}, + {file = "panda3d-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:1ddf01040b9c5497fb8659e3c5ef793a26c869cfdfb1b75e6d04d6fba0c03b73"}, + {file = "panda3d-1.10.14-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1bfbcee77779f12ecce6a3d5a856e573b25d6343f8c4b107e814d9702e70a65d"}, + {file = "panda3d-1.10.14-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:bc6540c5559d7e14a8992eff7de0157b7c42406b7ba221941ed224289496841c"}, + {file = "panda3d-1.10.14-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:143daab1ce6bedcba711ea3f6cab0ebe5082f22c5f43e7178fadd2dd01209da7"}, + {file = "panda3d-1.10.14-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:3c4399a286a142de7ff86f9356d7e526bbbd38892d7f7d39fecb5c33064972bc"}, + {file = "panda3d-1.10.14-cp312-cp312-win32.whl", hash = "sha256:e92e0dd907e2af33085a2c31ca2263dc8023b1b7bc70ce1b9fbc84631e130e51"}, + {file = "panda3d-1.10.14-cp312-cp312-win_amd64.whl", hash = "sha256:a5f2defd822d38848f8ae1956115adcb6cc7f464f03a67e73681cc72df125ef4"}, + {file = "panda3d-1.10.14-cp34-cp34m-macosx_10_6_i386.whl", hash = "sha256:84ca5855e03173f521a2b4edc84e62ad5ffb31e9454bd809efb8e71a5c32df4f"}, + {file = "panda3d-1.10.14-cp34-cp34m-macosx_10_6_x86_64.whl", hash = "sha256:9082e3d66a653589ffab7c5bd7a50eb46ab168104e08a86c77b58bbd77030d50"}, + {file = "panda3d-1.10.14-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:f1a46ca8427ffb9267b3951af81f6774b7259b7e8f89210af6366b208e5762e6"}, + {file = "panda3d-1.10.14-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:c23694788696d7bfe291ced98dd87e57532a23f821e71f7b2d0c2004a4648c8f"}, + {file = "panda3d-1.10.14-cp34-cp34m-win32.whl", hash = "sha256:f14455c459730f396e524d8b9e62875438481e1dc7dfd2083e9522a3bad45f28"}, + {file = "panda3d-1.10.14-cp34-cp34m-win_amd64.whl", hash = "sha256:110a33b20f15aa6333c8e72e30bcf0dc9afaef809a325d247bb7470de3942dae"}, + {file = "panda3d-1.10.14-cp35-cp35m-macosx_10_6_i386.whl", hash = "sha256:d91e37d43242d5132886c3850aa4a2f3ee7401de2360cb8313616b45ab78ecba"}, + {file = "panda3d-1.10.14-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:f4bcf78223df3c13e9a627a25856c91bdbf28f75b021630e5ca37a961eeabb27"}, + {file = "panda3d-1.10.14-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:e0a7a1d7cbe10474cad720898b384d642dcd3d7a07b3dbe09154107a5651be49"}, + {file = "panda3d-1.10.14-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:78404da82a5d93641b0f2a89790cad235b087e7e2e37bbe83c995e99495635cd"}, + {file = "panda3d-1.10.14-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aa1381f4c06b789b8a725185773397b0c7a2508eeb8156874537c07171e5cb99"}, + {file = "panda3d-1.10.14-cp35-cp35m-win32.whl", hash = "sha256:8a497dd7401b50fa372664c724f1cb949b8d8bc3ed6d9b6433af19086bcf9325"}, + {file = "panda3d-1.10.14-cp35-cp35m-win_amd64.whl", hash = "sha256:293c5441e4f2720da480abdb3c99e55e96a7616d8867ac9c7b94526694a5812e"}, + {file = "panda3d-1.10.14-cp36-cp36m-macosx_10_6_i386.whl", hash = "sha256:15083e9c82d884c279bca897576ed91848323f951617f7bbb43d2dba32e6d662"}, + {file = "panda3d-1.10.14-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:b8a20828a45a9215f7ecb59baec18a79adda56ad1b25eb99b9958d26d8ab2a40"}, + {file = "panda3d-1.10.14-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d4c555a5c47ffcaaed518c52b3bb9d5729a5dc4492f51e8a46b076ca54adcae0"}, + {file = "panda3d-1.10.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0c43a4dfd6b12323359439ea7c60e8fbf84ff0aa75abaccd5179cc6b27baa2b7"}, + {file = "panda3d-1.10.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:06a4595f1737170d316829b451cbfb01a130918a04481b1f217212c485c64586"}, + {file = "panda3d-1.10.14-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:24d2a38f9068f868ac23101a8601d88f3c137ededed2be222d5e711a4b114467"}, + {file = "panda3d-1.10.14-cp36-cp36m-win32.whl", hash = "sha256:d7d722fb5a62531e62f6b98db2b3e399deddde945f84c954aefac73989c75e4c"}, + {file = "panda3d-1.10.14-cp36-cp36m-win_amd64.whl", hash = "sha256:7d17fe989aaed3f35a7992742a4c4536936fb0a70a87d4625b92f185f0318c45"}, + {file = "panda3d-1.10.14-cp37-cp37m-macosx_10_6_i386.whl", hash = "sha256:0ab75f828daef54678e9f2a15bccfe4e7b090991ee025689be324194266fd82e"}, + {file = "panda3d-1.10.14-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:f04af7a7becaf30c0c256a0177a7493ab91247ea5a2798345b2df2ec71aeff23"}, + {file = "panda3d-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d41579867cb2998f4273bdece282c641dac6f40ca2e713dafa3fb9ce3ef12162"}, + {file = "panda3d-1.10.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4db1b920d501d3524e92467a21ecb08cc0063566a681991cc683f2070068f128"}, + {file = "panda3d-1.10.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:db28161d1f40adf0b0998f7064ac784e49a2f2ec3bb04810c2757ba938c7d2cf"}, + {file = "panda3d-1.10.14-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:0cbdbc41229fa7d075925a716477e7a8d1a17a2523fc88e97fa010c4e0706442"}, + {file = "panda3d-1.10.14-cp37-cp37m-win32.whl", hash = "sha256:145cd28de41261e0ac2bc24323417f8b21623f48de0bb7a9e0d70ff386c34c84"}, + {file = "panda3d-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:fadb6eb4106dc3fecea81a1e81186b3cc3ee0019d0b5435273113a18aec2352c"}, + {file = "panda3d-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a61c973d8d58305a502d05a82dc3ec6cf2b5e01ada7151a83415c51a959ff774"}, + {file = "panda3d-1.10.14-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:3e82b2f904efb9f7f78dbf6d88bc5ebc83c5c0fbc9e477a8ef2b154bd7935591"}, + {file = "panda3d-1.10.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:96d1ce3483a51ec5aebc43f3e30e552deacb660606d2b36e86ed3a9756e3eccf"}, + {file = "panda3d-1.10.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b658d75a5342ff175703ce780e8b383da438ff68ea941d0e5dff68c5189d38ec"}, + {file = "panda3d-1.10.14-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:3e564c44d879b01d01a7fcdc03977affcf3b1dabcf2d6e5c678e06b9de8855ac"}, + {file = "panda3d-1.10.14-cp38-cp38-win32.whl", hash = "sha256:06007592951730b52852771e98b306b3c1de9dc51abf0bab183c3ec3ac404131"}, + {file = "panda3d-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:b05713b5acfcfcbf1dc87c19b1d5babf926d21f83b3772180e7a5de83f45b454"}, + {file = "panda3d-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d55c7d52bd07e54a16219bb88a7cf34a5b9e7c720dce585e9de8427cc54d9dd2"}, + {file = "panda3d-1.10.14-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:39abf9330ebcfcff07c29ebcf397cf43c2e5be6c148cf45a0a025d3297c43289"}, + {file = "panda3d-1.10.14-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0f69c7232713f50a552b00ada4cbb03780bb97ec1ddb640e5162df61d1b26a2b"}, + {file = "panda3d-1.10.14-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:9dda6736e8c180fa9caab70aa9461d7042b74764f73e4d2c59fe560751989341"}, + {file = "panda3d-1.10.14-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:a91ab520fa8bd206460f0b7acfa6329440d72669048bac3eab4bb4d8dcd09991"}, + {file = "panda3d-1.10.14-cp39-cp39-win32.whl", hash = "sha256:2de074b0a3e0f243e4c1fba0376a26489d585cc71fd79986de274e282e3913d8"}, + {file = "panda3d-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:356c55c25c2227bb2cf20a9613410c470f4e9de887543ec8b3765c848af4bff0"}, ] [[package]] @@ -2826,13 +2871,13 @@ panda3d-simplepbr = ">=0.6" [[package]] name = "panda3d-simplepbr" -version = "0.11.2" -description = "A simple, lightweight PBR render pipeline for Panda3D" +version = "0.12.0" +description = "A straight-forward, easy-to-use, drop-in, PBR replacement for Panda3D's builtin auto shader" optional = false python-versions = ">=3.8" files = [ - {file = "panda3d-simplepbr-0.11.2.tar.gz", hash = "sha256:89469f9be0e15fd8888a277f60ba08eb31b90354dcba80fc5aa7ccccee56be2a"}, - {file = "panda3d_simplepbr-0.11.2-py3-none-any.whl", hash = "sha256:eca198004ed4a6635ac9180695440390d52c73eec0b6e02c317d000e1e4d1518"}, + {file = "panda3d-simplepbr-0.12.0.tar.gz", hash = "sha256:c71d490afeeb3a90455dcfde1d30c41f321a38742a97d18834e5c31016331ed5"}, + {file = "panda3d_simplepbr-0.12.0-py3-none-any.whl", hash = "sha256:6c43d1990ff07840cf1c557561d6122fd1250d8e76aacf227b61c3789149bcf9"}, ] [package.dependencies] @@ -2844,44 +2889,47 @@ test = ["pylint (>=3.0.0,<3.1.0)", "pytest", "pytest-pylint"] [[package]] name = "pandas" -version = "2.2.1" +version = "2.2.2" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {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"}, + {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, + {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, + {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, + {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, + {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, + {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, + {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, ] [package.dependencies] -numpy = {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""} +numpy = [ + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] python-dateutil = ">=2.8.2" pytz = ">=2020.1" tzdata = ">=2022.7" @@ -2927,79 +2975,80 @@ dev = ["jinja2"] [[package]] name = "pillow" -version = "10.2.0" +version = "10.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, - {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, - {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, - {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, - {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, - {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, - {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, - {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, - {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, - {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, - {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, - {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, - {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, - {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, - {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, - {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, - {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, - {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, + {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"}, + {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"}, + {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"}, + {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"}, + {file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"}, + {file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"}, + {file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"}, + {file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"}, + {file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"}, + {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"}, + {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"}, + {file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"}, + {file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"}, + {file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"}, + {file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"}, + {file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"}, + {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"}, + {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"}, + {file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"}, + {file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"}, + {file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"}, + {file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"}, + {file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"}, + {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"}, + {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"}, + {file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"}, + {file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"}, + {file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"}, + {file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"}, + {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"}, + {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"}, + {file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"}, + {file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"}, + {file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"}, + {file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"}, ] [package.extras] @@ -3012,28 +3061,29 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.2" +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.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] 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)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -3074,25 +3124,15 @@ docs = ["sphinx (>=1.7.1)"] redis = ["redis"] tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)", "types-redis"] -[[package]] -name = "pprofile" -version = "2.1.0" -description = "Line-granularity, thread-aware deterministic and statistic pure-python profiler" -optional = false -python-versions = "*" -files = [ - {file = "pprofile-2.1.0.tar.gz", hash = "sha256:b2bb56603dadf40c0bc0f61621f22c20e41638425f729945d9b7f8e4ae8cdd4a"}, -] - [[package]] name = "pre-commit" -version = "3.6.2" +version = "3.7.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {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"}, + {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, + {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, ] [package.dependencies] @@ -3114,22 +3154,22 @@ files = [ [[package]] name = "protobuf" -version = "4.25.3" +version = "5.27.1" description = "" optional = false python-versions = ">=3.8" files = [ - {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"}, + {file = "protobuf-5.27.1-cp310-abi3-win32.whl", hash = "sha256:3adc15ec0ff35c5b2d0992f9345b04a540c1e73bfee3ff1643db43cc1d734333"}, + {file = "protobuf-5.27.1-cp310-abi3-win_amd64.whl", hash = "sha256:25236b69ab4ce1bec413fd4b68a15ef8141794427e0b4dc173e9d5d9dffc3bcd"}, + {file = "protobuf-5.27.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4e38fc29d7df32e01a41cf118b5a968b1efd46b9c41ff515234e794011c78b17"}, + {file = "protobuf-5.27.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:917ed03c3eb8a2d51c3496359f5b53b4e4b7e40edfbdd3d3f34336e0eef6825a"}, + {file = "protobuf-5.27.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:ee52874a9e69a30271649be88ecbe69d374232e8fd0b4e4b0aaaa87f429f1631"}, + {file = "protobuf-5.27.1-cp38-cp38-win32.whl", hash = "sha256:7a97b9c5aed86b9ca289eb5148df6c208ab5bb6906930590961e08f097258107"}, + {file = "protobuf-5.27.1-cp38-cp38-win_amd64.whl", hash = "sha256:f6abd0f69968792da7460d3c2cfa7d94fd74e1c21df321eb6345b963f9ec3d8d"}, + {file = "protobuf-5.27.1-cp39-cp39-win32.whl", hash = "sha256:dfddb7537f789002cc4eb00752c92e67885badcc7005566f2c5de9d969d3282d"}, + {file = "protobuf-5.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:39309898b912ca6febb0084ea912e976482834f401be35840a008da12d189340"}, + {file = "protobuf-5.27.1-py3-none-any.whl", hash = "sha256:4ac7249a1530a2ed50e24201d6630125ced04b30619262f06224616e0030b6cf"}, + {file = "protobuf-5.27.1.tar.gz", hash = "sha256:df5e5b8e39b7d1c25b186ffdf9f44f40f810bbcc9d2b71d9d3156fee5a9adf15"}, ] [[package]] @@ -3160,6 +3200,54 @@ files = [ [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +[[package]] +name = "pyarrow" +version = "16.1.0" +description = "Python library for Apache Arrow" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyarrow-16.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:17e23b9a65a70cc733d8b738baa6ad3722298fa0c81d88f63ff94bf25eaa77b9"}, + {file = "pyarrow-16.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4740cc41e2ba5d641071d0ab5e9ef9b5e6e8c7611351a5cb7c1d175eaf43674a"}, + {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98100e0268d04e0eec47b73f20b39c45b4006f3c4233719c3848aa27a03c1aef"}, + {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f68f409e7b283c085f2da014f9ef81e885d90dcd733bd648cfba3ef265961848"}, + {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a8914cd176f448e09746037b0c6b3a9d7688cef451ec5735094055116857580c"}, + {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:48be160782c0556156d91adbdd5a4a7e719f8d407cb46ae3bb4eaee09b3111bd"}, + {file = "pyarrow-16.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cf389d444b0f41d9fe1444b70650fea31e9d52cfcb5f818b7888b91b586efff"}, + {file = "pyarrow-16.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:d0ebea336b535b37eee9eee31761813086d33ed06de9ab6fc6aaa0bace7b250c"}, + {file = "pyarrow-16.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e73cfc4a99e796727919c5541c65bb88b973377501e39b9842ea71401ca6c1c"}, + {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf9251264247ecfe93e5f5a0cd43b8ae834f1e61d1abca22da55b20c788417f6"}, + {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddf5aace92d520d3d2a20031d8b0ec27b4395cab9f74e07cc95edf42a5cc0147"}, + {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:25233642583bf658f629eb230b9bb79d9af4d9f9229890b3c878699c82f7d11e"}, + {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a33a64576fddfbec0a44112eaf844c20853647ca833e9a647bfae0582b2ff94b"}, + {file = "pyarrow-16.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:185d121b50836379fe012753cf15c4ba9638bda9645183ab36246923875f8d1b"}, + {file = "pyarrow-16.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:2e51ca1d6ed7f2e9d5c3c83decf27b0d17bb207a7dea986e8dc3e24f80ff7d6f"}, + {file = "pyarrow-16.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06ebccb6f8cb7357de85f60d5da50e83507954af617d7b05f48af1621d331c9a"}, + {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04707f1979815f5e49824ce52d1dceb46e2f12909a48a6a753fe7cafbc44a0c"}, + {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d32000693deff8dc5df444b032b5985a48592c0697cb6e3071a5d59888714e2"}, + {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8785bb10d5d6fd5e15d718ee1d1f914fe768bf8b4d1e5e9bf253de8a26cb1628"}, + {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e1369af39587b794873b8a307cc6623a3b1194e69399af0efd05bb202195a5a7"}, + {file = "pyarrow-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:febde33305f1498f6df85e8020bca496d0e9ebf2093bab9e0f65e2b4ae2b3444"}, + {file = "pyarrow-16.1.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b5f5705ab977947a43ac83b52ade3b881eb6e95fcc02d76f501d549a210ba77f"}, + {file = "pyarrow-16.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0d27bf89dfc2576f6206e9cd6cf7a107c9c06dc13d53bbc25b0bd4556f19cf5f"}, + {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d07de3ee730647a600037bc1d7b7994067ed64d0eba797ac74b2bc77384f4c2"}, + {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbef391b63f708e103df99fbaa3acf9f671d77a183a07546ba2f2c297b361e83"}, + {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19741c4dbbbc986d38856ee7ddfdd6a00fc3b0fc2d928795b95410d38bb97d15"}, + {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f2c5fb249caa17b94e2b9278b36a05ce03d3180e6da0c4c3b3ce5b2788f30eed"}, + {file = "pyarrow-16.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:e6b6d3cd35fbb93b70ade1336022cc1147b95ec6af7d36906ca7fe432eb09710"}, + {file = "pyarrow-16.1.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:18da9b76a36a954665ccca8aa6bd9f46c1145f79c0bb8f4f244f5f8e799bca55"}, + {file = "pyarrow-16.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:99f7549779b6e434467d2aa43ab2b7224dd9e41bdde486020bae198978c9e05e"}, + {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f07fdffe4fd5b15f5ec15c8b64584868d063bc22b86b46c9695624ca3505b7b4"}, + {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddfe389a08ea374972bd4065d5f25d14e36b43ebc22fc75f7b951f24378bf0b5"}, + {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3b20bd67c94b3a2ea0a749d2a5712fc845a69cb5d52e78e6449bbd295611f3aa"}, + {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ba8ac20693c0bb0bf4b238751d4409e62852004a8cf031c73b0e0962b03e45e3"}, + {file = "pyarrow-16.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:31a1851751433d89a986616015841977e0a188662fcffd1a5677453f1df2de0a"}, + {file = "pyarrow-16.1.0.tar.gz", hash = "sha256:15fbb22ea96d11f0b5768504a3f961edab25eaf4197c341720c4a387f6c60315"}, +] + +[package.dependencies] +numpy = ">=1.16.6" + [[package]] name = "pyaudio" version = "0.2.14" @@ -3205,53 +3293,93 @@ pytweening = ">=1.0.4" [[package]] name = "pycapnp" -version = "1.3.0" +version = "2.0.0" description = "A cython wrapping of the C++ Cap'n Proto library" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "pycapnp-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1410e38d1cd1d08fb36c781e8a91b2044306d18775524dfd6c8a3b83e665f47f"}, - {file = "pycapnp-1.3.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:debb37f96db664c140e557855ba433ea59fdf714cff358a50a619e007387692b"}, - {file = "pycapnp-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:108582647317c0f3a52ab1c615333d48546cede475887ebf8a055d1d6f20f249"}, - {file = "pycapnp-1.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1451d131ae0715b840eda1d0c84ea3909b637ac28554a13e57c089aef09e4f36"}, - {file = "pycapnp-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c09d0ea8bc6d6d101a1642f4d9366d929cacd3966bc510a7cd5b14df2fa2986"}, - {file = "pycapnp-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f26bb24bd28db2a6f99b7d17178112103dedef0ee275204f705e86936513c240"}, - {file = "pycapnp-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:90c07b969d90d012d0d309ec4c44605ad2d8f91595dcdf8ccc1ee798bf723b77"}, - {file = "pycapnp-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70a9d155d0dc17d343f295ae2228fb0436af329eab941e3b6637d5f5a050616d"}, - {file = "pycapnp-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cb575ab47069f990dc353d0c12f62013b08d0c00ba10c1aebffdc3150abd292"}, - {file = "pycapnp-1.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ba3adae76801b1a4c7c0f96367bdd153286469c45223bf6b50057e5d077e5d2"}, - {file = "pycapnp-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc939f85720451b1d4a05603b127c0ae241e67fcc43121685b906aa3da08d624"}, - {file = "pycapnp-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9d6af7aba49eda201243b0fbcb410b3a8a789a2f7b86adafbde7e95234a07e85"}, - {file = "pycapnp-1.3.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:32e56829e05fc54e14a8b40076d0a0958acef835379c6e39c26648cb754b9011"}, - {file = "pycapnp-1.3.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:f9d29bf65a5fb6cca1d0ed56cd2b0e6b1f63d47af72dfced80bbf6a8315aa2ae"}, - {file = "pycapnp-1.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8389da70ff8c4008a0aabcdbf04e272aebb0c71476bc29cb7fbefb9ae579c947"}, - {file = "pycapnp-1.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432b9d49bde68eca83b10f04791c65dc9a2070afcbd406703c4f4a073f02061c"}, - {file = "pycapnp-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cda76deffa45e567354234d2c21e084251dc270ba226e8d9863406c54e9cb87f"}, - {file = "pycapnp-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e351748b9a1bc3e6379183a24e1e68ecf4f76943872f63c8d42f206523b78cbb"}, - {file = "pycapnp-1.3.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:ff4a67282af21fb6a16b99f7d2397c4c6462b3a59b9b751e6860ccfb5c838398"}, - {file = "pycapnp-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f6f011ce96049cff07502e76d91e2a037234aa89bd4fc299c95e403797a73dae"}, - {file = "pycapnp-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b02fd11a155aca4e1b507bc7bd91963ec9a7695d5235837229d105d920385514"}, - {file = "pycapnp-1.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0bfdbae7344835c287be2ca9f3e9fdf8f208941933fb77f6252a937917347de"}, - {file = "pycapnp-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa7d2e0c22fb4f1c7bf44630170ba06df4e1fb47f0977df1d5fe425dc74ce354"}, - {file = "pycapnp-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b82d5c2ed388157b01ae7fbf754fd677d069150336ee74d6f286936af8ce0f18"}, - {file = "pycapnp-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:02acfc8d94ed0c2e1b68cde6c845149b5c89eb4c5a71c7fe4978a70370cb17b5"}, - {file = "pycapnp-1.3.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9333029a4c4f71942666ee1eb051740c38c27b034eca4c73d09f26217e2b2dc4"}, - {file = "pycapnp-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:886c09a5802b3ec232a8cd64f44c092bbf3daa1c0ff66942f1f6b9943a71a909"}, - {file = "pycapnp-1.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:77c95991b4fc30fd336e3cbed4ffd3fb23aa647427bc96cafd0f77ec5b046766"}, - {file = "pycapnp-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223f600854f46009715231237a02c18a6e5b28492c0976d6602c0d73021c9d3a"}, - {file = "pycapnp-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:45e52d201d4d2ae9ee3290cae8f62c1af982ca2d7a7eaa0b58f53ada9a07c877"}, - {file = "pycapnp-1.3.0.tar.gz", hash = "sha256:7cf514c3068064e593d0401503f7a623c24c55776702a7a2d9cad9854710aa56"}, + {file = "pycapnp-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12fc023da9acd062884c9b394113457908b3c5e26aeb85f668b59c0e84b7b150"}, + {file = "pycapnp-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c8f5e4e68a1b59ae73cd77550b95f8719aea624aa424cd77aa193c6d45ea97ab"}, + {file = "pycapnp-2.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50ff7f45d77dc2ef0c44a0aef41f37433a0c395b6a1db99b7e6f45e0e9237bd4"}, + {file = "pycapnp-2.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc68ef3d80d9e7e9b96ba2077d8e2effd42f936bda1024f1aedc05022c9401bb"}, + {file = "pycapnp-2.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74a75e5c190e4d722aa0d2702bd04fedc2cb8e0a893031813e7a50cc067a054a"}, + {file = "pycapnp-2.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db1bc53cbe5222f4fd0748ba6b53da9ec58e8f7b2219dc9cd50d15266a3fa85c"}, + {file = "pycapnp-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0509ef856239634be21375cbad73dc7cf7fdfb32c03c312ad41e994f0674f7"}, + {file = "pycapnp-2.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:48372a19b59e8d533768c12988a92af4ea6c2daa1a8ba1c42202cd0dd24a1d24"}, + {file = "pycapnp-2.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:46c16a98cec9ae6dce5ebf488bb0c8425484d7710eed1ee008a26b60470ee755"}, + {file = "pycapnp-2.0.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:be91cdb7895c4e2f1e1cd6b701ed66050c285d2c228f476a775bfd76bbd697f1"}, + {file = "pycapnp-2.0.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:21db83e5f0c3a944b567cd20e4df47dba023e936a45d7057f2a615b8c19356f8"}, + {file = "pycapnp-2.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:825a8a86e034d66d8df8d82b7bf9fdd3f344bd84ff43a838ec08f08fe7461be0"}, + {file = "pycapnp-2.0.0-cp310-cp310-win32.whl", hash = "sha256:13ff9dca5741079d7bbe4e2512634b8ce859b709a1b126481eed404bda0b3d4a"}, + {file = "pycapnp-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:279d9f7c34527d15a62dde0dfc82cb918ed0a900dfa9713960d64bed3f9236a4"}, + {file = "pycapnp-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:829c7eb4e5f23dbcac25466110faf72a691035cf87c5d46e5053da15790e428d"}, + {file = "pycapnp-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dab60fbe3e4eaf99ec97918a0a776216c6c149b6d49261383d91c2201adb475d"}, + {file = "pycapnp-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c48a0582078bb74d7326d28571db0b8e6919563365537a5a13e8f5360c12bfc"}, + {file = "pycapnp-2.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb5ab54aff857e3711d2c0cc934194aaffacdeb3481daa56863daef07d27941"}, + {file = "pycapnp-2.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9600778036e6fe9dbea68f0c37678c5f4d561d2f2306b3cb741de5e1670ef2ae"}, + {file = "pycapnp-2.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7278ba0262fab8c398e77d634ae7ba026866d44b52cbfc27262be8d396ecacd1"}, + {file = "pycapnp-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23b2458d43c82302980a96518c96df257429204d2cc02bfff0c8cb6ebb371e01"}, + {file = "pycapnp-2.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd7755cc3fedc2ad8cc7864a0729471ddeff10c184963fe0f3689e295130f1b2"}, + {file = "pycapnp-2.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d47baf6b3db9981625ffc5ff188e089f2ebca8e7e1afb97aa5eb7bebb7bf3650"}, + {file = "pycapnp-2.0.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b375be92d93fdb6f7ac127ea9390bcec0fed4e485db137b084f9e7114dde7c83"}, + {file = "pycapnp-2.0.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:959bfdf1cddb3e5528e2293c4a375382be9a1bf044b073bc2e7eca1eb6b3a9a2"}, + {file = "pycapnp-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d873af167cf5cc7578ce5432eefcb442f866c8f7a6c57d188baf8c5e709fa39d"}, + {file = "pycapnp-2.0.0-cp311-cp311-win32.whl", hash = "sha256:40ca8018e0b7686d549b920f087049b92a3e6f06976d9f5a8112603fc560cac4"}, + {file = "pycapnp-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:d15cd8e46d541a899c84809095d7d7b3951f43642d1859e7a39bd91910778479"}, + {file = "pycapnp-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0c111ef96676df25b8afef98f369d45f838ad4434e2898e48199eb43ef704efe"}, + {file = "pycapnp-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0d18906eb1fd1b9f206d93a9591ceedce1d52e7766b66e68f271453f104e9dca"}, + {file = "pycapnp-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5d1ed365ab1beabb8838068907a7190cc0b6f16de3499d783627e670fcc0eb2"}, + {file = "pycapnp-2.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:495b39a7aa2629931bbca27ad743ce591c6c41e8f81792276be424742d9cd1c1"}, + {file = "pycapnp-2.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50e814fbde072dcc3d868b5b5cbb9b7a66a70bff9ad03942f3be9baf3ca1cfc6"}, + {file = "pycapnp-2.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:920fdda62d5fdef7a48339104dff0ceb9dcc21b138491f854457ba3a3d4d63ec"}, + {file = "pycapnp-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f9142eb4714c152b09dda0b055ea9dd43fd8fd894132e7eb4fa235fb4915edd"}, + {file = "pycapnp-2.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c98f1d0c4d32109d03e42828ce3c65236afc895033633cbed3ca092993702e7b"}, + {file = "pycapnp-2.0.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4d3250c1875a309d67551843cd8bf3c5e7fccf159b7f5c118a92aee36c0e871c"}, + {file = "pycapnp-2.0.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:174e6babe01f5507111c0ed226cd0b5e9325a9d2850751cfe4a57c1670f13881"}, + {file = "pycapnp-2.0.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:ed38ece414341285695526792e020f391f29f5064b2126d0367c8bdeef28e3e9"}, + {file = "pycapnp-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a20b7dc55ef83a1fa446bf12680bce25caeb8f81788b623b072c3ec820db50d"}, + {file = "pycapnp-2.0.0-cp312-cp312-win32.whl", hash = "sha256:145eea66233fb5ac9152cd1c06b999ddb691815126f87f5cc37b9cda5d569f8a"}, + {file = "pycapnp-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:b8b03000769b29b36a8810f458b931f0f706f42027ee6676821eff28092d7734"}, + {file = "pycapnp-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6b1d547af10ecf4a3da25143c71ce64a3b0817bf229c560c9f9729d355c3b48d"}, + {file = "pycapnp-2.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecbbdbc2251d5e9d16148601e224ffaf9612d07751af72496472a3e285a1baed"}, + {file = "pycapnp-2.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8158ebb967f7b103d2d5514d6a8cc643b1744cc8e14e14a5c19697507719de7b"}, + {file = "pycapnp-2.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:999068b371fd4f21dddfff97640609f325cd8498c2a9d6a6c253164466628ba0"}, + {file = "pycapnp-2.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb46a9191a64ce532f82800e6885abe61e63ae4f612ed1d98c7a493dd6ee6d2b"}, + {file = "pycapnp-2.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fe49a1be55fb01f794434bab42643de7b4e37e774c3d63635f4a9ca9064437e"}, + {file = "pycapnp-2.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab6c94687908003fced23ce8c415d85e521e923b0bcbcc5323bac66d14911d5"}, + {file = "pycapnp-2.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5b814578f4f29c787deef30226c54f11188caef9b375776e8b1d73649e4119"}, + {file = "pycapnp-2.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f169bdc0a683f1cb716c1ab83c5028e75d7bf34674c00b302ca1ebf66e0d27ca"}, + {file = "pycapnp-2.0.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:5f348f8487df6a67891684e3c9c6476930fbb09258fe33f96c9bbdc4c51e6d4e"}, + {file = "pycapnp-2.0.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:57940b9898d8b2565671bdf8d8aa67e6b91c3ba879594b58287134ee20b242c7"}, + {file = "pycapnp-2.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:68a8d2ae0c078df11a5dbf2d2b5b5d48c3893584fe9bb6fa9b88dd6eadf65dda"}, + {file = "pycapnp-2.0.0-cp38-cp38-win32.whl", hash = "sha256:81bbaf65c70dfbe8bc67ea715778bd764f4b1126fd905c0778ab6fd4e358f8f4"}, + {file = "pycapnp-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:34acb733c3a4a2a13607f0d905f776d4567d0c9697e5a9865035d38d3b4fb53b"}, + {file = "pycapnp-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:87931c9322679c733bd522a0d73704d804d6531a8a5258fd8ec65930bf89f2dd"}, + {file = "pycapnp-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7d08748e55f0f49c86f0a1e2c0f96c4e6880c1c8fd5df98c12c3eae557aa441"}, + {file = "pycapnp-2.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ffd25a63c92f0ef85bccabc7c2c13fe6da7eadf5525025d49206d967afb670d"}, + {file = "pycapnp-2.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8b9b9ea78df282f5ce63ccd435b83b83aabb931807066e84d967444ea005571"}, + {file = "pycapnp-2.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:829b071216ae51c2ea55fd41476adaf3044a303038399276bdba6144b58157c0"}, + {file = "pycapnp-2.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16b78b58969924dd499c7c24627cf392368a3354fcc900667fcabda2621d8250"}, + {file = "pycapnp-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a365a7dff8cb05145616aecc04ea73ed493cd630c10d7ef67d833347ba264b6"}, + {file = "pycapnp-2.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:82ebe35487dcb8061e2f80403d29c20686d1d539562162f1658d36ef43e38cfa"}, + {file = "pycapnp-2.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f496c59face3195f32a50df141a7a042cd3504bd4da123c5dced096eae76699d"}, + {file = "pycapnp-2.0.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f2ed0033b56b1836a8b99de11b939d93aa93d01f5d52650da65447f4f3c03e21"}, + {file = "pycapnp-2.0.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d66097f70b0a0bff5d9431b117d359a0abe46c73ac0eb3b64ad252cf7e99d780"}, + {file = "pycapnp-2.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:245942fe441d5e7a6511110ddea44ea91f0544385ef8afbef7155bec4aaa266f"}, + {file = "pycapnp-2.0.0-cp39-cp39-win32.whl", hash = "sha256:1bcbb55fc12ff41d5e456991e9d4d368ca26ba11c65c41bd384f4edf1307b6f7"}, + {file = "pycapnp-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:d4bee40372c02bcce647084faa6a831d9884a80033f77c7aacbaac697c4bbe46"}, + {file = "pycapnp-2.0.0.tar.gz", hash = "sha256:503ab9b7b16773590ee226f2460408972c6b1c2cb2d819037115b919bef682be"}, ] [[package]] name = "pycparser" -version = "2.21" +version = "2.22" description = "C parser in Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] @@ -3393,17 +3521,16 @@ pyrect = "*" [[package]] name = "pygments" -version = "2.17.2" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] [[package]] @@ -3466,22 +3593,23 @@ dev = ["coverage[toml] (>=7.2.2)"] [[package]] name = "pymonctl" -version = "0.7" +version = "0.92" description = "Cross-Platform toolkit to get info on and control monitors connected" optional = false python-versions = "*" files = [ - {file = "PyMonCtl-0.7-py3-none-any.whl", hash = "sha256:20c1f7f4cf46ff3349fa60d4e63c4ac300a2b907c0d984a1dd64d452b85ea781"}, + {file = "PyMonCtl-0.92-py3-none-any.whl", hash = "sha256:2495d8dab78f9a7dbce37e74543e60b8bd404a35c3108935697dda7768611b5a"}, ] [package.dependencies] +ewmhlib = {version = ">=0.1", markers = "sys_platform == \"linux\""} pyobjc = {version = ">=8.1", markers = "sys_platform == \"darwin\""} python-xlib = {version = ">=0.21", markers = "sys_platform == \"linux\""} pywin32 = {version = ">=302", markers = "sys_platform == \"win32\""} typing-extensions = ">=4.4.0" [package.extras] -dev = ["mypy (>=0.990)", "types-python-xlib (>=0.32)", "types-pywin32 (>=305.0.0.3)", "types-setuptools (>=65.5)"] +dev = ["mypy (>=0.990)", "pywinctl (>=0.3)", "types-python-xlib (>=0.32)", "types-pywin32 (>=305.0.0.3)", "types-setuptools (>=65.5)"] [[package]] name = "pymsgbox" @@ -3495,2812 +3623,2908 @@ files = [ [[package]] name = "pyobjc" -version = "10.1" +version = "10.3.1" description = "Python<->ObjC Interoperability Module" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-10.1-py3-none-any.whl", hash = "sha256:2687ff02217e7b2aba52c6b948bccea864a8f034af6c90528564d496b343c418"}, - {file = "pyobjc-10.1.tar.gz", hash = "sha256:f54baff4c40d53c3fb3812816ebd130d3186805936628cc1f212f95979af5b98"}, + {file = "pyobjc-10.3.1-py3-none-any.whl", hash = "sha256:dfa9ff44a353b9d0bf1245c25c94d1eee6d0cb26d9c5433bbcd67a265f7654ae"}, + {file = "pyobjc-10.3.1.tar.gz", hash = "sha256:476dd5c72394e4cfcdac6dfd756839011a0159353247f45e3e07cc0b3536c9d4"}, ] [package.dependencies] -pyobjc-core = "10.1" -pyobjc-framework-Accessibility = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-Accounts = {version = "10.1", markers = "platform_release >= \"12.0\""} -pyobjc-framework-AddressBook = "10.1" -pyobjc-framework-AdServices = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-AdSupport = {version = "10.1", markers = "platform_release >= \"18.0\""} -pyobjc-framework-AppleScriptKit = "10.1" -pyobjc-framework-AppleScriptObjC = {version = "10.1", markers = "platform_release >= \"10.0\""} -pyobjc-framework-ApplicationServices = "10.1" -pyobjc-framework-AppTrackingTransparency = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-AudioVideoBridging = {version = "10.1", markers = "platform_release >= \"12.0\""} -pyobjc-framework-AuthenticationServices = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-AutomaticAssessmentConfiguration = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-Automator = "10.1" -pyobjc-framework-AVFoundation = {version = "10.1", markers = "platform_release >= \"11.0\""} -pyobjc-framework-AVKit = {version = "10.1", markers = "platform_release >= \"13.0\""} -pyobjc-framework-AVRouting = {version = "10.1", markers = "platform_release >= \"22.0\""} -pyobjc-framework-BackgroundAssets = {version = "10.1", markers = "platform_release >= \"22.0\""} -pyobjc-framework-BusinessChat = {version = "10.1", markers = "platform_release >= \"18.0\""} -pyobjc-framework-CalendarStore = {version = "10.1", markers = "platform_release >= \"9.0\""} -pyobjc-framework-CallKit = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-CFNetwork = "10.1" -pyobjc-framework-Cinematic = {version = "10.1", markers = "platform_release >= \"23.0\""} -pyobjc-framework-ClassKit = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-CloudKit = {version = "10.1", markers = "platform_release >= \"14.0\""} -pyobjc-framework-Cocoa = "10.1" -pyobjc-framework-Collaboration = {version = "10.1", markers = "platform_release >= \"9.0\""} -pyobjc-framework-ColorSync = {version = "10.1", markers = "platform_release >= \"17.0\""} -pyobjc-framework-Contacts = {version = "10.1", markers = "platform_release >= \"15.0\""} -pyobjc-framework-ContactsUI = {version = "10.1", markers = "platform_release >= \"15.0\""} -pyobjc-framework-CoreAudio = "10.1" -pyobjc-framework-CoreAudioKit = "10.1" -pyobjc-framework-CoreBluetooth = {version = "10.1", markers = "platform_release >= \"14.0\""} -pyobjc-framework-CoreData = "10.1" -pyobjc-framework-CoreHaptics = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-CoreLocation = {version = "10.1", markers = "platform_release >= \"10.0\""} -pyobjc-framework-CoreMedia = {version = "10.1", markers = "platform_release >= \"11.0\""} -pyobjc-framework-CoreMediaIO = {version = "10.1", markers = "platform_release >= \"11.0\""} -pyobjc-framework-CoreMIDI = "10.1" -pyobjc-framework-CoreML = {version = "10.1", markers = "platform_release >= \"17.0\""} -pyobjc-framework-CoreMotion = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-CoreServices = "10.1" -pyobjc-framework-CoreSpotlight = {version = "10.1", markers = "platform_release >= \"17.0\""} -pyobjc-framework-CoreText = "10.1" -pyobjc-framework-CoreWLAN = {version = "10.1", markers = "platform_release >= \"10.0\""} -pyobjc-framework-CryptoTokenKit = {version = "10.1", markers = "platform_release >= \"14.0\""} -pyobjc-framework-DataDetection = {version = "10.1", markers = "platform_release >= \"21.0\""} -pyobjc-framework-DeviceCheck = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-DictionaryServices = {version = "10.1", markers = "platform_release >= \"9.0\""} -pyobjc-framework-DiscRecording = "10.1" -pyobjc-framework-DiscRecordingUI = "10.1" -pyobjc-framework-DiskArbitration = "10.1" -pyobjc-framework-DVDPlayback = "10.1" -pyobjc-framework-EventKit = {version = "10.1", markers = "platform_release >= \"12.0\""} -pyobjc-framework-ExceptionHandling = "10.1" -pyobjc-framework-ExecutionPolicy = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-ExtensionKit = {version = "10.1", markers = "platform_release >= \"22.0\""} -pyobjc-framework-ExternalAccessory = {version = "10.1", markers = "platform_release >= \"17.0\""} -pyobjc-framework-FileProvider = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-FileProviderUI = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-FinderSync = {version = "10.1", markers = "platform_release >= \"14.0\""} -pyobjc-framework-FSEvents = {version = "10.1", markers = "platform_release >= \"9.0\""} -pyobjc-framework-GameCenter = {version = "10.1", markers = "platform_release >= \"12.0\""} -pyobjc-framework-GameController = {version = "10.1", markers = "platform_release >= \"13.0\""} -pyobjc-framework-GameKit = {version = "10.1", markers = "platform_release >= \"12.0\""} -pyobjc-framework-GameplayKit = {version = "10.1", markers = "platform_release >= \"15.0\""} -pyobjc-framework-HealthKit = {version = "10.1", markers = "platform_release >= \"22.0\""} -pyobjc-framework-ImageCaptureCore = {version = "10.1", markers = "platform_release >= \"10.0\""} -pyobjc-framework-InputMethodKit = {version = "10.1", markers = "platform_release >= \"9.0\""} -pyobjc-framework-InstallerPlugins = "10.1" -pyobjc-framework-InstantMessage = {version = "10.1", markers = "platform_release >= \"9.0\""} -pyobjc-framework-Intents = {version = "10.1", markers = "platform_release >= \"16.0\""} -pyobjc-framework-IntentsUI = {version = "10.1", markers = "platform_release >= \"21.0\""} -pyobjc-framework-IOBluetooth = "10.1" -pyobjc-framework-IOBluetoothUI = "10.1" -pyobjc-framework-IOSurface = {version = "10.1", markers = "platform_release >= \"10.0\""} -pyobjc-framework-iTunesLibrary = {version = "10.1", markers = "platform_release >= \"10.0\""} -pyobjc-framework-KernelManagement = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-LatentSemanticMapping = "10.1" -pyobjc-framework-LaunchServices = "10.1" -pyobjc-framework-libdispatch = {version = "10.1", markers = "platform_release >= \"12.0\""} -pyobjc-framework-libxpc = {version = "10.1", markers = "platform_release >= \"12.0\""} -pyobjc-framework-LinkPresentation = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-LocalAuthentication = {version = "10.1", markers = "platform_release >= \"14.0\""} -pyobjc-framework-LocalAuthenticationEmbeddedUI = {version = "10.1", markers = "platform_release >= \"21.0\""} -pyobjc-framework-MailKit = {version = "10.1", markers = "platform_release >= \"21.0\""} -pyobjc-framework-MapKit = {version = "10.1", markers = "platform_release >= \"13.0\""} -pyobjc-framework-MediaAccessibility = {version = "10.1", markers = "platform_release >= \"13.0\""} -pyobjc-framework-MediaLibrary = {version = "10.1", markers = "platform_release >= \"13.0\""} -pyobjc-framework-MediaPlayer = {version = "10.1", markers = "platform_release >= \"16.0\""} -pyobjc-framework-MediaToolbox = {version = "10.1", markers = "platform_release >= \"13.0\""} -pyobjc-framework-Metal = {version = "10.1", markers = "platform_release >= \"15.0\""} -pyobjc-framework-MetalFX = {version = "10.1", markers = "platform_release >= \"22.0\""} -pyobjc-framework-MetalKit = {version = "10.1", markers = "platform_release >= \"15.0\""} -pyobjc-framework-MetalPerformanceShaders = {version = "10.1", markers = "platform_release >= \"17.0\""} -pyobjc-framework-MetalPerformanceShadersGraph = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-MetricKit = {version = "10.1", markers = "platform_release >= \"21.0\""} -pyobjc-framework-MLCompute = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-ModelIO = {version = "10.1", markers = "platform_release >= \"15.0\""} -pyobjc-framework-MultipeerConnectivity = {version = "10.1", markers = "platform_release >= \"14.0\""} -pyobjc-framework-NaturalLanguage = {version = "10.1", markers = "platform_release >= \"18.0\""} -pyobjc-framework-NetFS = {version = "10.1", markers = "platform_release >= \"10.0\""} -pyobjc-framework-Network = {version = "10.1", markers = "platform_release >= \"18.0\""} -pyobjc-framework-NetworkExtension = {version = "10.1", markers = "platform_release >= \"15.0\""} -pyobjc-framework-NotificationCenter = {version = "10.1", markers = "platform_release >= \"14.0\""} -pyobjc-framework-OpenDirectory = {version = "10.1", markers = "platform_release >= \"10.0\""} -pyobjc-framework-OSAKit = "10.1" -pyobjc-framework-OSLog = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-PassKit = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-PencilKit = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-PHASE = {version = "10.1", markers = "platform_release >= \"21.0\""} -pyobjc-framework-Photos = {version = "10.1", markers = "platform_release >= \"15.0\""} -pyobjc-framework-PhotosUI = {version = "10.1", markers = "platform_release >= \"15.0\""} -pyobjc-framework-PreferencePanes = "10.1" -pyobjc-framework-PubSub = {version = "10.1", markers = "platform_release >= \"9.0\" and platform_release < \"18.0\""} -pyobjc-framework-PushKit = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-Quartz = "10.1" -pyobjc-framework-QuickLookThumbnailing = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-ReplayKit = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-SafariServices = {version = "10.1", markers = "platform_release >= \"16.0\""} -pyobjc-framework-SafetyKit = {version = "10.1", markers = "platform_release >= \"22.0\""} -pyobjc-framework-SceneKit = {version = "10.1", markers = "platform_release >= \"11.0\""} -pyobjc-framework-ScreenCaptureKit = {version = "10.1", markers = "platform_release >= \"21.4\""} -pyobjc-framework-ScreenSaver = "10.1" -pyobjc-framework-ScreenTime = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-ScriptingBridge = {version = "10.1", markers = "platform_release >= \"9.0\""} -pyobjc-framework-SearchKit = "10.1" -pyobjc-framework-Security = "10.1" -pyobjc-framework-SecurityFoundation = "10.1" -pyobjc-framework-SecurityInterface = "10.1" -pyobjc-framework-SensitiveContentAnalysis = {version = "10.1", markers = "platform_release >= \"23.0\""} -pyobjc-framework-ServiceManagement = {version = "10.1", markers = "platform_release >= \"10.0\""} -pyobjc-framework-SharedWithYou = {version = "10.1", markers = "platform_release >= \"22.0\""} -pyobjc-framework-SharedWithYouCore = {version = "10.1", markers = "platform_release >= \"22.0\""} -pyobjc-framework-ShazamKit = {version = "10.1", markers = "platform_release >= \"21.0\""} -pyobjc-framework-Social = {version = "10.1", markers = "platform_release >= \"12.0\""} -pyobjc-framework-SoundAnalysis = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-Speech = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-SpriteKit = {version = "10.1", markers = "platform_release >= \"13.0\""} -pyobjc-framework-StoreKit = {version = "10.1", markers = "platform_release >= \"11.0\""} -pyobjc-framework-Symbols = {version = "10.1", markers = "platform_release >= \"23.0\""} -pyobjc-framework-SyncServices = "10.1" -pyobjc-framework-SystemConfiguration = "10.1" -pyobjc-framework-SystemExtensions = {version = "10.1", markers = "platform_release >= \"19.0\""} -pyobjc-framework-ThreadNetwork = {version = "10.1", markers = "platform_release >= \"22.0\""} -pyobjc-framework-UniformTypeIdentifiers = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-UserNotifications = {version = "10.1", markers = "platform_release >= \"18.0\""} -pyobjc-framework-UserNotificationsUI = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-VideoSubscriberAccount = {version = "10.1", markers = "platform_release >= \"18.0\""} -pyobjc-framework-VideoToolbox = {version = "10.1", markers = "platform_release >= \"12.0\""} -pyobjc-framework-Virtualization = {version = "10.1", markers = "platform_release >= \"20.0\""} -pyobjc-framework-Vision = {version = "10.1", markers = "platform_release >= \"17.0\""} -pyobjc-framework-WebKit = "10.1" +pyobjc-core = "10.3.1" +pyobjc-framework-Accessibility = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-Accounts = {version = "10.3.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-AddressBook = "10.3.1" +pyobjc-framework-AdServices = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-AdSupport = {version = "10.3.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-AppleScriptKit = "10.3.1" +pyobjc-framework-AppleScriptObjC = {version = "10.3.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-ApplicationServices = "10.3.1" +pyobjc-framework-AppTrackingTransparency = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-AudioVideoBridging = {version = "10.3.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-AuthenticationServices = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-AutomaticAssessmentConfiguration = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Automator = "10.3.1" +pyobjc-framework-AVFoundation = {version = "10.3.1", markers = "platform_release >= \"11.0\""} +pyobjc-framework-AVKit = {version = "10.3.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-AVRouting = {version = "10.3.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-BackgroundAssets = {version = "10.3.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-BrowserEngineKit = {version = "10.3.1", markers = "platform_release >= \"23.4\""} +pyobjc-framework-BusinessChat = {version = "10.3.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-CalendarStore = {version = "10.3.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-CallKit = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-CFNetwork = "10.3.1" +pyobjc-framework-Cinematic = {version = "10.3.1", markers = "platform_release >= \"23.0\""} +pyobjc-framework-ClassKit = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-CloudKit = {version = "10.3.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-Cocoa = "10.3.1" +pyobjc-framework-Collaboration = {version = "10.3.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-ColorSync = {version = "10.3.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-Contacts = {version = "10.3.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-ContactsUI = {version = "10.3.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-CoreAudio = "10.3.1" +pyobjc-framework-CoreAudioKit = "10.3.1" +pyobjc-framework-CoreBluetooth = {version = "10.3.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-CoreData = "10.3.1" +pyobjc-framework-CoreHaptics = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-CoreLocation = {version = "10.3.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-CoreMedia = {version = "10.3.1", markers = "platform_release >= \"11.0\""} +pyobjc-framework-CoreMediaIO = {version = "10.3.1", markers = "platform_release >= \"11.0\""} +pyobjc-framework-CoreMIDI = "10.3.1" +pyobjc-framework-CoreML = {version = "10.3.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-CoreMotion = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-CoreServices = "10.3.1" +pyobjc-framework-CoreSpotlight = {version = "10.3.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-CoreText = "10.3.1" +pyobjc-framework-CoreWLAN = {version = "10.3.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-CryptoTokenKit = {version = "10.3.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-DataDetection = {version = "10.3.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-DeviceCheck = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-DictionaryServices = {version = "10.3.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-DiscRecording = "10.3.1" +pyobjc-framework-DiscRecordingUI = "10.3.1" +pyobjc-framework-DiskArbitration = "10.3.1" +pyobjc-framework-DVDPlayback = "10.3.1" +pyobjc-framework-EventKit = {version = "10.3.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-ExceptionHandling = "10.3.1" +pyobjc-framework-ExecutionPolicy = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ExtensionKit = {version = "10.3.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ExternalAccessory = {version = "10.3.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-FileProvider = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-FileProviderUI = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-FinderSync = {version = "10.3.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-FSEvents = {version = "10.3.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-GameCenter = {version = "10.3.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-GameController = {version = "10.3.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-GameKit = {version = "10.3.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-GameplayKit = {version = "10.3.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-HealthKit = {version = "10.3.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ImageCaptureCore = {version = "10.3.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-InputMethodKit = {version = "10.3.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-InstallerPlugins = "10.3.1" +pyobjc-framework-InstantMessage = {version = "10.3.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-Intents = {version = "10.3.1", markers = "platform_release >= \"16.0\""} +pyobjc-framework-IntentsUI = {version = "10.3.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-IOBluetooth = "10.3.1" +pyobjc-framework-IOBluetoothUI = "10.3.1" +pyobjc-framework-IOSurface = {version = "10.3.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-iTunesLibrary = {version = "10.3.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-KernelManagement = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-LatentSemanticMapping = "10.3.1" +pyobjc-framework-LaunchServices = "10.3.1" +pyobjc-framework-libdispatch = {version = "10.3.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-libxpc = {version = "10.3.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-LinkPresentation = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-LocalAuthentication = {version = "10.3.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-LocalAuthenticationEmbeddedUI = {version = "10.3.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MailKit = {version = "10.3.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MapKit = {version = "10.3.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaAccessibility = {version = "10.3.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaLibrary = {version = "10.3.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaPlayer = {version = "10.3.1", markers = "platform_release >= \"16.0\""} +pyobjc-framework-MediaToolbox = {version = "10.3.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-Metal = {version = "10.3.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MetalFX = {version = "10.3.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-MetalKit = {version = "10.3.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MetalPerformanceShaders = {version = "10.3.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-MetalPerformanceShadersGraph = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-MetricKit = {version = "10.3.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MLCompute = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-ModelIO = {version = "10.3.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MultipeerConnectivity = {version = "10.3.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-NaturalLanguage = {version = "10.3.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-NetFS = {version = "10.3.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-Network = {version = "10.3.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-NetworkExtension = {version = "10.3.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-NotificationCenter = {version = "10.3.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-OpenDirectory = {version = "10.3.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-OSAKit = "10.3.1" +pyobjc-framework-OSLog = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-PassKit = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-PencilKit = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-PHASE = {version = "10.3.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-Photos = {version = "10.3.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-PhotosUI = {version = "10.3.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-PreferencePanes = "10.3.1" +pyobjc-framework-PubSub = {version = "10.3.1", markers = "platform_release >= \"9.0\" and platform_release < \"18.0\""} +pyobjc-framework-PushKit = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Quartz = "10.3.1" +pyobjc-framework-QuickLookThumbnailing = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ReplayKit = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-SafariServices = {version = "10.3.1", markers = "platform_release >= \"16.0\""} +pyobjc-framework-SafetyKit = {version = "10.3.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-SceneKit = {version = "10.3.1", markers = "platform_release >= \"11.0\""} +pyobjc-framework-ScreenCaptureKit = {version = "10.3.1", markers = "platform_release >= \"21.4\""} +pyobjc-framework-ScreenSaver = "10.3.1" +pyobjc-framework-ScreenTime = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-ScriptingBridge = {version = "10.3.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-SearchKit = "10.3.1" +pyobjc-framework-Security = "10.3.1" +pyobjc-framework-SecurityFoundation = "10.3.1" +pyobjc-framework-SecurityInterface = "10.3.1" +pyobjc-framework-SensitiveContentAnalysis = {version = "10.3.1", markers = "platform_release >= \"23.0\""} +pyobjc-framework-ServiceManagement = {version = "10.3.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-SharedWithYou = {version = "10.3.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-SharedWithYouCore = {version = "10.3.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ShazamKit = {version = "10.3.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-Social = {version = "10.3.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-SoundAnalysis = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Speech = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-SpriteKit = {version = "10.3.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-StoreKit = {version = "10.3.1", markers = "platform_release >= \"11.0\""} +pyobjc-framework-Symbols = {version = "10.3.1", markers = "platform_release >= \"23.0\""} +pyobjc-framework-SyncServices = "10.3.1" +pyobjc-framework-SystemConfiguration = "10.3.1" +pyobjc-framework-SystemExtensions = {version = "10.3.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ThreadNetwork = {version = "10.3.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-UniformTypeIdentifiers = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-UserNotifications = {version = "10.3.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-UserNotificationsUI = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-VideoSubscriberAccount = {version = "10.3.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-VideoToolbox = {version = "10.3.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-Virtualization = {version = "10.3.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-Vision = {version = "10.3.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-WebKit = "10.3.1" [package.extras] -allbindings = ["pyobjc-core (==10.1)", "pyobjc-framework-AVFoundation (==10.1)", "pyobjc-framework-AVKit (==10.1)", "pyobjc-framework-AVRouting (==10.1)", "pyobjc-framework-Accessibility (==10.1)", "pyobjc-framework-Accounts (==10.1)", "pyobjc-framework-AdServices (==10.1)", "pyobjc-framework-AdSupport (==10.1)", "pyobjc-framework-AddressBook (==10.1)", "pyobjc-framework-AppTrackingTransparency (==10.1)", "pyobjc-framework-AppleScriptKit (==10.1)", "pyobjc-framework-AppleScriptObjC (==10.1)", "pyobjc-framework-ApplicationServices (==10.1)", "pyobjc-framework-AudioVideoBridging (==10.1)", "pyobjc-framework-AuthenticationServices (==10.1)", "pyobjc-framework-AutomaticAssessmentConfiguration (==10.1)", "pyobjc-framework-Automator (==10.1)", "pyobjc-framework-BackgroundAssets (==10.1)", "pyobjc-framework-BusinessChat (==10.1)", "pyobjc-framework-CFNetwork (==10.1)", "pyobjc-framework-CalendarStore (==10.1)", "pyobjc-framework-CallKit (==10.1)", "pyobjc-framework-Cinematic (==10.1)", "pyobjc-framework-ClassKit (==10.1)", "pyobjc-framework-CloudKit (==10.1)", "pyobjc-framework-Cocoa (==10.1)", "pyobjc-framework-Collaboration (==10.1)", "pyobjc-framework-ColorSync (==10.1)", "pyobjc-framework-Contacts (==10.1)", "pyobjc-framework-ContactsUI (==10.1)", "pyobjc-framework-CoreAudio (==10.1)", "pyobjc-framework-CoreAudioKit (==10.1)", "pyobjc-framework-CoreBluetooth (==10.1)", "pyobjc-framework-CoreData (==10.1)", "pyobjc-framework-CoreHaptics (==10.1)", "pyobjc-framework-CoreLocation (==10.1)", "pyobjc-framework-CoreMIDI (==10.1)", "pyobjc-framework-CoreML (==10.1)", "pyobjc-framework-CoreMedia (==10.1)", "pyobjc-framework-CoreMediaIO (==10.1)", "pyobjc-framework-CoreMotion (==10.1)", "pyobjc-framework-CoreServices (==10.1)", "pyobjc-framework-CoreSpotlight (==10.1)", "pyobjc-framework-CoreText (==10.1)", "pyobjc-framework-CoreWLAN (==10.1)", "pyobjc-framework-CryptoTokenKit (==10.1)", "pyobjc-framework-DVDPlayback (==10.1)", "pyobjc-framework-DataDetection (==10.1)", "pyobjc-framework-DeviceCheck (==10.1)", "pyobjc-framework-DictionaryServices (==10.1)", "pyobjc-framework-DiscRecording (==10.1)", "pyobjc-framework-DiscRecordingUI (==10.1)", "pyobjc-framework-DiskArbitration (==10.1)", "pyobjc-framework-EventKit (==10.1)", "pyobjc-framework-ExceptionHandling (==10.1)", "pyobjc-framework-ExecutionPolicy (==10.1)", "pyobjc-framework-ExtensionKit (==10.1)", "pyobjc-framework-ExternalAccessory (==10.1)", "pyobjc-framework-FSEvents (==10.1)", "pyobjc-framework-FileProvider (==10.1)", "pyobjc-framework-FileProviderUI (==10.1)", "pyobjc-framework-FinderSync (==10.1)", "pyobjc-framework-GameCenter (==10.1)", "pyobjc-framework-GameController (==10.1)", "pyobjc-framework-GameKit (==10.1)", "pyobjc-framework-GameplayKit (==10.1)", "pyobjc-framework-HealthKit (==10.1)", "pyobjc-framework-IOBluetooth (==10.1)", "pyobjc-framework-IOBluetoothUI (==10.1)", "pyobjc-framework-IOSurface (==10.1)", "pyobjc-framework-ImageCaptureCore (==10.1)", "pyobjc-framework-InputMethodKit (==10.1)", "pyobjc-framework-InstallerPlugins (==10.1)", "pyobjc-framework-InstantMessage (==10.1)", "pyobjc-framework-Intents (==10.1)", "pyobjc-framework-IntentsUI (==10.1)", "pyobjc-framework-KernelManagement (==10.1)", "pyobjc-framework-LatentSemanticMapping (==10.1)", "pyobjc-framework-LaunchServices (==10.1)", "pyobjc-framework-LinkPresentation (==10.1)", "pyobjc-framework-LocalAuthentication (==10.1)", "pyobjc-framework-LocalAuthenticationEmbeddedUI (==10.1)", "pyobjc-framework-MLCompute (==10.1)", "pyobjc-framework-MailKit (==10.1)", "pyobjc-framework-MapKit (==10.1)", "pyobjc-framework-MediaAccessibility (==10.1)", "pyobjc-framework-MediaLibrary (==10.1)", "pyobjc-framework-MediaPlayer (==10.1)", "pyobjc-framework-MediaToolbox (==10.1)", "pyobjc-framework-Metal (==10.1)", "pyobjc-framework-MetalFX (==10.1)", "pyobjc-framework-MetalKit (==10.1)", "pyobjc-framework-MetalPerformanceShaders (==10.1)", "pyobjc-framework-MetalPerformanceShadersGraph (==10.1)", "pyobjc-framework-MetricKit (==10.1)", "pyobjc-framework-ModelIO (==10.1)", "pyobjc-framework-MultipeerConnectivity (==10.1)", "pyobjc-framework-NaturalLanguage (==10.1)", "pyobjc-framework-NetFS (==10.1)", "pyobjc-framework-Network (==10.1)", "pyobjc-framework-NetworkExtension (==10.1)", "pyobjc-framework-NotificationCenter (==10.1)", "pyobjc-framework-OSAKit (==10.1)", "pyobjc-framework-OSLog (==10.1)", "pyobjc-framework-OpenDirectory (==10.1)", "pyobjc-framework-PHASE (==10.1)", "pyobjc-framework-PassKit (==10.1)", "pyobjc-framework-PencilKit (==10.1)", "pyobjc-framework-Photos (==10.1)", "pyobjc-framework-PhotosUI (==10.1)", "pyobjc-framework-PreferencePanes (==10.1)", "pyobjc-framework-PubSub (==10.1)", "pyobjc-framework-PushKit (==10.1)", "pyobjc-framework-Quartz (==10.1)", "pyobjc-framework-QuickLookThumbnailing (==10.1)", "pyobjc-framework-ReplayKit (==10.1)", "pyobjc-framework-SafariServices (==10.1)", "pyobjc-framework-SafetyKit (==10.1)", "pyobjc-framework-SceneKit (==10.1)", "pyobjc-framework-ScreenCaptureKit (==10.1)", "pyobjc-framework-ScreenSaver (==10.1)", "pyobjc-framework-ScreenTime (==10.1)", "pyobjc-framework-ScriptingBridge (==10.1)", "pyobjc-framework-SearchKit (==10.1)", "pyobjc-framework-Security (==10.1)", "pyobjc-framework-SecurityFoundation (==10.1)", "pyobjc-framework-SecurityInterface (==10.1)", "pyobjc-framework-SensitiveContentAnalysis (==10.1)", "pyobjc-framework-ServiceManagement (==10.1)", "pyobjc-framework-SharedWithYou (==10.1)", "pyobjc-framework-SharedWithYouCore (==10.1)", "pyobjc-framework-ShazamKit (==10.1)", "pyobjc-framework-Social (==10.1)", "pyobjc-framework-SoundAnalysis (==10.1)", "pyobjc-framework-Speech (==10.1)", "pyobjc-framework-SpriteKit (==10.1)", "pyobjc-framework-StoreKit (==10.1)", "pyobjc-framework-Symbols (==10.1)", "pyobjc-framework-SyncServices (==10.1)", "pyobjc-framework-SystemConfiguration (==10.1)", "pyobjc-framework-SystemExtensions (==10.1)", "pyobjc-framework-ThreadNetwork (==10.1)", "pyobjc-framework-UniformTypeIdentifiers (==10.1)", "pyobjc-framework-UserNotifications (==10.1)", "pyobjc-framework-UserNotificationsUI (==10.1)", "pyobjc-framework-VideoSubscriberAccount (==10.1)", "pyobjc-framework-VideoToolbox (==10.1)", "pyobjc-framework-Virtualization (==10.1)", "pyobjc-framework-Vision (==10.1)", "pyobjc-framework-WebKit (==10.1)", "pyobjc-framework-iTunesLibrary (==10.1)", "pyobjc-framework-libdispatch (==10.1)", "pyobjc-framework-libxpc (==10.1)"] +allbindings = ["pyobjc-core (==10.3.1)", "pyobjc-framework-AVFoundation (==10.3.1)", "pyobjc-framework-AVKit (==10.3.1)", "pyobjc-framework-AVRouting (==10.3.1)", "pyobjc-framework-Accessibility (==10.3.1)", "pyobjc-framework-Accounts (==10.3.1)", "pyobjc-framework-AdServices (==10.3.1)", "pyobjc-framework-AdSupport (==10.3.1)", "pyobjc-framework-AddressBook (==10.3.1)", "pyobjc-framework-AppTrackingTransparency (==10.3.1)", "pyobjc-framework-AppleScriptKit (==10.3.1)", "pyobjc-framework-AppleScriptObjC (==10.3.1)", "pyobjc-framework-ApplicationServices (==10.3.1)", "pyobjc-framework-AudioVideoBridging (==10.3.1)", "pyobjc-framework-AuthenticationServices (==10.3.1)", "pyobjc-framework-AutomaticAssessmentConfiguration (==10.3.1)", "pyobjc-framework-Automator (==10.3.1)", "pyobjc-framework-BackgroundAssets (==10.3.1)", "pyobjc-framework-BrowserEngineKit (==10.3.1)", "pyobjc-framework-BusinessChat (==10.3.1)", "pyobjc-framework-CFNetwork (==10.3.1)", "pyobjc-framework-CalendarStore (==10.3.1)", "pyobjc-framework-CallKit (==10.3.1)", "pyobjc-framework-Cinematic (==10.3.1)", "pyobjc-framework-ClassKit (==10.3.1)", "pyobjc-framework-CloudKit (==10.3.1)", "pyobjc-framework-Cocoa (==10.3.1)", "pyobjc-framework-Collaboration (==10.3.1)", "pyobjc-framework-ColorSync (==10.3.1)", "pyobjc-framework-Contacts (==10.3.1)", "pyobjc-framework-ContactsUI (==10.3.1)", "pyobjc-framework-CoreAudio (==10.3.1)", "pyobjc-framework-CoreAudioKit (==10.3.1)", "pyobjc-framework-CoreBluetooth (==10.3.1)", "pyobjc-framework-CoreData (==10.3.1)", "pyobjc-framework-CoreHaptics (==10.3.1)", "pyobjc-framework-CoreLocation (==10.3.1)", "pyobjc-framework-CoreMIDI (==10.3.1)", "pyobjc-framework-CoreML (==10.3.1)", "pyobjc-framework-CoreMedia (==10.3.1)", "pyobjc-framework-CoreMediaIO (==10.3.1)", "pyobjc-framework-CoreMotion (==10.3.1)", "pyobjc-framework-CoreServices (==10.3.1)", "pyobjc-framework-CoreSpotlight (==10.3.1)", "pyobjc-framework-CoreText (==10.3.1)", "pyobjc-framework-CoreWLAN (==10.3.1)", "pyobjc-framework-CryptoTokenKit (==10.3.1)", "pyobjc-framework-DVDPlayback (==10.3.1)", "pyobjc-framework-DataDetection (==10.3.1)", "pyobjc-framework-DeviceCheck (==10.3.1)", "pyobjc-framework-DictionaryServices (==10.3.1)", "pyobjc-framework-DiscRecording (==10.3.1)", "pyobjc-framework-DiscRecordingUI (==10.3.1)", "pyobjc-framework-DiskArbitration (==10.3.1)", "pyobjc-framework-EventKit (==10.3.1)", "pyobjc-framework-ExceptionHandling (==10.3.1)", "pyobjc-framework-ExecutionPolicy (==10.3.1)", "pyobjc-framework-ExtensionKit (==10.3.1)", "pyobjc-framework-ExternalAccessory (==10.3.1)", "pyobjc-framework-FSEvents (==10.3.1)", "pyobjc-framework-FileProvider (==10.3.1)", "pyobjc-framework-FileProviderUI (==10.3.1)", "pyobjc-framework-FinderSync (==10.3.1)", "pyobjc-framework-GameCenter (==10.3.1)", "pyobjc-framework-GameController (==10.3.1)", "pyobjc-framework-GameKit (==10.3.1)", "pyobjc-framework-GameplayKit (==10.3.1)", "pyobjc-framework-HealthKit (==10.3.1)", "pyobjc-framework-IOBluetooth (==10.3.1)", "pyobjc-framework-IOBluetoothUI (==10.3.1)", "pyobjc-framework-IOSurface (==10.3.1)", "pyobjc-framework-ImageCaptureCore (==10.3.1)", "pyobjc-framework-InputMethodKit (==10.3.1)", "pyobjc-framework-InstallerPlugins (==10.3.1)", "pyobjc-framework-InstantMessage (==10.3.1)", "pyobjc-framework-Intents (==10.3.1)", "pyobjc-framework-IntentsUI (==10.3.1)", "pyobjc-framework-KernelManagement (==10.3.1)", "pyobjc-framework-LatentSemanticMapping (==10.3.1)", "pyobjc-framework-LaunchServices (==10.3.1)", "pyobjc-framework-LinkPresentation (==10.3.1)", "pyobjc-framework-LocalAuthentication (==10.3.1)", "pyobjc-framework-LocalAuthenticationEmbeddedUI (==10.3.1)", "pyobjc-framework-MLCompute (==10.3.1)", "pyobjc-framework-MailKit (==10.3.1)", "pyobjc-framework-MapKit (==10.3.1)", "pyobjc-framework-MediaAccessibility (==10.3.1)", "pyobjc-framework-MediaLibrary (==10.3.1)", "pyobjc-framework-MediaPlayer (==10.3.1)", "pyobjc-framework-MediaToolbox (==10.3.1)", "pyobjc-framework-Metal (==10.3.1)", "pyobjc-framework-MetalFX (==10.3.1)", "pyobjc-framework-MetalKit (==10.3.1)", "pyobjc-framework-MetalPerformanceShaders (==10.3.1)", "pyobjc-framework-MetalPerformanceShadersGraph (==10.3.1)", "pyobjc-framework-MetricKit (==10.3.1)", "pyobjc-framework-ModelIO (==10.3.1)", "pyobjc-framework-MultipeerConnectivity (==10.3.1)", "pyobjc-framework-NaturalLanguage (==10.3.1)", "pyobjc-framework-NetFS (==10.3.1)", "pyobjc-framework-Network (==10.3.1)", "pyobjc-framework-NetworkExtension (==10.3.1)", "pyobjc-framework-NotificationCenter (==10.3.1)", "pyobjc-framework-OSAKit (==10.3.1)", "pyobjc-framework-OSLog (==10.3.1)", "pyobjc-framework-OpenDirectory (==10.3.1)", "pyobjc-framework-PHASE (==10.3.1)", "pyobjc-framework-PassKit (==10.3.1)", "pyobjc-framework-PencilKit (==10.3.1)", "pyobjc-framework-Photos (==10.3.1)", "pyobjc-framework-PhotosUI (==10.3.1)", "pyobjc-framework-PreferencePanes (==10.3.1)", "pyobjc-framework-PubSub (==10.3.1)", "pyobjc-framework-PushKit (==10.3.1)", "pyobjc-framework-Quartz (==10.3.1)", "pyobjc-framework-QuickLookThumbnailing (==10.3.1)", "pyobjc-framework-ReplayKit (==10.3.1)", "pyobjc-framework-SafariServices (==10.3.1)", "pyobjc-framework-SafetyKit (==10.3.1)", "pyobjc-framework-SceneKit (==10.3.1)", "pyobjc-framework-ScreenCaptureKit (==10.3.1)", "pyobjc-framework-ScreenSaver (==10.3.1)", "pyobjc-framework-ScreenTime (==10.3.1)", "pyobjc-framework-ScriptingBridge (==10.3.1)", "pyobjc-framework-SearchKit (==10.3.1)", "pyobjc-framework-Security (==10.3.1)", "pyobjc-framework-SecurityFoundation (==10.3.1)", "pyobjc-framework-SecurityInterface (==10.3.1)", "pyobjc-framework-SensitiveContentAnalysis (==10.3.1)", "pyobjc-framework-ServiceManagement (==10.3.1)", "pyobjc-framework-SharedWithYou (==10.3.1)", "pyobjc-framework-SharedWithYouCore (==10.3.1)", "pyobjc-framework-ShazamKit (==10.3.1)", "pyobjc-framework-Social (==10.3.1)", "pyobjc-framework-SoundAnalysis (==10.3.1)", "pyobjc-framework-Speech (==10.3.1)", "pyobjc-framework-SpriteKit (==10.3.1)", "pyobjc-framework-StoreKit (==10.3.1)", "pyobjc-framework-Symbols (==10.3.1)", "pyobjc-framework-SyncServices (==10.3.1)", "pyobjc-framework-SystemConfiguration (==10.3.1)", "pyobjc-framework-SystemExtensions (==10.3.1)", "pyobjc-framework-ThreadNetwork (==10.3.1)", "pyobjc-framework-UniformTypeIdentifiers (==10.3.1)", "pyobjc-framework-UserNotifications (==10.3.1)", "pyobjc-framework-UserNotificationsUI (==10.3.1)", "pyobjc-framework-VideoSubscriberAccount (==10.3.1)", "pyobjc-framework-VideoToolbox (==10.3.1)", "pyobjc-framework-Virtualization (==10.3.1)", "pyobjc-framework-Vision (==10.3.1)", "pyobjc-framework-WebKit (==10.3.1)", "pyobjc-framework-iTunesLibrary (==10.3.1)", "pyobjc-framework-libdispatch (==10.3.1)", "pyobjc-framework-libxpc (==10.3.1)"] [[package]] name = "pyobjc-core" -version = "10.1" +version = "10.3.1" description = "Python<->ObjC Interoperability Module" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-core-10.1.tar.gz", hash = "sha256:1844f1c8e282839e6fdcb9a9722396c1c12fb1e9331eb68828a26f28a3b2b2b1"}, - {file = "pyobjc_core-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2a72a88222539ad07b5c8be411edc52ff9147d7cef311a2c849869d7bb9603fd"}, - {file = "pyobjc_core-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fe1b9987b7b0437685fb529832876c2a8463500114960d4e76bb8ae96b6bf208"}, - {file = "pyobjc_core-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9f628779c345d3abd0e20048fb0e256d894c22254577a81a6dcfdb92c3647682"}, - {file = "pyobjc_core-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25a9e5a2de19238787d24cfa7def6b7fbb94bbe89c0e3109f71c1cb108e8ab44"}, - {file = "pyobjc_core-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2d43205d3a784aa87055b84c0ec0dfa76498e5f18d1ad16bdc58a3dcf5a7d5d0"}, - {file = "pyobjc_core-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0aa9799b5996a893944999a2f1afcf1de119cab3551c169ad9f54d12e1d38c99"}, + {file = "pyobjc_core-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ea46d2cda17921e417085ac6286d43ae448113158afcf39e0abe484c58fb3d78"}, + {file = "pyobjc_core-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:899d3c84d2933d292c808f385dc881a140cf08632907845043a333a9d7c899f9"}, + {file = "pyobjc_core-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6ff5823d13d0a534cdc17fa4ad47cf5bee4846ce0fd27fc40012e12b46db571b"}, + {file = "pyobjc_core-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2581e8e68885bcb0e11ec619e81ef28e08ee3fac4de20d8cc83bc5af5bcf4a90"}, + {file = "pyobjc_core-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ea98d4c2ec39ca29e62e0327db21418696161fb138ee6278daf2acbedf7ce504"}, + {file = "pyobjc_core-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:4c179c26ee2123d0aabffb9dbc60324b62b6f8614fb2c2328b09386ef59ef6d8"}, + {file = "pyobjc_core-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cb901fce65c9be420c40d8a6ee6fff5ff27c6945f44fd7191989b982baa66dea"}, + {file = "pyobjc_core-10.3.1.tar.gz", hash = "sha256:b204a80ccc070f9ab3f8af423a3a25a6fd787e228508d00c4c30f8ac538ba720"}, ] [[package]] name = "pyobjc-framework-accessibility" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Accessibility on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Accessibility-10.1.tar.gz", hash = "sha256:70b812cf2b04b57a520c3fde9df4184c2783795fb355b416a8058114e52ad24a"}, - {file = "pyobjc_framework_Accessibility-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ef8524f2b67240fb3c3f7928f2d73e9050a8b80e18db8336e7ba4d4ba1d368df"}, - {file = "pyobjc_framework_Accessibility-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d351d7799b197524a200c54bebe450e87f9c52812f6162811b7e84823e8946df"}, - {file = "pyobjc_framework_Accessibility-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:1dc4e0acedaa0232103714dd2daa3244a628426bee6e933078c89e8eb86b7961"}, + {file = "pyobjc_framework_Accessibility-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:4a1b10d2098b5e3887d4e52b13c2d7619f248ceeaa4e78bb21c51c25c7d391c3"}, + {file = "pyobjc_framework_Accessibility-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4926eeef40d750215f0787d2124407a4c65bc03407e402ea47901b713e8765e5"}, + {file = "pyobjc_framework_Accessibility-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b5b9d7373d1a8a06f57baca2f67279c3b0c61ecfb17fa6da964e0e275e7d18ed"}, + {file = "pyobjc_framework_Accessibility-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:94dbc5f223e6485820c14e7dee99d8f4d5cbf0600353033822dcab7ec4bc998e"}, + {file = "pyobjc_framework_accessibility-10.3.1.tar.gz", hash = "sha256:c973306417441e6bed5f9be6154e6399aa7f38fa9b6bcf3368fa42d92ef3030b"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-accounts" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Accounts on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Accounts-10.1.tar.gz", hash = "sha256:cb436af5b60f1d6a8a59f94d84ea6decba663598d5624fce29a0babf6fad0a89"}, - {file = "pyobjc_framework_Accounts-10.1-py2.py3-none-any.whl", hash = "sha256:30da31a76f2cfd0a4021eff4d4ff69e0a70b2f293290372f5909ae267d15a010"}, + {file = "pyobjc_framework_Accounts-10.3.1-py2.py3-none-any.whl", hash = "sha256:451488f91263afd23233287f223ba00c0ee5c93d64cd10e133d72bc6a0fc48aa"}, + {file = "pyobjc_framework_accounts-10.3.1.tar.gz", hash = "sha256:3d55738e7b3290af8cd4993fd2b670242a952deb995a69911be2a1be4c509a86"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-addressbook" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AddressBook on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AddressBook-10.1.tar.gz", hash = "sha256:9b8e01da07703990f0e745945b01cc75c59ade41913edbd6824194e21522efff"}, - {file = "pyobjc_framework_AddressBook-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2c6cb2278161ed55fba8b47515ff777a95265e484c51ad7a1c952747d8a411ee"}, - {file = "pyobjc_framework_AddressBook-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:10236b9112c8e5d83526804ca734a5f176bba435c2451c4b43c1247e76d9f73d"}, - {file = "pyobjc_framework_AddressBook-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e4305cf6366fa2e01040f490360283f572103be0a45d190774869915c2707c54"}, + {file = "pyobjc_framework_AddressBook-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:02ab8cb2300d55beaddee4f113a5c4a78ef737eda6c704678487529e391062e2"}, + {file = "pyobjc_framework_AddressBook-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:1cfa893c21920f5420f3e57da314315e92c855a83f0718482dc33bdb859b9f24"}, + {file = "pyobjc_framework_AddressBook-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7aa89a352f1729de1cb4d7841906487d9db752c2802af5695596b1cf5290acfb"}, + {file = "pyobjc_framework_AddressBook-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f25864847b4a81289dd08e7052455699ee011a4df98f4da0b07f46523c212592"}, + {file = "pyobjc_framework_addressbook-10.3.1.tar.gz", hash = "sha256:cde99b855c39b56ca52479b0a1e2daa3ef5de12cebfe780c3c802a5f59a484cc"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-adservices" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AdServices on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AdServices-10.1.tar.gz", hash = "sha256:54d2dd3084374213e31760bae9df1e6e9da3b3f1cc04787dae3ad53f8fc12f69"}, - {file = "pyobjc_framework_AdServices-10.1-py2.py3-none-any.whl", hash = "sha256:79ec6eb744635b72ffd0bdd5e55cb5ec57603633716861bbf40b236d8dba0dfd"}, + {file = "pyobjc_framework_AdServices-10.3.1-py2.py3-none-any.whl", hash = "sha256:c839c4267ad8443393e4d138396026764ee43776164da8a8ed9ac248b7d9c0d9"}, + {file = "pyobjc_framework_adservices-10.3.1.tar.gz", hash = "sha256:28123eb111d023f708e1d86f5f3f76bd4f6bb0d932466863f84b3e322b11537a"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-adsupport" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AdSupport on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AdSupport-10.1.tar.gz", hash = "sha256:df6b2d1cc1202905dcf6bcdbf35121acc45c346a57b1048f5f4d1ea15bc29c9c"}, - {file = "pyobjc_framework_AdSupport-10.1-py2.py3-none-any.whl", hash = "sha256:d61f2e44f6c2ed5c33b6520754ef8ea22470f8ac3154912aa44bee4fb792255c"}, + {file = "pyobjc_framework_AdSupport-10.3.1-py2.py3-none-any.whl", hash = "sha256:0e403ec206ada472b2c0b129ed656342a97c20110ca8398ab907100516b0e48c"}, + {file = "pyobjc_framework_adsupport-10.3.1.tar.gz", hash = "sha256:ba85a00cf20c42501d8083092f7ca0fcd1e616b1725e6512e75bcb60a6d58528"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-applescriptkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AppleScriptKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AppleScriptKit-10.1.tar.gz", hash = "sha256:e41cd0037cbe0af4ffecc42339d1b6255f2539dfb6dedf4f2ae00ac1a260eecf"}, - {file = "pyobjc_framework_AppleScriptKit-10.1-py2.py3-none-any.whl", hash = "sha256:b88bc8ae9e000d382c3e1d72b3c4f39499323fbe88cc84af259925448c187387"}, + {file = "pyobjc_framework_AppleScriptKit-10.3.1-py2.py3-none-any.whl", hash = "sha256:97ce878ff334b6853405a62e164debb9e6695110e64db5ed596008c0fde84970"}, + {file = "pyobjc_framework_applescriptkit-10.3.1.tar.gz", hash = "sha256:add2e63598b699666bcf00ac59f6f1046266df1665bec71b142cd21b89037064"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-applescriptobjc" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AppleScriptObjC on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AppleScriptObjC-10.1.tar.gz", hash = "sha256:cfcec31b25a4c201188936347697ff3eb1f79885a43af26559a572391c50cdf9"}, - {file = "pyobjc_framework_AppleScriptObjC-10.1-py2.py3-none-any.whl", hash = "sha256:500ed0e39bf2a4f2413d8d6dc398bb58f233ca3670f6946aa5c6d14d1b563465"}, + {file = "pyobjc_framework_AppleScriptObjC-10.3.1-py2.py3-none-any.whl", hash = "sha256:2d64c74a4af48656bb407eb177fe5f1d3c0f7bd9c578e5583dffde8e3d55f5df"}, + {file = "pyobjc_framework_applescriptobjc-10.3.1.tar.gz", hash = "sha256:a87101d86b08e06e2c0e51630ac76d4c70f01cf1ed7af281f3138e63146e279b"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-applicationservices" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ApplicationServices on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ApplicationServices-10.1.tar.gz", hash = "sha256:bb780eabadad0fbf36a128041dccfd71e30bbeb6b110852d37fd5c98f4a2ec04"}, - {file = "pyobjc_framework_ApplicationServices-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a74a0922b48ad5ac4e402a1ac5dda5d6ee0d177870b7e244be61bc95d639ba85"}, - {file = "pyobjc_framework_ApplicationServices-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff352c33cad3f7bf8dd9b955ebb5db02d451d88eb04478d83edf0edd0cc8bf5d"}, - {file = "pyobjc_framework_ApplicationServices-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6d0706d5d9436298c8d619a1bb5be11a1f4ff9f4733797a393c6a706568de110"}, - {file = "pyobjc_framework_ApplicationServices-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:95bd111583c3bf20656393c2a056a457b0cf08c76c0ab27cfcaedf92f707e8a9"}, - {file = "pyobjc_framework_ApplicationServices-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:90a2350ddae3d9fb7b2e35e3b672b64854edae497fda8d7d4d798679c8280fed"}, - {file = "pyobjc_framework_ApplicationServices-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8bc830ac60b73a4cab24d1b1fdd8b044f25fe02e0af63a92cd96c43a51808c96"}, + {file = "pyobjc_framework_ApplicationServices-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b694260d423c470cb90c3a7009cfde93e332ea6fb4b9b9526ad3acbd33460e3d"}, + {file = "pyobjc_framework_ApplicationServices-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d886ba1f65df47b77ff7546f3fc9bc7d08cfb6b3c04433b719f6b0689a2c0d1f"}, + {file = "pyobjc_framework_ApplicationServices-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:be157f2c3ffb254064ef38249670af8cada5e519a714d2aa5da3740934d89bc8"}, + {file = "pyobjc_framework_ApplicationServices-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:57737f41731661e4a3b78793ec9173f61242a32fa560c3e4e58484465d049c32"}, + {file = "pyobjc_framework_ApplicationServices-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c429eca69ee675e781e4e55f79e939196b47f02560ad865b1ba9ac753b90bd77"}, + {file = "pyobjc_framework_ApplicationServices-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:4f1814a17041a20adca454044080b52e39a4ebc567ad2c6a48866dd4beaa192a"}, + {file = "pyobjc_framework_ApplicationServices-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1252f1137f83eb2c6b9968d8c591363e8859dd2484bc9441d8f365bcfb43a0e4"}, + {file = "pyobjc_framework_applicationservices-10.3.1.tar.gz", hash = "sha256:f27cb64aa4d129ce671fd42638c985eb2a56d544214a95fe3214a007eacc4790"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-CoreText = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreText = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-apptrackingtransparency" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AppTrackingTransparency on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AppTrackingTransparency-10.1.tar.gz", hash = "sha256:7d75a1d2c07b4d60e79c014b509f7a217b8e43ffb856b05aac5e12dfb03aa662"}, - {file = "pyobjc_framework_AppTrackingTransparency-10.1-py2.py3-none-any.whl", hash = "sha256:5dee7e163a6b325315410ca4929f1e07162403fc0f62d7d6a8dd504b544e1626"}, + {file = "pyobjc_framework_AppTrackingTransparency-10.3.1-py2.py3-none-any.whl", hash = "sha256:7c0e3a5cad402e8c3c5da1f070be0f49bb827e6d9e5165744f64e082633a4b45"}, + {file = "pyobjc_framework_apptrackingtransparency-10.3.1.tar.gz", hash = "sha256:2e381db5f7d3985207b5ff2975e41bf0f9147080345b2e1b4b242f8799290d04"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-audiovideobridging" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AudioVideoBridging on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AudioVideoBridging-10.1.tar.gz", hash = "sha256:73d049a9d203541c12a672af37676c8dddf68217a3e9212510544cb457e77db0"}, - {file = "pyobjc_framework_AudioVideoBridging-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:574ead9349db4d37ec6db865cf71d8fdf74d5b4d4b577aa5c56c77a5c17f4fff"}, - {file = "pyobjc_framework_AudioVideoBridging-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e9ea894545e5ed1fa9b772dcea876bdb16dac9300e021a81f8b92ec8ed876efb"}, - {file = "pyobjc_framework_AudioVideoBridging-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:be025276db7bf431361f908c45af631c5c97a138069127ca43e679640fd2b935"}, - {file = "pyobjc_framework_AudioVideoBridging-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a8d470904288e9ea7a9fb758cb704cbbebaec941c1e11d358c5260f117cbcad6"}, - {file = "pyobjc_framework_AudioVideoBridging-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a4ad2cb237ffaa3868eed2ed8869488cb44a8a85a63b2dfe6421be2cb5cbde9e"}, - {file = "pyobjc_framework_AudioVideoBridging-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:41e9ed25b14b30e5eb488a8278fd86cb0973a5677698b534a18c4917b7ec9f9d"}, + {file = "pyobjc_framework_AudioVideoBridging-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8accf87f136a6aa71d89d3a2204127b48e64ec25d3e1159f0f23ede0c4d70e59"}, + {file = "pyobjc_framework_AudioVideoBridging-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f6067b2f50fb48c9ecb521b8865d93dfbd46510a4322cc2041b1e917678f39b"}, + {file = "pyobjc_framework_AudioVideoBridging-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1aebc6c1aafb3cdfc5f9fad2dfe2dfccfbab159dc8dbfe54cfea777108e80e44"}, + {file = "pyobjc_framework_AudioVideoBridging-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b1fb0af1df78800205cd7a7cd90e58b640513bbb944fe6a8d89df43e626a27a8"}, + {file = "pyobjc_framework_AudioVideoBridging-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2cc18c005c72a74654a000a1d6d405b6cb12b1d6c09be9bd6b58502ae06035e7"}, + {file = "pyobjc_framework_AudioVideoBridging-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b61dc632c31875cb090521b0139d61f528e5fe5fedf4055524522c0aa808a77d"}, + {file = "pyobjc_framework_AudioVideoBridging-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8de78207e617d4d89c66b4a66ff4ff49b60822249d2a68fc9356dd09475f0103"}, + {file = "pyobjc_framework_audiovideobridging-10.3.1.tar.gz", hash = "sha256:b2c1d5977a92915f6af2203e3b4c9b8a8392bc51e0fc13ccb393589419387119"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-authenticationservices" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AuthenticationServices on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AuthenticationServices-10.1.tar.gz", hash = "sha256:2d686019564f18390ac16d3b225c6c8fead03d929e8cee16942fc532599e15be"}, - {file = "pyobjc_framework_AuthenticationServices-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:364b4f94171c78d5da9172fdf30ef71958da010d923f6fc8f673f8d2e3c8e9ef"}, - {file = "pyobjc_framework_AuthenticationServices-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:09636bd6614d440e0475ba05beba42aa79a73c4fe310e9e79dea4821e57685ae"}, - {file = "pyobjc_framework_AuthenticationServices-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:dd03dc0c4ad5c40a688ceca813b5c05ae99b72e6201a5a700d1d2722eee8fba3"}, + {file = "pyobjc_framework_AuthenticationServices-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:fa0a11fb64e30f14f01ec2d3a2a89a3e1a554db62111b0612f1782722b6dd534"}, + {file = "pyobjc_framework_AuthenticationServices-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e2753cdd5480f97753dc32d9f41d7e6cb75b09f7ce950b2eea4a9851e0a437db"}, + {file = "pyobjc_framework_AuthenticationServices-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:91c2cc0e963d6ac44c3a6014270b54e6499f1aae64d82482d96114c38fb99817"}, + {file = "pyobjc_framework_AuthenticationServices-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d715bbdf1c94ea838830930a41de0554905760943cff1510268d8e485c826ee8"}, + {file = "pyobjc_framework_authenticationservices-10.3.1.tar.gz", hash = "sha256:0ac834f4a5cbe3cf20acd4f6a96df77bc643a1ae248e394d06964db9fe0d6310"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-automaticassessmentconfiguration" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AutomaticAssessmentConfiguration on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AutomaticAssessmentConfiguration-10.1.tar.gz", hash = "sha256:c8f32f5586f7d7f9fd12343714c7439a1dfad5b5393f403aee440b5f91ef9f7d"}, - {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf009cddaa8d62eedaeb4878cc7acab80f4e9bb0bd83a5dff79590bab08b81ce"}, - {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c0ea6fa98e07a9cbcd90b7482022be5e1dc99e3170dcf2d4937ab17c5c2879dd"}, - {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4d47b95c515781fa5553443f38782f5e9b1aa7c1938449bcbb2377776441c54c"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:f717f6df5886123a55584f8dd85626c42387f5b55edcd3d68ff306b3fe56a206"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ab84a771835f346e8a45d19e05f0c2ef8bb3dca81461fb2acc6c9f031794ec63"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:66fd582549ef602a8bcaadb57de8a06cb0dc0367c2a508b20c580fde2232daed"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:578512ae1443e031c3fbfec7eefa71e5ac5fc8cac892ad7183c5ac0b894d494d"}, + {file = "pyobjc_framework_automaticassessmentconfiguration-10.3.1.tar.gz", hash = "sha256:f7846d04493e90eddbacfb7cffebc11b3f76f0800d3dc2bec39441732a20ac56"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-automator" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Automator on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Automator-10.1.tar.gz", hash = "sha256:0e95fc90a2930d108d38b61b4365f3678edd5aa25d26598fe39924c890813e80"}, - {file = "pyobjc_framework_Automator-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5c46ca5a97f6193432ad5195f6dfc261d66f70aea8371aa04f5c0ef85eb959f9"}, - {file = "pyobjc_framework_Automator-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e16540ca8f432de665997566e1cf1b43e8c7bea90a3460ab0aaccdb51bdac13c"}, - {file = "pyobjc_framework_Automator-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:0a8b26890e1b0728c7150cd81dfb7c3d091752e71550a5a8db27c703915b7f40"}, + {file = "pyobjc_framework_Automator-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:63632d2c1d069ca29a077b15ab20a0a0acc0a5f33ee322c9c8cc854702c66549"}, + {file = "pyobjc_framework_Automator-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4bc8afe0a52bee09f7f99bdfc62100654f08113de47e74488c0af2afcd646e23"}, + {file = "pyobjc_framework_Automator-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8609e1295030d2a46916965cd070072a90b6528abc25ae4d02e50818d2cb252f"}, + {file = "pyobjc_framework_Automator-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:2f482464b3f91405a5a59e3b96ae89c5062af81023ea0fc803353fdfe8cc4a9d"}, + {file = "pyobjc_framework_automator-10.3.1.tar.gz", hash = "sha256:330042475479f054ac98abd568b523fc0165c39eeefffc23bd65d35780939316"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-avfoundation" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AVFoundation on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AVFoundation-10.1.tar.gz", hash = "sha256:07e065c6904fbd6afc434a79888461cdd4097b4153dd592dcbe9c8bef01ee701"}, - {file = "pyobjc_framework_AVFoundation-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3475f2a5c18cab80a23266470bc7014a88c8e1e8894e96f9f75e960b82679723"}, - {file = "pyobjc_framework_AVFoundation-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fad5c9190d633f51193a62c4354f2fb7be3511c31a0c58f17e351bb30bfadad3"}, - {file = "pyobjc_framework_AVFoundation-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:6ee76be15a6ad7caf9db71c682fb677d29df6c1bb2972ed2f21283f1b3e99f45"}, + {file = "pyobjc_framework_AVFoundation-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:0896f6650df35f0229d1fb3aa3fbf632647dd815d4921cb61d9eb7fa26be6237"}, + {file = "pyobjc_framework_AVFoundation-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0cb27cc95288d95df7504adf474596f8855de7fa7798bbc1bbfbdfbbcb940952"}, + {file = "pyobjc_framework_AVFoundation-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb606ef0806d952a04db45ae691167678121df1d8d7c2f8cc73745695097033"}, + {file = "pyobjc_framework_AVFoundation-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:00889eb915479aa9ea392cdd241e4b635ae0fa3114f043d08cf3e1d1b5a23bd4"}, + {file = "pyobjc_framework_avfoundation-10.3.1.tar.gz", hash = "sha256:2f94bee3a4217b46d9416cad066e4f357bf0f344079c328736114451ae19ae94"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-CoreAudio = ">=10.1" -pyobjc-framework-CoreMedia = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreAudio = ">=10.3.1" +pyobjc-framework-CoreMedia = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-avkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AVKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AVKit-10.1.tar.gz", hash = "sha256:15779995d4bb3b231a09d9032cf42e8f2681e4271ee677076a08c60a1b45fac7"}, - {file = "pyobjc_framework_AVKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e73da23397f0d9397a5f78223b06a49873d11cce71f06d486316a006220b587"}, - {file = "pyobjc_framework_AVKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2ea71aa0c9230c37da6dab710b237ea67ea16a5ed2cd5f6123a562c8c6b6fa20"}, - {file = "pyobjc_framework_AVKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3c00b4448d0480e92d7a0dfe62d92d42554ddb45c7183c256931e47dafca1dce"}, + {file = "pyobjc_framework_AVKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:bb6025331e5ed493d5ca0a35fab14026820e0d8b0a091fc6010b4ef77aa4bf16"}, + {file = "pyobjc_framework_AVKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:675832ec9c088c2010bd9cd9f912ff5c45ff608d7d94233347d49f1b91f690ca"}, + {file = "pyobjc_framework_AVKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1d92e1d5d098af87667f3eac0609c39c58320c095cdcb7737958cc4895569f22"}, + {file = "pyobjc_framework_AVKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:91bf61fa8d8ef3764345b085038a4081165a8c54b4f0c2a11ee07f86a1556689"}, + {file = "pyobjc_framework_avkit-10.3.1.tar.gz", hash = "sha256:97ca35b5f0cec98f5c8521fedb8537bb23d82739b7102e4ac732d3c3944c8ccc"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-avrouting" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework AVRouting on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-AVRouting-10.1.tar.gz", hash = "sha256:148fc29d0d5e73fb23ed64edede3f74d902ec41b7a7869435816a7a1b37aa038"}, - {file = "pyobjc_framework_AVRouting-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2a2d6524ac870a0b022beb33f0a9ec8870dfb62524d778b7cb54b7946705a3ac"}, - {file = "pyobjc_framework_AVRouting-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:643412674490719dc05c3e4c010e464d50b51e834428e97739510f513ecc008d"}, - {file = "pyobjc_framework_AVRouting-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:103e99db20099331afe637d4bcc39ec7c5d8fe3edefa2dd0a865d6f5d15b0f65"}, + {file = "pyobjc_framework_AVRouting-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:30b05ea44f21d481e39905684c79176c04060e0e92c1ad31756fed6aa39b07df"}, + {file = "pyobjc_framework_AVRouting-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:af940e322cb9ce9d79b47b829c5df41ac4980aca2cda1fbe1ead4ed0f9f589a4"}, + {file = "pyobjc_framework_AVRouting-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3e5101311ee84c884c0eba201b3b7f92e1a2325132a9e44b9b7ad84cdd28b4c2"}, + {file = "pyobjc_framework_AVRouting-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:186e891c2a271492612b3db2b5c2050c56ed1bfce1f6146de8dbf05e7cd7623b"}, + {file = "pyobjc_framework_avrouting-10.3.1.tar.gz", hash = "sha256:7026059b24daf8e1da05d7867f450e82abe412fe5c438faf9344f46e3b83da39"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-backgroundassets" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework BackgroundAssets on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-BackgroundAssets-10.1.tar.gz", hash = "sha256:0a770f77f7fe6d715cf02e95a5efb70895ee19736cf0fa0ecbb3c320f4fa3430"}, - {file = "pyobjc_framework_BackgroundAssets-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:82bfc758b8c542e0b155a922e0dc901fdcd6b6a7574f4575725cfadb8d248825"}, - {file = "pyobjc_framework_BackgroundAssets-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a4f6e2dea9f2cb507e94e0c3c621e2e6af613770a8595ff17aedb34dc2fa56b4"}, - {file = "pyobjc_framework_BackgroundAssets-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4dfe00a649dd7c7aee0f25daf96c8c35438ed69ec324bcad81d5a87110759a72"}, + {file = "pyobjc_framework_BackgroundAssets-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:f725d33a5d633c514e4973489e1bdca391976a5c04443451acaaacc5ccd4095e"}, + {file = "pyobjc_framework_BackgroundAssets-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:14cc84ad7bef64986fe240d23205870fc40dd7b1d2a1819d3dd7924c4898b5c2"}, + {file = "pyobjc_framework_BackgroundAssets-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e28624ecfba067b5e0fc91d5818cb3d20d0ba189a7e8a724678abbecc233c13e"}, + {file = "pyobjc_framework_BackgroundAssets-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:aed2307c7fdd690e4dd214cbfacf4e7d5dd07e6cdd88ce1c02c4ddde3deea843"}, + {file = "pyobjc_framework_backgroundassets-10.3.1.tar.gz", hash = "sha256:5e1198f81db6f30ead2a55e8ea39264f9fce83dcf8e31a68e5f0ea08c5cfe9b5"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" + +[[package]] +name = "pyobjc-framework-browserenginekit" +version = "10.3.1" +description = "Wrappers for the framework BrowserEngineKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc_framework_BrowserEngineKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:86c92ef4e79db4066f7887426e99cfec8902fc8949fb666359cf2a9e519106fc"}, + {file = "pyobjc_framework_BrowserEngineKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:69766ba235976e0a1961d3925228d2ef12808298acd0cd66fe9e883424f0f9a4"}, + {file = "pyobjc_framework_BrowserEngineKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5394a5a801563834764ae46204f8ce4d61a0e2d4567716361eaf5f5e3a27aba7"}, + {file = "pyobjc_framework_BrowserEngineKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d5aeff43abed7e87f637086a05f1b77083cfc7cab07c09c447ae2b23621b2945"}, + {file = "pyobjc_framework_browserenginekit-10.3.1.tar.gz", hash = "sha256:0f6ea100bcf06f2b3f915dab27cf2f038698b39510fb47d3769f72ff62c1e80b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreAudio = ">=10.3.1" +pyobjc-framework-CoreMedia = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-businesschat" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework BusinessChat on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-BusinessChat-10.1.tar.gz", hash = "sha256:f361139464532d84bb29d520f2b045a4a63e960d07a0dd574c6c15dd67f890ed"}, - {file = "pyobjc_framework_BusinessChat-10.1-py2.py3-none-any.whl", hash = "sha256:60df5660a9a90a461c68a6cb49326c25e81f3412e951e84be7ccc98b62eb5404"}, + {file = "pyobjc_framework_BusinessChat-10.3.1-py2.py3-none-any.whl", hash = "sha256:952b60f558e3d3498e6191d717bf62c1803f4e1ad040ae29d130090671ec004f"}, + {file = "pyobjc_framework_businesschat-10.3.1.tar.gz", hash = "sha256:53e52981f9da336fcaf6783e82509e06faf8868931213ac70e6bd7395a5859a4"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-calendarstore" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CalendarStore on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CalendarStore-10.1.tar.gz", hash = "sha256:6274d7eb94353813aefca236276c5b6dc6445a48fff39e832478db17c47e34c1"}, - {file = "pyobjc_framework_CalendarStore-10.1-py2.py3-none-any.whl", hash = "sha256:cbd8ec495d9b13cc986b018d8740e25a4e18a25732ee19de1311f0c30ab53120"}, + {file = "pyobjc_framework_CalendarStore-10.3.1-py2.py3-none-any.whl", hash = "sha256:7afb59e793ea6d28706423faa50bb1f25532d8ed7388c8540596ce41891445ca"}, + {file = "pyobjc_framework_calendarstore-10.3.1.tar.gz", hash = "sha256:21f627b0afb9a667794b451dd3a03f12ea3f74358dc5977c33b8ecc8b9736c27"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-callkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CallKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CallKit-10.1.tar.gz", hash = "sha256:9a5165f35e31d98b7d1539c9b979cabd01064926903389fc558cbc71bf86ddd4"}, - {file = "pyobjc_framework_CallKit-10.1-py2.py3-none-any.whl", hash = "sha256:f82e791b2dbae4adfcc596949975573309a0127ba02d4c35743501f6665ec610"}, + {file = "pyobjc_framework_CallKit-10.3.1-py2.py3-none-any.whl", hash = "sha256:495354bea298efdc81c970154083b83aff985f2c294d4883a62de3cc4129e34e"}, + {file = "pyobjc_framework_callkit-10.3.1.tar.gz", hash = "sha256:350390023e9ac98ff6c91b1f51da2489eef2e23aa649d0f63c13cf1d8be1e0df"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-cfnetwork" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CFNetwork on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CFNetwork-10.1.tar.gz", hash = "sha256:898fa3ec863b9d72b3262135e1b0a24bc73879b65c69a2a7b213fe840e2a11de"}, - {file = "pyobjc_framework_CFNetwork-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:82f6fa09d67e25ef1cd92596b25328a6c295341c40a572e899c9e858ce949a1d"}, - {file = "pyobjc_framework_CFNetwork-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c598f18e50480a92df3c69c22cd1752844eb487176ada5e1c1b80670fb05e4eb"}, - {file = "pyobjc_framework_CFNetwork-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:90697ae10c7fb83d81f25d3800f33846329121bedefd495b45d47a0f0d996a73"}, + {file = "pyobjc_framework_CFNetwork-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:e6027a90c5442e36a4ef91c9e10896efb5bc1bb4743d732adf3422112922f6cf"}, + {file = "pyobjc_framework_CFNetwork-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:dff41296378029f1a5e9cedbc133b243f096a93fcc8d6985c555459328cfe11e"}, + {file = "pyobjc_framework_CFNetwork-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:01f1c4c43792e993c613b5b8923953eea774d4a7567fbc1861edb2c6c0cfa770"}, + {file = "pyobjc_framework_CFNetwork-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f6a5d6fe5e230cc0d53b9673902f1571ab68b542f3630d7c1319ea1e3e480f22"}, + {file = "pyobjc_framework_cfnetwork-10.3.1.tar.gz", hash = "sha256:0e4c51a75dbf4e2b1c0d4ee60a363f9d31d682d2dd2f6b74aded769d2d883aa8"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-cinematic" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Cinematic on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Cinematic-10.1.tar.gz", hash = "sha256:a1210338de5a739b00304555ce15b70b36deebdbd3c6940f8e9531253219edce"}, - {file = "pyobjc_framework_Cinematic-10.1-py2.py3-none-any.whl", hash = "sha256:73408d3bfd9b08389eb6787b0b5df4fe9c351c936fa9b1f95a9c723951e9a988"}, + {file = "pyobjc_framework_Cinematic-10.3.1-py2.py3-none-any.whl", hash = "sha256:48bf35d594f4f010266a028bbf93bd953cc78db7658d3c614e219b482c8d73b2"}, + {file = "pyobjc_framework_cinematic-10.3.1.tar.gz", hash = "sha256:7edaaa7e325aeb39cd0c33329c25783dd54af294229884556daad36d1d1b9d72"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-AVFoundation = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-CoreMedia = ">=10.1" -pyobjc-framework-Metal = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-AVFoundation = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreMedia = ">=10.3.1" +pyobjc-framework-Metal = ">=10.3.1" [[package]] name = "pyobjc-framework-classkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ClassKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ClassKit-10.1.tar.gz", hash = "sha256:baf79b1296662525d0fa486d4488720cceebe63595765cfeade61aeb78a4216f"}, - {file = "pyobjc_framework_ClassKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:502949573701363947bf64f7ac9dedab7247037c0e53c7db080c871f3ca52aa8"}, - {file = "pyobjc_framework_ClassKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e6d7c2e8b87b285ce21582c602be23960349e23111c8d02bcc3b9192090b437e"}, - {file = "pyobjc_framework_ClassKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5ac34c1d491e15f81df83b406a281d3176fff8476e053bb8476cad7e4fa102e7"}, + {file = "pyobjc_framework_ClassKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:980d2605296428d177b0111af914d0dd4a0c5116da5ae944cdd8b6bba733e758"}, + {file = "pyobjc_framework_ClassKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:17dec45d5ec7db663bc87ddf80b8185d2134177f265a12a9a6df778901183412"}, + {file = "pyobjc_framework_ClassKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b74155902851e8e2b31b34c606dd33f9e24d9b8992568cc71b60e1ddc553d99e"}, + {file = "pyobjc_framework_ClassKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:776a600182b7de58676ac661b235356f46683e758d99db1cf60f52aac335389f"}, + {file = "pyobjc_framework_classkit-10.3.1.tar.gz", hash = "sha256:e15700d32007bf77c5c740bc9931c864bb7739cdfcd2b0595377c3ed35ecfe25"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-cloudkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CloudKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CloudKit-10.1.tar.gz", hash = "sha256:8f0109f29ac6554c22cc21c06f6fd0a23e3e49556b0ab2532eb1d69ac2a7cd96"}, - {file = "pyobjc_framework_CloudKit-10.1-py2.py3-none-any.whl", hash = "sha256:ffdedaaa8384a64df6b30d45c834dffa002a63b8e74578012b6261780f31c28c"}, + {file = "pyobjc_framework_CloudKit-10.3.1-py2.py3-none-any.whl", hash = "sha256:53670f47320063b80aa60edd2d813308dce85dfd2112461dd13c060aa9e5b47a"}, + {file = "pyobjc_framework_cloudkit-10.3.1.tar.gz", hash = "sha256:4c7db72c2bb2fcf63365df91bf2eefa83cee4004606b901e1da89b75da652309"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Accounts = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-CoreData = ">=10.1" -pyobjc-framework-CoreLocation = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Accounts = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreData = ">=10.3.1" +pyobjc-framework-CoreLocation = ">=10.3.1" [[package]] name = "pyobjc-framework-cocoa" -version = "10.1" +version = "10.3.1" description = "Wrappers for the Cocoa frameworks on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Cocoa-10.1.tar.gz", hash = "sha256:8faaf1292a112e488b777d0c19862d993f3f384f3927dc6eca0d8d2221906a14"}, - {file = "pyobjc_framework_Cocoa-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e82c2e20b89811d92a7e6e487b6980f360b7c142e2576e90f0e7569caf8202b"}, - {file = "pyobjc_framework_Cocoa-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0860a9beb7e5c72a1f575679a6d1428a398fa19ad710fb116df899972912e304"}, - {file = "pyobjc_framework_Cocoa-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:34b791ea740e1afce211f19334e45469fea9a48d8fce5072e146199fd19ff49f"}, - {file = "pyobjc_framework_Cocoa-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1398c1a9bebad1a0f2549980e20f4aade00c341b9bac56b4493095a65917d34a"}, - {file = "pyobjc_framework_Cocoa-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:22be21226e223d26c9e77645564225787f2b12a750dd17c7ad99c36f428eda14"}, - {file = "pyobjc_framework_Cocoa-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0280561f4fb98a864bd23f2c480d907b0edbffe1048654f5dfab160cea8198e6"}, + {file = "pyobjc_framework_Cocoa-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4cb4f8491ab4d9b59f5187e42383f819f7a46306a4fa25b84f126776305291d1"}, + {file = "pyobjc_framework_Cocoa-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5f31021f4f8fdf873b57a97ee1f3c1620dbe285e0b4eaed73dd0005eb72fd773"}, + {file = "pyobjc_framework_Cocoa-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11b4e0bad4bbb44a4edda128612f03cdeab38644bbf174de0c13129715497296"}, + {file = "pyobjc_framework_Cocoa-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:de5e62e5ccf2871a94acf3bf79646b20ea893cc9db78afa8d1fe1b0d0f7cbdb0"}, + {file = "pyobjc_framework_Cocoa-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c5af24610ab639bd1f521ce4500484b40787f898f691b7a23da3339e6bc8b90"}, + {file = "pyobjc_framework_Cocoa-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a7151186bb7805deea434fae9a4423335e6371d105f29e73cc2036c6779a9dbc"}, + {file = "pyobjc_framework_Cocoa-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:743d2a1ac08027fd09eab65814c79002a1d0421d7c0074ffd1217b6560889744"}, + {file = "pyobjc_framework_cocoa-10.3.1.tar.gz", hash = "sha256:1cf20714daaa986b488fb62d69713049f635c9d41a60c8da97d835710445281a"}, ] [package.dependencies] -pyobjc-core = ">=10.1" +pyobjc-core = ">=10.3.1" [[package]] name = "pyobjc-framework-collaboration" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Collaboration on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Collaboration-10.1.tar.gz", hash = "sha256:e85c6bd8b74b1707f66847ed71de077565d5e9fe6e7ed4db3cdafc2408723da5"}, - {file = "pyobjc_framework_Collaboration-10.1-py2.py3-none-any.whl", hash = "sha256:9a2137aaed1ad71bf6c92c7c275253c2dc6f0062af9d2d8a1590d00bf30c1ecb"}, + {file = "pyobjc_framework_Collaboration-10.3.1-py2.py3-none-any.whl", hash = "sha256:889b1e00bdea09c2423e9b8d493492ec45a70787ddc533bf67d060c7ec0e1f78"}, + {file = "pyobjc_framework_collaboration-10.3.1.tar.gz", hash = "sha256:bbca3de3679b058cbb89ad911e3bdfe491a02b4fa219d5f9219c022774ba237a"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-colorsync" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ColorSync on Mac OS X" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ColorSync-10.1.tar.gz", hash = "sha256:2c6ee65dfca6bc41f0e9dffaf1adebc78a7fb5cee63740b092ade226710c1c32"}, - {file = "pyobjc_framework_ColorSync-10.1-py2.py3-none-any.whl", hash = "sha256:58596365b270453c3ce10bb168383c615321fa377a983eba3021f664c98f852a"}, + {file = "pyobjc_framework_ColorSync-10.3.1-py2.py3-none-any.whl", hash = "sha256:0c37075e9b0f1dabc0aa1755687e1a5dada08ae0914ebb593c7810bf8090cf83"}, + {file = "pyobjc_framework_colorsync-10.3.1.tar.gz", hash = "sha256:180960ed6f76084b35073eff49fcca41a8fa883c3236949a40f75daa28ee8f94"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-contacts" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Contacts on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Contacts-10.1.tar.gz", hash = "sha256:949d61ff7f4f07956949f8945ad627ffa89cce3d10af9442591e519791a25cc4"}, - {file = "pyobjc_framework_Contacts-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b10c068b5a79fcb0240ea4cd1048162277f36567a84333a0bd0168f851168f99"}, - {file = "pyobjc_framework_Contacts-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a3bb6fb24deae41a0879ac321e6401b43e5fbedba0a75ced67b2048a4852c3ff"}, - {file = "pyobjc_framework_Contacts-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:16556f06202b1b4fd9da8e3186b6140b582a4032437cdab2f5f8b32b24f3e3ed"}, + {file = "pyobjc_framework_Contacts-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:401e40ff638712d011fe54c7b1e9929af994e87cb03d129cd95df2fb90439e4e"}, + {file = "pyobjc_framework_Contacts-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:83de186cd4037171c63899987caa66cc01527688b15176e899cf1a06e6baab09"}, + {file = "pyobjc_framework_Contacts-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e0bbffb430505ad3f91fd58f65b0a6e7535ab5bb28c2ca69ee8a6349f3edfe3c"}, + {file = "pyobjc_framework_Contacts-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d47f694977cf33f5d0b73e2f111edcd57f2ef0cd9a6a38e03b1dea965b8657cc"}, + {file = "pyobjc_framework_contacts-10.3.1.tar.gz", hash = "sha256:7120b5593a20e936cb5589b93ef7fd5558c86bd6ec8003f427afb87c04bbea20"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-contactsui" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ContactsUI on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ContactsUI-10.1.tar.gz", hash = "sha256:0b97e4c5599ab269f53597dd8f47a45599434c833e72185d5d3a257413a6faf4"}, - {file = "pyobjc_framework_ContactsUI-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5c70ff6b07e48331f25138bc159f7215d9b5d6825da844fec26ba403aad53f52"}, - {file = "pyobjc_framework_ContactsUI-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:876c1280fcb13c89a5fd89e7c3ace04bfd3c3b418cb64b6579dcbee1e9156377"}, - {file = "pyobjc_framework_ContactsUI-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a5ee22f1e893eb79633ed425972e50c5ec9b0a1d20cf6fbf21bf68d1bbfec436"}, + {file = "pyobjc_framework_ContactsUI-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:0e939e46ccff1e07e7fa6768a35d646b7302886a99b9efe6b31dea4ea67674ad"}, + {file = "pyobjc_framework_ContactsUI-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4adc77f2fb7ae4e2999cfd72f5d3b8e0e039880f9d60cb8e15050607b249c730"}, + {file = "pyobjc_framework_ContactsUI-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1a605d93b4826cd4646cced6383cb253e65c8babcfd230d5894c1c4d67f6e147"}, + {file = "pyobjc_framework_ContactsUI-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:62ae604a000233c729712e420de734c97afdd9187fdd0bef8e61fbc8c4f6eda0"}, + {file = "pyobjc_framework_contactsui-10.3.1.tar.gz", hash = "sha256:51601501d5bc94c59ad458c7bb1d1994c497b373307dad8bd2ea2aa348f66c4a"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Contacts = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Contacts = ">=10.3.1" [[package]] name = "pyobjc-framework-coreaudio" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreAudio on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreAudio-10.1.tar.gz", hash = "sha256:713ca82fc363ea6cf373d2db0b183f39058bcadceb8229d9e8839b783104f8e2"}, - {file = "pyobjc_framework_CoreAudio-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9348f613a1f35bbeb7d1d899e2ee3876881cd0433e59f584f30ba96e179d960a"}, - {file = "pyobjc_framework_CoreAudio-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0192dddd2f99db51cdb0959e80f29f9f531ba8bd0421e06ae9212f34a05c48a"}, - {file = "pyobjc_framework_CoreAudio-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b1a4612ce87dfcca3c939ec5885d4578955f5ff4d017f95d4459d5fb3bdc8970"}, - {file = "pyobjc_framework_CoreAudio-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:678d2b916850daf7fe38a95af0f73b4dd39b463ea87ec36fe287d81d050c31f7"}, - {file = "pyobjc_framework_CoreAudio-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:807fa54de91d53ff64537e50aa123c5b262952c57eea6928ecb3d526078229c2"}, - {file = "pyobjc_framework_CoreAudio-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:db149cabae1b91ea437536e1741b6e7573a71ec2aae4274318172936a5ac7190"}, + {file = "pyobjc_framework_CoreAudio-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:76a37e27cfdd67e4dcf27f57b680a881c4a2f3bf44ce3b31d7cdb32596e1e269"}, + {file = "pyobjc_framework_CoreAudio-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd58e69cabbc987d0c2854ab2d13516889cfe4a2094b80296591ad7df0f30e40"}, + {file = "pyobjc_framework_CoreAudio-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e0aeca61a425d846afc92350ffba970e1e503469182f5f0ea436de98cfd00d96"}, + {file = "pyobjc_framework_CoreAudio-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:21cecd1b023b6960d1071c106345656de45a399196701b07c7e5c076321f25ad"}, + {file = "pyobjc_framework_CoreAudio-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:443b14cc6e64e09e6fb4eae61f6ac1ce19618d9074ae1627d75754fa434ef87f"}, + {file = "pyobjc_framework_CoreAudio-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:4f8d4c4c4fa0529f178ef6d3676cfe08c9e8ae20c3cdbfe067b562d7395debfa"}, + {file = "pyobjc_framework_CoreAudio-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b93cc6f670feea29f8f4cd2a9511d2220aefde41a89912d5ab8eb06a198e344b"}, + {file = "pyobjc_framework_coreaudio-10.3.1.tar.gz", hash = "sha256:c81c709bf955aea474a4de380b187f3c2e56c864ca7de520b08362b73070c795"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-coreaudiokit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreAudioKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreAudioKit-10.1.tar.gz", hash = "sha256:85472aaee6360940f679a5e068b5a21160f8cee676d9fd0937b43b39c447d78e"}, - {file = "pyobjc_framework_CoreAudioKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bde3012be239328fdc928d0ff9da9f4627e6ab4832e05faaa0c0ea4e11078d14"}, - {file = "pyobjc_framework_CoreAudioKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:43e9643ce390e36c64dca98a1bbcb0c2c282c527d31eb52aa2b7a18e2f7c97d1"}, - {file = "pyobjc_framework_CoreAudioKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:65bb2c5870b1739703fce056cdc4daddcdcf644c1ddcb590e4b88b5ed2fc45a4"}, + {file = "pyobjc_framework_CoreAudioKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:dfc967bc448cc0a1fce932e6af15ad42f5ea3eb2f793396e364cf39005c812eb"}, + {file = "pyobjc_framework_CoreAudioKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:6634f3f15d257e93cad4644eb08f5b32376e8a8a7ae2e95d99d36d935b5e9ba2"}, + {file = "pyobjc_framework_CoreAudioKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c2bf1b0c9f6c1d27c98b87bf4cf1ba3d0fa550dd771de948614ca21d15779a01"}, + {file = "pyobjc_framework_CoreAudioKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d5b3adabd7278e64a0deab6d0f6912bfcc667bc26054f5141a556897157dd8f9"}, + {file = "pyobjc_framework_coreaudiokit-10.3.1.tar.gz", hash = "sha256:81f35d5dc45cda043e01f0ca045311f4aebc36c51cb71e859b30ea0edf90b3db"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-CoreAudio = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreAudio = ">=10.3.1" [[package]] name = "pyobjc-framework-corebluetooth" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreBluetooth on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreBluetooth-10.1.tar.gz", hash = "sha256:81f50fcd9ee24332f1ad85798d489cfc05be739fcc1389caa6d682e034215efd"}, - {file = "pyobjc_framework_CoreBluetooth-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:145540ae4f35992774e559840a778554f3d3d29b359ff6d7f450c954cacccf0f"}, - {file = "pyobjc_framework_CoreBluetooth-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1ef97e8479895048fa96d5afa2f88139a8432158d6b0fb80ad1db03666c1d4ad"}, - {file = "pyobjc_framework_CoreBluetooth-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e8bce7f425caa55a87b7519eff03eaa7d08ff5e5e09e9318706d3f5087b63b08"}, + {file = "pyobjc_framework_CoreBluetooth-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:c89ee6fba0ed359c46b4908a7d01f88f133be025bd534cbbf4fb9c183e62fc97"}, + {file = "pyobjc_framework_CoreBluetooth-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2f261a386aa6906f9d4601d35ff71a13315dbca1a0698bf1f1ecfe3971de4648"}, + {file = "pyobjc_framework_CoreBluetooth-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5211df0da2e8be511d9a54a48505dd7af0c4d04546fe2027dd723801d633c6ba"}, + {file = "pyobjc_framework_CoreBluetooth-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b8becd4e406be289a2d423611d3ad40730532a1f6728effb2200e68c9c04c3e8"}, + {file = "pyobjc_framework_corebluetooth-10.3.1.tar.gz", hash = "sha256:dc5d326ab5541b8b68e7e920aa8363851e779cb8c33842f6cfeef4674cc62f94"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-coredata" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreData on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreData-10.1.tar.gz", hash = "sha256:01dfbf2bfdaa4e0aa3e636025dc868ddb62aedf710890e6af94106278f1659aa"}, - {file = "pyobjc_framework_CoreData-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ff280c893c6d472168696fa0732690809af2694167081b5db87395c25cdf6e27"}, - {file = "pyobjc_framework_CoreData-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c51d8e4723ed113684d0ddd4240900a937682859a9e75d830f35783098f04e95"}, - {file = "pyobjc_framework_CoreData-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a8a8d509ff17f65f4cec7fb35d77a21937f2c8232b5ce357e783cbf971616ad9"}, + {file = "pyobjc_framework_CoreData-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:2313aba4236f3a909d2747f4327da83be884adadb734a602ed4319addf88edd7"}, + {file = "pyobjc_framework_CoreData-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:065ff3058f4bc8544422ad1f10dff037bdeed25263cc8ec5c609e54231bf9347"}, + {file = "pyobjc_framework_CoreData-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:049acf980e5226b132a4285c3a94cc8266380e57050c8fd4caec3c5df4ef8c4d"}, + {file = "pyobjc_framework_CoreData-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:2f1f4e1217fc02f66435dc2a5cb2e0b41c684c435f13d96bf05cd3d1e0ad4e2c"}, + {file = "pyobjc_framework_coredata-10.3.1.tar.gz", hash = "sha256:8a75094942c8f3ddc1bcbde920c87658d7bb4c7534a4652e60db42d17f4b4a4a"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-corehaptics" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreHaptics on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreHaptics-10.1.tar.gz", hash = "sha256:87c1913078963b2d7dba231839566f831f47499c4c029f8beaa46209630e75e1"}, - {file = "pyobjc_framework_CoreHaptics-10.1-py2.py3-none-any.whl", hash = "sha256:ae6143c041b0846a58199826c0094cfb2fb9080f139c93e6b63f51a6b2766552"}, + {file = "pyobjc_framework_CoreHaptics-10.3.1-py2.py3-none-any.whl", hash = "sha256:163b83ea727cbac5c0963d16ffda89c9f1626ed633d5e52830c7918b8599a693"}, + {file = "pyobjc_framework_corehaptics-10.3.1.tar.gz", hash = "sha256:5a7cc117c0b64428e1f08dc9c8b76dbc5d8f61f80dc41e911d11ddee4e0e2059"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-corelocation" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreLocation on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreLocation-10.1.tar.gz", hash = "sha256:f43637443c4386233c52b0af3131a545968229543f7b0050764298cac1604fd8"}, - {file = "pyobjc_framework_CoreLocation-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:66bad050f37966526017763e5a8c424e257a0974cfbe0c8875fa149bdc1d41c2"}, - {file = "pyobjc_framework_CoreLocation-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:eaa4ff8e3cc388f045a6c15f3ee5a950860164f6fb5a13aed29e37b6cb481607"}, - {file = "pyobjc_framework_CoreLocation-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f4dcf52c4934e99b20056f2ebc8398c9f8f8a61ceac0e5de1e2bb719b0f844c2"}, + {file = "pyobjc_framework_CoreLocation-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:4cc83a730db7f78ca5ceef45ea4c992084d8c90bed189939fa3f51f8e9ea83ae"}, + {file = "pyobjc_framework_CoreLocation-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:97d5aa35214e417c9a03ac7209630dc445b98652932642dd0497f1ec52624bfe"}, + {file = "pyobjc_framework_CoreLocation-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:86d0e00b7eef5a3e2f01ea309cdcf58807b251138008edcfc65d3c31af8a5bd2"}, + {file = "pyobjc_framework_CoreLocation-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5c4ded6388f99a18ce2b9d7082b8a43a19b6d9f8f121e2147d10bb37a25e1714"}, + {file = "pyobjc_framework_corelocation-10.3.1.tar.gz", hash = "sha256:8ae54e5bd4c07f7224639d815f7a6537fadee17c11cb35dd99c2804bac1825ab"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-coremedia" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreMedia on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreMedia-10.1.tar.gz", hash = "sha256:8210d03ff9533d5cef3244515c1aa4bb54abaeb93dfc20be6d87e3a6b3377b36"}, - {file = "pyobjc_framework_CoreMedia-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7bd43d614f0427732ce1690e0640f1d7a40a73dd90142ac08c5dab2ba0d49e8d"}, - {file = "pyobjc_framework_CoreMedia-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5c1f849ce7de96da6fc81dc8975ecf04444c7179129976b3fe064d9f85a91082"}, - {file = "pyobjc_framework_CoreMedia-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ab9e44e0d3ce584a119c3b3d539de9d228b635cb98bf60f1e1a221f8aa20681e"}, - {file = "pyobjc_framework_CoreMedia-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fffc355c038dfaa83f7d7c01497fb20590f9090421564b275cd8fd12e8e10e8e"}, - {file = "pyobjc_framework_CoreMedia-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:e4e9221c5a4c0b7ff7484eb8a21e06be0cafc1c95b9bcc27a57c139b64692dbe"}, - {file = "pyobjc_framework_CoreMedia-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b61e986b686d4e928208c26a4a2163210e101fcec56eeb61d62b969802eaa8ca"}, + {file = "pyobjc_framework_CoreMedia-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ef02d87f12ba54e6182ea72fd5732cf6d348982c4263dc9c0b11e4163fbb877"}, + {file = "pyobjc_framework_CoreMedia-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e63b002cf5e34540cba3f3a1704603ea0fb076ffc1ea42c2393a0679f40846de"}, + {file = "pyobjc_framework_CoreMedia-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c6eaf48f202becab10679e3b5dd62607ddec2739495db45882524592cabf3997"}, + {file = "pyobjc_framework_CoreMedia-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:98b8ab02e6ec979007b706e05166e16bd61121e47fbc6e449f4b2de2c58f3cb6"}, + {file = "pyobjc_framework_CoreMedia-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:77ca460cc35e39b9f820d9f20e93bfa89439b23dfb350ba201448b1ee958902c"}, + {file = "pyobjc_framework_CoreMedia-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:6ed3adf48fe3001d1b5acce688ecd5b75e0fa5f56d9f296ec120748cd36f2d24"}, + {file = "pyobjc_framework_CoreMedia-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b5bbed395bb8a8b0b4b8bb5475fd19f2a28d385c4d3a783cb590c9ea5e801baa"}, + {file = "pyobjc_framework_coremedia-10.3.1.tar.gz", hash = "sha256:bc3e0cddf5f546b5d8407d8f46b203f1bd4396ad5dbfdc0d064a560b3fe31221"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-coremediaio" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreMediaIO on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreMediaIO-10.1.tar.gz", hash = "sha256:c07177c58c88b6de229f88f3b88b4d97bfc59d2406f751b5aff6bed5cac4d938"}, - {file = "pyobjc_framework_CoreMediaIO-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d44781e21632f3af8eab86194b2fe32ce235b6c6a03ff87f09e0ba034a1e7a73"}, - {file = "pyobjc_framework_CoreMediaIO-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c97bd803a17204a2ec2d9f22c14176009067359efe80b9df69e8ec197783091c"}, - {file = "pyobjc_framework_CoreMediaIO-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d137b0eef1533af244c70ab02f1ed5716dcc8739b0ba6b6c703d36a61d9bab2e"}, + {file = "pyobjc_framework_CoreMediaIO-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:07e19e89489a372ebea9e8a5cfaf1ec03fd570e65ed3fa2dfa44719d1e337a36"}, + {file = "pyobjc_framework_CoreMediaIO-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c3bc3d8f3648b6a4126983119d9fc4e21d2c7ec06beb284c57039ca1033ceb03"}, + {file = "pyobjc_framework_CoreMediaIO-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b158612a62cabd7f61c1f2a4d08d4cb4682e1b2ba140a4d09ca88b1ae3014443"}, + {file = "pyobjc_framework_CoreMediaIO-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b3e9a9813bf5331bd0981e0a66ff380c5c927854f644e5cb62a2beaabd73b15f"}, + {file = "pyobjc_framework_coremediaio-10.3.1.tar.gz", hash = "sha256:5da3ed78475223dd3400fdb55fb97d543a248086f5cf8b77bf4aceac3df1513c"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-coremidi" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreMIDI on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreMIDI-10.1.tar.gz", hash = "sha256:e2e407dcb9d5ed53e0a8ed4622429a56c9770c26e2e4455dcb76a6620a12eba6"}, - {file = "pyobjc_framework_CoreMIDI-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b632773bea0c943a1f733166aece7560f32237a42706124d1f001b10620c4bcc"}, - {file = "pyobjc_framework_CoreMIDI-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:065cf89ee58b01700780fbbed0c00e1c5f5f383ac3d54f31642ee6d59e3c03c2"}, - {file = "pyobjc_framework_CoreMIDI-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:8984ad04837efc8bc7cbf1d48ff3433eb7fea1d298ed8b72344ec1641826df48"}, + {file = "pyobjc_framework_CoreMIDI-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:00a6869c2365b90cdf6e1ed0bbde6d87fe4daaa40ed243ee810e3cc3945f185a"}, + {file = "pyobjc_framework_CoreMIDI-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:92d04962f7ea63be03127880d4659f718f5b44b6a57a0e619a96d9def619bddb"}, + {file = "pyobjc_framework_CoreMIDI-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1be5f2d3c0c7c23817ad49a25d8cf8c84b9e420bd5fedc0065ec1322f5d5f2ab"}, + {file = "pyobjc_framework_CoreMIDI-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:bbe2bfb537e8adcca703a2c76b21ca1741d32691205af00a5ec67c025ee5d8e1"}, + {file = "pyobjc_framework_coremidi-10.3.1.tar.gz", hash = "sha256:818454b56edae082a3a4b4366a7e93b8bb54856be01ee21bb8527a22a4732efc"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-coreml" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreML on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreML-10.1.tar.gz", hash = "sha256:4cda220d5ad5b677a95d4d29256b076b411b692b64219c2dcb81c702fc34d57d"}, - {file = "pyobjc_framework_CoreML-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:fb85e653a0f7984fff908890b7988d9d5ac42ff92b213cd9371bb255982ee787"}, - {file = "pyobjc_framework_CoreML-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a361d35c75e749a975330f7647084a58c2166f076ecc5573491542b96bc84c28"}, - {file = "pyobjc_framework_CoreML-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4c3b43da4afb279b02bbb6a57a3c5fb4d24ad6d48ef40c20efcda783e41077d2"}, + {file = "pyobjc_framework_CoreML-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:c1fdcc0487807afa9cd0f88f25697e0e2e093d0219e8e1aa42aa3674dd78c2cb"}, + {file = "pyobjc_framework_CoreML-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:21c87e84c807b5dbe61e0f016d9aefa32d3212f175cc4b976b5c08770be7a58c"}, + {file = "pyobjc_framework_CoreML-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a0877aed5d4cdbb63d1246cd5384c09d78a0667e83c435a1257d10017c11c1a4"}, + {file = "pyobjc_framework_CoreML-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4bd3f1acfb3245727727b71cbcf7d21a33d7e00fa488e41ad01527764b969b92"}, + {file = "pyobjc_framework_coreml-10.3.1.tar.gz", hash = "sha256:6b7091142cfaafee76f1a804329e7a4e3aeca921eea8644e9ceba4cc2751f705"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-coremotion" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreMotion on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreMotion-10.1.tar.gz", hash = "sha256:907a2f6da592f61d49f06559b34fc5addd8c0f2b85f9f277c5e4ea5d95247b67"}, - {file = "pyobjc_framework_CoreMotion-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:630487c14e22c0c05ddc33a149db673d8a28a876b59a78ed672f1a4825ebf40e"}, - {file = "pyobjc_framework_CoreMotion-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2219096ceb41aea91819df747c08059885f94ca14c66a078d3161ba49c1cb56e"}, - {file = "pyobjc_framework_CoreMotion-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:de694971994df2791047c2a1039556ea54683fd09cdc30c23ee5891c63414232"}, - {file = "pyobjc_framework_CoreMotion-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81b138a672519ecbea8a2f2392a7f015f3d7caf150368f83b3b278cb60743e8c"}, - {file = "pyobjc_framework_CoreMotion-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b6cbbee7d26ef1837e382566316cb5d5fae6bca10418437608ebc312f396f898"}, - {file = "pyobjc_framework_CoreMotion-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9968b07199532b1c4ff56d1d6a6195e8ce8bc2beabbf55dc53193f473b3741f9"}, + {file = "pyobjc_framework_CoreMotion-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f8c320df1806ccd8c2b1ac32c9b9a7337816ff13cf338a710a2f15ee550f58cb"}, + {file = "pyobjc_framework_CoreMotion-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a003478eeb520c7f28be4d9dc8f9e02df6ffa8921d46c8879e2b298c9fbc6926"}, + {file = "pyobjc_framework_CoreMotion-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:63c8f831ad522212627f99ae8d6f34161628230afd544203646e7d66596d6437"}, + {file = "pyobjc_framework_CoreMotion-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b78d2bcc71149821a49266eb548faea23abd7a25b7cd3f7a7f20b1d343a8786"}, + {file = "pyobjc_framework_CoreMotion-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28b431e448b884830c846d156d9c6626b265d9ede70ad233d77ceceb67366a17"}, + {file = "pyobjc_framework_CoreMotion-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7d4f95398ac5741a6dd1711e129f6173111385e26d858c59be8462543f62a8a1"}, + {file = "pyobjc_framework_CoreMotion-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:89c78be31e276aa88d848b69a2f8d11360deaa7297222bd5369ecd1910de166d"}, + {file = "pyobjc_framework_coremotion-10.3.1.tar.gz", hash = "sha256:6ba61ffd360473b018702b9ae025eb16b8aaa45c6e533121522f26eef93a9f71"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-coreservices" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreServices on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreServices-10.1.tar.gz", hash = "sha256:43d507f2b3d84a5aab808c3f67bf21fb6a7d1367d506e2e9877bf1cac9bad2eb"}, - {file = "pyobjc_framework_CoreServices-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:73162798c506f6d767e2684d7c739907c96a5547045d42e01bee47639130b848"}, - {file = "pyobjc_framework_CoreServices-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:eba7abba8f5ba194a5ef1ffeb5f9d3c0daa751e07ef0d3662e35e27e75a24d73"}, - {file = "pyobjc_framework_CoreServices-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:924bca5a67e9046e8c6146dbc1301fe22c2a1bd49bef92358fd6330ef19cfa65"}, + {file = "pyobjc_framework_CoreServices-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:58d5708ee0a2ad7eea180864fe68123a2464b987ea089d0646ce69e2002500b0"}, + {file = "pyobjc_framework_CoreServices-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:9d43b77fa11af139ba39d94921e695cf804226737f68167f8bdb8f1c449c932e"}, + {file = "pyobjc_framework_CoreServices-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e6c7b6bb821cc7fb4f04c08560d460783a7fa08093f5e153241bf10296a16cb4"}, + {file = "pyobjc_framework_CoreServices-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ccb64113ee612a05308ab8ed57ec224e22445d5a125bec11e24c35d58d1f77e4"}, + {file = "pyobjc_framework_coreservices-10.3.1.tar.gz", hash = "sha256:2e46d008ee4ff586420175888c45f8eb0f002ed5b840c8f7893c560af01b2d72"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-FSEvents = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-FSEvents = ">=10.3.1" [[package]] name = "pyobjc-framework-corespotlight" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreSpotlight on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreSpotlight-10.1.tar.gz", hash = "sha256:b50e13d55d90b88800c2cc2955c000ea6b1de6481ff6e0092c7b7bf94fceea69"}, - {file = "pyobjc_framework_CoreSpotlight-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:421e18ded971c212fd3f2878658c209c81d1f8859eb62dd6a965abcb19a4ce5a"}, - {file = "pyobjc_framework_CoreSpotlight-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c775e0d42ee21f7d6b9374b01df1a0d4ece0b765e99c7011cb2ea74a2c2ef275"}, - {file = "pyobjc_framework_CoreSpotlight-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7dc11f607429e21c2aee18f950cdde141a414c874369dbb66920930802cfd0fa"}, + {file = "pyobjc_framework_CoreSpotlight-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:4aa9b01b8d722ba9e803ec4a2329ee8da0bdecb9a004a668b793b957544a6d81"}, + {file = "pyobjc_framework_CoreSpotlight-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:9684f735fd3d3e8fae447e90a2c246bf6a8d4ca37f619174208d65daa86d9ca0"}, + {file = "pyobjc_framework_CoreSpotlight-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:31b2a1309c747bb6e4d1ddc8368885af2948af55441fbf9817ebac193c1b815b"}, + {file = "pyobjc_framework_CoreSpotlight-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ba5a20e860af7246da67bb00db15d8bd5c5110b8a12a44568bd68030f51db478"}, + {file = "pyobjc_framework_corespotlight-10.3.1.tar.gz", hash = "sha256:6b8ad243a65943d631434a9ff4696458cdd3d0cb631cfeb501a967fe29445c30"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-coretext" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreText on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreText-10.1.tar.gz", hash = "sha256:b6a112e2ae8720be42af19e0fe9b866b43d7e9196726caa366d61d18294e6248"}, - {file = "pyobjc_framework_CoreText-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6ea2920c126a8a39e8a13b6de731b78b391300cec242812c9fbcf65a66ae40cf"}, - {file = "pyobjc_framework_CoreText-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:37b203d832dd82bd9566c72eea815eb89f00f128a4c9a2f352843914da4effec"}, - {file = "pyobjc_framework_CoreText-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:083700483b18f337b0c43bdfaafc43467846f8555075669d4962d460d9d6cd00"}, - {file = "pyobjc_framework_CoreText-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:59472cd1a33e83803fa62b3db20ac0e899f5ed706d22704ea81129d3f49ff0c7"}, - {file = "pyobjc_framework_CoreText-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:cc1a63ca2c6921768b1c794ce60e2a278e0177731aa4bf8f620fdde857e4835e"}, - {file = "pyobjc_framework_CoreText-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fbbdde4ce747bcad45c2aded36167ad00fead309a265d89ab22289c221038e57"}, + {file = "pyobjc_framework_CoreText-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd6123cfccc38e32be884d1a13fb62bd636ecb192b9e8ae2b8011c977dec229e"}, + {file = "pyobjc_framework_CoreText-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:834142a14235bd80edaef8d3a28d1e203ed3c988810a9b78005df7c561390288"}, + {file = "pyobjc_framework_CoreText-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ae6c09d29eeaf30a67aa70e08a465b1f1e47d12e22b3a34ae8bc8fdb7e2e7342"}, + {file = "pyobjc_framework_CoreText-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:51ca95df1db9401366f11a7467f64be57f9a0630d31c357237d4062df0216938"}, + {file = "pyobjc_framework_CoreText-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b75bdc267945b3f33c937c108d79405baf9d7c4cd530f922e5df243082a5031"}, + {file = "pyobjc_framework_CoreText-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:029b24c338f58fc32a004256d8559507e4f366dfe4eb09d3144273d536012d90"}, + {file = "pyobjc_framework_CoreText-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:418a55047dbff999fcd2b78cca167c4105587020b6c51567cfa28993bbfdc8ed"}, + {file = "pyobjc_framework_coretext-10.3.1.tar.gz", hash = "sha256:b8fa2d5078ed774431ae64ba886156e319aec0b8c6cc23dabfd86778265b416f"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-corewlan" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CoreWLAN on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CoreWLAN-10.1.tar.gz", hash = "sha256:a4316e992521878fb75ccff6bd633ee9c9a9bf72d5e2741e8804b43e8eeef8ac"}, - {file = "pyobjc_framework_CoreWLAN-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e5374ebd6e258d6cdaa9fdeb21c10830c50fc1c00eaa91b2293833b0182479f7"}, - {file = "pyobjc_framework_CoreWLAN-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:57cb3fbb69500df92a111655c129b0f658ec16e14e57b08b9c1ef400f33f3bb5"}, - {file = "pyobjc_framework_CoreWLAN-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:eb2360b20ab14a0f6cc7d06dc7bf2b0832d0c95892d9f364e03c6ecf77dfc328"}, + {file = "pyobjc_framework_CoreWLAN-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:0b9b9a6f54c11b6adcb04eb07686c8a8372140619876073d6355498da7ecd074"}, + {file = "pyobjc_framework_CoreWLAN-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8d68dabd2fdb74e5263f1e05fa039621c197907347e045cd672a54b3ac537140"}, + {file = "pyobjc_framework_CoreWLAN-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:995260e02f858ffa0781ec0d632e60e0567c45fd551102d36fe67a351f81697e"}, + {file = "pyobjc_framework_CoreWLAN-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e7120d4f7a73cfc283ca499165e8aaf2628bfb82773917e144c293447cabbdba"}, + {file = "pyobjc_framework_corewlan-10.3.1.tar.gz", hash = "sha256:d340d976b5d072b917c6d3de130cb4e7a944ee0fdf4e1335b2aa6b1d4d6b4e14"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-cryptotokenkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework CryptoTokenKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-CryptoTokenKit-10.1.tar.gz", hash = "sha256:ad8fb3c4f314cc5f35cd26a5e3fdd68dd71ea0f7b063f31cffb9d78050ce76f0"}, - {file = "pyobjc_framework_CryptoTokenKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e691877b2e96c8f257873943147315561cda79b63c911afa8d0103d6b351a88f"}, - {file = "pyobjc_framework_CryptoTokenKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5a4ce0650ad70eedadc46091d61878e28a4cf491d1c2e8da32feab2f661a4ee5"}, - {file = "pyobjc_framework_CryptoTokenKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c0013c7795d208547d9f92c0539bc7fec09ca049d791458b62c177585552abc4"}, + {file = "pyobjc_framework_CryptoTokenKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:6c88948bc8d30cd125ae29ffe551315e08c0fb49654d4f36ba4b3f0fe2f85259"}, + {file = "pyobjc_framework_CryptoTokenKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf0735d5d8e3ff61650deaa9670df62032dc218b865f21cd6b36b0d4c00b88ae"}, + {file = "pyobjc_framework_CryptoTokenKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bd58e48912a9b044c0203e39938b2108cab9b3a4903134ebb1ef610b45570802"}, + {file = "pyobjc_framework_CryptoTokenKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b53fdc79a095d327dff7e68e2545b0825437520f105b1341e5f638e8e1afb490"}, + {file = "pyobjc_framework_cryptotokenkit-10.3.1.tar.gz", hash = "sha256:ef1c4a3b9bc5429eceda59724279428e1f8740df2c5a511d061b244113b6fd97"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-datadetection" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework DataDetection on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-DataDetection-10.1.tar.gz", hash = "sha256:f81d1ca971aa8034faeb6e457144df0832f870d7e19905886593bafe4cbfe21f"}, - {file = "pyobjc_framework_DataDetection-10.1-py2.py3-none-any.whl", hash = "sha256:f23fa282297ed385c9df384a0765e4f9743b8916de8a58137f981ab0425b80f5"}, + {file = "pyobjc_framework_DataDetection-10.3.1-py2.py3-none-any.whl", hash = "sha256:618ea90267fd4b83d09b557b67342ad5f3ac579090020e081dca6c664f1ae598"}, + {file = "pyobjc_framework_datadetection-10.3.1.tar.gz", hash = "sha256:5394350cd7e7f40562dc0777f26dd9ddf4a595d20cb6e3cd601938e9490c963e"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-devicecheck" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework DeviceCheck on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-DeviceCheck-10.1.tar.gz", hash = "sha256:f89e3acd15ec48134f95bf027778ca1d7e3088b9c2204e48f8c6e3bdcb28cf82"}, - {file = "pyobjc_framework_DeviceCheck-10.1-py2.py3-none-any.whl", hash = "sha256:31d5a83d85a4d95e238f432ac66cbf110a7b70afa82fd230ab4b911a5e2b9cb4"}, + {file = "pyobjc_framework_DeviceCheck-10.3.1-py2.py3-none-any.whl", hash = "sha256:9a3b291a2583bac2b65ff902c4b7872c1068736e249765906f530ae5a6eb8085"}, + {file = "pyobjc_framework_devicecheck-10.3.1.tar.gz", hash = "sha256:7f6f95c84dc3d1f62aa07061f79b47d19463390d977e5afb444ef9fdd9177a9d"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-dictionaryservices" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework DictionaryServices on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-DictionaryServices-10.1.tar.gz", hash = "sha256:03b3b19a9b911beb3bdc8809f5d02356a497a75dbefa0f355825ec610c050a3e"}, - {file = "pyobjc_framework_DictionaryServices-10.1-py2.py3-none-any.whl", hash = "sha256:7ace031cc3df1fa9a4eb663ff55eee0a4c7c8c34653aa1529988d579d273150b"}, + {file = "pyobjc_framework_DictionaryServices-10.3.1-py2.py3-none-any.whl", hash = "sha256:e40933bc96764450dff16cd8ca8080ec83157a93ed43574441848ea52f24918d"}, + {file = "pyobjc_framework_dictionaryservices-10.3.1.tar.gz", hash = "sha256:c9fb8ed1b92f63c6f568bcdbadf628baab1cb8bb4cd01dbd65424d59c236a552"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-CoreServices = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-CoreServices = ">=10.3.1" [[package]] name = "pyobjc-framework-discrecording" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework DiscRecording on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-DiscRecording-10.1.tar.gz", hash = "sha256:321c69b6494c57d75d4a0ecf5d90ceac3800441bf877eac8196ab25dcf15ebde"}, - {file = "pyobjc_framework_DiscRecording-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:581141bd645436d009cc6b42ca6255322de9a3a36052e7dcf497e90959c7bc77"}, - {file = "pyobjc_framework_DiscRecording-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2c4144c4d1d7bf7ad537c6159cefb490876b7eff62ec95d4af7bc857813b95cd"}, - {file = "pyobjc_framework_DiscRecording-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:1c52f7ace5936edbe160aa4d6cb456a49e7bc1a43a5e34572d48cd65dee22d1e"}, + {file = "pyobjc_framework_DiscRecording-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:99ee7b1762c7e2a4e0b74c36416f4095695ea33505c7de03875a4f46a5729af7"}, + {file = "pyobjc_framework_DiscRecording-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c4233f2649e34be2dd158752f0f0180c7db4ee705cc14aa62bc03c1f77318ca3"}, + {file = "pyobjc_framework_DiscRecording-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54016cd30b81f1f065d3a4d90b008c1bcfa77891cc79c68d72dff65e9d81e083"}, + {file = "pyobjc_framework_DiscRecording-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:da84170af530cda7b1f32023d5e8b98b62914c573c6ef571e562473d0b94fe6f"}, + {file = "pyobjc_framework_discrecording-10.3.1.tar.gz", hash = "sha256:47865c9a0d24366b6ede01d326d57404346c3d01e249f417bd2b0b3de00d6c54"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-discrecordingui" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework DiscRecordingUI on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-DiscRecordingUI-10.1.tar.gz", hash = "sha256:2a44278b19738e8d4f2180433df37b59a0b645d9ddc0f3c3a6c81e506afc1953"}, - {file = "pyobjc_framework_DiscRecordingUI-10.1-py2.py3-none-any.whl", hash = "sha256:684925119e4c8f8ea43cfede1a3e39f785b5aa94a48f1aa7a98fd4cdc4c1d2e3"}, + {file = "pyobjc_framework_DiscRecordingUI-10.3.1-py2.py3-none-any.whl", hash = "sha256:cb25c70117a5c5eeb4ef74a96da48e2da171f01b7e92d1b7bbd7808068e8960c"}, + {file = "pyobjc_framework_discrecordingui-10.3.1.tar.gz", hash = "sha256:4b9c804a97c89001feddb58106cdc3e099e241314f7c4de062842d27b1318b68"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-DiscRecording = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-DiscRecording = ">=10.3.1" [[package]] name = "pyobjc-framework-diskarbitration" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework DiskArbitration on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-DiskArbitration-10.1.tar.gz", hash = "sha256:c3ab3dc91375dabaf4d3470e01358e4acfecf6b425abf9ad95a26a7a56398f56"}, - {file = "pyobjc_framework_DiskArbitration-10.1-py2.py3-none-any.whl", hash = "sha256:a3bd1883b60aa1d8cff3bc18957f81ed14e5d0406d18a4a9095539ddf51dd72e"}, + {file = "pyobjc_framework_DiskArbitration-10.3.1-py2.py3-none-any.whl", hash = "sha256:f0f727435da388efd035bdd510607a5f5769b22be2361afc5b8d4ee081c70cce"}, + {file = "pyobjc_framework_diskarbitration-10.3.1.tar.gz", hash = "sha256:0776318cb56f8e095066a880812c4fc5db2071687846e23a000a947a079f6c6c"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-dvdplayback" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework DVDPlayback on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-DVDPlayback-10.1.tar.gz", hash = "sha256:2af92907a50a47c44a8dd1521217a564ad9a3dd9e9056f0a76b13275d380bee1"}, - {file = "pyobjc_framework_DVDPlayback-10.1-py2.py3-none-any.whl", hash = "sha256:bcbfb832a3f04e47aef03606a21fd58458bc28e25e1a444e7a9388bfee2f9dd3"}, + {file = "pyobjc_framework_DVDPlayback-10.3.1-py2.py3-none-any.whl", hash = "sha256:c0fb2e96ce4eae8def642f1c4beaec2da3cdf61db1562d4b5199d1334d1a10fe"}, + {file = "pyobjc_framework_dvdplayback-10.3.1.tar.gz", hash = "sha256:1f7c22624dee9b1b54def15f12a3f7cacb28052cd864a845eb24b7f59de12257"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-eventkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Accounts on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-EventKit-10.1.tar.gz", hash = "sha256:53473df48000b39dec276e3b8ead88b153c66cf0fdc07b016f759b42f0b2ec50"}, - {file = "pyobjc_framework_EventKit-10.1-py2.py3-none-any.whl", hash = "sha256:3265ef0bfab38508c50febfa922b4abf6ebc55a44f105d499e19231c225a027c"}, + {file = "pyobjc_framework_EventKit-10.3.1-py2.py3-none-any.whl", hash = "sha256:ad9f42431bd058ff72feba3bbce6fbd88b2a278c3b2c1cdb4625ea5f60f1ecda"}, + {file = "pyobjc_framework_eventkit-10.3.1.tar.gz", hash = "sha256:3eef14ba439be1c5bc47da561ccea3941daba663577efac7a58e3031d27e056b"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-exceptionhandling" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ExceptionHandling on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ExceptionHandling-10.1.tar.gz", hash = "sha256:ac75ac230249d6f26f750beb406c133a49d4a284e7ee62ba1139e9d9bc5ec44d"}, - {file = "pyobjc_framework_ExceptionHandling-10.1-py2.py3-none-any.whl", hash = "sha256:b8eb9142f141361982e498610bfd33803acb4f471f80b5cd9df8d382142449e9"}, + {file = "pyobjc_framework_ExceptionHandling-10.3.1-py2.py3-none-any.whl", hash = "sha256:79843a681a1d0f9ee2b7014dcf7e1182c99c83e49cf6cea81df934ebbdf4050b"}, + {file = "pyobjc_framework_exceptionhandling-10.3.1.tar.gz", hash = "sha256:ff6208777739f8a886d0cbfe20692b41cc4e5e0607419c47d2c5d405b6b4c6ee"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-executionpolicy" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ExecutionPolicy on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ExecutionPolicy-10.1.tar.gz", hash = "sha256:5ab7da37722b468a5e230354fa45a6a96e545c6c2aab5a76029e2227b1bae326"}, - {file = "pyobjc_framework_ExecutionPolicy-10.1-py2.py3-none-any.whl", hash = "sha256:556aa28220438b64e6f75f539f133616a343abe3e2565f0d016091f4dc4a9c3d"}, + {file = "pyobjc_framework_ExecutionPolicy-10.3.1-py2.py3-none-any.whl", hash = "sha256:f2eb203fa4c7dcf18a0ab3a4a94cb30a9f82cf888c237994dbbdb15adf01c8d2"}, + {file = "pyobjc_framework_executionpolicy-10.3.1.tar.gz", hash = "sha256:cc066dc8378fc2a1a4e6129c4d09e2076dc9a5b09925f8dd959aad591cbf9a44"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-extensionkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ExtensionKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ExtensionKit-10.1.tar.gz", hash = "sha256:4f0a5256149502eeb1b4e1af1455de629a3c3326aaf4d766937212e56355ad58"}, - {file = "pyobjc_framework_ExtensionKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:82eece8d6d807bafb5cf8a220c58f2b42b350a0bc9131cb0cdfd29e90294858d"}, - {file = "pyobjc_framework_ExtensionKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9936cfe8a17c09457aa10c21f90f77151328596bd72b55fd9b6c3e78a11384"}, - {file = "pyobjc_framework_ExtensionKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:94cdc24e19ed554bc1d8e9d11139839033b26997f5b29a381ed4be8633ad2569"}, + {file = "pyobjc_framework_ExtensionKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:e2b54a5b32c959fc1500936b73c7ebd6d7ffcad23d74b9a6ff3db4ea5660f731"}, + {file = "pyobjc_framework_ExtensionKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f5b1e9c7e3a64224acae7b4c2d26d2a654edc54382e9e88aa3b056f4033508f8"}, + {file = "pyobjc_framework_ExtensionKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:62bc537cbeabb24667434c82515827e64c31d761bdcd02cc3ea6bb6a9a35873e"}, + {file = "pyobjc_framework_ExtensionKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c66797352b71a944810447c81133935656d8ea9bb847775a1532cf06d8deb1d3"}, + {file = "pyobjc_framework_extensionkit-10.3.1.tar.gz", hash = "sha256:91946030195fa17c5248655b10786ea60b9aee7d83a4627ba56768600b4e7674"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-externalaccessory" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ExternalAccessory on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ExternalAccessory-10.1.tar.gz", hash = "sha256:1c206f2e27aedb0258a3cf425ed89cbea0657521829f061362b4fca586e033a8"}, - {file = "pyobjc_framework_ExternalAccessory-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:519c36e63011a797f1747306132957168eed53456e801973c38c52b06b265a0e"}, - {file = "pyobjc_framework_ExternalAccessory-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8c54367b52cc74231df057c9bbf722896d98efd91f6d6a7415e0ca7227f311b9"}, - {file = "pyobjc_framework_ExternalAccessory-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:64facb48377840e72e459f9ae88d482d0d17a1726733b2d79205de4e4449eb89"}, + {file = "pyobjc_framework_ExternalAccessory-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:4395607570f1dd91abd786422fc516f83b4b2a5185321e6581d33dbe74a52c63"}, + {file = "pyobjc_framework_ExternalAccessory-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3fcdbb2338e7bd0fb66e9074dc95f2f989146ae92c66eb4282cfc1a6533cbe6c"}, + {file = "pyobjc_framework_ExternalAccessory-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b076ba07c84b1f4ef9a8a1aa095b66416119b1484b5111b2dd3041f2d301d1a1"}, + {file = "pyobjc_framework_ExternalAccessory-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4771cddfed6422502831a9bf88fa572918d1ca71a3e34e068f442d1197630267"}, + {file = "pyobjc_framework_externalaccessory-10.3.1.tar.gz", hash = "sha256:3ba1a7242448126b4af0fb93963790d0066766bcba2770d935111093e87b7b83"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-fileprovider" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework FileProvider on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-FileProvider-10.1.tar.gz", hash = "sha256:617811be28bd5d9b0cc87389073ade7593f89ee342a5d6d5ce619912748d8e00"}, - {file = "pyobjc_framework_FileProvider-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9e98efd27f69c8275dc8220dfb2bb41a486400c1fb839940cd298b8d1e44adca"}, - {file = "pyobjc_framework_FileProvider-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7046144d86b94707fea6d8bb00b2850f99e0ebaef136ee2b3db884516b585529"}, - {file = "pyobjc_framework_FileProvider-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2f4c816b87237ab2ddfb0314296e5824411cec11f9c1b5919f8b4e8c02069ff1"}, - {file = "pyobjc_framework_FileProvider-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8476daf2c291f6bc1c9f4a26f4492236a2e427774fd02a03c561c667e9ec0931"}, - {file = "pyobjc_framework_FileProvider-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:de3dcbe70943b3bf057f634be4003fdcc112e3d7296f1631be1bf20f494db212"}, - {file = "pyobjc_framework_FileProvider-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a53ebe7e4a6ef24cf3ade1c936a96a1cb0c40dd7639899e3e238e050d4813417"}, + {file = "pyobjc_framework_FileProvider-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:36f66bbd308fdf80d8fe21b89212af4b89bc80dff8cee5f785d5a6fcce942bec"}, + {file = "pyobjc_framework_FileProvider-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b44bcbf3d826fd8a0cbc53142c65655433d553205fb36811486757e2089e6c5f"}, + {file = "pyobjc_framework_FileProvider-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b28294768dc71190019c2b2638e27b7ebf6edb65a90721b86613083bd86f6b2d"}, + {file = "pyobjc_framework_FileProvider-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bb07a0b7e259030c7bc034c590c77a22e44427320c99bf74e5348551fe0da011"}, + {file = "pyobjc_framework_FileProvider-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e61b20aef5083df2303bf36f181fb83b02b9a7f4868af0e9229d94d7bc1828f"}, + {file = "pyobjc_framework_FileProvider-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2ad657fa433d1922f40d581e87af1c2f7002c4835fa49235fdb3909eda23e1e8"}, + {file = "pyobjc_framework_FileProvider-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e66548dfc9e81489bc66e245c97760c457371c25ced87a28bdeca655b548447e"}, + {file = "pyobjc_framework_fileprovider-10.3.1.tar.gz", hash = "sha256:63a4160e6cbede0f682145f4303ed889bd9f3c9fccfecdc32636a8d95aeceeab"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-fileproviderui" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework FileProviderUI on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-FileProviderUI-10.1.tar.gz", hash = "sha256:e8f40b41f63d51401fb2aa5881dbf57ef6eacaa6c4d95f3dd1d9eb1b392a2d84"}, - {file = "pyobjc_framework_FileProviderUI-10.1-py2.py3-none-any.whl", hash = "sha256:ef85cead617c3e9b851589505503d201197bbc0ee27117a77243a1a4e99fad7d"}, + {file = "pyobjc_framework_FileProviderUI-10.3.1-py2.py3-none-any.whl", hash = "sha256:14be680a7fc78def5174ec5a6d890d407678cff723d6b359bba66bc0a78bd31a"}, + {file = "pyobjc_framework_fileproviderui-10.3.1.tar.gz", hash = "sha256:2a3f3b9b81aff216df76bc72c8e8730d7ba7f3b2412720f68b722bae58f82797"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-FileProvider = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-FileProvider = ">=10.3.1" [[package]] name = "pyobjc-framework-findersync" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework FinderSync on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-FinderSync-10.1.tar.gz", hash = "sha256:5b1fb13c10d0f9bf8bccdacd0ecd894d79376747bd13aca5a410f65306bcbc29"}, - {file = "pyobjc_framework_FinderSync-10.1-py2.py3-none-any.whl", hash = "sha256:e5a5ff1e7d55edb5208ce04868fcf2f92611053476fbbf8f48daa3064d56deb5"}, + {file = "pyobjc_framework_FinderSync-10.3.1-py2.py3-none-any.whl", hash = "sha256:d4778de8a9b386c16812d470d1ad44d7370970d1dbc75d8bea390d4f5cd12be4"}, + {file = "pyobjc_framework_findersync-10.3.1.tar.gz", hash = "sha256:b4a08e0a87c54f62623038de1929fab018fe44fca5372a455bb524b9f90e9196"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-fsevents" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework FSEvents on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-FSEvents-10.1.tar.gz", hash = "sha256:f5dee8cfbd878006814db264c5f70aeb1a43c06620e98f628ca6c0008beb1b1d"}, - {file = "pyobjc_framework_FSEvents-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3c2bf6ffd49fd21df73a39e61b81d7c6651e1063f72b62b2218c6ab4bf91dc02"}, - {file = "pyobjc_framework_FSEvents-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e57dccf14720c0789811580cb99e325353259cc96514e2622ca512e70f392c2"}, - {file = "pyobjc_framework_FSEvents-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f026f417b25f804c6994d49af0743177ca7119d5d9e885a80d71c624e12a5d47"}, + {file = "pyobjc_framework_FSEvents-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:7348e1aa268819b005e1ab648b5bac348052d3513aacf768c2c3999d6ffbf81e"}, + {file = "pyobjc_framework_FSEvents-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:420736c645e15006c1ed962767bfd164b1d5dba7d9dc3cd9730e4c9922b05c27"}, + {file = "pyobjc_framework_FSEvents-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:56b48def729acaa0b7c27335a40519ca7d17e6d45649ba68e0f9f1c70e902994"}, + {file = "pyobjc_framework_FSEvents-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:282ebeeba0190524fe1d5d21d729ebcb7034e379a8039a6ccdf5f5c6e4470e02"}, + {file = "pyobjc_framework_fsevents-10.3.1.tar.gz", hash = "sha256:6269fd8aa3f62d8a6312e316043aca6d7d792812bff09b617bbd6ca9f0f6e440"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-gamecenter" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework GameCenter on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-GameCenter-10.1.tar.gz", hash = "sha256:0d124b3f18bb1b3e134268c99bf92c29791e8c62a97095c1fb1eb912ebe495e0"}, - {file = "pyobjc_framework_GameCenter-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5d6cf2405e5107c8befcb61a5339c0766fbab9448a2c4e8f5dd4401a7ef380ab"}, - {file = "pyobjc_framework_GameCenter-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8ede397f1ca31022e7507cb1cf801617094e407300ee29c19415fd32f64fa758"}, - {file = "pyobjc_framework_GameCenter-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:db4e36a573e91715d8ed5250f6784fe5b03c8b2e769b97f8cf836eb7111c3777"}, + {file = "pyobjc_framework_GameCenter-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:eba1058859fb30bef8227ce649dcf7531545aeff81c3cfd3e3b80ef8fe87bf70"}, + {file = "pyobjc_framework_GameCenter-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3329261e8a6a3b0df3abd4a7a3cc66cc8e47be919279a08249e08f94a0e4448f"}, + {file = "pyobjc_framework_GameCenter-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d2420c7d28f9eab337955a1872179295ba92e3d8db0e5b1b8d40442e7079d711"}, + {file = "pyobjc_framework_GameCenter-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:6bd556e798cf52b94434c48cabf299060dd79b668f0021826995ceee520db8af"}, + {file = "pyobjc_framework_gamecenter-10.3.1.tar.gz", hash = "sha256:221ae88ee69816b95861b1a0dc781c1c17775d38fcf0388327620535479b6a07"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-gamecontroller" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework GameController on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-GameController-10.1.tar.gz", hash = "sha256:50a17fdd151f31b3a5eae9ae811f6f48680316a5c2686413b9a607c25b6be4bc"}, - {file = "pyobjc_framework_GameController-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ec7e84c2dbc90065db8d0293c29e34d95b4fa14beeb3fb3c818fa3bcdf24d89a"}, - {file = "pyobjc_framework_GameController-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:672d99f849b803c6c6b8e89445e2c446379ae23f1f0f7e355a2a94f91d591fea"}, - {file = "pyobjc_framework_GameController-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c56232fc2f0e95901e66534d18a008857c59363078ac75fedb2d18dcbd5dda63"}, + {file = "pyobjc_framework_GameController-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:cfb754d29660e90f295ba443cc84fb189e5ca9d1f019c35a382cb24dd4e08bf8"}, + {file = "pyobjc_framework_GameController-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a7a1e34a4ebcf9f653bc4a066b7d08d9124d462bc7e1c434ead983a6ae093382"}, + {file = "pyobjc_framework_GameController-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a23bb9c97063fb334990cac20dcab1389942495cb028350a232faebb804d73c2"}, + {file = "pyobjc_framework_GameController-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e90afaa5a5d28771e60b4f60ff89b80037d4e9e558d680872810216299aea1a8"}, + {file = "pyobjc_framework_gamecontroller-10.3.1.tar.gz", hash = "sha256:f9f252b5fed5de2a8c7fdd2e302b6ed6e0b82015d7da75b28984c5ea5909345e"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-gamekit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework GameKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-GameKit-10.1.tar.gz", hash = "sha256:6fa87a29556cdaf78c86851fc61edb6d384f1a7370a75a66bdd208ed1250899f"}, - {file = "pyobjc_framework_GameKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bb00618d256f67b6f784b49db78bde80677a2004af4558266009de30e8804660"}, - {file = "pyobjc_framework_GameKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:38bcce65b781c5967eb7985b551ca015cda89903d18f29eab74518a52f626fec"}, - {file = "pyobjc_framework_GameKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:773e6f645731dac7c2b6e55ec7ecd92928b070f7a33b4c5ce33a3a52565ecd49"}, + {file = "pyobjc_framework_GameKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:57930e2b65296719b2998c9816ab844983460f3358c57a120f09ebe78013b64c"}, + {file = "pyobjc_framework_GameKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5634ac5f8b3569355f4fe8b2e6a06450d8aee555119607f7d738f5c85900c1b6"}, + {file = "pyobjc_framework_GameKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:83f689c959cdfaa311f6702d9e99402faf47e390334fb3880d255dc72e2d2a90"}, + {file = "pyobjc_framework_GameKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c861e575ed4543241adbc53162fb651395ba73c68a697f4b5aceaa61754e19c1"}, + {file = "pyobjc_framework_gamekit-10.3.1.tar.gz", hash = "sha256:7d21a8f45c32ac94ce0e70b6c6fe72928fe75cb1a6bd6d7715e2bf39b291b70b"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-gameplaykit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework GameplayKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-GameplayKit-10.1.tar.gz", hash = "sha256:12a5d1dc59668df155436250476af029b1765ca68e7a1e2d440158e7130232a3"}, - {file = "pyobjc_framework_GameplayKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f877d2c449aea09187856540b3d5a3e6a98573673a09af6163b1217040d93e5f"}, - {file = "pyobjc_framework_GameplayKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:86e320c79707ab7a3de2f23d0d32bd151b685865f43d13fb58daa2963b4da5cc"}, - {file = "pyobjc_framework_GameplayKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:93a1e62c2875d7705d1aa70115f646258ecffc4d4702ed940a5214dc0ea580f5"}, + {file = "pyobjc_framework_GameplayKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:cb54cfc4dbc69f1888c59ddce9da97ddc03f5003794fe47d56942a89c478e138"}, + {file = "pyobjc_framework_GameplayKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:eb49a732c82b17ff66f6e878b2cb9ba27e2222be303a337f2af4ed1a34a404bf"}, + {file = "pyobjc_framework_GameplayKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8a0c2148e9b421922accfe565a398effe9622653c71d0bb4eb1b69ac90ee257f"}, + {file = "pyobjc_framework_GameplayKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:628406ca0d0ce283cc0735a4e532dd04a3a4d57a4c22c3ee4338ba64d1b13790"}, + {file = "pyobjc_framework_gameplaykit-10.3.1.tar.gz", hash = "sha256:2035b81f7bc34b93636753cc3f9b06cd08171afc5c95bb2327a82fd3195f3c36"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-SpriteKit = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-SpriteKit = ">=10.3.1" [[package]] name = "pyobjc-framework-healthkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework HealthKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-HealthKit-10.1.tar.gz", hash = "sha256:9479c467514c506f9083889f11da6b8f34d705f716ffe9cbbb5a3157000d24de"}, - {file = "pyobjc_framework_HealthKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b32171f6d4ee6fa37718f5b299c6b866a4ae395ff8764ccc040b9d1263a3e74f"}, - {file = "pyobjc_framework_HealthKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a628c777c02df6c5dbbc5f26576f52239dab79ac1afe5ca53d40d561d55adb52"}, - {file = "pyobjc_framework_HealthKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4d44c5ace78dce1f0c76b96010d9446b90a9474946a25bfb33d373a152e22524"}, + {file = "pyobjc_framework_HealthKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:80aee8bf0e7af6e434e9023c2d2050c2a2fe8e53abbf0e1f2285a932836fdd17"}, + {file = "pyobjc_framework_HealthKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac9abec44c02edfe7f2151f529bac1c9544fb99ee1caffff55b331281b374463"}, + {file = "pyobjc_framework_HealthKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b64d4e61a8009a0a5586d464b591186570f654b20937f78941875ef3b7865505"}, + {file = "pyobjc_framework_HealthKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5bfcaac3a3947d070611d1fed1abe8858e049ef0ab1050f46974a7333b6369f6"}, + {file = "pyobjc_framework_healthkit-10.3.1.tar.gz", hash = "sha256:45622fedb42bbd95dcc096248bbc41dacd857d9db120ff7310f74f3bad4b23e1"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-imagecapturecore" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ImageCaptureCore on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ImageCaptureCore-10.1.tar.gz", hash = "sha256:29b85ee9af77bba7e1ea9191bf84edad39d07681b9bd267c8f5057db3b0cdd64"}, - {file = "pyobjc_framework_ImageCaptureCore-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8d2dbc09aed984f7d92e7b835e87608d356f5f4b6dd03e84258963391791ae5"}, - {file = "pyobjc_framework_ImageCaptureCore-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5eef95798d340000ddfb9c59c9468b75bb4cd389d311fd27078c3f4a4a3af29a"}, - {file = "pyobjc_framework_ImageCaptureCore-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:832cbe0d957935553183586556d2036cfcc9aae593defe71e6a0726e5c63abf6"}, + {file = "pyobjc_framework_ImageCaptureCore-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:649e0b7bf428ad3fe90faaf63dbd09d234563a32b3cee5a6f2d28ab31927bd54"}, + {file = "pyobjc_framework_ImageCaptureCore-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5bab844c665b3ac07b191e94130f543eb23c5cfd014035f025efacb7a48aa68c"}, + {file = "pyobjc_framework_ImageCaptureCore-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0fd14f9183c1fcee67cf09f36ccbe156186da84a83419917fd3ca81a5de23d97"}, + {file = "pyobjc_framework_ImageCaptureCore-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d625d84094f51dd4389d1dea7c53480c3311dd19447d96b3c9eb06be5a935fd6"}, + {file = "pyobjc_framework_imagecapturecore-10.3.1.tar.gz", hash = "sha256:9ce83c38b8ccee6b022faadb9cd7b8716119092cd41b6c2cabce3670101119bf"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-inputmethodkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework InputMethodKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-InputMethodKit-10.1.tar.gz", hash = "sha256:b995f43a8859016474098c894c966718afe9fbcc18996ce3c6bebfc6a64cfad7"}, - {file = "pyobjc_framework_InputMethodKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5288d12d1a2a6da9261c0cadbee03f31c80a0a3bb77645b4e7c2836864f54533"}, - {file = "pyobjc_framework_InputMethodKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e04ac004ac848492242fda193e63322abce87ecdef081f1b7268cac7f2af8ad"}, - {file = "pyobjc_framework_InputMethodKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:538d5955a8ab3a9c7a7286c72dba87634ba0babe7cd0a4cec335100df8789c01"}, + {file = "pyobjc_framework_InputMethodKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:d1c3beb88f94eb6b0bdc458ef49f331d064c0260758134cef1cf941684f46c83"}, + {file = "pyobjc_framework_InputMethodKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d0f7538ac549b61afc93df895375114464f778e06a66a4c2684cbbf247a8f4c7"}, + {file = "pyobjc_framework_InputMethodKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7f5ff42a59209f3ede24181e5d1bb8d69f8ad428189f177c1bfd55d328f85575"}, + {file = "pyobjc_framework_InputMethodKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3fcd444b97cf2343a6c37b828dcc85080cc6a2c5ba508010dae4ebe836480d2b"}, + {file = "pyobjc_framework_inputmethodkit-10.3.1.tar.gz", hash = "sha256:637ba2da38da5f558443b4529b33bab276380336e977807347ee9dad81d42109"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-installerplugins" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework InstallerPlugins on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-InstallerPlugins-10.1.tar.gz", hash = "sha256:4c9f8b46d43f1277aad3bea648f84754e9f48251a6fb385ad8119c1b44dffe9b"}, - {file = "pyobjc_framework_InstallerPlugins-10.1-py2.py3-none-any.whl", hash = "sha256:195e7d559421bf36479b085bf74d56f8549fff715596fc21e0e0c95989a3149a"}, + {file = "pyobjc_framework_InstallerPlugins-10.3.1-py2.py3-none-any.whl", hash = "sha256:2b32cde6fb8bbb3e1ffd04d7acbe3132291ad5937fc7af5820062e8aece7b5cc"}, + {file = "pyobjc_framework_installerplugins-10.3.1.tar.gz", hash = "sha256:280808bbce36090b59197756fdb56c19838845a5fc25966a435dbc5fc4ddbbf0"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-instantmessage" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework InstantMessage on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-InstantMessage-10.1.tar.gz", hash = "sha256:8e8e7e2c64a3a6b0aa67cace58f7cea1971bb93de57be40b7ba285e305fab0b5"}, - {file = "pyobjc_framework_InstantMessage-10.1-py2.py3-none-any.whl", hash = "sha256:c03a9a99faaa14ff0a477114b691d628117422a15995523deb25ff2d1d07a36d"}, + {file = "pyobjc_framework_InstantMessage-10.3.1-py2.py3-none-any.whl", hash = "sha256:51a096e55a423423dbfbf19cc976a49915987ce68b9038f8ce3db9c3cde11718"}, + {file = "pyobjc_framework_instantmessage-10.3.1.tar.gz", hash = "sha256:bb1560a2f92a2def179b6381c17d406331b7818fa0fd1ba98f09ed94415f8a7b"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-intents" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Intents on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Intents-10.1.tar.gz", hash = "sha256:85a84a5912b8d8a876767ca8fa220dc24bf1c075ed81b58c386d25c835cec804"}, - {file = "pyobjc_framework_Intents-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:64a3cef4af536de1153937d99a4cb8d0568ca20ee5c74458dca4f270b01a3c1a"}, - {file = "pyobjc_framework_Intents-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:89f49f82245d4facb329dd65434a602506246e6585f544ab78b0ab4bd151f4f7"}, - {file = "pyobjc_framework_Intents-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:bc6f93d151c4474150b6c76fe43067d2d0d06446851d66df3bb9968682a2d225"}, + {file = "pyobjc_framework_Intents-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:4d5a899e8cffd63096aec69edafac78e76d591afd07059a96377d6893ba56649"}, + {file = "pyobjc_framework_Intents-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b6da33ba8a1e6ae169eb67c1c83bf4e0aeb365fb368162ba002ef26cdc9f853f"}, + {file = "pyobjc_framework_Intents-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:332e5b74dceb8315e289d462058b9ec65544562936098b50b8003999954032b6"}, + {file = "pyobjc_framework_Intents-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:6e77fea5fc4136888459c8ece7d48fe3eac3a37c665e9ba8aaeb18c1671eb083"}, + {file = "pyobjc_framework_intents-10.3.1.tar.gz", hash = "sha256:89f0ed49c515b75c8811d9f6771ac635e799dfaf11921172729f8e0857c8c0e9"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-intentsui" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Intents on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-IntentsUI-10.1.tar.gz", hash = "sha256:01948fbd8f956a79d3c2e27f75bc9954ad12cb4113982f58654122cfa8095ebb"}, - {file = "pyobjc_framework_IntentsUI-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e829659751ff47e3b85980075897ddebbf62d5644478c1bb2ff1dcdc116b8897"}, - {file = "pyobjc_framework_IntentsUI-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0d6ac451433ec0602c661b32216cd3c44b1c99b9f41781b3af79b7941118552"}, - {file = "pyobjc_framework_IntentsUI-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1806e6189cf09c0b031594ad445da1a93c30c399298c6fce2369a49bac7eade4"}, - {file = "pyobjc_framework_IntentsUI-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:33518f549b6c501d7c6c36542154ae5d2255d7223804470e14cd76b325676a48"}, - {file = "pyobjc_framework_IntentsUI-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:91498d3cf4098fe412ea66c01b080919906dd23d53653d49addc7a26c50e570f"}, - {file = "pyobjc_framework_IntentsUI-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c73626bfad3f098eed4a446cee90154dec39d9a9c0775532980c5266bc91a4c"}, + {file = "pyobjc_framework_IntentsUI-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:23a606af7ea47bd899012b896e0b2d10c677f7facec80197ab45a3bcf899874b"}, + {file = "pyobjc_framework_IntentsUI-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd2fed37c96f1d97abcbd6d98b2da90ba2c744f968e2c4e0735dce77bbbc95f4"}, + {file = "pyobjc_framework_IntentsUI-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e9fe0ba78c9dd500ef9c13227dd1a60e39df460c84180d8325f5022edd80178b"}, + {file = "pyobjc_framework_IntentsUI-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6c124ceb8e7b7a1f3fb6c2c159e47f9dca42ece6e1645d763235660ea98e7915"}, + {file = "pyobjc_framework_IntentsUI-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bfed2841fc9099cad850e058e3dfa23845a0988e53666f5ffc82cd1b91bbe824"}, + {file = "pyobjc_framework_IntentsUI-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d6a802bc5ccb01a22c312f4dfaf88300a2edd4db2e2f7568b10d29176ae05edd"}, + {file = "pyobjc_framework_IntentsUI-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3ae02ba54c689cd672bfc49c039d18cc5e9515d7d95608bcbb34357ae90fd9ff"}, + {file = "pyobjc_framework_intentsui-10.3.1.tar.gz", hash = "sha256:68f42cabbd36889060d07b21f156f1dae78243d42b34c652448c687d07cbca62"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Intents = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Intents = ">=10.3.1" [[package]] name = "pyobjc-framework-iobluetooth" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework IOBluetooth on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-IOBluetooth-10.1.tar.gz", hash = "sha256:9a30554f1c9229ba120b2626d420fb44059e4aa7013c11f36ab9637dc3aba21f"}, - {file = "pyobjc_framework_IOBluetooth-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5fd71294464b9d891d3a7ebb674bcc462feb6fbdf33ebd08c1411ef104460f7f"}, - {file = "pyobjc_framework_IOBluetooth-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:88e44f1bb3495c3104d9b0a73b2155e4326366c5f08a6e31ef431ab17f342b24"}, - {file = "pyobjc_framework_IOBluetooth-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:15c1a049e1431996188352784defe54a37181da38a7e5a378fcda77fdc007aea"}, + {file = "pyobjc_framework_IOBluetooth-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:cdd8bd3da07c6935657bbcf47e6fc6b3b7a2ed9bd7e8ef83bbbd5f4a0e8884f3"}, + {file = "pyobjc_framework_IOBluetooth-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d5c7ca666050b63c5dd87ede54beebdba7fc12a60bd754d77155da9f7e60618c"}, + {file = "pyobjc_framework_IOBluetooth-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9f90d8ea40f770b41ad97e17e19ad10e49daa1684f750e681db42707dbec9768"}, + {file = "pyobjc_framework_IOBluetooth-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:17303009a3c1ac48eaaa2c3a96a114ef47adcb1fffbf965e7538447bb02adde1"}, + {file = "pyobjc_framework_iobluetooth-10.3.1.tar.gz", hash = "sha256:bca424889d7fdd5bcb728d312e91ee681e73c0c2ac16ba37068603d699043d39"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-iobluetoothui" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework IOBluetoothUI on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-IOBluetoothUI-10.1.tar.gz", hash = "sha256:979c0d9638c0f31e62afe90d8089e61a912d08e0db893a47d3e423b9b23e0db2"}, - {file = "pyobjc_framework_IOBluetoothUI-10.1-py2.py3-none-any.whl", hash = "sha256:809eeb98ce71d0d4a7538fb77f14d1e7cd2c2b91c10605fb8c0d69dbac205de5"}, + {file = "pyobjc_framework_IOBluetoothUI-10.3.1-py2.py3-none-any.whl", hash = "sha256:ae283c3fecbeb99adba9b3c3d5d06caaad741da726fc7a1dd50ecc0376e03ae8"}, + {file = "pyobjc_framework_iobluetoothui-10.3.1.tar.gz", hash = "sha256:6db82aeb360641b878f8ed73c2074db0425664d9411317b2e01962e0929fa29c"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-IOBluetooth = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-IOBluetooth = ">=10.3.1" [[package]] name = "pyobjc-framework-iosurface" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework IOSurface on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-IOSurface-10.1.tar.gz", hash = "sha256:e41c635c5e259019df243da8910675db10480a36d0c316539a8ab3fa0d941000"}, - {file = "pyobjc_framework_IOSurface-10.1-py2.py3-none-any.whl", hash = "sha256:46239320148b82c1f2364d5468999b48fa9c94fc404aff6c5451d23896ece694"}, + {file = "pyobjc_framework_IOSurface-10.3.1-py2.py3-none-any.whl", hash = "sha256:4171a33a09ee006ad28ba29e6d12cee821e2c0ba09b4beddae8db16580fb9bc7"}, + {file = "pyobjc_framework_iosurface-10.3.1.tar.gz", hash = "sha256:94e4a109a94f0e365bd60ce68aab6ff166fef6f30a40f7682c76902f5fc3aa34"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-ituneslibrary" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework iTunesLibrary on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-iTunesLibrary-10.1.tar.gz", hash = "sha256:18766b2fb016d33cde8ec2c81b05ddfb77d65cb8c92e16864d0c288edd02e812"}, - {file = "pyobjc_framework_iTunesLibrary-10.1-py2.py3-none-any.whl", hash = "sha256:043a2ede182f41a3ca70be50bf95f18641e2945f0077797ff2bb42a3e1984e37"}, + {file = "pyobjc_framework_iTunesLibrary-10.3.1-py2.py3-none-any.whl", hash = "sha256:9485e986f6075d04e10c196e5dc36e4c3b60116a45849683a61c876e5fb45fde"}, + {file = "pyobjc_framework_ituneslibrary-10.3.1.tar.gz", hash = "sha256:3899f8555ae02f6441a711892cdc6537404215b3d5f8a7ea4594f7460c58c9b2"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-kernelmanagement" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework KernelManagement on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-KernelManagement-10.1.tar.gz", hash = "sha256:da8ac0e6a2de33b823e07ce0462a64340cfebd04f24426b1022374933bbd8d0a"}, - {file = "pyobjc_framework_KernelManagement-10.1-py2.py3-none-any.whl", hash = "sha256:923ff2bbab35a92b9becd9762348f6f690fa463ef07a0e5c4a2b8eb1d3e096af"}, + {file = "pyobjc_framework_KernelManagement-10.3.1-py2.py3-none-any.whl", hash = "sha256:e07134bf3815370d3d9c37f9813edec12758f86fdbbbc67036ab72e8b767ddee"}, + {file = "pyobjc_framework_kernelmanagement-10.3.1.tar.gz", hash = "sha256:04c41bc0d0ce014318acf9e333aba302092d2698ec408cbf0b022f3a507ecfa1"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-latentsemanticmapping" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework LatentSemanticMapping on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-LatentSemanticMapping-10.1.tar.gz", hash = "sha256:46e95532c71083d1e63bcfa4b89a56fcf860288f8fb04fc0313e4c40685d1916"}, - {file = "pyobjc_framework_LatentSemanticMapping-10.1-py2.py3-none-any.whl", hash = "sha256:f0b14a1a2a6d6b25b902a2cc5949f0145926f0b0a3132d17210b1a580dc7f0f5"}, + {file = "pyobjc_framework_LatentSemanticMapping-10.3.1-py2.py3-none-any.whl", hash = "sha256:c80c709b983273c8f29e86a04c52e98e8e6b0e723a400f7d6036fcabfd850367"}, + {file = "pyobjc_framework_latentsemanticmapping-10.3.1.tar.gz", hash = "sha256:0bca94fd00f59f49874c8266bfacb09a7c56ad13b4d405c241421cef201f8943"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-launchservices" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework LaunchServices on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-LaunchServices-10.1.tar.gz", hash = "sha256:c0ef72f7cee77556c81e620ae8c511e73bdea4f644a233c8a5e3a333f5cd3d7d"}, - {file = "pyobjc_framework_LaunchServices-10.1-py2.py3-none-any.whl", hash = "sha256:b792a863427a2c59c884952737041e25ef05bdb541471ce94fb26a05b48abbbc"}, + {file = "pyobjc_framework_LaunchServices-10.3.1-py2.py3-none-any.whl", hash = "sha256:3ce840027b43c4bd95dc31aaa9b4bfff1d431e782669b4c95e2b12d386c05000"}, + {file = "pyobjc_framework_launchservices-10.3.1.tar.gz", hash = "sha256:7f16af2acabca0c2953eb7333bfe652bf853bb9d9e59b771f9d228468bccdea3"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-CoreServices = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-CoreServices = ">=10.3.1" [[package]] name = "pyobjc-framework-libdispatch" -version = "10.1" +version = "10.3.1" description = "Wrappers for libdispatch on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-libdispatch-10.1.tar.gz", hash = "sha256:444ca20e348cbdd2963523b89661d67743a6c87a57caf9e5d546665baf384a5b"}, - {file = "pyobjc_framework_libdispatch-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da0fa1e63b7e72010c69341bcd2f523ade827c7f30e0ef5c901a2536f43a1262"}, - {file = "pyobjc_framework_libdispatch-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd72ff7f399079eaf8135503c3658b3ce967076a9e3fdcd155c8a589134e476a"}, - {file = "pyobjc_framework_libdispatch-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ec061cba47247a5fd5788c3b9d5eba30936df3328f91fea63a565d09c53a0a02"}, - {file = "pyobjc_framework_libdispatch-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c71c8c9dca56b0e89ac7c4aff4b53bc74f64a2290e48c31cc77d87771c5203bd"}, - {file = "pyobjc_framework_libdispatch-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b24a76fe2de4422685323e4f533b7bfd11a27edf06094c0f730f3f243f94a8bd"}, - {file = "pyobjc_framework_libdispatch-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:606954514e5747b05f9c608614f1affa44512888d18805452fade5d9b7938c14"}, + {file = "pyobjc_framework_libdispatch-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5543aea8acd53fb02bcf962b003a2a9c2bdacf28dc290c31a3d2de7543ef8392"}, + {file = "pyobjc_framework_libdispatch-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3e0db3138aae333f0b87b42586bc016430a76638af169aab9cef6afee4e5f887"}, + {file = "pyobjc_framework_libdispatch-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b209dbc9338cd87e053ede4d782b8c445bcc0b9a3d0365a6ffa1f9cd5143c301"}, + {file = "pyobjc_framework_libdispatch-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a74e62314376dc2d34bc5d4a86cedaf5795786178ebccd0553c58e8fa73400a3"}, + {file = "pyobjc_framework_libdispatch-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8e8fb27ac86d48605eb2107ac408ed8de281751df81f5430fe66c8228d7626b8"}, + {file = "pyobjc_framework_libdispatch-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:0a7a19afef70c98b3b527fb2c9adb025444bcb50f65c8d7b949f1efb51bde577"}, + {file = "pyobjc_framework_libdispatch-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:109044cddecb3332cbb75f14819cd01b98aacfefe91204c776b491eccc58a112"}, + {file = "pyobjc_framework_libdispatch-10.3.1.tar.gz", hash = "sha256:f5c3475498cb32f54d75e21952670e4a32c8517fb2db2e90869f634edc942446"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-libxpc" -version = "10.1" +version = "10.3.1" description = "Wrappers for xpc on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-libxpc-10.1.tar.gz", hash = "sha256:8e768bb3052b30ef3938c41c9b9a52ad9d454c105d2011f5247f9ffb151e3702"}, - {file = "pyobjc_framework_libxpc-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5a3efe43b370fdc4d166ddfd8d1f74b5c3ae5f9b273e5738253c3d9a2bebf27"}, - {file = "pyobjc_framework_libxpc-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc16190cbcaf8639f4783058ec63b1aa5d03e3586311f171177b9275ed5725d8"}, - {file = "pyobjc_framework_libxpc-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3f0756945995da4cb503dc9ca31b0633b7044722b08348a240ebe6f594d43c0c"}, - {file = "pyobjc_framework_libxpc-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8d5a7f06f437c6a23c469299a3a15b62f8b4661563499b0f04d9fe8ea5e75a95"}, - {file = "pyobjc_framework_libxpc-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9341237ffaedb3169037a666564615fefd921e190e6ec3e951dc75384169a320"}, - {file = "pyobjc_framework_libxpc-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:57eaa7242ef4afe3e8d1fbe48f259613322549353250400c8d508afff251dde4"}, + {file = "pyobjc_framework_libxpc-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9e287591a708f96e4e3d1929425a50c4e83188eb8bf3094b688de149946ac752"}, + {file = "pyobjc_framework_libxpc-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0afba29b6bf5c3de3ef51f60e06c026ab7be7ce44600047dece5d3bf4e758af"}, + {file = "pyobjc_framework_libxpc-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:123b72dff148a56d48120448bd9742190326f87661f4ae6f5363e112de0e554f"}, + {file = "pyobjc_framework_libxpc-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:72a35a21f4bbbfb4a5c4c23e3180c3273b7720fe4cd150b975cb5d08cbc4fe13"}, + {file = "pyobjc_framework_libxpc-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d14f6fa976df50de90affa41b4dc2381554fe1e8503564f54cd71a8c6cc4b0eb"}, + {file = "pyobjc_framework_libxpc-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:0d8a5d29952db92d9d7112c642ba7cdc9d8496118ec14e8186f93353b092a818"}, + {file = "pyobjc_framework_libxpc-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f4232996766f4c21f80da51344296651f40ec8fc14f353fca0bc75af718ee950"}, + {file = "pyobjc_framework_libxpc-10.3.1.tar.gz", hash = "sha256:b3c9e9fd281b5610e3bef486e91570b0afa8ab8eb0c01c0baa5e2ec49ccb7329"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-linkpresentation" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework LinkPresentation on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-LinkPresentation-10.1.tar.gz", hash = "sha256:d35f9436f6a72c0877479083118f57a42c0d01879df41ee832378bebef37e93c"}, - {file = "pyobjc_framework_LinkPresentation-10.1-py2.py3-none-any.whl", hash = "sha256:077c28c038b1aac0e5cd158cbf8b80863627f1254f0a1884440fabf95d46d62f"}, + {file = "pyobjc_framework_LinkPresentation-10.3.1-py2.py3-none-any.whl", hash = "sha256:e49ac094eb3a60a87f37edc24657feb051614a4d4464ad2580831288ead521f9"}, + {file = "pyobjc_framework_linkpresentation-10.3.1.tar.gz", hash = "sha256:535d452cc33d61074ba9bad7707d6c0a23d474fb045ed4322e5f87bfb0b7e865"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-localauthentication" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework LocalAuthentication on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-LocalAuthentication-10.1.tar.gz", hash = "sha256:e2b06bf7af1b6f8ba08bd59e1a3616732d801284362dd5482181b0b1488eca2d"}, - {file = "pyobjc_framework_LocalAuthentication-10.1-py2.py3-none-any.whl", hash = "sha256:3df6ac268f79f28e5b5e76b4fd6e095bdc9a200e1908f24cc33e805fa789f716"}, + {file = "pyobjc_framework_LocalAuthentication-10.3.1-py2.py3-none-any.whl", hash = "sha256:565910d7d2075cd53c6d4ffdbb15d9b93267f1b1ba4c502d354778865d0dc2ec"}, + {file = "pyobjc_framework_localauthentication-10.3.1.tar.gz", hash = "sha256:ad85411f1899a2ba89349df6a92db99fcaa80a4232a4934a1a176c60698d46b1"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Security = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Security = ">=10.3.1" [[package]] name = "pyobjc-framework-localauthenticationembeddedui" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework LocalAuthenticationEmbeddedUI on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-LocalAuthenticationEmbeddedUI-10.1.tar.gz", hash = "sha256:5a201e26c7710f8e8a6507dbb861baa545dc5abcbe0f3f6a19b2e270562c7bec"}, - {file = "pyobjc_framework_LocalAuthenticationEmbeddedUI-10.1-py2.py3-none-any.whl", hash = "sha256:a8e8101ca74441a862ffb8e2309fe382789c759d0951fb7b7b4e46652b4cb068"}, + {file = "pyobjc_framework_LocalAuthenticationEmbeddedUI-10.3.1-py2.py3-none-any.whl", hash = "sha256:d69ef723f4525e6476e51bd166d56e97c9274500f98aa209a659e7567793dc01"}, + {file = "pyobjc_framework_localauthenticationembeddedui-10.3.1.tar.gz", hash = "sha256:f915190f6106b9f9234750abf48f87445c364ccbca8f8dd565bba1b66ddd55a3"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-LocalAuthentication = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-LocalAuthentication = ">=10.3.1" [[package]] name = "pyobjc-framework-mailkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MailKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MailKit-10.1.tar.gz", hash = "sha256:a4589b13361a49ff0b3e9be43cd6f935a35acfc7a9f0da8b5db64283401da181"}, - {file = "pyobjc_framework_MailKit-10.1-py2.py3-none-any.whl", hash = "sha256:498d743e56d876d6d128970e6c0674470d9a4bcf9c021f0b115aa0c6ade1f5ae"}, + {file = "pyobjc_framework_MailKit-10.3.1-py2.py3-none-any.whl", hash = "sha256:7c403525e01bed0888946552e512ca4d1b018c3f6ef3d293fff85b90dc02a411"}, + {file = "pyobjc_framework_mailkit-10.3.1.tar.gz", hash = "sha256:90ad82786ae01a275aeec842e73b1fef12d9f91a67edcde8ff6a145859dc9f92"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-mapkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MapKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MapKit-10.1.tar.gz", hash = "sha256:4e5b295ce1e94ed38888a0c4e3a5a92004e63e6d2ba9a86b5a277bbe658ddf05"}, - {file = "pyobjc_framework_MapKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:7628e7d8e4181f06fc3138b7426593d09fe3d49a056e7e3d5853f7bbcc62b240"}, - {file = "pyobjc_framework_MapKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d5d78c56b2806148f7b4a2975e580bc039f1898ca8953041405683ba6c22f19b"}, - {file = "pyobjc_framework_MapKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b9c942b3a705021561de2a4e1590c58212131c2c7dc721290f68371558a42f35"}, + {file = "pyobjc_framework_MapKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:cfee94ad82e673eaebc3159732b5cff1fc4f7e3bee6f17cc4fabd641c260b769"}, + {file = "pyobjc_framework_MapKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:dd65d6eff1397afc0b2108b0e9387a6c2390b1387731a8e0dd8298b8d0641635"}, + {file = "pyobjc_framework_MapKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ffadc4968c3d815dd8ffc2867e962546a8681620c704888dfe1e7aa718cb3d90"}, + {file = "pyobjc_framework_MapKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f14f90bcb68baee70a838b3cecb9d909da43175da49cbe90dd6cca57c37a6ae5"}, + {file = "pyobjc_framework_mapkit-10.3.1.tar.gz", hash = "sha256:f433228c404b9ef4a66e808995daccc1306f7123296317651078a6a734ac1ab0"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-CoreLocation = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreLocation = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-mediaaccessibility" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MediaAccessibility on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MediaAccessibility-10.1.tar.gz", hash = "sha256:f487d83f948c12679c1ce06caabaedade1f4aee1f35163f835213c251a4639c7"}, - {file = "pyobjc_framework_MediaAccessibility-10.1-py2.py3-none-any.whl", hash = "sha256:2301cc554396efe449b079f99a0b5812e8e3dc364195dfcd2cc2b8a9c8915f11"}, + {file = "pyobjc_framework_MediaAccessibility-10.3.1-py2.py3-none-any.whl", hash = "sha256:c4304ea53c7e85320b58d773cce2288f62dcb5b9c5a295be1ecfaa6645a9fea6"}, + {file = "pyobjc_framework_mediaaccessibility-10.3.1.tar.gz", hash = "sha256:c249e1eee636e77b5f00db3bf85174cb3e0cb53ca991a17e53a1f200377f4289"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-medialibrary" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MediaLibrary on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MediaLibrary-10.1.tar.gz", hash = "sha256:cd4815cb270aa2f28acdad8185a4b9d4b76a6177f70e8ed62a610484f4bd44a9"}, - {file = "pyobjc_framework_MediaLibrary-10.1-py2.py3-none-any.whl", hash = "sha256:420d5006c624aaaf583058666fd5900a5619ff1f230e99cdd3acb76c12351a37"}, + {file = "pyobjc_framework_MediaLibrary-10.3.1-py2.py3-none-any.whl", hash = "sha256:25f16d610e3ea5d983175a6c07351596bd5dd2fcca194f1eac5686c670bbdb3b"}, + {file = "pyobjc_framework_medialibrary-10.3.1.tar.gz", hash = "sha256:703ffd0904fc86d4fbfbbd4952be43e91a6d477c53ce2868e4c18c3eb295f5c6"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-mediaplayer" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MediaPlayer on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MediaPlayer-10.1.tar.gz", hash = "sha256:394acea4fb61f8c4605494c7c64c52a105760aa2ec7e2c34db484e576ed10ad6"}, - {file = "pyobjc_framework_MediaPlayer-10.1-py2.py3-none-any.whl", hash = "sha256:10e25a8682cd0d1d8fc0041f0a34e8acf785b541d8c1ebe493c2d17caeef5648"}, + {file = "pyobjc_framework_MediaPlayer-10.3.1-py2.py3-none-any.whl", hash = "sha256:5b428cc28e57c1778bd431156c3adb948650f7503f266689559d0ece94b34e8a"}, + {file = "pyobjc_framework_mediaplayer-10.3.1.tar.gz", hash = "sha256:97043df5ef89d4fbe217813e8f4ee1e226d8a43dee4eac00fff95e6b8a7772be"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-AVFoundation = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-AVFoundation = ">=10.3.1" [[package]] name = "pyobjc-framework-mediatoolbox" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MediaToolbox on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MediaToolbox-10.1.tar.gz", hash = "sha256:56906cd90e1f969656db1fecd5874c6345e160044f54596c288fb0ffdb35cdc5"}, - {file = "pyobjc_framework_MediaToolbox-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:23b02872d910481a75db8eeb9c053a16b9a3cff1e030ca29d855ba8291c9501a"}, - {file = "pyobjc_framework_MediaToolbox-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c96557db9540ed18748f47d4cd0b2ba7273acb756bf7e91d8b2a943211850614"}, - {file = "pyobjc_framework_MediaToolbox-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:17ee7f045f39e3f11945bf951dfb4238c695ca49938e8b43c78fe12a8eb05628"}, + {file = "pyobjc_framework_MediaToolbox-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:d4ef616e233c40fcac99298ee4ab95a9b6439cffe28e96ce07b66c00f598bd30"}, + {file = "pyobjc_framework_MediaToolbox-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8acfa377b593b043e8c9ff1aa6cbbde92b7feaea60578ea2d0b61ac3edd15dc"}, + {file = "pyobjc_framework_MediaToolbox-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f989f07304dae82b1307e179f185a80b66ed36b0dd15d1b3bf6ec8092b766100"}, + {file = "pyobjc_framework_MediaToolbox-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4114fb3749dacf6dd83113823b846e77671c0e8b1209ca6e4402f09e6727c185"}, + {file = "pyobjc_framework_mediatoolbox-10.3.1.tar.gz", hash = "sha256:f141056dce0dc16ec21be596fea58acb4a668045f53e12a0f250990d936b44f2"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-metal" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Metal on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Metal-10.1.tar.gz", hash = "sha256:bde76fe5d285c9c875d411a7cf6cdd7617559eabf4fb9a588f90762a0634148c"}, - {file = "pyobjc_framework_Metal-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0701fe5e5aaa5471843fa0d5fe8fe3279313ff83c8bf5230ab6e11f7cba79a78"}, - {file = "pyobjc_framework_Metal-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5f542aaab62b9803e7e644b86dd82718aa9f1bcfc11cb4a666a59f248b3ae2e0"}, - {file = "pyobjc_framework_Metal-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:90ba5118ebf56a152e2404336ad7732dc60f252cd2414d34c9b32c07897f4512"}, + {file = "pyobjc_framework_Metal-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:4a42f3aa47eb5e0d3f29bf07f239c2ba33725701546ea6d3c5d019133e0fbce1"}, + {file = "pyobjc_framework_Metal-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:cc33d05e376d3a2f42da24b397f6353d9cb8e5990c79c9255a82637b7b8f256c"}, + {file = "pyobjc_framework_Metal-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:22762ba170d0fc4538dd7f582da1fd7673160e369eca74efe3d6d35bfdd50522"}, + {file = "pyobjc_framework_Metal-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7fef3ebfd6cfbd7cbfe45aaa86dd32034303d933871d8450f4c990dbdefda176"}, + {file = "pyobjc_framework_metal-10.3.1.tar.gz", hash = "sha256:495307db0bfd2063f28b7c8fa350af71afcfbf5f5f2186a6a751b4cb2ef46a1a"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-metalfx" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MetalFX on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MetalFX-10.1.tar.gz", hash = "sha256:64c96595c2489e41d93a1c75d1eace70619d973e5c9e90e7cfca29c934fc5d06"}, - {file = "pyobjc_framework_MetalFX-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3d456581a76fde824a9109f9dfdd3fc4819e81ae27527b23d2855656ed0ab6ed"}, - {file = "pyobjc_framework_MetalFX-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a3fd4384c83c0818484a3a90120131114a0866b2004309cda24ce873e4ff1e50"}, - {file = "pyobjc_framework_MetalFX-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:76a9ef5abf114c1e2d60b1e971619183f87918eafeb0719a281d1475c88592ad"}, + {file = "pyobjc_framework_MetalFX-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:ea4bed69e6a9ab4cd4fa6160554cb7df72316e849a34b839bf8d9c69ab445b24"}, + {file = "pyobjc_framework_MetalFX-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3ecae357688a4730191d57ffbc3db6abb950b756e8b98f23689d4bf08f34c20"}, + {file = "pyobjc_framework_MetalFX-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0179ce8ac828b6d6e2542d2d4f68850290eb3f16112205a758419225dd51b448"}, + {file = "pyobjc_framework_MetalFX-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b3ae624f7e0a5c60b856889753af560b7cbd1a5f47db01f120a668e429afd1c9"}, + {file = "pyobjc_framework_metalfx-10.3.1.tar.gz", hash = "sha256:3ea0f259397523a84a320b3925dcaaa5c039494accc3cb412b63e6f7f66f9513"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Metal = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Metal = ">=10.3.1" [[package]] name = "pyobjc-framework-metalkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MetalKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MetalKit-10.1.tar.gz", hash = "sha256:da0357868824e6ec506ff92d18d729f8462c4c5ca8f26ecc86e8c031d78fa80d"}, - {file = "pyobjc_framework_MetalKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8723c6b009bf0ce7eb77aa7bdc54f1ee6d0450a3bc2f8ce85523170e92a62152"}, - {file = "pyobjc_framework_MetalKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e94303a78a883e3aa53115c4ebb8329989fcf36be353e908252bba3ba3dc807d"}, - {file = "pyobjc_framework_MetalKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:184922a11f273e604f2c5af2e14ce1f4ef2dce0f5c09aadda857c5a5ca6acab1"}, + {file = "pyobjc_framework_MetalKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:c198c61d967812837538a793b1ff862bbd868d954abcd6ee558662c45c4dbf12"}, + {file = "pyobjc_framework_MetalKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:7c0155d731766be52cf18de6ad61339c16217bde330e17ef50808366856c1b85"}, + {file = "pyobjc_framework_MetalKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:48ae5e7c81c97e231c52036c8e2acb22bb59feaf0cb13f7678c87b16d9faaf9f"}, + {file = "pyobjc_framework_MetalKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:de632a7cdca1a0a13097a363dd441b9550ad91de6da6c88952c77acfd4b3a100"}, + {file = "pyobjc_framework_metalkit-10.3.1.tar.gz", hash = "sha256:905eaad9dce29082efd5cc56195337d2e8bff86ccfad36ec5127f818155ec038"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Metal = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Metal = ">=10.3.1" [[package]] name = "pyobjc-framework-metalperformanceshaders" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MetalPerformanceShaders on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MetalPerformanceShaders-10.1.tar.gz", hash = "sha256:335a49c69ac95e412c581592a148a32c0fcf434097e50da378f21fe09be13738"}, - {file = "pyobjc_framework_MetalPerformanceShaders-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a410b6dce52f7a2adebdb66891bfc33939ffe24bd75691fc30c1f7539521df86"}, - {file = "pyobjc_framework_MetalPerformanceShaders-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7218e89bccadc451f5ba040d84b049fe8b4a4bf7d9a4fdfb20fe6851e433cd49"}, - {file = "pyobjc_framework_MetalPerformanceShaders-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:9452e07d38c7a199c2eebb1280227285f918b97d3d597940e1e6e6471636b44a"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:a6f72fd528033ff0b176e909394edfc34f90b711fc6dcb225ba41b042929b71a"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:59c971d79c0d02a451571cd0122296aba05b46d7eecedea75ed8ce892d1119a1"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:969bc8caea30d71f963fb763c8d837f36d403e47c1ff6ed449ceb09783322944"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b02da6a5dc9088cadf38dce21aa39027bc668493876d3af684b91f039f123df2"}, + {file = "pyobjc_framework_metalperformanceshaders-10.3.1.tar.gz", hash = "sha256:1a9e91dc9e748834c95b7a596b943203761f6533352631c7abe612f804b23d50"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Metal = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Metal = ">=10.3.1" [[package]] name = "pyobjc-framework-metalperformanceshadersgraph" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MetalPerformanceShadersGraph on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MetalPerformanceShadersGraph-10.1.tar.gz", hash = "sha256:f587a0728e5240669d23a649f50bb25c10577f9775ba4f2576b19423575464a0"}, - {file = "pyobjc_framework_MetalPerformanceShadersGraph-10.1-py2.py3-none-any.whl", hash = "sha256:467e84983c5ded8cfaea8cb425872d5069eda8c4cc1f803ca3afaed0e184c763"}, + {file = "pyobjc_framework_MetalPerformanceShadersGraph-10.3.1-py2.py3-none-any.whl", hash = "sha256:a0288c53a024bc47348da2ccd8dc980d389dacc9d1d33b3412614e88732dc424"}, + {file = "pyobjc_framework_metalperformanceshadersgraph-10.3.1.tar.gz", hash = "sha256:4bf2045036f97dcaabbf16ee8527f1787c7e9366611b9b9ed4bfabc81c19343f"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-MetalPerformanceShaders = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-MetalPerformanceShaders = ">=10.3.1" [[package]] name = "pyobjc-framework-metrickit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MetricKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MetricKit-10.1.tar.gz", hash = "sha256:6887cec4b7aa489ec16af2f77f7c447bc0a0493456fe1c4910d95a5b3e587fcd"}, - {file = "pyobjc_framework_MetricKit-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02c8775000effbb00f6dde0a493b9ae955e54be4f9e72c4d0f2350d0864b46ac"}, - {file = "pyobjc_framework_MetricKit-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:49f6d9d9c46eb6605799fe0d898945cb62fc5c2d2fea1f7e51950765bbf7b03b"}, - {file = "pyobjc_framework_MetricKit-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6494dac683181dd1ca55b2fc912f693f51483a4e468a3fac05543539a643ca40"}, - {file = "pyobjc_framework_MetricKit-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cb7b318a2e2f4bb841a5427ab53448c827de0f2617123b804c41e6d595581321"}, - {file = "pyobjc_framework_MetricKit-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:5484980ef21e68389ed452f8a4d357f00dabf8711cb5268efe683f758441f23f"}, - {file = "pyobjc_framework_MetricKit-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7dc5a4da156e7f8724969ae329c8bae4fab58c2d7376ae96f62e2d646cc1175c"}, + {file = "pyobjc_framework_MetricKit-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad28bb10e452b33af285137fb89f573294759571fa02d66201b78304add91513"}, + {file = "pyobjc_framework_MetricKit-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59b6959d6f79f71080d386ad08665c19e52d0cc57943716da180bbb3369c9569"}, + {file = "pyobjc_framework_MetricKit-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f73d240b5e8f2351d6c2258b71a6d06b45ec964523f54acf05af50dc4ffac821"}, + {file = "pyobjc_framework_MetricKit-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:58dbfc7b9ae7701a59b9b2a5a5f874a9e393f10b27f39155714d1b49ea725226"}, + {file = "pyobjc_framework_MetricKit-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f462b68c22632f952d52b7c858d03727315107bb22c0a2152994baec1350534b"}, + {file = "pyobjc_framework_MetricKit-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:321ea41e7dcfb142f6ef91916cdfb5296a9888571f7c1ccd8799e51b1fc4a6c5"}, + {file = "pyobjc_framework_MetricKit-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3460fad9a31f6331884775905bb55d38db621b2f3613f44fd037777eb304d787"}, + {file = "pyobjc_framework_metrickit-10.3.1.tar.gz", hash = "sha256:f0b96fe9da0e26759f38d9e4cdf7d9c8be9c6ba35403bc8e675183a6f81dd0b3"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-mlcompute" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MLCompute on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MLCompute-10.1.tar.gz", hash = "sha256:02e947ddb90c236acb2cb34f41838e6c78cb070886ddfb98bb71a8f02f991fd6"}, - {file = "pyobjc_framework_MLCompute-10.1-py2.py3-none-any.whl", hash = "sha256:25ed4d3002bd33c039f4ad9bf05b4830d53f67282a8399df7c65bd1430a01183"}, + {file = "pyobjc_framework_MLCompute-10.3.1-py2.py3-none-any.whl", hash = "sha256:e5f8d98ee43fc795f44dab322299cf8957d7e6acb54139cecebfc7f4b2562b6c"}, + {file = "pyobjc_framework_mlcompute-10.3.1.tar.gz", hash = "sha256:9ac94b0a9511fedceacda846865daa05358eec5a4bf62be534b69eb4d7aced9b"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-modelio" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ModelIO on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ModelIO-10.1.tar.gz", hash = "sha256:75fe5405165264a5059c16bfd492593e3becba50811a47dedbfc699ff73d4bfb"}, - {file = "pyobjc_framework_ModelIO-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:6911dfa7821e1b97cf48b593d3ccd6c9f2401ed1715a677df3cdfdfeec7dad14"}, - {file = "pyobjc_framework_ModelIO-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e1a1f3b276999032ff13b1f985ae06a95b5ffc9c53771b10ea3496a70e809d58"}, - {file = "pyobjc_framework_ModelIO-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:8c6d43cca1c952858b2f31cab7b9ef01daae5aa1322240895d2e965cc72230cd"}, + {file = "pyobjc_framework_ModelIO-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:e841be15f1847ffe1d30e0efdbc57d3c6f1a2db7553946bc15dde0d8f57b620d"}, + {file = "pyobjc_framework_ModelIO-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:53d4cc794e0dbc94e622ed164556f82db723f9c3b2d9dbf72bdf44468eb4efa5"}, + {file = "pyobjc_framework_ModelIO-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:856e8d63c10238f4b23d30b9fb903d64b9f2b44e4f30f30a28bfc665e1adc5ac"}, + {file = "pyobjc_framework_ModelIO-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b59069c79c0865334e0036b1938e009addf035bfec36f7d4d871037c14c7accd"}, + {file = "pyobjc_framework_modelio-10.3.1.tar.gz", hash = "sha256:b1da37d10c38c63006d5173b49d18891b2db2c9acdbb6dbd21c73f17c0ccab7e"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-multipeerconnectivity" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework MultipeerConnectivity on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-MultipeerConnectivity-10.1.tar.gz", hash = "sha256:ab83e57953bb3f3476c77ed863e1138ab58a0711a77a1a11924b9d22e90f116b"}, - {file = "pyobjc_framework_MultipeerConnectivity-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e5770351b75484bbb4f6b0f0a20e0a0197a0b192a35228b087bc06f149242b0f"}, - {file = "pyobjc_framework_MultipeerConnectivity-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a06db4ee86ee85bd085659a965f1448b27bf0018f1842ae3fb6ec1c195b5352c"}, - {file = "pyobjc_framework_MultipeerConnectivity-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:becab88974b1669f5ca9c76dddb5591d4ed9acb4176980d763e22d298b6ba83d"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:1c0b65734f1e9b907c6198b035ad47c49819711c49694fe73cdf475be37db82e"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f0e466da15a3daf2c140be66da99ac0526b584fbd6dc08ed82e542e706964449"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5538caad124908481ee43755d439898f9d7eb0f200856ba246c730ca5fd77e15"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5350d13115b252bf6fa374dba1ef13ffd746b15b16f45d1b77b9231aebdf5b57"}, + {file = "pyobjc_framework_multipeerconnectivity-10.3.1.tar.gz", hash = "sha256:eb801d44194eb7eafcb0a21094c4ce78bcf41ed727125b048755838b59de3271"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-naturallanguage" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework NaturalLanguage on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-NaturalLanguage-10.1.tar.gz", hash = "sha256:b017a75d7275606f1732fef891198e916743871ca7663ddbb1ffae7d4d93a855"}, - {file = "pyobjc_framework_NaturalLanguage-10.1-py2.py3-none-any.whl", hash = "sha256:02bb4df955ecf329cf6da77ca6952777e5b2a10aee67452ea6314ec632cbc475"}, + {file = "pyobjc_framework_NaturalLanguage-10.3.1-py2.py3-none-any.whl", hash = "sha256:d0e47fad66bb74fa68b50093500f5cb49d8a772b522f8c92e725f2e65942dd9c"}, + {file = "pyobjc_framework_naturallanguage-10.3.1.tar.gz", hash = "sha256:49f19d0dba34802696a270d690db310ff03f1c85d6fb411734cb13667db90dd9"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-netfs" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework NetFS on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-NetFS-10.1.tar.gz", hash = "sha256:0bd9c58a0939df29729c0ab5c5fe3e7eb7fc066a15bd885ddbbbfc6d6b1524b6"}, - {file = "pyobjc_framework_NetFS-10.1-py2.py3-none-any.whl", hash = "sha256:a317c30a367af22f94858ca73cfe38a0dc4b63d0783f93532cb33780cd98a942"}, + {file = "pyobjc_framework_NetFS-10.3.1-py2.py3-none-any.whl", hash = "sha256:84faa7325c4ecc2f4ad199d8c52cebcb520ad2e7713f356c7a5a849839398d77"}, + {file = "pyobjc_framework_netfs-10.3.1.tar.gz", hash = "sha256:46466917f7b0aca3772bf4dfd586b583992c60ecd71c39f7d28ff7665d057637"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-network" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Network on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Network-10.1.tar.gz", hash = "sha256:39c02fdcac4e487e14296f5d60458b9a0cd58c2a830591a7cfacc0bca191e03f"}, - {file = "pyobjc_framework_Network-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5a31831cd9949efbc82bcea854ea3c22dcb5dc85072f909710cde666efd5cfb6"}, - {file = "pyobjc_framework_Network-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6704cbbf5f50d73208e7c9d92a1212f581280430da2606e07e88669120c82a36"}, - {file = "pyobjc_framework_Network-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:fe52bac9aa16429f7a138aad4cbb1e95a2be5c052c1cfda7e8c4dd16d1147dec"}, + {file = "pyobjc_framework_Network-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:56df88c10b948b3b09dd6d0e9061da33683e294d0450efd9076354f41e214f58"}, + {file = "pyobjc_framework_Network-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0336fc0b80a2481d3a032b94e7dfddbb8d0f1ec10e36e80ad424a028b00679ac"}, + {file = "pyobjc_framework_Network-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1e2cbc4d209b6789d8a3bd85cfe472bae50ca2d54355beb25b8336ed65d846e0"}, + {file = "pyobjc_framework_Network-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c665f3dcfb1375835dd0c6ce10079f22be73f92213fc3d48b176d9c67fc221a9"}, + {file = "pyobjc_framework_network-10.3.1.tar.gz", hash = "sha256:87a5839d4ab2ae452b4e563bd7a00569557ede4b8cd1eb77c973cdf45fb8f5ab"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-networkextension" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework NetworkExtension on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-NetworkExtension-10.1.tar.gz", hash = "sha256:f49a3bd117ca40a1ea8ae751aca630f7b7e7d7305aa5dfa969beb07299eb2784"}, - {file = "pyobjc_framework_NetworkExtension-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfc5a7b402c8daced465c6b18683930a2ece91e98134cc1801657ad0a9256b1e"}, - {file = "pyobjc_framework_NetworkExtension-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:dfd808295c3b68a2f225410a67645b184187848be86abc2e6ba90db27e5c470f"}, - {file = "pyobjc_framework_NetworkExtension-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a925cfdabb4d882e8b9c3524a729c3b683e7a7ca18e291509625d3e63d3840cb"}, + {file = "pyobjc_framework_NetworkExtension-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:baacdb4ad595d5c5ce9660e10ea476fab9bfd1a1def2357eae7918f5019bb8c0"}, + {file = "pyobjc_framework_NetworkExtension-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c1efc35dc4ddced3f0e5400e8ae9b28b7585f0cf5701023dd957f3cbd58d361f"}, + {file = "pyobjc_framework_NetworkExtension-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aeba32d9b9809bb1296d85da00f0c221daf54b25fd864dc9bf03a7007f5ad601"}, + {file = "pyobjc_framework_NetworkExtension-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:1c8ef5fce8846fb3bf459bedf7a31ff0428a9c3184c8b26ced8e322c956e8ec0"}, + {file = "pyobjc_framework_networkextension-10.3.1.tar.gz", hash = "sha256:c5a094862061565ca6d37457db42f55f344ec24dd7604ddf5d72e20ae7f63fdd"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-notificationcenter" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework NotificationCenter on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-NotificationCenter-10.1.tar.gz", hash = "sha256:697e29da4fdd5899e5db5bda7bdb5afc97f4a6e4d959caf2316aef3b300c5575"}, - {file = "pyobjc_framework_NotificationCenter-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f9078ba52e1cfa77c797a9aed972c182acfcc79cc2eb083c7b06ba76738b5f6d"}, - {file = "pyobjc_framework_NotificationCenter-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:25bf6f521f99ccb057d0df063242d5d28223672525706317134caa887ffd6b07"}, - {file = "pyobjc_framework_NotificationCenter-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7a6876f4b25023562ddf2558fba5e52d72a7ce3ec41c8e96533779d25e2b7722"}, + {file = "pyobjc_framework_NotificationCenter-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:2818922c2c3f45721515733b452d20a507368a87b793fa976c21945773582abc"}, + {file = "pyobjc_framework_NotificationCenter-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e08264759b84a21631e143afce1696920207d418be7f8853dbde18dbc7881667"}, + {file = "pyobjc_framework_NotificationCenter-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1560edad4e9fe01cbf3a70bb6e2668985e13903497d3fdc92276d6cecf9e4742"}, + {file = "pyobjc_framework_NotificationCenter-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:992ea7c86d0330cf10113b829525d74a95d97d0d7245e4e277f75ecbb37b596e"}, + {file = "pyobjc_framework_notificationcenter-10.3.1.tar.gz", hash = "sha256:3e6efe0fe6389601bb87086f5585fa7e74b2143236b7d5afd02b617a73656419"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-opendirectory" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework OpenDirectory on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-OpenDirectory-10.1.tar.gz", hash = "sha256:5d25c254876378966ce58e0de9e3d3594ca25922773d6526235b5e2f2c4103e1"}, - {file = "pyobjc_framework_OpenDirectory-10.1-py2.py3-none-any.whl", hash = "sha256:83601f3b5694b1087616566019c300aa38b2a15b59d215e96c5dae18430e8c96"}, + {file = "pyobjc_framework_OpenDirectory-10.3.1-py2.py3-none-any.whl", hash = "sha256:7e787e65409aad082faed2ed0c2cd52cccea61702d9c83b6acdcac3fa50a562f"}, + {file = "pyobjc_framework_opendirectory-10.3.1.tar.gz", hash = "sha256:cddc25632eebeb6bf0d886ae0fc919d574e458c597691226ba15bbf134ab51a6"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-osakit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework OSAKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-OSAKit-10.1.tar.gz", hash = "sha256:59ad344fed2ddbc24c5dad3345f596cd6ecb73e4c97a05051e3680709f66a42f"}, - {file = "pyobjc_framework_OSAKit-10.1-py2.py3-none-any.whl", hash = "sha256:af34b4dccc17a772d80c283c9356bdb5a5a300bd54c2557c26671aca4f2f86cb"}, + {file = "pyobjc_framework_OSAKit-10.3.1-py2.py3-none-any.whl", hash = "sha256:aaa60e6f455febe45f9be6487c59f11450de81bd7f49a6e4db576a38bcaf1c68"}, + {file = "pyobjc_framework_osakit-10.3.1.tar.gz", hash = "sha256:0af326b831fa29fca11ffe2b641807ad3c233be9eb403e62328fa784528da4ab"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-oslog" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework OSLog on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-OSLog-10.1.tar.gz", hash = "sha256:bfce0067351115ae273489768f93692dcda88bd5b54f28bb741c08855c114dfe"}, - {file = "pyobjc_framework_OSLog-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:57b2920c5c9393fb4fe9e1d5d87eabead32ebe821853d06d577bdb5503327a49"}, - {file = "pyobjc_framework_OSLog-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e94a3ce153fe72f7fe463e468d94e3657db54b133aaf5a313fb31b6b52ed60f2"}, - {file = "pyobjc_framework_OSLog-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:09e64565e4a293f3a9d486e1376f2c9651d5cec500b2c2245de9ae0baecfb29e"}, + {file = "pyobjc_framework_OSLog-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:4799619f9ae12287923e2bc39fc021c75ea4e8bcb0e8ff44201f1018d017db98"}, + {file = "pyobjc_framework_OSLog-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:7ff719367249c09318a87df47ef8e1c8d18ab5f4b9e94862e7ca9c8fad21ed9a"}, + {file = "pyobjc_framework_OSLog-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5ae0848bf6de69c95802f11bb4aff5c2edac5c5e6179b9a06a0e4fe05ed48b53"}, + {file = "pyobjc_framework_OSLog-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:cded4c706fcf3fd78ef257ab096f4f8cefc70cca81553d2fae88841aaf5d620d"}, + {file = "pyobjc_framework_oslog-10.3.1.tar.gz", hash = "sha256:592c3e50cf824c2c07779771aa0065de2dbb4c615de43e8949b39d19ba04d744"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-CoreMedia = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreMedia = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-passkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework PassKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-PassKit-10.1.tar.gz", hash = "sha256:bc19a46fad004137f207a5a9643d3f9a3602ea3f1d75c57841de986019a3c805"}, - {file = "pyobjc_framework_PassKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:011e2f32bc465b634d171a2500e6a153b479b807a50659cc164883bbeec74e59"}, - {file = "pyobjc_framework_PassKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:791f7d4317164130e8232e75e40ba565633a01bc5777dc3d0ba0a8b5f4aeab92"}, - {file = "pyobjc_framework_PassKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:58115c31a2e8b8a57ca048de408444cc4ba94da386656e0eeeac919b157f03a4"}, + {file = "pyobjc_framework_PassKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:ce09205aae4e15d3639d76a558c072ae106e8c7dafe9a451c5e27967498b74cd"}, + {file = "pyobjc_framework_PassKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:79ff6fa8ad4c0f9b4a992122b22e2b2b4200534221eb1bfe86bf473fb3c7ca23"}, + {file = "pyobjc_framework_PassKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bbb71ac6fc51da06d0fd6cb4d83eb79b704c2bab694a93899d3f83c753c9740b"}, + {file = "pyobjc_framework_PassKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d41409ee24eee2318023c3ba23218db152fc262ebcbea9021dc533fe80a73f32"}, + {file = "pyobjc_framework_passkit-10.3.1.tar.gz", hash = "sha256:4c3eea19c1ae3edf6e7858ab815bcd8ecf517a146928ce6a843910729372f010"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-pencilkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework PencilKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-PencilKit-10.1.tar.gz", hash = "sha256:712654dc9373014aa5472b10ba269d95f455c722ebb7504caa04dfae209ed63a"}, - {file = "pyobjc_framework_PencilKit-10.1-py2.py3-none-any.whl", hash = "sha256:baf856d274653d74d66099ae81005ddb3923f7128d36d2f87100481cbb8b2b27"}, + {file = "pyobjc_framework_PencilKit-10.3.1-py2.py3-none-any.whl", hash = "sha256:dc05376fbc5887391ff4e778b6f4b2fa20264aac58cd5fe5f47e4930186c1674"}, + {file = "pyobjc_framework_pencilkit-10.3.1.tar.gz", hash = "sha256:4dfd8e0179be5ecf67b768317a88d86d93df1c8674d422afa14957cf80e6e01f"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-phase" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework PHASE on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-PHASE-10.1.tar.gz", hash = "sha256:b7e0ef3567359a4f8ab3e5f8319f2201e775e3db6d7498409701664568c8c7c6"}, - {file = "pyobjc_framework_PHASE-10.1-py2.py3-none-any.whl", hash = "sha256:329cd6dd040a7ef8091dda9d8e57d9613bc9c8edf3cfd3af549f5cd9d64a0941"}, + {file = "pyobjc_framework_PHASE-10.3.1-py2.py3-none-any.whl", hash = "sha256:eec5a38983d65a4cf022dd01dc6f290ec163399e79592203443b4115ea3c8510"}, + {file = "pyobjc_framework_phase-10.3.1.tar.gz", hash = "sha256:5be2ea5d36ea9620f5278f5ad3b02cc243511be3b137aa28b1523e8f6da54f99"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-AVFoundation = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-AVFoundation = ">=10.3.1" [[package]] name = "pyobjc-framework-photos" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Photos on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Photos-10.1.tar.gz", hash = "sha256:eb0e83d01c8eb0652fac8382e68fd9643b7530f6580c2a51846444cae09ec094"}, - {file = "pyobjc_framework_Photos-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d91ead1ec33e05bf9e42b7df3f8fe7e3d4cf2814482f6878060c259453491d65"}, - {file = "pyobjc_framework_Photos-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f8415346689213b30488eb023d699c0512fcddeb7e1e4aa833860c312dddf780"}, - {file = "pyobjc_framework_Photos-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:61174248e07025d4696b6164346b43147250d03ae8378f70a458c0583e003656"}, + {file = "pyobjc_framework_Photos-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:9a382201dd14a3f3076d8689267d63520803a1ad1716fb66df5c1bb578bc6384"}, + {file = "pyobjc_framework_Photos-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:9c72efff52bed483b0dfaa569a21355f7620f25672a0245c5db8c1df86abc7b8"}, + {file = "pyobjc_framework_Photos-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4841948ce4fe3d5060ec08e760b0b6e476e923782f32b9af2ffe8eb2a4fbb385"}, + {file = "pyobjc_framework_Photos-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:50710703d18d0b13c48e02ab4edcca72a67ee20aac2a75664bfb725c8cb3a677"}, + {file = "pyobjc_framework_photos-10.3.1.tar.gz", hash = "sha256:8d538c399720062523694f7669aa82dcb75a7b192fb4aca93cf782d04e4c39be"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-photosui" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework PhotosUI on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-PhotosUI-10.1.tar.gz", hash = "sha256:4b90755c6c62a0668782cf05d92fca6277485f2cb3473981760c0dc0e40de1d8"}, - {file = "pyobjc_framework_PhotosUI-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:206641f2f7f3169fecc0014b9b93c89b5503841014e911d4686684de137c79f9"}, - {file = "pyobjc_framework_PhotosUI-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:02c1861bcce433294b00f6c614559addc87fcf57aaa1ede2b6dfea50a3795378"}, - {file = "pyobjc_framework_PhotosUI-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:8df716fb2e9994bfc2d716d6930defb3e3911a0337788ef36eea0c2eb0f899ad"}, + {file = "pyobjc_framework_PhotosUI-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:88665561d9ef7e9fd2ceb4e7e5da0fc3412a397d4c48a2121250462781106d30"}, + {file = "pyobjc_framework_PhotosUI-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3254605c33e090742d0303a3b5b8ddab5489dbf28ea8a287566106e6887ee561"}, + {file = "pyobjc_framework_PhotosUI-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9370e36bb4ae1e7128006ff6a6b43448ebf8586784749e2a1fa30bbb2ef8fedf"}, + {file = "pyobjc_framework_PhotosUI-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a756c3bbf945ceaa7d184c41053c2a9c1a89b32ffcdf752664b1180927b22cb2"}, + {file = "pyobjc_framework_photosui-10.3.1.tar.gz", hash = "sha256:e9eb961c6be1f3e00d76cc0a8ec15b50ac0692bd5b5c86268ad08f6d09cf390d"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-preferencepanes" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework PreferencePanes on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-PreferencePanes-10.1.tar.gz", hash = "sha256:3a26cd8959dac30203410eb432a361caf2a0b8552c74edd3d7406d722ecc1014"}, - {file = "pyobjc_framework_PreferencePanes-10.1-py2.py3-none-any.whl", hash = "sha256:9b16c93d7f684cbe097932c8260a0b6460ad9fc68230648981340b7e3ee7053e"}, + {file = "pyobjc_framework_PreferencePanes-10.3.1-py2.py3-none-any.whl", hash = "sha256:319b58b6c8f876f67e879b3a4f74afd6d892aa43a7f9ef4320819265b281c9e5"}, + {file = "pyobjc_framework_preferencepanes-10.3.1.tar.gz", hash = "sha256:eef150416a39a0109a8a37e9978ac4a55ad0b125ef6053a7431524ede5c69783"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-pubsub" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework PubSub on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-PubSub-10.1.tar.gz", hash = "sha256:7992344ae82d9566d300b3d2c92ff9fa9a28e060bd42d0988df351f5fb729156"}, - {file = "pyobjc_framework_PubSub-10.1-py2.py3-none-any.whl", hash = "sha256:af0b1ed0328f06d7d96390a4b95bfb4a790d5b38142825a222923f908dc46db9"}, + {file = "pyobjc_framework_PubSub-10.3.1-py2.py3-none-any.whl", hash = "sha256:dd8bc334b3acbdd82163d511bc71af7addc98dfaf473736545487f3168836dcd"}, + {file = "pyobjc_framework_pubsub-10.3.1.tar.gz", hash = "sha256:d123e75288c2f9d785ed1c4d92a96e5118c4b6f1cd9c55605eb4b4a74c2e36f4"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-pushkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework PushKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-PushKit-10.1.tar.gz", hash = "sha256:12798ad9ae87f7e78690d2bce2ea46f0714d30dd938f5b288717660120a00795"}, - {file = "pyobjc_framework_PushKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:99edcd057d5cc7e015fe6b464b83f07c843fba878f5b0636ff30cd6377ec2915"}, - {file = "pyobjc_framework_PushKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:986216b9021ed6aff3a528c2b6a3885426e8acac9a744397ede998d2e7a83d06"}, - {file = "pyobjc_framework_PushKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:cf27a49a3b9eadde0bc518b54f7b38fd5d0e1c2203350d1286527b6177afa3c3"}, + {file = "pyobjc_framework_PushKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:39e21ac2b9a529e99d6d33c439c96d4d2bb97d2c56bd6d92b2c5bd497e925851"}, + {file = "pyobjc_framework_PushKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e20f94ad3fbebf34c613c880e655d0f918d891b0a70763f99bb5d11e0af65c73"}, + {file = "pyobjc_framework_PushKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:79f0e66b88473e7fdef304dce06d7d63c3e5e38b12deafcd06a5bd3506ed9398"}, + {file = "pyobjc_framework_PushKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ee90959fc7b171930cffdf07cb7865d56a2cb4b723c5826ccf95d6e865dee4bd"}, + {file = "pyobjc_framework_pushkit-10.3.1.tar.gz", hash = "sha256:cc4da5382cf48c29637af1c633490203358a6ab0c76f0c006079cf144eeb9568"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-quartz" -version = "10.1" +version = "10.3.1" description = "Wrappers for the Quartz frameworks on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Quartz-10.1.tar.gz", hash = "sha256:b7439c0a3be9590d261cd2d340ba8dd24a75877b0be3ebce56e022a19cc05738"}, - {file = "pyobjc_framework_Quartz-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:69db14ac9814839471e3cf5a8d81fb5edd1b762739ad806d3cf244836dac0154"}, - {file = "pyobjc_framework_Quartz-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ddcd18e96511e618ce43e288a043e25524c131f5e6d58775db7aaf15553d849"}, - {file = "pyobjc_framework_Quartz-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c4257a2fb5580e5ebe927a66cf36a11749685a4681a30f90e954a3f08894cb62"}, - {file = "pyobjc_framework_Quartz-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28315ca6e04a08ae9e4eaf35b364ee77e081605d5865021018217626097c5e80"}, - {file = "pyobjc_framework_Quartz-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9cb859a2fd7e15f2de85c16b028148dea06002d1a4142922b3441d3802fab372"}, - {file = "pyobjc_framework_Quartz-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:993c71009e6374e57205e6aeaa577b7af2df245a5d1d2feff0f88ca0fa7b8626"}, + {file = "pyobjc_framework_Quartz-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ef4fd315ed2bc42ef77fdeb2bae28a88ec986bd7b8079a87ba3b3475348f96e"}, + {file = "pyobjc_framework_Quartz-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:96578d4a3e70164efe44ad7dc320ecd4e211758ffcde5dcd694de1bbdfe090a4"}, + {file = "pyobjc_framework_Quartz-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ca35f92486869a41847a1703bb176aab8a53dbfd8e678d1f4d68d8e6e1581c71"}, + {file = "pyobjc_framework_Quartz-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:00a0933267e3a46ea4afcc35d117b2efb920f06de797fa66279c52e7057e3590"}, + {file = "pyobjc_framework_Quartz-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a161bedb4c5257a02ad56a910cd7eefb28bdb0ea78607df0d70ed4efe4ea54c1"}, + {file = "pyobjc_framework_Quartz-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d7a8028e117a94923a511944bfa9daf9744e212f06cf89010c60934a479863a5"}, + {file = "pyobjc_framework_Quartz-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:de00c983b3267eb26fa42c6ed9f15e2bf006bde8afa7fe2b390646aa21a5d6fc"}, + {file = "pyobjc_framework_quartz-10.3.1.tar.gz", hash = "sha256:b6d7e346d735c9a7f147cd78e6da79eeae416a0b7d3874644c83a23786c6f886"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-quicklookthumbnailing" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework QuickLookThumbnailing on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-QuickLookThumbnailing-10.1.tar.gz", hash = "sha256:b6c4ea3701cb18abaffcceb65adc9dcfd6bb28811af7a99148c71e71d538a3a6"}, - {file = "pyobjc_framework_QuickLookThumbnailing-10.1-py2.py3-none-any.whl", hash = "sha256:984e4e92727caa5b2ebbe8c91fcde6acc416f15dd8e7aef94cb3999c4a7028ec"}, + {file = "pyobjc_framework_QuickLookThumbnailing-10.3.1-py2.py3-none-any.whl", hash = "sha256:e5095c0ad8cf1f91a6ed9aa5c4fb7c9b1da122d495ce131bae8d7faa06da9505"}, + {file = "pyobjc_framework_quicklookthumbnailing-10.3.1.tar.gz", hash = "sha256:ee26be78df9ce46ffa6f971f4ce167a0e98f38167aeb86cfc1b41270f15c96a3"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-replaykit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ReplayKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ReplayKit-10.1.tar.gz", hash = "sha256:c74d092afd8da7448e3b96a28d9cde09ad11269b345a5df21ce971c87671e421"}, - {file = "pyobjc_framework_ReplayKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:592cd22d78a92691d3dce98cd526e95fbb692142541a62c99d989c8941ec9f55"}, - {file = "pyobjc_framework_ReplayKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:05358af8daef82de6fa40fb5e04639d0f29d3f22f34b0901d5a224f8d2a7da69"}, - {file = "pyobjc_framework_ReplayKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:771af451363b7c81c920a1290f673501762da6f691f54920d0866028098390dd"}, + {file = "pyobjc_framework_ReplayKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:1d89020e770763947c0e3e3a201b541b81b455656e574d390ad0b0e32a67640a"}, + {file = "pyobjc_framework_ReplayKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b753658337d25964acb83f5edc465b772f276b4c88fc8bd03ad633b4466f6bd4"}, + {file = "pyobjc_framework_ReplayKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fafbc37ef6e44ac5019b4a4b751b3de6d5983500705e8dea2fc62134f8c0dc24"}, + {file = "pyobjc_framework_ReplayKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:22ca748ae8325e41e0526f13822d2c477ab879cb2d454eee1db47ca91353c986"}, + {file = "pyobjc_framework_replaykit-10.3.1.tar.gz", hash = "sha256:21762c8674b629fb670c3cbd515c593f1b5f98ee24ee4834a09055cb08849068"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-safariservices" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SafariServices on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SafariServices-10.1.tar.gz", hash = "sha256:5a9105d3aea43cd214583acd06609f56ed704ce816cb103916324e8ed8388fce"}, - {file = "pyobjc_framework_SafariServices-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f672434748e7d9b303535969bcb03d99cdbf79162292ad439c0347455f38f1db"}, - {file = "pyobjc_framework_SafariServices-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:64d37455a8bd541bd799604ee9e41120cc7c9c19f26776b6d8e16f1902738b70"}, - {file = "pyobjc_framework_SafariServices-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a5aa2fb6333ec0929f6b9689992b76eb6442e5ef4bad8b5a72c7796f24898868"}, + {file = "pyobjc_framework_SafariServices-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:c8afb977b5858d3fd27136146db46a9f2ec43c35f5af269dbcb51c46a422dd7e"}, + {file = "pyobjc_framework_SafariServices-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:adb0332cbfa086892524efa253c873ecc4a8b00c60db45be3362759c4b4ae87d"}, + {file = "pyobjc_framework_SafariServices-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7a171f08527c6bd297633e72bc597d68ec74419b5315f8c8122311537a8a6340"}, + {file = "pyobjc_framework_SafariServices-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:aaba0ea92410820d94fef0b93d337478f0e416967cb2aa4e667dd4d1bb561c1e"}, + {file = "pyobjc_framework_safariservices-10.3.1.tar.gz", hash = "sha256:9c5278576e7c28c3d93e74ebe5d39d07c5c91572ddf03ea01cc45d9a06dc8d0a"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-safetykit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SafetyKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SafetyKit-10.1.tar.gz", hash = "sha256:f1ac7201d32129c9c1a200724a5d3e75c6da8793f9c8a628be206cdebcd548e5"}, - {file = "pyobjc_framework_SafetyKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3499fd9d85c8c93ae7be2949c1b2e91e0f74b9a0d39be9c66440c40195ef4633"}, - {file = "pyobjc_framework_SafetyKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ab7d47dbcdeafb56f0c2c6e1be847e74840038c19fecbbaf883e68cd44511eb9"}, - {file = "pyobjc_framework_SafetyKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5c68ab2994c21bd32540595ec92922b0234e48fbb6998fcb22bacee46286e999"}, + {file = "pyobjc_framework_SafetyKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:e554cb94bc2f89f525ccaa4ed827390d188eba8bb24049acdc764f3dfee9af08"}, + {file = "pyobjc_framework_SafetyKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8eb160cc4f3b7e6d58f2eecc205f016afd5f4278b9641e49d5ef9c4df7b6df8c"}, + {file = "pyobjc_framework_SafetyKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3674b92d8b72d0a6006cb899ec2f605d1d5a6a59ff2d90c51ee2ccfd0f069d10"}, + {file = "pyobjc_framework_SafetyKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ac700aa2cbddc8a31626815886e42d0d1591572914c2b15df2dd07871ee84568"}, + {file = "pyobjc_framework_safetykit-10.3.1.tar.gz", hash = "sha256:8421be801ce29053e67a2c91673913562c3ad9d4bb1fbaa934a3a365d8bff12d"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-scenekit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SceneKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SceneKit-10.1.tar.gz", hash = "sha256:f6565f3dba3bacf6099677ef713f9c95bcb9d8c4ea755c1866d113f95f110fc9"}, - {file = "pyobjc_framework_SceneKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:70d3a7f78238255bf62fab33a3e9ac20e13ec228eafd1aa0ef579b3792e5d9b9"}, - {file = "pyobjc_framework_SceneKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1bbdee819b638530c53271a4f302357cf8c829dbfc6e40b062335c900816bb01"}, - {file = "pyobjc_framework_SceneKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e179e37613814661be86c8316dd92497012cec48bb4febdc3d432ac5e7594a3f"}, + {file = "pyobjc_framework_SceneKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:7a108ef1a660a7928d1d44e415c84f544d296745d5350235addaab82777b080a"}, + {file = "pyobjc_framework_SceneKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:08fb5dd8c10a5624a0bf3dd9f448b2fe4c8b9c7c3c98708d28b0b4c72dd2a107"}, + {file = "pyobjc_framework_SceneKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:08601223416fa048d9468f961b170e3c2009d7e2434eea92c12ee12f6c672961"}, + {file = "pyobjc_framework_SceneKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:2984a543221190547bff603a6a59c931cda7099b91ac7412e11c962e8f6c3bc1"}, + {file = "pyobjc_framework_scenekit-10.3.1.tar.gz", hash = "sha256:99cf0db3055d9bae0a8643400e528a8c012235db8ee6a1864ea0b03a0854c9d0"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-screencapturekit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ScreenCaptureKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ScreenCaptureKit-10.1.tar.gz", hash = "sha256:a00d85c97bf0cdd454d57181c371f372b8549c4edd949e2b66f42782f426f855"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bc05248b9ae9ed4aa474b4e54927216046c284a4c6c27d30db9df659887b7b1d"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98eaec608bd9858a265541b14d6708bcc0b8c8276c2a5b41b80d828c0c2a8c64"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ff8657865f6280a942d175b87933ff0f1b6064e672a7f1efb5e66d864b435c27"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:30615c2a9f0a04cca41afe0cee21e3179f72f055e9cac94fe1e4f31fcccb0919"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2cbd9957e9823615a494b2fd6815688eb0ad2eed7df007b25e3f7d83261653a9"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3b732bad05c973844ea3b25ccabf0d41b4c3eec4f7b5d78650337685cb43142"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e69a1e15502543c80c084399d7e06d6ebfb089a3afd248860e24dd03e654456a"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0953f8d43bd7f0012decebe34401d361c4a64472190960204b3214e4850e4ef2"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a80928dc483046ac72294fd576c53f793045aad700b740ec9591b23a3ccc011d"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4d8e280a076e7bc40eaa9730f97da84421b891c30f6e3fdea4f6c30bdb298243"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:757a62e30ae68307d4705288bb808d91e10569c8ba3b54192bb5bbaaddcfa802"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:c55a2bdc3ff934841a668db1b5b2f74b9352696419ce87fea31637b89a04b6c7"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a0ccf558123fe0b7fcfbb74280678aa0f0973c96765a03cb658fd4d8c5e732e9"}, + {file = "pyobjc_framework_screencapturekit-10.3.1.tar.gz", hash = "sha256:cb1a2e746e0f98ea14a11ea35d059d38587340beeeb905812085e2c7ceb14e0c"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-CoreMedia = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreMedia = ">=10.3.1" [[package]] name = "pyobjc-framework-screensaver" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ScreenSaver on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ScreenSaver-10.1.tar.gz", hash = "sha256:d1b890c7cae9e5c43582fe834aebcb6a1ecf52467a8ed7a28ba9d873bbf582d5"}, - {file = "pyobjc_framework_ScreenSaver-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b464de6398ef3a700c4ac19ed92b25cf2d30900b574533a659967446fddede3b"}, - {file = "pyobjc_framework_ScreenSaver-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cc8f81b2073920ca84d8e83435b95731e798ad422e0a3d67b09feb208a3920c6"}, - {file = "pyobjc_framework_ScreenSaver-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:96868cd9dc6613144821bb4db50ca68efa3ae8e07c31a626ce02d78b4eeaaeff"}, + {file = "pyobjc_framework_ScreenSaver-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:31bffb17ec131509c4cc584e98328497310ea644c764b50fb6bf7850617ec5a5"}, + {file = "pyobjc_framework_ScreenSaver-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ff25ad7cfffa116feaa117989f0a4b68dd6b7318aea4320ece6f75caf0092cfa"}, + {file = "pyobjc_framework_ScreenSaver-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:51e5bce05a3ada526c5c19b21cfb383cd7653d78f175c5abaf500a67ddab1c19"}, + {file = "pyobjc_framework_ScreenSaver-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:616ffb4bac2a56108778e33be6fe75b28f19511157abc411960b72324276635d"}, + {file = "pyobjc_framework_screensaver-10.3.1.tar.gz", hash = "sha256:0bbc5b574f12e95f6f6e48ced40b601e46e560ef6786845c15c57d78e6cd083e"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-screentime" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ScreenTime on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ScreenTime-10.1.tar.gz", hash = "sha256:6221e0f5122042b280212a6555f72d94020c2fd62319c4562cdfc7b58960dd07"}, - {file = "pyobjc_framework_ScreenTime-10.1-py2.py3-none-any.whl", hash = "sha256:734e090debb954a890a564869f2af20b55b9f7b7875d360795c9875279d09bd9"}, + {file = "pyobjc_framework_ScreenTime-10.3.1-py2.py3-none-any.whl", hash = "sha256:d4f8a0784dc7d7060dadae2e4b5aae5e1afe8bbf453ce49cbb1860b8920114c3"}, + {file = "pyobjc_framework_screentime-10.3.1.tar.gz", hash = "sha256:351179d5bf951aa754c72f50ba8785212adf1b26abe10149c750fafd0a3108ae"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-scriptingbridge" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ScriptingBridge on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ScriptingBridge-10.1.tar.gz", hash = "sha256:7dce35a25d1f3b125e4b68a07c7f9eaa33fc9f00dde32356d0f7f73eb09429a3"}, - {file = "pyobjc_framework_ScriptingBridge-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:15e60d3783d7611f4d35e6b2905921a01162cfa04eb1a6426135585c84806d19"}, - {file = "pyobjc_framework_ScriptingBridge-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:30c0aac8623d0e96442801219004c32d527d4b4bbbf5c271517d73c5eeae85a3"}, - {file = "pyobjc_framework_ScriptingBridge-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ca0dc8ccb443f5a3ab9afb500d6c730723faf38c5880a243a65b4e44be64fa55"}, + {file = "pyobjc_framework_ScriptingBridge-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:3a88e1a6c6b7d8935ab4baa9dcdeccb9cb08a44906bdd69b77302f48c88408d9"}, + {file = "pyobjc_framework_ScriptingBridge-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:66b55a9c12572f9bd6c00fd0a5aa5353354e7b717e37ffd1e843614d2fbde3d5"}, + {file = "pyobjc_framework_ScriptingBridge-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:387c0720842a1285afde5b56c43d9ed1332736ff8f6119ba6c6a551018552182"}, + {file = "pyobjc_framework_ScriptingBridge-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:90022f44f2bf0563bf5a75669198b9d778f76ece719f237750e9c5fcb00a601d"}, + {file = "pyobjc_framework_scriptingbridge-10.3.1.tar.gz", hash = "sha256:6bfb45efd0a1cda38a37154afe69f86ea086d5cbdfbc33b3e31c0bda97537fe9"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-searchkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SearchKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SearchKit-10.1.tar.gz", hash = "sha256:75234ee6e8490cf792453bf9a9854d7b5f1cebd65e81903d5ce0ecc3e65fc277"}, - {file = "pyobjc_framework_SearchKit-10.1-py2.py3-none-any.whl", hash = "sha256:2e42e7cacb0a7f9b327d1c770e52fe13dfaaac377cb4e413b609e478993552e0"}, + {file = "pyobjc_framework_SearchKit-10.3.1-py2.py3-none-any.whl", hash = "sha256:48ec776603e350405c7311f29d5941e0e9d6b6a75e2aec69e0acd28be0979905"}, + {file = "pyobjc_framework_searchkit-10.3.1.tar.gz", hash = "sha256:7efb76b7af9d8f0f08efb91543f4dba0b00261ed41abb121ada80175cc82d65d"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-CoreServices = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-CoreServices = ">=10.3.1" [[package]] name = "pyobjc-framework-security" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Security on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Security-10.1.tar.gz", hash = "sha256:33becccea5488a4044792034d8cf4faf1913f8ca9ba912dceeaa54db311bd284"}, - {file = "pyobjc_framework_Security-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:72955f4faf503e6a41076fcaa3ec138eb1cc794f483db77104acf2ee480f8a04"}, - {file = "pyobjc_framework_Security-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b02075026d78feda8c1af9462199c2cde65b87e4adde65b90ca6965f06cb422"}, - {file = "pyobjc_framework_Security-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1d19785d8531a6cdcdbb4c545b560f63306ff947592e7fad27811f87ee64854c"}, - {file = "pyobjc_framework_Security-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:569a9243d4044e3e433335ded891dc357880787df647de8220659f022a03f467"}, - {file = "pyobjc_framework_Security-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d8b8c402c395ac3868727f04e98b2ded675534de1349df8f5813b3c483b50a2c"}, - {file = "pyobjc_framework_Security-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:aaca360a28b6333a8a93b091426daa5ffd22006bbb1122d3d6a78d33177f612a"}, + {file = "pyobjc_framework_Security-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9de850e5d29cb8605c0d7f7fb4a8d29ad5ece6c58a2ab16fba3f985e6b9ee05a"}, + {file = "pyobjc_framework_Security-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:499372b4b87de0198601e0c6b3d3e43d48605218149a5808c6567b601147a9bf"}, + {file = "pyobjc_framework_Security-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b3daed9661bd80245dd8dbc738a17870226969db27f6dbb3424ec0ebdbb6ba83"}, + {file = "pyobjc_framework_Security-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6120e91282985bcec3b44c1bc4f9c26d40cd07b4ac3dc81f745d73c13f8b5523"}, + {file = "pyobjc_framework_Security-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43450895cd84256b313b547c9b256a4c0dc4b91195ec3e744f4f247d3cda0b77"}, + {file = "pyobjc_framework_Security-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:de52be2ce5ea98f2c224fea248227dcb3e7028d9814d0894ea6ea3c6da0f9160"}, + {file = "pyobjc_framework_Security-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:df39effeeb79482dd5805fd05e34a7c3ab30233a6e06e251cf7f8620430574fe"}, + {file = "pyobjc_framework_security-10.3.1.tar.gz", hash = "sha256:0d4e679a8aebaef9b54bd24e2fe2794ad5c28d601b6d140ed38370594bcb6fa0"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-securityfoundation" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SecurityFoundation on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SecurityFoundation-10.1.tar.gz", hash = "sha256:11def85a7a4ea490fa24df79d01ea137f378534fedf1da248068ddf137f38c7e"}, - {file = "pyobjc_framework_SecurityFoundation-10.1-py2.py3-none-any.whl", hash = "sha256:bbd67737afec25f2e3d41c8c2e7b4a6f9aae4231242e215b82a950eef6432ce0"}, + {file = "pyobjc_framework_SecurityFoundation-10.3.1-py2.py3-none-any.whl", hash = "sha256:c352cde672c9c2afa89575c362a0714e589c118485cec49ba230327e92c8a9a6"}, + {file = "pyobjc_framework_securityfoundation-10.3.1.tar.gz", hash = "sha256:414b13acabfae584729cd614e27247d250ec225d31140e8d971aa08536d6150c"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Security = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Security = ">=10.3.1" [[package]] name = "pyobjc-framework-securityinterface" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SecurityInterface on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SecurityInterface-10.1.tar.gz", hash = "sha256:444a0dc7d50390750c28185b6496ee913011ac886d9e634bfc9a0856372d0a94"}, - {file = "pyobjc_framework_SecurityInterface-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c0e52408e25845a960b0fe339c274650fd211f9fee5944c643d9ba16861e45ac"}, - {file = "pyobjc_framework_SecurityInterface-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bef4a63d31808531f5806006945d1f9b5650221e4adc973302387ab7b2e1b349"}, - {file = "pyobjc_framework_SecurityInterface-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:479e555df16ff7f79bf7622ab3341b0ef176fbd85ef3f7301931a57d2def682f"}, + {file = "pyobjc_framework_SecurityInterface-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:7a9bbca70e8a27bd79a0b8846f7d08b693dd309ff780f92f713fee57ff9d3ddf"}, + {file = "pyobjc_framework_SecurityInterface-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b964ecbde276611c14fd70884ab9c08c4aae75633deda1a29813dbe42dc83dd6"}, + {file = "pyobjc_framework_SecurityInterface-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8c2998c09767f0214891c553fadb8ec72a8273c95fca820c44cb11f3b0cdb8a4"}, + {file = "pyobjc_framework_SecurityInterface-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d913d30d04c9f01679ead33c95ad53dade587995f48c778a6de4d7da239b43e3"}, + {file = "pyobjc_framework_securityinterface-10.3.1.tar.gz", hash = "sha256:cd25f342a2b53cbffd134443506d5e75c96ba7145135debb8932c1252d57269a"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Security = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Security = ">=10.3.1" [[package]] name = "pyobjc-framework-sensitivecontentanalysis" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SensitiveContentAnalysis on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SensitiveContentAnalysis-10.1.tar.gz", hash = "sha256:435906a3fcc6cba50cd7c5bfd693368c6042c17c5f64bcd560a3761d947425de"}, - {file = "pyobjc_framework_SensitiveContentAnalysis-10.1-py2.py3-none-any.whl", hash = "sha256:472c0fb0f1ad9c370cbc7cf636bb5888cbcf0ee8c9ecb9c5f6de25e2587771e5"}, + {file = "pyobjc_framework_SensitiveContentAnalysis-10.3.1-py2.py3-none-any.whl", hash = "sha256:6b5cfe771a28300d766ff14ff81313fda946943d54a039d5574478a070933e03"}, + {file = "pyobjc_framework_sensitivecontentanalysis-10.3.1.tar.gz", hash = "sha256:ac8dd18d77ccc0bb4eb24839cf39da9981db64e53f52b09636e47bd7b3066f84"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-servicemanagement" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ServiceManagement on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ServiceManagement-10.1.tar.gz", hash = "sha256:ebe38b80ed74112fdd356e19165c365f6281baad83818774a0da6d790fd13044"}, - {file = "pyobjc_framework_ServiceManagement-10.1-py2.py3-none-any.whl", hash = "sha256:d05289948558cf4c7fbc101946f6ccadcc33826b2056c14d5494a8ae7f136936"}, + {file = "pyobjc_framework_ServiceManagement-10.3.1-py2.py3-none-any.whl", hash = "sha256:6b7ca0de9cf74439df0947aae29f5f31d58eeacb0ff55e1465faed540d31de4b"}, + {file = "pyobjc_framework_servicemanagement-10.3.1.tar.gz", hash = "sha256:d048a803ad34c997dcc99ba778fca9d91cc5adfa1d115202e4bf22d9b04fd92c"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-sharedwithyou" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SharedWithYou on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SharedWithYou-10.1.tar.gz", hash = "sha256:bcac8ffa2642589a416c62ff436148586db9c41f92419a0164b1e9d6f6c73e38"}, - {file = "pyobjc_framework_SharedWithYou-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:05fceedcd7b6e8753cb8dc5f09a947686dd454c304965c959bc101cfd7349fcd"}, - {file = "pyobjc_framework_SharedWithYou-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6f4f9fb6d335b54eb0a02b277ca8a2cb87962a579bafdc9df5f94c8af1063ee4"}, - {file = "pyobjc_framework_SharedWithYou-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a1c7c688c15117f1c6ea638e83285ce1b2fbd9d8c76ee405e43b24fa4fea766d"}, + {file = "pyobjc_framework_SharedWithYou-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:175cb32c93c55511f86ccf02b1da2fdcbc2a0f92422d451ff5dd4a864b3f3faf"}, + {file = "pyobjc_framework_SharedWithYou-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8a1ee78d6220e02e073c5fe6fea8f223ef870086c1afe60f346e7bb3dbdcb165"}, + {file = "pyobjc_framework_SharedWithYou-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:824d56ecf85dbaa938a2db42bf2f5a55a4e9f94019dee2dc90578040cc2bbd3c"}, + {file = "pyobjc_framework_SharedWithYou-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:bc37e1c86f460095085a5060149ca013d4dcb9b54337887bff86a2e07302c0f1"}, + {file = "pyobjc_framework_sharedwithyou-10.3.1.tar.gz", hash = "sha256:7e35b631bc77b040151515e4fee9c8f47bc313924fc3e5970e940f1343db039b"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-SharedWithYouCore = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-SharedWithYouCore = ">=10.3.1" [[package]] name = "pyobjc-framework-sharedwithyoucore" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SharedWithYouCore on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SharedWithYouCore-10.1.tar.gz", hash = "sha256:2b4f62b0df4bd44198f6d3a3aae4d054592261d36fc2af71f9dd81744aa99815"}, - {file = "pyobjc_framework_SharedWithYouCore-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a7f41415a3ca40d4ee18955155a4141e0d2d55e713b513aa567305ae54716cb7"}, - {file = "pyobjc_framework_SharedWithYouCore-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc133f07a71cb828073dc671cb1e8ffa5bde714b376a8eba0a8110ac41927ae9"}, - {file = "pyobjc_framework_SharedWithYouCore-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d7169a2492ed4fd7d45ad0eafbecebffec0b22f08e756f2e251eda62cd5ba42a"}, + {file = "pyobjc_framework_SharedWithYouCore-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:b0fb4e84e15765d913d5383b61004b7ff9a637f198ec7c3dee07632f3a4e2b4c"}, + {file = "pyobjc_framework_SharedWithYouCore-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:9b480058016216e5a3715e3ff50f5430159ec4235dcd110141f53637f5dbb51d"}, + {file = "pyobjc_framework_SharedWithYouCore-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:17a5e29f832d395030b1ab5dfcf43dad2a9c0f917e6b443ac383dea06e3aa304"}, + {file = "pyobjc_framework_SharedWithYouCore-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:1e51f315b5b16b340a1a12d4a0f3244f1925f14c9e148ca4dfdcfad6baa7ee5a"}, + {file = "pyobjc_framework_sharedwithyoucore-10.3.1.tar.gz", hash = "sha256:e4768b7fdb18730e225bbebc9c9f9acfa7e44e648875816aff8c7e7f0e82afbf"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-shazamkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ShazamKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ShazamKit-10.1.tar.gz", hash = "sha256:d091c5104adda8d54e65463862550e59f86646fdafcdcd234c9a7a2624584f1d"}, - {file = "pyobjc_framework_ShazamKit-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6670ed380dacc6aa86f571a18d9e321bd075da11bf144cba2802b19bb0868a21"}, - {file = "pyobjc_framework_ShazamKit-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d4efd57dac3f50621cc23be38cefbd6c80b4b55f0339312b4f2a340cd6ffde9b"}, - {file = "pyobjc_framework_ShazamKit-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:07e58fc6b70bf961f230044cf46324ab4239864955299957e231ba7cda8fafa9"}, - {file = "pyobjc_framework_ShazamKit-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be2da82d9a58c2605a1a17a88fbc389931b8fd8ad7d60926755b50316fe5e04f"}, - {file = "pyobjc_framework_ShazamKit-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:e500c6794f2b7cea57dea6f64c1fc9e067a14ddb9446e9d7739dcb57683b5a8a"}, - {file = "pyobjc_framework_ShazamKit-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4289a1109148a1a314c6bae9b33e90eca6d18a06a767b431cdff1178024f3310"}, + {file = "pyobjc_framework_ShazamKit-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dac6a729d7fed5e96ac4cb2a7894c79002670b4c06559ef4795dfe8c6fa15dda"}, + {file = "pyobjc_framework_ShazamKit-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc77fde1503ec1f96d7b9a05ad784d29d94065732f0cfe089321be423e5045f3"}, + {file = "pyobjc_framework_ShazamKit-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:012560cab1997c1da6e19388b5192f68dcdf806c2f8d7bd1e66da37512d18b30"}, + {file = "pyobjc_framework_ShazamKit-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4356bdcb9ca50da243b9d79da27851b799483865072f7c86a8a9674300d31819"}, + {file = "pyobjc_framework_ShazamKit-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:26dafb6ac23c15d8cdee4c2083757aac29fad4f945b97f120f6351aed5cdab72"}, + {file = "pyobjc_framework_ShazamKit-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:95e4e717cb7a675544e08c53fb86d55715aa5d633b4ec35737fb02293098d368"}, + {file = "pyobjc_framework_ShazamKit-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:808bdfe5dcfc44d4c1df3ae3f384396908e9a1d3832e34408950db522859e91e"}, + {file = "pyobjc_framework_shazamkit-10.3.1.tar.gz", hash = "sha256:deef11a43e773b876df31eeadbfc447892fda0607e314ca2afb2c012284cfa32"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-social" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Social on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Social-10.1.tar.gz", hash = "sha256:50757d982c712090e93b6ee4bd97ed3f6acfe7005f981f060433e94b7aca818b"}, - {file = "pyobjc_framework_Social-10.1-py2.py3-none-any.whl", hash = "sha256:81363d9d06c9c8ede16d96ec1d3cdba6deef195ef54cc64618e58c7fc1f574df"}, + {file = "pyobjc_framework_Social-10.3.1-py2.py3-none-any.whl", hash = "sha256:f83617c07db7c8bd15b3b9e0878ab2b6a286dcbd9a8c7f451da204f75db880e9"}, + {file = "pyobjc_framework_social-10.3.1.tar.gz", hash = "sha256:d1303cf3860ad02a8795c099e17888923505a9c45be407da50721a9a8a5b2efd"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-soundanalysis" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SoundAnalysis on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SoundAnalysis-10.1.tar.gz", hash = "sha256:42e0ae24f11ef8cf097c71e5b2378eaba26f66cb39959fec4ca79812bc0ed417"}, - {file = "pyobjc_framework_SoundAnalysis-10.1-py2.py3-none-any.whl", hash = "sha256:a33bc8a1ecee11387beb9db06aaf9c362f7dc171d60da913277ac482d67beabb"}, + {file = "pyobjc_framework_SoundAnalysis-10.3.1-py2.py3-none-any.whl", hash = "sha256:ef67207ff4045e5db37f017bdcd07d4b42464c86fe6c3b56524f6f22a9cde216"}, + {file = "pyobjc_framework_soundanalysis-10.3.1.tar.gz", hash = "sha256:faa533644231f99dbdc2fcc3ecb9181c5df4f4dc68d42856729214021c83c881"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-speech" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Speech on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Speech-10.1.tar.gz", hash = "sha256:a9eddebd4e4bcdb9c165129bea510c5e9f1353528a8211cc38c22711792bf30d"}, - {file = "pyobjc_framework_Speech-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0cc26aca43d738d25f615def32eb8ce27675fc616584c009e57f6b82dec75cc5"}, - {file = "pyobjc_framework_Speech-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9a448c58149f5bbf0128f2c6492ea281b51f50bdc4f0ecd52bea43c80f7e2063"}, - {file = "pyobjc_framework_Speech-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:76a528fc587c8eb86cdba61bf6b94ddb7e3fb38a41f1a46217e2ce7fc21d6c26"}, + {file = "pyobjc_framework_Speech-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:07afd82d76b98b67a78c07098a814a0b862bcf00fc072d49d8da435202008a18"}, + {file = "pyobjc_framework_Speech-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:444994d062286ff2769e07425d511503b2047b381afad1d64c8517ef4e44d01f"}, + {file = "pyobjc_framework_Speech-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e7fad907f9c0c49c5272f316d20e66ba0415038203ca277381753b035c16061a"}, + {file = "pyobjc_framework_Speech-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:2294b60e1ab59b1340b820e6fee67766a959edd580d73e3b938f15653805d64c"}, + {file = "pyobjc_framework_speech-10.3.1.tar.gz", hash = "sha256:5b77b1d1ced0d1ad7244037b865dda2b83e8f9562d76d1e9fa4fea3b89e437b8"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-spritekit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SpriteKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SpriteKit-10.1.tar.gz", hash = "sha256:998be1d6c7fd5cc66bd54bae37c45cf3394df7bc689b5d0c813f0449c8eee53f"}, - {file = "pyobjc_framework_SpriteKit-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:23f6657e48f7d8cb434bcf6a76b2c336eb29be69ade933f88299465a0c83cb3b"}, - {file = "pyobjc_framework_SpriteKit-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b5556d8469b20fe35a0ec5f9e493c30ebc531bce3be4e48fc99cb87338ba5cfb"}, - {file = "pyobjc_framework_SpriteKit-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:aa51855f7bfed3dc1bcda95b140d71c4dc1e21c3480216df19f6fddc7dc7ce39"}, - {file = "pyobjc_framework_SpriteKit-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2210920a4f9a39dc3bea9287e012cdfb740a0748faa6ab13bf8a58d07da913cc"}, - {file = "pyobjc_framework_SpriteKit-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:66df1436d17bf0c17432d2d66ebeef8efee012240297e5aabc1118b014947375"}, - {file = "pyobjc_framework_SpriteKit-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5564ed8648afba01f9877062204ed03d3fef8a980b6b4155c69d3662e4732947"}, + {file = "pyobjc_framework_SpriteKit-10.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:31a5ce3ba471bf12f3e9ae3b5865d400e4e70ab4bb94317880783ee04c07b1f5"}, + {file = "pyobjc_framework_SpriteKit-10.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:af355eccc18437e0932e1b327817e8f50d194bfa471b65b4733185ba606ab792"}, + {file = "pyobjc_framework_SpriteKit-10.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c3fd6aaecd7abd6ebc83d4c37a22950d55b75911fdb99628b2677f44085a0212"}, + {file = "pyobjc_framework_SpriteKit-10.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a1f0537bc0331cca58cc50307f3b114ab8bfd51576df3038b6037353aa77085f"}, + {file = "pyobjc_framework_SpriteKit-10.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffe7a07c8a3e17552e73b517b4fdcff7b2e6ca7e89b093a5daccfc285708216c"}, + {file = "pyobjc_framework_SpriteKit-10.3.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:e1d79aab298f7b42436b2468e37ff84718f1b8b579c1440de7002a55d5f8762e"}, + {file = "pyobjc_framework_SpriteKit-10.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cd08925baf8b3f511842f49fb5512ae56875a923d23254fcc022124788180d47"}, + {file = "pyobjc_framework_spritekit-10.3.1.tar.gz", hash = "sha256:2c11f35f48302f487c51ba8030f5cf79493a9dc0993dd901016ae4b8d8047c8b"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-storekit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework StoreKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-StoreKit-10.1.tar.gz", hash = "sha256:4e91d77d1b9745eca6730ddf6cde964e2bd956fafad303591f671ebd1d4de64b"}, - {file = "pyobjc_framework_StoreKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:456641cbe97eab4bb68dccec6f8bf3bc435adaa0b2ae6a7a4a3da0adc84a9405"}, - {file = "pyobjc_framework_StoreKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:356966d260bd1e19c7cdba7551b3e477078d3d4b0df04b7f38013dd044913727"}, - {file = "pyobjc_framework_StoreKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:652657006d3c8fefcdbb662f8f33ef6ee8e01ba30a0b4d6e2fcd2e4046951766"}, + {file = "pyobjc_framework_StoreKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:f9c50a980846b236884504ad42690d49935bda3de5d779fca3cdfb16d43fa8d1"}, + {file = "pyobjc_framework_StoreKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b3846b249a6fc5d26c80320098858978e0a16f46e98fc998075f16f539ac89fc"}, + {file = "pyobjc_framework_StoreKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c3575facbb3d238b8c9f3054c1422488414d89a42906dbcbfdbb47ef8be3da66"}, + {file = "pyobjc_framework_StoreKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:2b5848f0e08b42dd4dabe5822b436b904a697e923f529ccaad910dfb822b2b9d"}, + {file = "pyobjc_framework_storekit-10.3.1.tar.gz", hash = "sha256:913b4aad7dc31df7d8011c54a695da65cc57819f4b48c98abaed4e6d9ff7d323"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-symbols" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Symbols on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Symbols-10.1.tar.gz", hash = "sha256:63f5345fa90b31ea017c01ffd39e6e0289ef0258c6af7941263083d2289f5d3d"}, - {file = "pyobjc_framework_Symbols-10.1-py2.py3-none-any.whl", hash = "sha256:88b48102ba33ac3d8bc5c047cc892ab21e8e102c3b25b4186b77c5d1f5c1bc40"}, + {file = "pyobjc_framework_Symbols-10.3.1-py2.py3-none-any.whl", hash = "sha256:6e171da5af2612e186cc4cf896ad7f2672c021a1fc18bc8ef49b79c4c831202d"}, + {file = "pyobjc_framework_symbols-10.3.1.tar.gz", hash = "sha256:3e3f35ef3646b5f9c0653d9dbf5693cf87de3df04420cb2679dad74c75965d18"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-syncservices" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SyncServices on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SyncServices-10.1.tar.gz", hash = "sha256:644d394b84468fa6178b5aa609771252ca416ca2be2bac5501222b3c5151846d"}, - {file = "pyobjc_framework_SyncServices-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:edf7d5de135ec44b8ecf260265cb7bd9bf938d3fcc2204282aea674a86918c60"}, - {file = "pyobjc_framework_SyncServices-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53ef6096359d182952fdb40734f17302edf35757578c0c52314f703322d855cb"}, - {file = "pyobjc_framework_SyncServices-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b9d7ec3f784fc89847ad136bb3d67d159310a2e072a724d4ffddccf0ee5dec2b"}, + {file = "pyobjc_framework_SyncServices-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:aa98a38f59f230da7817dc08055c8049fa961193af16caa262c51e4ec8b5de20"}, + {file = "pyobjc_framework_SyncServices-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:9d3798cc745143d44da101bb0eb9d482326b761f48c3699251191e9c3656ee6c"}, + {file = "pyobjc_framework_SyncServices-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:97020a88d743a6f12ed3f72d062ab96712a7cc0e7b0293c7c5860c014002cc73"}, + {file = "pyobjc_framework_SyncServices-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b9b7bc58b04654172968cdc1cfe74b43ca0ad2e03c24c7921234a57be2c22985"}, + {file = "pyobjc_framework_syncservices-10.3.1.tar.gz", hash = "sha256:1cd5e3eaf85a11996184afad1da47037cd921e302ccb35fe388b19f91a685669"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-CoreData = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreData = ">=10.3.1" [[package]] name = "pyobjc-framework-systemconfiguration" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SystemConfiguration on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SystemConfiguration-10.1.tar.gz", hash = "sha256:7e125872d4b54c8d04f15d83e7f7f706c18bd87960b3873c797e6a71b95030b0"}, - {file = "pyobjc_framework_SystemConfiguration-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ab80e4272937643de8569a711e5adee8afca2bf071b6cfc6b7fc4143010d258"}, - {file = "pyobjc_framework_SystemConfiguration-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:431c32557bde3dad18fb245bf1e5ce80963f28caa4d2691b5a82e6db2b5efc2f"}, - {file = "pyobjc_framework_SystemConfiguration-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7e8ae510b11ceca8800bc7b4b0c7735cf26de803771199d6c2d8f24fbb5467df"}, + {file = "pyobjc_framework_SystemConfiguration-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:200c4b7d3483e5ccb86d4d5783ace8c26d4b3f9130684280f30080d53f62d4c5"}, + {file = "pyobjc_framework_SystemConfiguration-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:403392b0e2085cf528547d468bc523fbee418381bb61446eb4aa1f9a1c8a8494"}, + {file = "pyobjc_framework_SystemConfiguration-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f38a824261a30dc9961e13a7b9759dd0c77e1c7c4681e3c80c5751c560735289"}, + {file = "pyobjc_framework_SystemConfiguration-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4e8816524cbb6c6ea9e1c596fc9a16c91f2063eba394135be9437bdbd469e125"}, + {file = "pyobjc_framework_systemconfiguration-10.3.1.tar.gz", hash = "sha256:1bf6ffe98faa4e208bf594c857ba23cd56fe404bc579188ff53b704844d9c402"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-systemextensions" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework SystemExtensions on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-SystemExtensions-10.1.tar.gz", hash = "sha256:3eb7ad8f1a6901294b02cd6d6581bd6960a48fcfd82475f5970d1c909f12670d"}, - {file = "pyobjc_framework_SystemExtensions-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8dc306dd07f9ff071759bb2237b7f7fddd0d2624966bdb0801dc5a70b026f431"}, - {file = "pyobjc_framework_SystemExtensions-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c76c6d7dbf253abe11ebfa83bbbfa7f2fc4c700db052771075c26dabbd5ee1e9"}, - {file = "pyobjc_framework_SystemExtensions-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:2586323fbe9382edebd7ca5dfe50b432c842b7ef45ef26444edcb7238bcf006f"}, + {file = "pyobjc_framework_SystemExtensions-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:b24edd33c71cb02a16bdd9b26d3a229a6b2a815c26a4c908b1b8b6cdf4f6c84a"}, + {file = "pyobjc_framework_SystemExtensions-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e2a24501a782d2b6f7fa339619eb5c988db6b31a6043d029f40f67c54fd9399e"}, + {file = "pyobjc_framework_SystemExtensions-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1cacd2356142307cf221d568f3e7adda654c28c90c223db3830746f4c2b12aea"}, + {file = "pyobjc_framework_SystemExtensions-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:6561876b2fd5c40620073fae9b924f2d8e0110e8573756ffc9521e4fbc825249"}, + {file = "pyobjc_framework_systemextensions-10.3.1.tar.gz", hash = "sha256:34412e75c95a585d222ea23efc3eba436210ec0345cec6c7dc78d16e027d03e1"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-threadnetwork" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework ThreadNetwork on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-ThreadNetwork-10.1.tar.gz", hash = "sha256:72694dce8b10937f4d8fef67d14e15fa65ba590dec8298df439fd0cc953a83fa"}, - {file = "pyobjc_framework_ThreadNetwork-10.1-py2.py3-none-any.whl", hash = "sha256:720d4a14619598431a22be2a720bf877f996d65cee430b96c5d7ec833b676b68"}, + {file = "pyobjc_framework_ThreadNetwork-10.3.1-py2.py3-none-any.whl", hash = "sha256:95ddbed8a114cc1f05db613bb53247465ec48ccaad2b56f5da466317808fc32b"}, + {file = "pyobjc_framework_threadnetwork-10.3.1.tar.gz", hash = "sha256:1038210a8e5dfa86aefd10894563913e767e95d1c1bd4333ae5f9c6cabbb3ce9"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-uniformtypeidentifiers" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework UniformTypeIdentifiers on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-UniformTypeIdentifiers-10.1.tar.gz", hash = "sha256:e8a6e8d4c3c6d8213d18fab44704055a5fca91e0a74891b4f1bfe6574cd51d97"}, - {file = "pyobjc_framework_UniformTypeIdentifiers-10.1-py2.py3-none-any.whl", hash = "sha256:4c867b298956d74398d2b6354bd932dc109431d9726c8ea2fc9c83e6946a2a7d"}, + {file = "pyobjc_framework_UniformTypeIdentifiers-10.3.1-py2.py3-none-any.whl", hash = "sha256:8bd1516ccec1807e2ad92a071242d83e9d1eda08ddbfae04dacc30d76c3d734c"}, + {file = "pyobjc_framework_uniformtypeidentifiers-10.3.1.tar.gz", hash = "sha256:1985fee7e1f1157db36f3c19ee0ad273677eeff10467f575086246bbeffcdf50"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-usernotifications" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework UserNotifications on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-UserNotifications-10.1.tar.gz", hash = "sha256:eca638b04b60d5d8f5efecafc1fd021a1b55d4a6d1ebd22e65771eddb3dd478f"}, - {file = "pyobjc_framework_UserNotifications-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a44b89659eae1015da9148fc24f931108ff7a05ba61509bfab34af50806beb0c"}, - {file = "pyobjc_framework_UserNotifications-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:00aa84f29bcbe8f302d20c96ef51fb48af519d83e0b72d22bd075ea1af86629f"}, - {file = "pyobjc_framework_UserNotifications-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:fe9170b5c4da8e75288ada553cc821b9e3fc1279eb56fa9e3d4278b35a26c5ce"}, + {file = "pyobjc_framework_UserNotifications-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:d85c293e0520530ac7152b3f293c5606f0495b7727de331ef4e198278bf40e98"}, + {file = "pyobjc_framework_UserNotifications-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0001bb25f15e6f8cac81bcb58cab8161a15a6fe41f6c475e8da364c913bdb090"}, + {file = "pyobjc_framework_UserNotifications-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0bba49b0f290f5597b94a80608c785b61d61aa3885cdf05529862e47fe1cec67"}, + {file = "pyobjc_framework_UserNotifications-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:59294fa6110778de145c9299c220990d057a6a171d37ab5d8a6ab1780bf7888f"}, + {file = "pyobjc_framework_usernotifications-10.3.1.tar.gz", hash = "sha256:ddb5de88fb659c2241429b6408ddcabbfc0c2c9e4a7f5f543a6fab1c4953403b"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-usernotificationsui" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework UserNotificationsUI on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-UserNotificationsUI-10.1.tar.gz", hash = "sha256:09df08f47a7e605642a6c6846e365cab8b8631a7f87b41a65c35c52e484b9f8a"}, - {file = "pyobjc_framework_UserNotificationsUI-10.1-py2.py3-none-any.whl", hash = "sha256:6640c6d04f459b6927096696dac98ce5fcb702a507a757d6d1b909b341bb8a0d"}, + {file = "pyobjc_framework_UserNotificationsUI-10.3.1-py2.py3-none-any.whl", hash = "sha256:bc6cb92a6ac947e8fe9685f3c1964c5a9f98e576b936b0bbff8c528f47f976d2"}, + {file = "pyobjc_framework_usernotificationsui-10.3.1.tar.gz", hash = "sha256:80390b5361bb6014dc32d0afbf581280e7762a4f67460a736f461f613d397094"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-UserNotifications = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-UserNotifications = ">=10.3.1" [[package]] name = "pyobjc-framework-videosubscriberaccount" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework VideoSubscriberAccount on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-VideoSubscriberAccount-10.1.tar.gz", hash = "sha256:6410c68ea37a4ba4667b8c71fbfd3c011bf6ecdb9f1d6adf3c9a35584b7c8804"}, - {file = "pyobjc_framework_VideoSubscriberAccount-10.1-py2.py3-none-any.whl", hash = "sha256:f32716070f849989e3ff052effb54f951b89a538208651426848d9d924ac1625"}, + {file = "pyobjc_framework_VideoSubscriberAccount-10.3.1-py2.py3-none-any.whl", hash = "sha256:4b489f0007dce8ea17f37316175dac2768cd173a4d4c7a1d5f87fb3991c1e518"}, + {file = "pyobjc_framework_videosubscriberaccount-10.3.1.tar.gz", hash = "sha256:f62509e976b805bc76ff5928444677ac542b52dd9f7781ac0731d7c4b22bed96"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-videotoolbox" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework VideoToolbox on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-VideoToolbox-10.1.tar.gz", hash = "sha256:56c9d4b74965fe79f050884ffa560ff71ffe709c24923d3d0b34459fb626eb11"}, - {file = "pyobjc_framework_VideoToolbox-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a4115690b8ed4266e52a4d200c870e68dd03119993280020a1a4d6a9d4764fcf"}, - {file = "pyobjc_framework_VideoToolbox-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:64874d253c2996216c6d56e03e848cf845c3f0eac84d06ba97d83871dbf19490"}, - {file = "pyobjc_framework_VideoToolbox-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:977b2981532442c4c99fff75ffcc2b5a4b0f8108abcabdafcda2addf8b2ffa21"}, + {file = "pyobjc_framework_VideoToolbox-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:bc52aa91f568060e0087762f2a4d876bf7b683e5396779e93925252e26f0330b"}, + {file = "pyobjc_framework_VideoToolbox-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3d20ea92b597f4758f24ed8e19f7a7a372faf6478d739e7cb7f7cd73d3e8617a"}, + {file = "pyobjc_framework_VideoToolbox-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:761e3cd05090f5d9e9fbae2c25e3f352f19aa86a0d02a1189b94ec5c4bc37111"}, + {file = "pyobjc_framework_VideoToolbox-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7d4fb38f95e91f62d14b05fafcc6197775e9eaf4e5b698bc1fb313756f59b10d"}, + {file = "pyobjc_framework_videotoolbox-10.3.1.tar.gz", hash = "sha256:7ebc281523b2b37aff17ce6eabd0c81081864b3e3e4a83ae72b18fd70c57c521"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-CoreMedia = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreMedia = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-virtualization" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Virtualization on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Virtualization-10.1.tar.gz", hash = "sha256:48f2484a7627caa246f55daf203927f10600e615e620a2d9ca22e483ed0bb9b4"}, - {file = "pyobjc_framework_Virtualization-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a434c40038c0c1acd31805795f28f959ea231252dc3ab34ed5a268c21227682c"}, - {file = "pyobjc_framework_Virtualization-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8ad3e40ec5970e881f92af337354be68c1f2512690545a2da826684daeaa3535"}, - {file = "pyobjc_framework_Virtualization-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:2aba907617075394718bc8883c650197e21b2ea0d284ca51811229386114040a"}, + {file = "pyobjc_framework_Virtualization-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:c046abcebf6a64a47ea50311f3e4aaae424dbac719e847cd15b375ebe99a51ed"}, + {file = "pyobjc_framework_Virtualization-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:dc14f6c3deaf367adff18b509e766dc78c4695a4cd76e4aad3ff4b1e207a4635"}, + {file = "pyobjc_framework_Virtualization-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bd79c1bb45113957f24cbf8b6d0ff175361c52c97e326313ecd76cfa015f6cef"}, + {file = "pyobjc_framework_Virtualization-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:6e5e6645e31acd07d30b1607c4cdaf0ae0e4d8223471a8a089004c2deb6fdea5"}, + {file = "pyobjc_framework_virtualization-10.3.1.tar.gz", hash = "sha256:8348ddef18eb943d151e5b5977e4d410012ee2e3f6048c16f7cfe0c1a73536cb"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyobjc-framework-vision" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework Vision on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-Vision-10.1.tar.gz", hash = "sha256:ff50fb7577be8d8862a076a6cde5ebdc9ef07d9045e2158faaf0f04b5b051208"}, - {file = "pyobjc_framework_Vision-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c6330d8b22f75f1e7d9a5456f3e2c7299d05d575b2e9b2f1e50230b18f17abed"}, - {file = "pyobjc_framework_Vision-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:91b4d740b6943f6b228915ece2e027555f28ccf49c8d063a580b8f9e5af56fd0"}, - {file = "pyobjc_framework_Vision-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:bb2d7334b4b725c5e5346a8cce2a0064259a09e90ec189b0c776304d5fc01e49"}, + {file = "pyobjc_framework_Vision-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:dff3582678930461a0bb11bf070854d49f6944a851dc89edc63fac93c75ddf39"}, + {file = "pyobjc_framework_Vision-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:32626183c51674efb3b5738e2884c3fea37edca010117cf71bd72cb3c49c869a"}, + {file = "pyobjc_framework_Vision-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2473b346a112c51ac485184305bd13c402e0db45f2df3d277315bd49efba18e9"}, + {file = "pyobjc_framework_Vision-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4302e2c5f68c9667ecd4273809cbc4611af6368b123d69596e5b088f1b1aa16b"}, + {file = "pyobjc_framework_vision-10.3.1.tar.gz", hash = "sha256:aa071656d395afc2d624600a9f30d6a3344aa747bf37f613ff3972158c40881c"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" -pyobjc-framework-CoreML = ">=10.1" -pyobjc-framework-Quartz = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" +pyobjc-framework-CoreML = ">=10.3.1" +pyobjc-framework-Quartz = ">=10.3.1" [[package]] name = "pyobjc-framework-webkit" -version = "10.1" +version = "10.3.1" description = "Wrappers for the framework WebKit on macOS" optional = false python-versions = ">=3.8" files = [ - {file = "pyobjc-framework-WebKit-10.1.tar.gz", hash = "sha256:311974b626facee73cab5a7e53da4cc8966cbe60b606ba11fd0f3547e0ba1762"}, - {file = "pyobjc_framework_WebKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ad9e1bd2fa9885818e1228c60e0d95100df69252f230ea8bb451fae73fcace61"}, - {file = "pyobjc_framework_WebKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c901fc6977b3298de789002a76a34c353ed38faedfc5ba63ef94a149ec9e5b02"}, - {file = "pyobjc_framework_WebKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f2d45dfc2c41792a5a983263d5b06c4fe70bf2f24943e2bf3097e4c9449a4516"}, + {file = "pyobjc_framework_WebKit-10.3.1-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:df52913e4c3cb77d4551d9848e30be01b82cace54cede850c88a7e0ab41a20a9"}, + {file = "pyobjc_framework_WebKit-10.3.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:207a7aa57817d3ab3fa033e42ab612f8c00521f13ff2871547c92cd94ed51f85"}, + {file = "pyobjc_framework_WebKit-10.3.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c0d4c1ba0fafae503a8df95e7b6bd4236951ba72508ec62809e37825f382c635"}, + {file = "pyobjc_framework_WebKit-10.3.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c8e3334978a1bd927ea14ed73f56d6741561a69d31d082d2b23d1b48446917c8"}, + {file = "pyobjc_framework_webkit-10.3.1.tar.gz", hash = "sha256:7ad9f4eb91a6dff39848d9c2ab71f170aeab4b6396bcec8e5a780739f9be4222"}, ] [package.dependencies] -pyobjc-core = ">=10.1" -pyobjc-framework-Cocoa = ">=10.1" +pyobjc-core = ">=10.3.1" +pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pyopencl" -version = "2023.1.4" +version = "2024.2.6" description = "Python wrapper for OpenCL" optional = false python-versions = "~=3.8" files = [ - {file = "pyopencl-2023.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce71196cee0171a923d9ef6a8c21ce26fd7342ddaee88a29aa372ae3295f5257"}, - {file = "pyopencl-2023.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7bdf472b8e36f81145ac526d51f550ba539b765199da9da16732a222c7b85bee"}, - {file = "pyopencl-2023.1.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e99bb71267ce5223814177ff8c73e98057a70d20fc6555050d77d98b01bba5"}, - {file = "pyopencl-2023.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d8cbe0d2121babf7daf0bcd6ded687c191c6d2effbac85c5421659fbc26947"}, - {file = "pyopencl-2023.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08ebc3d5776ec2a8853c11234d605754bdf0d5af8322a08d558d661eb85c6fa3"}, - {file = "pyopencl-2023.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f199eff64be353f5badac62576d14cb40cc137c9f92f5260452de097dd9fe7a0"}, - {file = "pyopencl-2023.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:38a0a3c8389f44b51c0a916dc8e532d77ea37ab5a11f10e1ba97cc8f9a634695"}, - {file = "pyopencl-2023.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a1158956a59e73a57e98acc681ff01a64574d0b3715634703437aa8b7c785c"}, - {file = "pyopencl-2023.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55f09e9d12a036830d71f60af9233ff493c83ff2f99b472baa1f779688c816b7"}, - {file = "pyopencl-2023.1.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:742fdb18bcc933f20b795c787fe513c69b83f074626bc62ff3c9cd1c8b243022"}, - {file = "pyopencl-2023.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4102eb3f5b9fe4b08c7900820a908cec3004e9a6990cc7202162ae46e07869d"}, - {file = "pyopencl-2023.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:36d79cd6445a86b4db7399488c301b49bda5fde6c1455c36b1ce58e03c690916"}, - {file = "pyopencl-2023.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a561e42ee3030b2966e7dc5cc764e705d9ff1bb5aa1124fa7d6ba4009ebfb96"}, - {file = "pyopencl-2023.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:0f7889dce94dba10738225fabec929fa977bfe8777a64f5699f6f2fe2b00742c"}, - {file = "pyopencl-2023.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ab31b35875cecd2b1c10ac47ea06e224c5881cca942fba94387317357d73c0b9"}, - {file = "pyopencl-2023.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:230c63354fe6a18043e67042769ddc5e329002fb55fb99f692e4f5c8fcf3007c"}, - {file = "pyopencl-2023.1.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef091d4a4802b267b0aa5ec46c4ebc00fd664d9178ad9866b485006eeff180b4"}, - {file = "pyopencl-2023.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c11fc7445e21dcd97bd8f5534531ed7bbd09ded853b520157623f48fad5b739"}, - {file = "pyopencl-2023.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0b6a0ebbf67b2ef7098bdf3632177b71c7430883b2a48b2b09b84a02d8cbb4b0"}, - {file = "pyopencl-2023.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1767616f795c598531cd19d89f451db7e25393add300204e7d1a7dd2a017709f"}, - {file = "pyopencl-2023.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:8bbb02f230b969109bdf00f36e463fed1de6e1c70e088f2f2f9b41fab128f20c"}, - {file = "pyopencl-2023.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0e444320491744fe52e49f87674c931219a5be254a8a129175db61378f5b6f18"}, - {file = "pyopencl-2023.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0649a9d6249612e79cffef46618cccaaa9eac7b0c3f1833a3576ea0bd985d887"}, - {file = "pyopencl-2023.1.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f790dda26812cad5288fe1831158f55c48e6ede46ae4a37db66394645b07ef8e"}, - {file = "pyopencl-2023.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:571c6430c6fb046643040d4a6d99ee677e4c6c99b09577dbb51177001e93c21c"}, - {file = "pyopencl-2023.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0ca2d4f14e1fc716b1928679c3182595a4862f577fa0f4a5d8edf37ef8db059b"}, - {file = "pyopencl-2023.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6ac9e8e84dc11b82df51916cf41f34a9ca327adbaf9f4a03a8d1f4f1e1f35382"}, - {file = "pyopencl-2023.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:802e5eb27fd311af42133bb2da83b5777a84f5c7c11e0a4b8d1d50d265b22e37"}, - {file = "pyopencl-2023.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ae4b7f34aa56a4dc5ce5b4d795dc872f9a1aac66f15ded575aeabdfd15da0bc"}, - {file = "pyopencl-2023.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3f5861de88a7d5054cfc8a0f78c42c7b7cd7c65c43a1426a72411111b024658"}, - {file = "pyopencl-2023.1.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c658eb9198235e8866afedb9b32bad4c6a4988c7dff2103e61794cd9ea261b2a"}, - {file = "pyopencl-2023.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d148b21de9f7aa542b576c09ba3c68106658c8a3429f41c0120c7cd4cb55970f"}, - {file = "pyopencl-2023.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:63be569b566ad627e7a1331db6cfda3eb82a2076872549f1c89f4e24ee12601a"}, - {file = "pyopencl-2023.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d61a6ca8c2d8f2d7bcf106abff6ac58cb79f335303b02b90b66591b25d1af4aa"}, - {file = "pyopencl-2023.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:daeff57a66c7a2be03345dd919507f2a2b2ed4ce64c3d8416fc01fa947807e59"}, - {file = "pyopencl-2023.1.4-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c059f15d71c680e35704650bc02d7026b5566687fd45ca9f4c789567d0731cfc"}, - {file = "pyopencl-2023.1.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38c1ab4ed770eb2b2f9c34bced444fc81e96dddd188848f028d36cd16fe9fcb9"}, - {file = "pyopencl-2023.1.4-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d195dad3d3a0473373bbf173671900d4519662824b5a81ced5b491cfae5c5e23"}, - {file = "pyopencl-2023.1.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4917db2d0ec5ea7dd3c0cc66dc6a5acadc39a577a6b532293dde57ba3b23fb"}, - {file = "pyopencl-2023.1.4-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c926fb5886a561fe01be907575e396096a75fd35a2a6df5fc080c19959ae8c5c"}, - {file = "pyopencl-2023.1.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35fc3161b2f609018b6e1b22f4aa79fb571a388b61e90177c1d474975af15ce8"}, - {file = "pyopencl-2023.1.4.tar.gz", hash = "sha256:220174efca900e9d5de5aef2aa1b77a6f2550501de92b035a91013aeae4d4c5e"}, + {file = "pyopencl-2024.2.6-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:bb114056d04613c6ba7f92de4c8daa545dbdcca15379a06367d5a3780cf5930d"}, + {file = "pyopencl-2024.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c38d95bac4176e07865f8d750dee66958dced82ca22e3483898f2d43fcf13ae"}, + {file = "pyopencl-2024.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3e8c14ba292be4b9aef8c8f6011ab7344737239478e55076cc71e2f0d0b6a6a"}, + {file = "pyopencl-2024.2.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355b17304784b484e127978bd5fde58c7758734991aa637b579fea4686055b85"}, + {file = "pyopencl-2024.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:96912ca852ca590eb5334615a3d88017b164ef70ca5791605802f83144ffad3f"}, + {file = "pyopencl-2024.2.6-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:688bff1033fd21d420437986db04b8ab11543b23090c90da2963b4ed4fe94e82"}, + {file = "pyopencl-2024.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4750fc441cc599b17c2c7a654420bb17a9818a02fd1541031a2cb9f867ecc6ef"}, + {file = "pyopencl-2024.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb30fea4527db88e1c037712d4bf0bab44cfc2e38265925aaf6fc8c7efe79b9f"}, + {file = "pyopencl-2024.2.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ad333bfca7f074dd0cf616358408ea69a605e3cab3fdc4d782bcf8c0a78c630a"}, + {file = "pyopencl-2024.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:a538e9551dfacf24f6270bddd581be21abdb1bfbcf64e1b5ba67ff29493ba23d"}, + {file = "pyopencl-2024.2.6-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:a3218b1eafcad4918532aa75f2c9dc0b3491bcf2b602edcdd6e3a4e45a72c68a"}, + {file = "pyopencl-2024.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:30cce55b34bc0a2a4955f3fd37baf34da3969296191f4cd602fd62e320611606"}, + {file = "pyopencl-2024.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:630f48ef777479f1c82b3ad9b3d208abeca2639828defe1c6da01f4d8c8b1ae5"}, + {file = "pyopencl-2024.2.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:725a941be6be4377145f6b9a364221aae397c38b9d475086fa65401a34f39a82"}, + {file = "pyopencl-2024.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:ff7ed484ea950022dcc5ee79fde3c9d9cf6312aa94e010ed462c011062428326"}, + {file = "pyopencl-2024.2.6-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:bc491df0c62cf6d67bdac94bfd892ff4fceb5ec527efac096eeb84ae6cbcaae0"}, + {file = "pyopencl-2024.2.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:743fdae7648f916826d3dda02df6c4627b6dc3a923b95e658abb53f451418f10"}, + {file = "pyopencl-2024.2.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:175fad6fd79ec23d6822395d274838b42e06a3f4b4bb4f35bef4e9559b5f2767"}, + {file = "pyopencl-2024.2.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:114a2058dfaa70724f1cc5f985d2154e885819180e2f2e1c454d83c5a02d5593"}, + {file = "pyopencl-2024.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:554f529b04534ec80a71d10c41deacc26094b5095c5b850829a2717c86c024f6"}, + {file = "pyopencl-2024.2.6-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:fd06bf87b53b360a5b03ef9b31b730abadffdca5fbd6b6b78425cf3bf0883f90"}, + {file = "pyopencl-2024.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ac3d682d27203a5204769d5590164c482afa03b8434cec1f75d0cb805251c857"}, + {file = "pyopencl-2024.2.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f86d5473c1a564f038f7298c2e176dd28b36610569cdac5d3e9ef02f604d2549"}, + {file = "pyopencl-2024.2.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7f9be442abdfdfce5e1238aadfc23756ac2292dd081cd3d1a653d728397ede78"}, + {file = "pyopencl-2024.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:bb693a20b553453c7e3a2eb5f7a43e6d851644ba76a85ee8e185ece0ecf7933b"}, + {file = "pyopencl-2024.2.6.tar.gz", hash = "sha256:ceb9beeef0b6162e601c05922fede29b5094554a66eb473c610cabc14308ba82"}, ] [package.dependencies] numpy = "*" platformdirs = ">=2.2.0" -pytools = ">=2021.2.7" +pytools = ">=2024.1.5" [package.extras] -oclgrind = ["oclgrind-binary-distribution (>=18.3)"] -pocl = ["pocl-binary-distribution (>=1.2)"] +oclgrind = ["oclgrind_binary_distribution (>=18.3)"] +pocl = ["pocl_binary_distribution (>=1.2)"] test = ["Mako", "pytest (>=7.0.0)"] [[package]] name = "pyopenssl" -version = "24.0.0" +version = "24.1.0" description = "Python wrapper module around the OpenSSL library" optional = false python-versions = ">=3.7" files = [ - {file = "pyOpenSSL-24.0.0-py3-none-any.whl", hash = "sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"}, - {file = "pyOpenSSL-24.0.0.tar.gz", hash = "sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf"}, + {file = "pyOpenSSL-24.1.0-py3-none-any.whl", hash = "sha256:17ed5be5936449c5418d1cd269a1a9e9081bc54c17aed272b45856a3d3dc86ad"}, + {file = "pyOpenSSL-24.1.0.tar.gz", hash = "sha256:cabed4bfaa5df9f1a16c0ef64a0cb65318b5cd077a7eda7d6970131ca2f41a6f"}, ] [package.dependencies] @@ -6308,17 +6532,17 @@ cryptography = ">=41.0.5,<43" [package.extras] docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] -test = ["flaky", "pretend", "pytest (>=3.0.1)"] +test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"] [[package]] name = "pyparsing" -version = "3.1.1" +version = "3.1.2" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, ] [package.extras] @@ -6469,33 +6693,51 @@ cp2110 = ["hidapi"] [[package]] name = "pytest" -version = "8.0.2" +version = "8.2.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"}, - {file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"}, + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.3.0,<2.0" +pluggy = ">=1.5,<2.0" [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.23.7" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, + {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" -version = "4.1.0" +version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, ] [package.dependencies] @@ -6503,7 +6745,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-cpp" @@ -6520,6 +6762,23 @@ files = [ colorama = "*" pytest = ">=7.0" +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "pytest-randomly" version = "3.15.0" @@ -6534,15 +6793,29 @@ files = [ [package.dependencies] pytest = "*" +[[package]] +name = "pytest-repeat" +version = "0.9.3" +description = "pytest plugin for repeating tests" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest_repeat-0.9.3-py3-none-any.whl", hash = "sha256:26ab2df18226af9d5ce441c858f273121e92ff55f5bb311d25755b8d7abdd8ed"}, + {file = "pytest_repeat-0.9.3.tar.gz", hash = "sha256:ffd3836dfcd67bb270bec648b330e20be37d2966448c4148c4092d1e8aba8185"}, +] + +[package.dependencies] +pytest = "*" + [[package]] name = "pytest-subtests" -version = "0.11.0" +version = "0.12.1" description = "unittest subTest() support and subtests fixture" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-subtests-0.11.0.tar.gz", hash = "sha256:51865c88457545f51fb72011942f0a3c6901ee9e24cbfb6d1b9dc1348bafbe37"}, - {file = "pytest_subtests-0.11.0-py3-none-any.whl", hash = "sha256:453389984952eec85ab0ce0c4f026337153df79587048271c7fd0f49119c07e4"}, + {file = "pytest-subtests-0.12.1.tar.gz", hash = "sha256:d6605dcb88647e0b7c1889d027f8ef1c17d7a2c60927ebfdc09c7b0d8120476d"}, + {file = "pytest_subtests-0.12.1-py3-none-any.whl", hash = "sha256:100d9f7eb966fc98efba7026c802812ae327e8b5b37181fb260a2ea93226495c"}, ] [package.dependencies] @@ -6551,32 +6824,32 @@ pytest = ">=7.0" [[package]] name = "pytest-timeout" -version = "2.2.0" +version = "2.3.1" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-timeout-2.2.0.tar.gz", hash = "sha256:3b0b95dabf3cb50bac9ef5ca912fa0cfc286526af17afc806824df20c2f72c90"}, - {file = "pytest_timeout-2.2.0-py3-none-any.whl", hash = "sha256:bde531e096466f49398a59f2dde76fa78429a09a12411466f88a07213e220de2"}, + {file = "pytest-timeout-2.3.1.tar.gz", hash = "sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9"}, + {file = "pytest_timeout-2.3.1-py3-none-any.whl", hash = "sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e"}, ] [package.dependencies] -pytest = ">=5.0.0" +pytest = ">=7.0.0" [[package]] name = "pytest-xdist" -version = "3.5.0" +version = "3.6.1" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"}, - {file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"}, + {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, + {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, ] [package.dependencies] -execnet = ">=1.1" -pytest = ">=6.2.0" +execnet = ">=2.1" +pytest = ">=7.0.0" [package.extras] psutil = ["psutil (>=3.0)"] @@ -6585,13 +6858,13 @@ testing = ["filelock"] [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] @@ -6623,13 +6896,13 @@ files = [ [[package]] name = "pytools" -version = "2023.1.1" +version = "2024.1.5" description = "A collection of tools for Python" optional = false python-versions = "~=3.8" files = [ - {file = "pytools-2023.1.1-py2.py3-none-any.whl", hash = "sha256:53b98e5d6c01a90e343f8be2f5271e94204a210ef3e74fbefa3d47ec7480f150"}, - {file = "pytools-2023.1.1.tar.gz", hash = "sha256:80637873d206f6bcedf7cdb46ad93e868acb4ea2256db052dfcca872bdd0321f"}, + {file = "pytools-2024.1.5-py2.py3-none-any.whl", hash = "sha256:dd2b4a1cbe078699742ab37ae3a94a81c5cc598c3247536d12e90588c39b0cc6"}, + {file = "pytools-2024.1.5.tar.gz", hash = "sha256:8c377bb1ffdcb5103301b8e7e94f01cbe36573a01e020434fea96291b1f1ac19"}, ] [package.dependencies] @@ -6684,39 +6957,41 @@ files = [ [[package]] name = "pywinbox" -version = "0.6" +version = "0.7" description = "Cross-Platform and multi-monitor toolkit to handle rectangular areas and windows box" optional = false python-versions = "*" files = [ - {file = "PyWinBox-0.6-py3-none-any.whl", hash = "sha256:3547e459caf466d3beb2230b02208a348478428ebca61ce3e004fb5468976f33"}, + {file = "PyWinBox-0.7-py3-none-any.whl", hash = "sha256:8b2506a8dd7afa0a910b368762adfac885274132ef9151b0c81b0d2c6ffd6f83"}, ] [package.dependencies] +ewmhlib = {version = ">=0.1", markers = "sys_platform == \"linux\""} pyobjc = {version = ">=8.1", markers = "sys_platform == \"darwin\""} python-xlib = {version = ">=0.21", markers = "sys_platform == \"linux\""} pywin32 = {version = ">=302", markers = "sys_platform == \"win32\""} typing-extensions = ">=4.4.0" [package.extras] -dev = ["mypy (>=0.990)", "pywinctl (>=0.1)", "types-python-xlib (>=0.32)", "types-pywin32 (>=305.0.0.3)", "types-setuptools (>=65.5)"] +dev = ["mypy (>=0.990)", "pywinctl (>=0.3)", "types-python-xlib (>=0.32)", "types-pywin32 (>=305.0.0.3)", "types-setuptools (>=65.5)"] [[package]] name = "pywinctl" -version = "0.3" +version = "0.4" description = "Cross-Platform toolkit to get info on and control windows on screen" optional = false python-versions = "*" files = [ - {file = "PyWinCtl-0.3-py3-none-any.whl", hash = "sha256:3603981c87b0c64987e7be857d89450f98792b01f49006a17dac758e11141dd7"}, + {file = "PyWinCtl-0.4-py3-none-any.whl", hash = "sha256:8c4a92bd57e35fd280c5c04f048cc822e236abffe2fa17351096b0e28907172d"}, ] [package.dependencies] -pymonctl = ">=0.6" +ewmhlib = {version = ">=0.2", markers = "sys_platform == \"linux\""} +pymonctl = ">=0.92" pyobjc = {version = ">=8.1", markers = "sys_platform == \"darwin\""} python-xlib = {version = ">=0.21", markers = "sys_platform == \"linux\""} pywin32 = {version = ">=302", markers = "sys_platform == \"win32\""} -pywinbox = ">=0.6" +pywinbox = ">=0.7" typing-extensions = ">=4.4.0" [package.extras] @@ -6784,104 +7059,99 @@ files = [ [[package]] name = "pyzmq" -version = "25.1.2" +version = "26.0.3" description = "Python bindings for 0MQ" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:e624c789359f1a16f83f35e2c705d07663ff2b4d4479bad35621178d8f0f6ea4"}, - {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49151b0efece79f6a79d41a461d78535356136ee70084a1c22532fc6383f4ad0"}, - {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9a5f194cf730f2b24d6af1f833c14c10f41023da46a7f736f48b6d35061e76e"}, - {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:faf79a302f834d9e8304fafdc11d0d042266667ac45209afa57e5efc998e3872"}, - {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f51a7b4ead28d3fca8dda53216314a553b0f7a91ee8fc46a72b402a78c3e43d"}, - {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0ddd6d71d4ef17ba5a87becf7ddf01b371eaba553c603477679ae817a8d84d75"}, - {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:246747b88917e4867e2367b005fc8eefbb4a54b7db363d6c92f89d69abfff4b6"}, - {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:00c48ae2fd81e2a50c3485de1b9d5c7c57cd85dc8ec55683eac16846e57ac979"}, - {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a68d491fc20762b630e5db2191dd07ff89834086740f70e978bb2ef2668be08"}, - {file = "pyzmq-25.1.2-cp310-cp310-win32.whl", hash = "sha256:09dfe949e83087da88c4a76767df04b22304a682d6154de2c572625c62ad6886"}, - {file = "pyzmq-25.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:fa99973d2ed20417744fca0073390ad65ce225b546febb0580358e36aa90dba6"}, - {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:82544e0e2d0c1811482d37eef297020a040c32e0687c1f6fc23a75b75db8062c"}, - {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01171fc48542348cd1a360a4b6c3e7d8f46cdcf53a8d40f84db6707a6768acc1"}, - {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc69c96735ab501419c432110016329bf0dea8898ce16fab97c6d9106dc0b348"}, - {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e124e6b1dd3dfbeb695435dff0e383256655bb18082e094a8dd1f6293114642"}, - {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7598d2ba821caa37a0f9d54c25164a4fa351ce019d64d0b44b45540950458840"}, - {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d1299d7e964c13607efd148ca1f07dcbf27c3ab9e125d1d0ae1d580a1682399d"}, - {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4e6f689880d5ad87918430957297c975203a082d9a036cc426648fcbedae769b"}, - {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cc69949484171cc961e6ecd4a8911b9ce7a0d1f738fcae717177c231bf77437b"}, - {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9880078f683466b7f567b8624bfc16cad65077be046b6e8abb53bed4eeb82dd3"}, - {file = "pyzmq-25.1.2-cp311-cp311-win32.whl", hash = "sha256:4e5837af3e5aaa99a091302df5ee001149baff06ad22b722d34e30df5f0d9097"}, - {file = "pyzmq-25.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:25c2dbb97d38b5ac9fd15586e048ec5eb1e38f3d47fe7d92167b0c77bb3584e9"}, - {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:11e70516688190e9c2db14fcf93c04192b02d457b582a1f6190b154691b4c93a"}, - {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:313c3794d650d1fccaaab2df942af9f2c01d6217c846177cfcbc693c7410839e"}, - {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b3cbba2f47062b85fe0ef9de5b987612140a9ba3a9c6d2543c6dec9f7c2ab27"}, - {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc31baa0c32a2ca660784d5af3b9487e13b61b3032cb01a115fce6588e1bed30"}, - {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c9087b109070c5ab0b383079fa1b5f797f8d43e9a66c07a4b8b8bdecfd88ee"}, - {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f8429b17cbb746c3e043cb986328da023657e79d5ed258b711c06a70c2ea7537"}, - {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5074adeacede5f810b7ef39607ee59d94e948b4fd954495bdb072f8c54558181"}, - {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7ae8f354b895cbd85212da245f1a5ad8159e7840e37d78b476bb4f4c3f32a9fe"}, - {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b264bf2cc96b5bc43ce0e852be995e400376bd87ceb363822e2cb1964fcdc737"}, - {file = "pyzmq-25.1.2-cp312-cp312-win32.whl", hash = "sha256:02bbc1a87b76e04fd780b45e7f695471ae6de747769e540da909173d50ff8e2d"}, - {file = "pyzmq-25.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:ced111c2e81506abd1dc142e6cd7b68dd53747b3b7ae5edbea4578c5eeff96b7"}, - {file = "pyzmq-25.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7b6d09a8962a91151f0976008eb7b29b433a560fde056ec7a3db9ec8f1075438"}, - {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967668420f36878a3c9ecb5ab33c9d0ff8d054f9c0233d995a6d25b0e95e1b6b"}, - {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5edac3f57c7ddaacdb4d40f6ef2f9e299471fc38d112f4bc6d60ab9365445fb0"}, - {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0dabfb10ef897f3b7e101cacba1437bd3a5032ee667b7ead32bbcdd1a8422fe7"}, - {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2c6441e0398c2baacfe5ba30c937d274cfc2dc5b55e82e3749e333aabffde561"}, - {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:16b726c1f6c2e7625706549f9dbe9b06004dfbec30dbed4bf50cbdfc73e5b32a"}, - {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:a86c2dd76ef71a773e70551a07318b8e52379f58dafa7ae1e0a4be78efd1ff16"}, - {file = "pyzmq-25.1.2-cp36-cp36m-win32.whl", hash = "sha256:359f7f74b5d3c65dae137f33eb2bcfa7ad9ebefd1cab85c935f063f1dbb245cc"}, - {file = "pyzmq-25.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:55875492f820d0eb3417b51d96fea549cde77893ae3790fd25491c5754ea2f68"}, - {file = "pyzmq-25.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8c8a419dfb02e91b453615c69568442e897aaf77561ee0064d789705ff37a92"}, - {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8807c87fa893527ae8a524c15fc505d9950d5e856f03dae5921b5e9aa3b8783b"}, - {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5e319ed7d6b8f5fad9b76daa0a68497bc6f129858ad956331a5835785761e003"}, - {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3c53687dde4d9d473c587ae80cc328e5b102b517447456184b485587ebd18b62"}, - {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9add2e5b33d2cd765ad96d5eb734a5e795a0755f7fc49aa04f76d7ddda73fd70"}, - {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e690145a8c0c273c28d3b89d6fb32c45e0d9605b2293c10e650265bf5c11cfec"}, - {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:00a06faa7165634f0cac1abb27e54d7a0b3b44eb9994530b8ec73cf52e15353b"}, - {file = "pyzmq-25.1.2-cp37-cp37m-win32.whl", hash = "sha256:0f97bc2f1f13cb16905a5f3e1fbdf100e712d841482b2237484360f8bc4cb3d7"}, - {file = "pyzmq-25.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6cc0020b74b2e410287e5942e1e10886ff81ac77789eb20bec13f7ae681f0fdd"}, - {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:bef02cfcbded83473bdd86dd8d3729cd82b2e569b75844fb4ea08fee3c26ae41"}, - {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e10a4b5a4b1192d74853cc71a5e9fd022594573926c2a3a4802020360aa719d8"}, - {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8c5f80e578427d4695adac6fdf4370c14a2feafdc8cb35549c219b90652536ae"}, - {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5dde6751e857910c1339890f3524de74007958557593b9e7e8c5f01cd919f8a7"}, - {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea1608dd169da230a0ad602d5b1ebd39807ac96cae1845c3ceed39af08a5c6df"}, - {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0f513130c4c361201da9bc69df25a086487250e16b5571ead521b31ff6b02220"}, - {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:019744b99da30330798bb37df33549d59d380c78e516e3bab9c9b84f87a9592f"}, - {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2e2713ef44be5d52dd8b8e2023d706bf66cb22072e97fc71b168e01d25192755"}, - {file = "pyzmq-25.1.2-cp38-cp38-win32.whl", hash = "sha256:07cd61a20a535524906595e09344505a9bd46f1da7a07e504b315d41cd42eb07"}, - {file = "pyzmq-25.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb7e49a17fb8c77d3119d41a4523e432eb0c6932187c37deb6fbb00cc3028088"}, - {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:94504ff66f278ab4b7e03e4cba7e7e400cb73bfa9d3d71f58d8972a8dc67e7a6"}, - {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6dd0d50bbf9dca1d0bdea219ae6b40f713a3fb477c06ca3714f208fd69e16fd8"}, - {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:004ff469d21e86f0ef0369717351073e0e577428e514c47c8480770d5e24a565"}, - {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c0b5ca88a8928147b7b1e2dfa09f3b6c256bc1135a1338536cbc9ea13d3b7add"}, - {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9a79f1d2495b167119d02be7448bfba57fad2a4207c4f68abc0bab4b92925b"}, - {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:518efd91c3d8ac9f9b4f7dd0e2b7b8bf1a4fe82a308009016b07eaa48681af82"}, - {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1ec23bd7b3a893ae676d0e54ad47d18064e6c5ae1fadc2f195143fb27373f7f6"}, - {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db36c27baed588a5a8346b971477b718fdc66cf5b80cbfbd914b4d6d355e44e2"}, - {file = "pyzmq-25.1.2-cp39-cp39-win32.whl", hash = "sha256:39b1067f13aba39d794a24761e385e2eddc26295826530a8c7b6c6c341584289"}, - {file = "pyzmq-25.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:8e9f3fabc445d0ce320ea2c59a75fe3ea591fdbdeebec5db6de530dd4b09412e"}, - {file = "pyzmq-25.1.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a8c1d566344aee826b74e472e16edae0a02e2a044f14f7c24e123002dcff1c05"}, - {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759cfd391a0996345ba94b6a5110fca9c557ad4166d86a6e81ea526c376a01e8"}, - {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c61e346ac34b74028ede1c6b4bcecf649d69b707b3ff9dc0fab453821b04d1e"}, - {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb8fc1f8d69b411b8ec0b5f1ffbcaf14c1db95b6bccea21d83610987435f1a4"}, - {file = "pyzmq-25.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3c00c9b7d1ca8165c610437ca0c92e7b5607b2f9076f4eb4b095c85d6e680a1d"}, - {file = "pyzmq-25.1.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:df0c7a16ebb94452d2909b9a7b3337940e9a87a824c4fc1c7c36bb4404cb0cde"}, - {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:45999e7f7ed5c390f2e87ece7f6c56bf979fb213550229e711e45ecc7d42ccb8"}, - {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ac170e9e048b40c605358667aca3d94e98f604a18c44bdb4c102e67070f3ac9b"}, - {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b604734bec94f05f81b360a272fc824334267426ae9905ff32dc2be433ab96"}, - {file = "pyzmq-25.1.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a793ac733e3d895d96f865f1806f160696422554e46d30105807fdc9841b9f7d"}, - {file = "pyzmq-25.1.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0806175f2ae5ad4b835ecd87f5f85583316b69f17e97786f7443baaf54b9bb98"}, - {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ef12e259e7bc317c7597d4f6ef59b97b913e162d83b421dd0db3d6410f17a244"}, - {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea253b368eb41116011add00f8d5726762320b1bda892f744c91997b65754d73"}, - {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b9b1f2ad6498445a941d9a4fee096d387fee436e45cc660e72e768d3d8ee611"}, - {file = "pyzmq-25.1.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8b14c75979ce932c53b79976a395cb2a8cd3aaf14aef75e8c2cb55a330b9b49d"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:889370d5174a741a62566c003ee8ddba4b04c3f09a97b8000092b7ca83ec9c49"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a18fff090441a40ffda8a7f4f18f03dc56ae73f148f1832e109f9bffa85df15"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99a6b36f95c98839ad98f8c553d8507644c880cf1e0a57fe5e3a3f3969040882"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4345c9a27f4310afbb9c01750e9461ff33d6fb74cd2456b107525bbeebcb5be3"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3516e0b6224cf6e43e341d56da15fd33bdc37fa0c06af4f029f7d7dfceceabbc"}, - {file = "pyzmq-25.1.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:146b9b1f29ead41255387fb07be56dc29639262c0f7344f570eecdcd8d683314"}, - {file = "pyzmq-25.1.2.tar.gz", hash = "sha256:93f1aa311e8bb912e34f004cf186407a4e90eec4f0ecc0efd26056bf7eda0226"}, + {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625"}, + {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8"}, + {file = "pyzmq-26.0.3-cp310-cp310-win32.whl", hash = "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537"}, + {file = "pyzmq-26.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47"}, + {file = "pyzmq-26.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7"}, + {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32"}, + {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83"}, + {file = "pyzmq-26.0.3-cp311-cp311-win32.whl", hash = "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3"}, + {file = "pyzmq-26.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500"}, + {file = "pyzmq-26.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94"}, + {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753"}, + {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798"}, + {file = "pyzmq-26.0.3-cp312-cp312-win32.whl", hash = "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0"}, + {file = "pyzmq-26.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf"}, + {file = "pyzmq-26.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b"}, + {file = "pyzmq-26.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5"}, + {file = "pyzmq-26.0.3-cp37-cp37m-win32.whl", hash = "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf"}, + {file = "pyzmq-26.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a"}, + {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18"}, + {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97"}, + {file = "pyzmq-26.0.3-cp38-cp38-win32.whl", hash = "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"}, + {file = "pyzmq-26.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972"}, + {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606"}, + {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920"}, + {file = "pyzmq-26.0.3-cp39-cp39-win32.whl", hash = "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879"}, + {file = "pyzmq-26.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2"}, + {file = "pyzmq-26.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad"}, + {file = "pyzmq-26.0.3.tar.gz", hash = "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a"}, ] [package.dependencies] @@ -6889,13 +7159,13 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -6908,103 +7178,124 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rerun-sdk" +version = "0.16.1" +description = "The Rerun Logging SDK" +optional = false +python-versions = "<3.13,>=3.8" +files = [ + {file = "rerun_sdk-0.16.1-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:170c6976634008611753e10dfef8cdc395ce8180e634c169e7c61cef2f89a277"}, + {file = "rerun_sdk-0.16.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c9a76eab7eb5559276737dad655200e9350df0837158dbc5a896970ab4201454"}, + {file = "rerun_sdk-0.16.1-cp38-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:4d6436752d57e8b8038489a0e7e37f0c760b088e96db5fb81667d3a376d63fea"}, + {file = "rerun_sdk-0.16.1-cp38-abi3-manylinux_2_31_x86_64.whl", hash = "sha256:37b7b47948471873e84f224b16f417a94a91c7cbd6c72c68281eeff1ba414b8f"}, + {file = "rerun_sdk-0.16.1-cp38-abi3-win_amd64.whl", hash = "sha256:be88799c8afdf68eafa99e64e2e4f0a484e187e017a180219abbe6bb988acd4e"}, +] + +[package.dependencies] +attrs = ">=23.1.0" +numpy = ">=1.23,<2" +pillow = ">=8.0.0" +pyarrow = ">=14.0.2" +typing-extensions = ">=4.5" + +[package.extras] +tests = ["pytest (==7.1.2)"] + [[package]] name = "rubicon-objc" -version = "0.4.7" +version = "0.4.9" description = "A bridge between an Objective C runtime environment and Python." optional = false python-versions = ">=3.8" files = [ - {file = "rubicon-objc-0.4.7.tar.gz", hash = "sha256:be937d864bd1229f860defabb89b40c53634eedc36448d89ad3c14eb3286e509"}, - {file = "rubicon_objc-0.4.7-py3-none-any.whl", hash = "sha256:f37108e35d5da1a78ab3eed2d03b095934f5f618329a939e4bd2ada9894eff6e"}, + {file = "rubicon_objc-0.4.9-py3-none-any.whl", hash = "sha256:c351b3800cf74c8c23f7d534f008fd5de46c63818de7a44de96daffdb3ed8b8c"}, + {file = "rubicon_objc-0.4.9.tar.gz", hash = "sha256:3d77a5b2d10cb1e49679aa90b7824b46f67b3fd636229aa4a1b902d24aec6a58"}, ] [package.extras] -dev = ["pre-commit (==3.5.0)", "pytest (==7.4.2)", "pytest-tldr (==0.2.5)", "setuptools-scm[toml] (==8.0.4)", "tox (==4.11.3)"] -docs = ["furo (==2023.9.10)", "pyenchant (==3.2.2)", "sphinx (==7.1.2)", "sphinx (==7.2.6)", "sphinx-autobuild (==2021.3.14)", "sphinx-copybutton (==0.5.2)", "sphinx-tabs (==3.4.1)", "sphinxcontrib-spelling (==8.0.0)"] +dev = ["pre-commit (==3.5.0)", "pre-commit (==3.7.0)", "pytest (==8.2.0)", "pytest-tldr (==0.2.5)", "setuptools-scm (==8.0.4)", "tox (==4.15.0)"] +docs = ["furo (==2024.4.27)", "pyenchant (==3.2.2)", "sphinx (==7.1.2)", "sphinx (==7.3.7)", "sphinx-autobuild (==2021.3.14)", "sphinx-autobuild (==2024.4.16)", "sphinx-copybutton (==0.5.2)", "sphinx-tabs (==3.4.5)", "sphinxcontrib-spelling (==8.0.0)"] [[package]] name = "ruff" -version = "0.2.2" +version = "0.4.8" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {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"}, + {file = "ruff-0.4.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7663a6d78f6adb0eab270fa9cf1ff2d28618ca3a652b60f2a234d92b9ec89066"}, + {file = "ruff-0.4.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eeceb78da8afb6de0ddada93112869852d04f1cd0f6b80fe464fd4e35c330913"}, + {file = "ruff-0.4.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aad360893e92486662ef3be0a339c5ca3c1b109e0134fcd37d534d4be9fb8de3"}, + {file = "ruff-0.4.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:284c2e3f3396fb05f5f803c9fffb53ebbe09a3ebe7dda2929ed8d73ded736deb"}, + {file = "ruff-0.4.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7354f921e3fbe04d2a62d46707e569f9315e1a613307f7311a935743c51a764"}, + {file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:72584676164e15a68a15778fd1b17c28a519e7a0622161eb2debdcdabdc71883"}, + {file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9678d5c9b43315f323af2233a04d747409d1e3aa6789620083a82d1066a35199"}, + {file = "ruff-0.4.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704977a658131651a22b5ebeb28b717ef42ac6ee3b11e91dc87b633b5d83142b"}, + {file = "ruff-0.4.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05f8d6f0c3cce5026cecd83b7a143dcad503045857bc49662f736437380ad45"}, + {file = "ruff-0.4.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ea874950daca5697309d976c9afba830d3bf0ed66887481d6bca1673fc5b66a"}, + {file = "ruff-0.4.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fc95aac2943ddf360376be9aa3107c8cf9640083940a8c5bd824be692d2216dc"}, + {file = "ruff-0.4.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:384154a1c3f4bf537bac69f33720957ee49ac8d484bfc91720cc94172026ceed"}, + {file = "ruff-0.4.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e9d5ce97cacc99878aa0d084c626a15cd21e6b3d53fd6f9112b7fc485918e1fa"}, + {file = "ruff-0.4.8-py3-none-win32.whl", hash = "sha256:6d795d7639212c2dfd01991259460101c22aabf420d9b943f153ab9d9706e6a9"}, + {file = "ruff-0.4.8-py3-none-win_amd64.whl", hash = "sha256:e14a3a095d07560a9d6769a72f781d73259655919d9b396c650fc98a8157555d"}, + {file = "ruff-0.4.8-py3-none-win_arm64.whl", hash = "sha256:14019a06dbe29b608f6b7cbcec300e3170a8d86efaddb7b23405cb7f7dcaf780"}, + {file = "ruff-0.4.8.tar.gz", hash = "sha256:16d717b1d57b2e2fd68bd0bf80fb43931b79d05a7131aa477d66fc40fbd86268"}, ] [[package]] name = "scipy" -version = "1.12.0" +version = "1.13.1" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b"}, - {file = "scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1"}, - {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563"}, - {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c"}, - {file = "scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd"}, - {file = "scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2"}, - {file = "scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08"}, - {file = "scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c"}, - {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467"}, - {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a"}, - {file = "scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba"}, - {file = "scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70"}, - {file = "scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372"}, - {file = "scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3"}, - {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc"}, - {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c"}, - {file = "scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338"}, - {file = "scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c"}, - {file = "scipy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35"}, - {file = "scipy-1.12.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067"}, - {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371"}, - {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490"}, - {file = "scipy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc"}, - {file = "scipy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e"}, - {file = "scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, + {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, + {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, + {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, + {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, + {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, + {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, + {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, + {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, + {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, ] [package.dependencies] -numpy = ">=1.22.4,<1.29.0" +numpy = ">=1.22.4,<2.3" [package.extras] -dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] +test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "scons" -version = "4.6.0.post1" +version = "4.7.0" description = "Open Source next-generation build tool." optional = false python-versions = ">=3.6" files = [ - {file = "SCons-4.6.0.post1-py3-none-any.whl", hash = "sha256:9e0527b7a924e7af2b312c1f8961ef2776847bdc46a4d886af5a75f301da7ce3"}, - {file = "SCons-4.6.0.post1.tar.gz", hash = "sha256:d467a34546f5366a32e4d4419611d0138dd680210f24aa8491ebe9e4b83456cf"}, + {file = "SCons-4.7.0-py3-none-any.whl", hash = "sha256:93308e564966760a63a4c1e016b2cc15d07bb40db67b1c907732da0b9e9f8959"}, + {file = "SCons-4.7.0.tar.gz", hash = "sha256:d8b617f6610a73e46509de70dcf82f76861b79762ff602d546f4e80918ec81f3"}, ] -[package.dependencies] -setuptools = "*" - [[package]] name = "seaborn" version = "0.13.2" @@ -7028,36 +7319,41 @@ stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] [[package]] name = "sentry-sdk" -version = "1.40.5" +version = "2.5.1" description = "Python client for Sentry (https://sentry.io)" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {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"}, + {file = "sentry_sdk-2.5.1-py2.py3-none-any.whl", hash = "sha256:1f87acdce4a43a523ae5aa21a3fc37522d73ebd9ec04b1dbf01aa3d173852def"}, + {file = "sentry_sdk-2.5.1.tar.gz", hash = "sha256:fbc40a78a8a9c6675133031116144f0d0940376fa6e4e1acd5624c90b0aaf58b"}, ] [package.dependencies] certifi = "*" -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} +urllib3 = ">=1.26.11" [package.extras] aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] arq = ["arq (>=0.23)"] asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] chalice = ["chalice (>=1.16.0)"] clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] -grpcio = ["grpcio (>=1.21.1)"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] +huggingface-hub = ["huggingface-hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] 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"] @@ -7071,174 +7367,73 @@ starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] -[[package]] -name = "setproctitle" -version = "1.3.3" -description = "A Python module to customize the process title" -optional = false -python-versions = ">=3.7" -files = [ - {file = "setproctitle-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:897a73208da48db41e687225f355ce993167079eda1260ba5e13c4e53be7f754"}, - {file = "setproctitle-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c331e91a14ba4076f88c29c777ad6b58639530ed5b24b5564b5ed2fd7a95452"}, - {file = "setproctitle-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbbd6c7de0771c84b4aa30e70b409565eb1fc13627a723ca6be774ed6b9d9fa3"}, - {file = "setproctitle-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c05ac48ef16ee013b8a326c63e4610e2430dbec037ec5c5b58fcced550382b74"}, - {file = "setproctitle-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1342f4fdb37f89d3e3c1c0a59d6ddbedbde838fff5c51178a7982993d238fe4f"}, - {file = "setproctitle-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc74e84fdfa96821580fb5e9c0b0777c1c4779434ce16d3d62a9c4d8c710df39"}, - {file = "setproctitle-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9617b676b95adb412bb69645d5b077d664b6882bb0d37bfdafbbb1b999568d85"}, - {file = "setproctitle-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6a249415f5bb88b5e9e8c4db47f609e0bf0e20a75e8d744ea787f3092ba1f2d0"}, - {file = "setproctitle-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:38da436a0aaace9add67b999eb6abe4b84397edf4a78ec28f264e5b4c9d53cd5"}, - {file = "setproctitle-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:da0d57edd4c95bf221b2ebbaa061e65b1788f1544977288bdf95831b6e44e44d"}, - {file = "setproctitle-1.3.3-cp310-cp310-win32.whl", hash = "sha256:a1fcac43918b836ace25f69b1dca8c9395253ad8152b625064415b1d2f9be4fb"}, - {file = "setproctitle-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:200620c3b15388d7f3f97e0ae26599c0c378fdf07ae9ac5a13616e933cbd2086"}, - {file = "setproctitle-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:334f7ed39895d692f753a443102dd5fed180c571eb6a48b2a5b7f5b3564908c8"}, - {file = "setproctitle-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:950f6476d56ff7817a8fed4ab207727fc5260af83481b2a4b125f32844df513a"}, - {file = "setproctitle-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:195c961f54a09eb2acabbfc90c413955cf16c6e2f8caa2adbf2237d1019c7dd8"}, - {file = "setproctitle-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f05e66746bf9fe6a3397ec246fe481096664a9c97eb3fea6004735a4daf867fd"}, - {file = "setproctitle-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5901a31012a40ec913265b64e48c2a4059278d9f4e6be628441482dd13fb8b5"}, - {file = "setproctitle-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64286f8a995f2cd934082b398fc63fca7d5ffe31f0e27e75b3ca6b4efda4e353"}, - {file = "setproctitle-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:184239903bbc6b813b1a8fc86394dc6ca7d20e2ebe6f69f716bec301e4b0199d"}, - {file = "setproctitle-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:664698ae0013f986118064b6676d7dcd28fefd0d7d5a5ae9497cbc10cba48fa5"}, - {file = "setproctitle-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e5119a211c2e98ff18b9908ba62a3bd0e3fabb02a29277a7232a6fb4b2560aa0"}, - {file = "setproctitle-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:417de6b2e214e837827067048f61841f5d7fc27926f2e43954567094051aff18"}, - {file = "setproctitle-1.3.3-cp311-cp311-win32.whl", hash = "sha256:6a143b31d758296dc2f440175f6c8e0b5301ced3b0f477b84ca43cdcf7f2f476"}, - {file = "setproctitle-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a680d62c399fa4b44899094027ec9a1bdaf6f31c650e44183b50d4c4d0ccc085"}, - {file = "setproctitle-1.3.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d4460795a8a7a391e3567b902ec5bdf6c60a47d791c3b1d27080fc203d11c9dc"}, - {file = "setproctitle-1.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bdfd7254745bb737ca1384dee57e6523651892f0ea2a7344490e9caefcc35e64"}, - {file = "setproctitle-1.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477d3da48e216d7fc04bddab67b0dcde633e19f484a146fd2a34bb0e9dbb4a1e"}, - {file = "setproctitle-1.3.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ab2900d111e93aff5df9fddc64cf51ca4ef2c9f98702ce26524f1acc5a786ae7"}, - {file = "setproctitle-1.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088b9efc62d5aa5d6edf6cba1cf0c81f4488b5ce1c0342a8b67ae39d64001120"}, - {file = "setproctitle-1.3.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6d50252377db62d6a0bb82cc898089916457f2db2041e1d03ce7fadd4a07381"}, - {file = "setproctitle-1.3.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:87e668f9561fd3a457ba189edfc9e37709261287b52293c115ae3487a24b92f6"}, - {file = "setproctitle-1.3.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:287490eb90e7a0ddd22e74c89a92cc922389daa95babc833c08cf80c84c4df0a"}, - {file = "setproctitle-1.3.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:4fe1c49486109f72d502f8be569972e27f385fe632bd8895f4730df3c87d5ac8"}, - {file = "setproctitle-1.3.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4a6ba2494a6449b1f477bd3e67935c2b7b0274f2f6dcd0f7c6aceae10c6c6ba3"}, - {file = "setproctitle-1.3.3-cp312-cp312-win32.whl", hash = "sha256:2df2b67e4b1d7498632e18c56722851ba4db5d6a0c91aaf0fd395111e51cdcf4"}, - {file = "setproctitle-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:f38d48abc121263f3b62943f84cbaede05749047e428409c2c199664feb6abc7"}, - {file = "setproctitle-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:816330675e3504ae4d9a2185c46b573105d2310c20b19ea2b4596a9460a4f674"}, - {file = "setproctitle-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f960bc22d8d8e4ac886d1e2e21ccbd283adcf3c43136161c1ba0fa509088e0"}, - {file = "setproctitle-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e6e7adff74796ef12753ff399491b8827f84f6c77659d71bd0b35870a17d8f"}, - {file = "setproctitle-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53bc0d2358507596c22b02db079618451f3bd720755d88e3cccd840bafb4c41c"}, - {file = "setproctitle-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6d20f9541f5f6ac63df553b6d7a04f313947f550eab6a61aa758b45f0d5657"}, - {file = "setproctitle-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c1c84beab776b0becaa368254801e57692ed749d935469ac10e2b9b825dbdd8e"}, - {file = "setproctitle-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:507e8dc2891021350eaea40a44ddd887c9f006e6b599af8d64a505c0f718f170"}, - {file = "setproctitle-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b1067647ac7aba0b44b591936118a22847bda3c507b0a42d74272256a7a798e9"}, - {file = "setproctitle-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2e71f6365744bf53714e8bd2522b3c9c1d83f52ffa6324bd7cbb4da707312cd8"}, - {file = "setproctitle-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:7f1d36a1e15a46e8ede4e953abb104fdbc0845a266ec0e99cc0492a4364f8c44"}, - {file = "setproctitle-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9a402881ec269d0cc9c354b149fc29f9ec1a1939a777f1c858cdb09c7a261df"}, - {file = "setproctitle-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ff814dea1e5c492a4980e3e7d094286077054e7ea116cbeda138819db194b2cd"}, - {file = "setproctitle-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:accb66d7b3ccb00d5cd11d8c6e07055a4568a24c95cf86109894dcc0c134cc89"}, - {file = "setproctitle-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554eae5a5b28f02705b83a230e9d163d645c9a08914c0ad921df363a07cf39b1"}, - {file = "setproctitle-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a911b26264dbe9e8066c7531c0591cfab27b464459c74385b276fe487ca91c12"}, - {file = "setproctitle-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2982efe7640c4835f7355fdb4da313ad37fb3b40f5c69069912f8048f77b28c8"}, - {file = "setproctitle-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df3f4274b80709d8bcab2f9a862973d453b308b97a0b423a501bcd93582852e3"}, - {file = "setproctitle-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:af2c67ae4c795d1674a8d3ac1988676fa306bcfa1e23fddb5e0bd5f5635309ca"}, - {file = "setproctitle-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:af4061f67fd7ec01624c5e3c21f6b7af2ef0e6bab7fbb43f209e6506c9ce0092"}, - {file = "setproctitle-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:37a62cbe16d4c6294e84670b59cf7adcc73faafe6af07f8cb9adaf1f0e775b19"}, - {file = "setproctitle-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a83ca086fbb017f0d87f240a8f9bbcf0809f3b754ee01cec928fff926542c450"}, - {file = "setproctitle-1.3.3-cp38-cp38-win32.whl", hash = "sha256:059f4ce86f8cc92e5860abfc43a1dceb21137b26a02373618d88f6b4b86ba9b2"}, - {file = "setproctitle-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:ab92e51cd4a218208efee4c6d37db7368fdf182f6e7ff148fb295ecddf264287"}, - {file = "setproctitle-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c7951820b77abe03d88b114b998867c0f99da03859e5ab2623d94690848d3e45"}, - {file = "setproctitle-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5bc94cf128676e8fac6503b37763adb378e2b6be1249d207630f83fc325d9b11"}, - {file = "setproctitle-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f5d9027eeda64d353cf21a3ceb74bb1760bd534526c9214e19f052424b37e42"}, - {file = "setproctitle-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e4a8104db15d3462e29d9946f26bed817a5b1d7a47eabca2d9dc2b995991503"}, - {file = "setproctitle-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c32c41ace41f344d317399efff4cffb133e709cec2ef09c99e7a13e9f3b9483c"}, - {file = "setproctitle-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbf16381c7bf7f963b58fb4daaa65684e10966ee14d26f5cc90f07049bfd8c1e"}, - {file = "setproctitle-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e18b7bd0898398cc97ce2dfc83bb192a13a087ef6b2d5a8a36460311cb09e775"}, - {file = "setproctitle-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69d565d20efe527bd8a9b92e7f299ae5e73b6c0470f3719bd66f3cd821e0d5bd"}, - {file = "setproctitle-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ddedd300cd690a3b06e7eac90ed4452348b1348635777ce23d460d913b5b63c3"}, - {file = "setproctitle-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:415bfcfd01d1fbf5cbd75004599ef167a533395955305f42220a585f64036081"}, - {file = "setproctitle-1.3.3-cp39-cp39-win32.whl", hash = "sha256:21112fcd2195d48f25760f0eafa7a76510871bbb3b750219310cf88b04456ae3"}, - {file = "setproctitle-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:5a740f05d0968a5a17da3d676ce6afefebeeeb5ce137510901bf6306ba8ee002"}, - {file = "setproctitle-1.3.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6b9e62ddb3db4b5205c0321dd69a406d8af9ee1693529d144e86bd43bcb4b6c0"}, - {file = "setproctitle-1.3.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e3b99b338598de0bd6b2643bf8c343cf5ff70db3627af3ca427a5e1a1a90dd9"}, - {file = "setproctitle-1.3.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ae9a02766dad331deb06855fb7a6ca15daea333b3967e214de12cfae8f0ef5"}, - {file = "setproctitle-1.3.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:200ede6fd11233085ba9b764eb055a2a191fb4ffb950c68675ac53c874c22e20"}, - {file = "setproctitle-1.3.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0d3a953c50776751e80fe755a380a64cb14d61e8762bd43041ab3f8cc436092f"}, - {file = "setproctitle-1.3.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5e08e232b78ba3ac6bc0d23ce9e2bee8fad2be391b7e2da834fc9a45129eb87"}, - {file = "setproctitle-1.3.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1da82c3e11284da4fcbf54957dafbf0655d2389cd3d54e4eaba636faf6d117a"}, - {file = "setproctitle-1.3.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:aeaa71fb9568ebe9b911ddb490c644fbd2006e8c940f21cb9a1e9425bd709574"}, - {file = "setproctitle-1.3.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:59335d000c6250c35989394661eb6287187854e94ac79ea22315469ee4f4c244"}, - {file = "setproctitle-1.3.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3ba57029c9c50ecaf0c92bb127224cc2ea9fda057b5d99d3f348c9ec2855ad3"}, - {file = "setproctitle-1.3.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d876d355c53d975c2ef9c4f2487c8f83dad6aeaaee1b6571453cb0ee992f55f6"}, - {file = "setproctitle-1.3.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:224602f0939e6fb9d5dd881be1229d485f3257b540f8a900d4271a2c2aa4e5f4"}, - {file = "setproctitle-1.3.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d7f27e0268af2d7503386e0e6be87fb9b6657afd96f5726b733837121146750d"}, - {file = "setproctitle-1.3.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5e7266498cd31a4572378c61920af9f6b4676a73c299fce8ba93afd694f8ae7"}, - {file = "setproctitle-1.3.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33c5609ad51cd99d388e55651b19148ea99727516132fb44680e1f28dd0d1de9"}, - {file = "setproctitle-1.3.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:eae8988e78192fd1a3245a6f4f382390b61bce6cfcc93f3809726e4c885fa68d"}, - {file = "setproctitle-1.3.3.tar.gz", hash = "sha256:c913e151e7ea01567837ff037a23ca8740192880198b7fbb90b16d181607caae"}, -] - -[package.extras] -test = ["pytest"] - [[package]] name = "setuptools" -version = "69.1.1" +version = "70.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, - {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, + {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, + {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, ] [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)", "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"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "shapely" -version = "2.0.3" +version = "2.0.4" description = "Manipulation and analysis of geometric objects" optional = false python-versions = ">=3.7" files = [ - {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"}, + {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:011b77153906030b795791f2fdfa2d68f1a8d7e40bce78b029782ade3afe4f2f"}, + {file = "shapely-2.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9831816a5d34d5170aa9ed32a64982c3d6f4332e7ecfe62dc97767e163cb0b17"}, + {file = "shapely-2.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c4849916f71dc44e19ed370421518c0d86cf73b26e8656192fcfcda08218fbd"}, + {file = "shapely-2.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841f93a0e31e4c64d62ea570d81c35de0f6cea224568b2430d832967536308e6"}, + {file = "shapely-2.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b4431f522b277c79c34b65da128029a9955e4481462cbf7ebec23aab61fc58"}, + {file = "shapely-2.0.4-cp310-cp310-win32.whl", hash = "sha256:92a41d936f7d6743f343be265ace93b7c57f5b231e21b9605716f5a47c2879e7"}, + {file = "shapely-2.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:30982f79f21bb0ff7d7d4a4e531e3fcaa39b778584c2ce81a147f95be1cd58c9"}, + {file = "shapely-2.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de0205cb21ad5ddaef607cda9a3191eadd1e7a62a756ea3a356369675230ac35"}, + {file = "shapely-2.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7d56ce3e2a6a556b59a288771cf9d091470116867e578bebced8bfc4147fbfd7"}, + {file = "shapely-2.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58b0ecc505bbe49a99551eea3f2e8a9b3b24b3edd2a4de1ac0dc17bc75c9ec07"}, + {file = "shapely-2.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:790a168a808bd00ee42786b8ba883307c0e3684ebb292e0e20009588c426da47"}, + {file = "shapely-2.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4310b5494271e18580d61022c0857eb85d30510d88606fa3b8314790df7f367d"}, + {file = "shapely-2.0.4-cp311-cp311-win32.whl", hash = "sha256:63f3a80daf4f867bd80f5c97fbe03314348ac1b3b70fb1c0ad255a69e3749879"}, + {file = "shapely-2.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:c52ed79f683f721b69a10fb9e3d940a468203f5054927215586c5d49a072de8d"}, + {file = "shapely-2.0.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5bbd974193e2cc274312da16b189b38f5f128410f3377721cadb76b1e8ca5328"}, + {file = "shapely-2.0.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:41388321a73ba1a84edd90d86ecc8bfed55e6a1e51882eafb019f45895ec0f65"}, + {file = "shapely-2.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0776c92d584f72f1e584d2e43cfc5542c2f3dd19d53f70df0900fda643f4bae6"}, + {file = "shapely-2.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c75c98380b1ede1cae9a252c6dc247e6279403fae38c77060a5e6186c95073ac"}, + {file = "shapely-2.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3e700abf4a37b7b8b90532fa6ed5c38a9bfc777098bc9fbae5ec8e618ac8f30"}, + {file = "shapely-2.0.4-cp312-cp312-win32.whl", hash = "sha256:4f2ab0faf8188b9f99e6a273b24b97662194160cc8ca17cf9d1fb6f18d7fb93f"}, + {file = "shapely-2.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:03152442d311a5e85ac73b39680dd64a9892fa42bb08fd83b3bab4fe6999bfa0"}, + {file = "shapely-2.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:994c244e004bc3cfbea96257b883c90a86e8cbd76e069718eb4c6b222a56f78b"}, + {file = "shapely-2.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05ffd6491e9e8958b742b0e2e7c346635033d0a5f1a0ea083547fcc854e5d5cf"}, + {file = "shapely-2.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbdc1140a7d08faa748256438291394967aa54b40009f54e8d9825e75ef6113"}, + {file = "shapely-2.0.4-cp37-cp37m-win32.whl", hash = "sha256:5af4cd0d8cf2912bd95f33586600cac9c4b7c5053a036422b97cfe4728d2eb53"}, + {file = "shapely-2.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:464157509ce4efa5ff285c646a38b49f8c5ef8d4b340f722685b09bb033c5ccf"}, + {file = "shapely-2.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:489c19152ec1f0e5c5e525356bcbf7e532f311bff630c9b6bc2db6f04da6a8b9"}, + {file = "shapely-2.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b79bbd648664aa6f44ef018474ff958b6b296fed5c2d42db60078de3cffbc8aa"}, + {file = "shapely-2.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:674d7baf0015a6037d5758496d550fc1946f34bfc89c1bf247cabdc415d7747e"}, + {file = "shapely-2.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cd4ccecc5ea5abd06deeaab52fcdba372f649728050c6143cc405ee0c166679"}, + {file = "shapely-2.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb5cdcbbe3080181498931b52a91a21a781a35dcb859da741c0345c6402bf00c"}, + {file = "shapely-2.0.4-cp38-cp38-win32.whl", hash = "sha256:55a38dcd1cee2f298d8c2ebc60fc7d39f3b4535684a1e9e2f39a80ae88b0cea7"}, + {file = "shapely-2.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec555c9d0db12d7fd777ba3f8b75044c73e576c720a851667432fabb7057da6c"}, + {file = "shapely-2.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9103abd1678cb1b5f7e8e1af565a652e036844166c91ec031eeb25c5ca8af0"}, + {file = "shapely-2.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:263bcf0c24d7a57c80991e64ab57cba7a3906e31d2e21b455f493d4aab534aaa"}, + {file = "shapely-2.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddf4a9bfaac643e62702ed662afc36f6abed2a88a21270e891038f9a19bc08fc"}, + {file = "shapely-2.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:485246fcdb93336105c29a5cfbff8a226949db37b7473c89caa26c9bae52a242"}, + {file = "shapely-2.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8de4578e838a9409b5b134a18ee820730e507b2d21700c14b71a2b0757396acc"}, + {file = "shapely-2.0.4-cp39-cp39-win32.whl", hash = "sha256:9dab4c98acfb5fb85f5a20548b5c0abe9b163ad3525ee28822ffecb5c40e724c"}, + {file = "shapely-2.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:31c19a668b5a1eadab82ff070b5a260478ac6ddad3a5b62295095174a8d26398"}, + {file = "shapely-2.0.4.tar.gz", hash = "sha256:5dc736127fac70009b8d309a0eeb74f3e08979e530cf7017f2f507ef62e6cfb8"}, ] [package.dependencies] -numpy = ">=1.14,<2" +numpy = ">=1.14,<3" [package.extras] docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] @@ -7295,16 +7490,16 @@ files = [ [[package]] name = "sounddevice" -version = "0.4.6" +version = "0.4.7" description = "Play and Record Sound with Python" optional = false python-versions = ">=3.7" files = [ - {file = "sounddevice-0.4.6-py3-none-any.whl", hash = "sha256:5de768ba6fe56ad2b5aaa2eea794b76b73e427961c95acad2ee2ed7f866a4b20"}, - {file = "sounddevice-0.4.6-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:8b0b806c205dd3e3cd5a97262b2482624fd21db7d47083b887090148a08051c8"}, - {file = "sounddevice-0.4.6-py3-none-win32.whl", hash = "sha256:e3ba6e674ffa8f79a591d744a1d4ab922fe5bdfd4faf8b25069a08e051010b7b"}, - {file = "sounddevice-0.4.6-py3-none-win_amd64.whl", hash = "sha256:7830d4f8f8570f2e5552942f81d96999c5fcd9a0b682d6fc5d5c5529df23be2c"}, - {file = "sounddevice-0.4.6.tar.gz", hash = "sha256:3236b78f15f0415bdf006a620cef073d0c0522851d66f4a961ed6d8eb1482fe9"}, + {file = "sounddevice-0.4.7-py3-none-any.whl", hash = "sha256:1c3f18bfa4d9a257f5715f2ab83f2c0eb412a09f3e6a9fa73720886ca88f6bc7"}, + {file = "sounddevice-0.4.7-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:d6ddfd341ad7412b14ca001f2c4dbf5fa2503bdc9eb15ad2c3105f6c260b698a"}, + {file = "sounddevice-0.4.7-py3-none-win32.whl", hash = "sha256:1ec1df094c468a210113aa22c4f390d5b4d9c7a73e41a6cb6ecfec83db59b380"}, + {file = "sounddevice-0.4.7-py3-none-win_amd64.whl", hash = "sha256:0c8b3543da1496f282b66a7bc54b755577ba638b1af06c146d4ac7f39d86b548"}, + {file = "sounddevice-0.4.7.tar.gz", hash = "sha256:69b386818d50a2d518607d4b973442e8d524760c7cd6c8b8be03d8c98fc4bce7"}, ] [package.dependencies] @@ -7315,20 +7510,20 @@ numpy = ["NumPy"] [[package]] name = "sphinx" -version = "7.2.6" +version = "7.3.7" description = "Python documentation generator" optional = false python-versions = ">=3.9" files = [ - {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, - {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, + {file = "sphinx-7.3.7-py3-none-any.whl", hash = "sha256:413f75440be4cacf328f580b4274ada4565fb2187d696a84970c23f77b64d8c3"}, + {file = "sphinx-7.3.7.tar.gz", hash = "sha256:a4a7db75ed37531c05002d56ed6948d4c42f473a36f46e1382b0bd76ca9627bc"}, ] [package.dependencies] -alabaster = ">=0.7,<0.8" +alabaster = ">=0.7.14,<0.8.0" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.21" +docutils = ">=0.18.1,<0.22" imagesize = ">=1.3" Jinja2 = ">=3.0" packaging = ">=21.0" @@ -7344,8 +7539,8 @@ sphinxcontrib-serializinghtml = ">=1.1.9" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] +lint = ["flake8 (>=3.5.0)", "importlib_metadata", "mypy (==1.9.0)", "pytest (>=6.0)", "ruff (==0.3.7)", "sphinx-lint", "tomli", "types-docutils", "types-requests"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=6.0)", "setuptools (>=67.0)"] [[package]] name = "sphinx-rtd-theme" @@ -7368,18 +7563,21 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinx-sitemap" -version = "2.5.1" +version = "2.6.0" description = "Sitemap generator for Sphinx" optional = false python-versions = "*" files = [ - {file = "sphinx-sitemap-2.5.1.tar.gz", hash = "sha256:984bef068bbdbc26cfae209a8b61392e9681abc9191b477cd30da406e3a60ee5"}, - {file = "sphinx_sitemap-2.5.1-py3-none-any.whl", hash = "sha256:0b7bce2835f287687f75584d7695e4eb8efaec028e5e7b36e9f791de3c344686"}, + {file = "sphinx_sitemap-2.6.0-py3-none-any.whl", hash = "sha256:7478e417d141f99c9af27ccd635f44c03a471a08b20e778a0f9daef7ace1d30b"}, + {file = "sphinx_sitemap-2.6.0.tar.gz", hash = "sha256:5e0c66b9f2e371ede80c659866a9eaad337d46ab02802f9c7e5f7bc5893c28d2"}, ] [package.dependencies] sphinx = ">=1.2" +[package.extras] +dev = ["build", "flake8", "pre-commit", "pytest", "sphinx", "tox"] + [[package]] name = "sphinxcontrib-applehelp" version = "1.0.8" @@ -7501,17 +7699,17 @@ files = [ [[package]] name = "sympy" -version = "1.12" +version = "1.12.1" description = "Computer algebra system (CAS) in Python" optional = false python-versions = ">=3.8" files = [ - {file = "sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5"}, - {file = "sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8"}, + {file = "sympy-1.12.1-py3-none-any.whl", hash = "sha256:9b2cbc7f1a640289430e13d2a56f02f867a1da0190f2f99d8968c2f74da0e515"}, + {file = "sympy-1.12.1.tar.gz", hash = "sha256:2877b03f998cd8c08f07cd0de5b767119cd3ef40d09f41c30d722f6686b0fb88"}, ] [package.dependencies] -mpmath = ">=0.19" +mpmath = ">=1.1.0,<1.4.0" [[package]] name = "tabulate" @@ -7527,39 +7725,25 @@ files = [ [package.extras] widechars = ["wcwidth"] -[[package]] -name = "tenacity" -version = "8.2.3" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, - {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, -] - -[package.extras] -doc = ["reno", "sphinx", "tornado (>=4.5)"] - [[package]] name = "timezonefinder" -version = "6.4.0" +version = "6.5.0" description = "python package for finding the timezone of any point on earth (coordinates) offline" optional = false -python-versions = ">=3.9,<4" +python-versions = ">=3.8,<4" files = [ - {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"}, + {file = "timezonefinder-6.5.0-cp38-cp38-manylinux_2_35_x86_64.whl", hash = "sha256:3c70e1ea468ecac11272aa0a727450fd7cf43039ae2a2c62f7900a0a42efb6d6"}, + {file = "timezonefinder-6.5.0.tar.gz", hash = "sha256:7afdb1516927e7766deb6da80c8bd66734a44ba3d898038fe73aef1e9cd39bb0"}, ] [package.dependencies] cffi = ">=1.15.1,<2" h3 = ">=3.7.6,<4" -numpy = ">=1.18,<2" +numpy = {version = ">=1.23,<2", markers = "python_version >= \"3.9\""} setuptools = ">=65.5" [package.extras] -numba = ["numba (>=0.59,<1)"] +numba = ["numba (>=0.56,<1)", "numba (>=0.59,<1)"] pytz = ["pytz (>=2022.7.1)"] [[package]] @@ -7575,13 +7759,13 @@ files = [ [[package]] name = "tqdm" -version = "4.66.2" +version = "4.66.4" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, - {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, + {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, + {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, ] [package.dependencies] @@ -7595,13 +7779,13 @@ telegram = ["requests"] [[package]] name = "types-requests" -version = "2.31.0.20240218" +version = "2.32.0.20240602" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" files = [ - {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"}, + {file = "types-requests-2.32.0.20240602.tar.gz", hash = "sha256:3f98d7bbd0dd94ebd10ff43a7fbe20c3b8528acace6d8efafef0b6a184793f06"}, + {file = "types_requests-2.32.0.20240602-py3-none-any.whl", hash = "sha256:ed3946063ea9fbc6b5fc0c44fa279188bae42d582cb63760be6cb4b9d06c3de8"}, ] [package.dependencies] @@ -7620,13 +7804,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -7659,13 +7843,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.26.2" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, + {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, ] [package.dependencies] @@ -7674,22 +7858,22 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "websocket-client" -version = "1.7.0" +version = "1.8.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, - {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, + {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, + {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, ] [package.extras] -docs = ["Sphinx (>=6.0)", "sphinx-rtd-theme (>=1.1.0)"] +docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] optional = ["python-socks", "wsaccel"] test = ["websockets"] @@ -7814,20 +7998,116 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.17.0" +version = "3.19.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "zstd" +version = "1.5.5.1" +description = "ZSTD Bindings for Python" +optional = false +python-versions = "*" +files = [ + {file = "zstd-1.5.5.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:555779789bc75cd05089c3ba857f45a0a8c4b87d45e5ced02fec77fa8719237a"}, + {file = "zstd-1.5.5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:86496bd4830cdb7b4b05a9ce6ce2baee87d327ff90845da4ee308452bfbbed4e"}, + {file = "zstd-1.5.5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b487c2e67ed42a4e0d47997d209f4456b01b334023083ef61873f79577c84c62"}, + {file = "zstd-1.5.5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:45ccd45a5b681088fca1a863ca9236ded5112b8011f1d5bf69e908f5eb32023a"}, + {file = "zstd-1.5.5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8403fe84207d8b0c7b17bca6c4caad431ac765b1b9b626ad9fae4bb93a64a9d8"}, + {file = "zstd-1.5.5.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:0ab979c6357b8927f0c025ea2f72f25e15d03ce17a8a6c1789e2d5b108bf39ae"}, + {file = "zstd-1.5.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:98cbee6c1b2fe85f02fd475d885f98363c63bc64eebc249d7eb7469a0ff70283"}, + {file = "zstd-1.5.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9962714b89641301029f3832bdf07c20f60b9e64e39e8d7b6253451a82b54f5c"}, + {file = "zstd-1.5.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f59cc92d71537f8082306f75aa403ddb4a4a1069a39f104525673110e4d23f7"}, + {file = "zstd-1.5.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:569f13d0c926ddafceebce8ac73baddfc2bd9cbbbbc922b6b3073338cc43dae6"}, + {file = "zstd-1.5.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba530c44f252016acc6ef906d7d2070c1ad0cfe835c498fdcd37493e4772ac6e"}, + {file = "zstd-1.5.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ee3496ed8fff3add6c6e658b207f18d96474c3db0c28ab7a69623380b1a0a8c"}, + {file = "zstd-1.5.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:530d69bea2791cde8afa7fe988f3a37c3ba37015f6a1d5593c0500f089f3090e"}, + {file = "zstd-1.5.5.1-cp310-cp310-win32.whl", hash = "sha256:cf179e51f447b6a7ff47e449fcb98fb5fe15aedcc90401697cf7c93dd6e4434e"}, + {file = "zstd-1.5.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:5f5e6e0805d710d7509c8d175a467eb89c631a4142b1a630ceeb8e3e3138d152"}, + {file = "zstd-1.5.5.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:022f935a8666e08f0fff6204938a84d9fe4fcd8235a205787275933a07a164fb"}, + {file = "zstd-1.5.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3d15a2d18dac8bcafdde52fdf5d40ecae1f73b7de19b171f42339d2e51346d0"}, + {file = "zstd-1.5.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45b9c67989f50ba63ffa0c50c9eaa037c2d14abacb0813e838ad705135245b4b"}, + {file = "zstd-1.5.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97da6a842ba7e4acf8bba7c596057143ee39b3c4a467196c2096d460e44accd6"}, + {file = "zstd-1.5.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dafd492fb8ee4ae04c81ab00f5f137860e7071f611335dd4cdb1c38bd8f11bc"}, + {file = "zstd-1.5.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9ee83e0bcbfd776200b026b3b9e86c6c86b8f414749f58d87c85dcf456b27066"}, + {file = "zstd-1.5.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ae2fd4bc8ea772a7b5f1acd1cac9e34bb9cd8fcde191f170092fdeea779a3a12"}, + {file = "zstd-1.5.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:edea52a0109f48fd46f4763689d3d356dcafd20ddf6789c559a1bd2e62b40a32"}, + {file = "zstd-1.5.5.1-cp311-cp311-win32.whl", hash = "sha256:88410481209520298ec4430e0d1d57e004c45e0b27c3035674fb182ccd2d8b7b"}, + {file = "zstd-1.5.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:dce18aaefbacf8b133367be86beec670baf68c0420bfcca49be08dbdbf933db6"}, + {file = "zstd-1.5.5.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:634dc632f7cf87e95dabf74dcf682e3507bd5cb9dd1bcdb81f92a6521aab0bd2"}, + {file = "zstd-1.5.5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:608414eb75ead573891d97a1e529848b8f31749d21a440e80838548a19d8c0e6"}, + {file = "zstd-1.5.5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:384128f7a731e3f45da49976591cec03fc4079e70653df10d9ea43a1d3b49d50"}, + {file = "zstd-1.5.5.1-cp35-cp35m-win32.whl", hash = "sha256:4bce254174ef05cea01021d67e18489d5d08db1168e758b62ecee121572a52a9"}, + {file = "zstd-1.5.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:3f0ff81232b49d7eb4f4d9e6f92443c9d242c139ad98ffedac0e889568f900ce"}, + {file = "zstd-1.5.5.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a871df41b801a260cc849c2c76f300ebb9d286c4b7a1fd6ce45fe0c91340b767"}, + {file = "zstd-1.5.5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5a53860dbfbea281eb690ce09cae28967cf1df8e6d7560e4a8bf5b9fcb258147"}, + {file = "zstd-1.5.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:a37cbc0580fdfd66c8b3ec65f9af00a4a34e9781b54dfb89f04d301dc375c90a"}, + {file = "zstd-1.5.5.1-cp36-cp36m-win32.whl", hash = "sha256:5531b683539ae1f7b2ad23dacee8a73e5d7eaa6702ea8df5a24bd3318647dee1"}, + {file = "zstd-1.5.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eeaff418269b41eee8c7971fbba9d32d07d3f6aa26f962a72aff725071096a1b"}, + {file = "zstd-1.5.5.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:8bd6a9050de8bbe844447348372ca17d01bc05207619f6a5d448567d111b5cd9"}, + {file = "zstd-1.5.5.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2ece3d20ef357370584f304407fbd1e4ff9c231209320e08a889b8e3725d56e"}, + {file = "zstd-1.5.5.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687f9e03dc9f9b8803840425bb23bf6bc700888b4860afcf43c4f238102752d2"}, + {file = "zstd-1.5.5.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a649daac9c8f1b37d29f2b3d0a43f134061659b54877fe4b0da6df2965dc91f"}, + {file = "zstd-1.5.5.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:bddc7e3c3ce31c01fe1edaa7c03c0b9e71eadf4ce1609746d32f86d95a0449e6"}, + {file = "zstd-1.5.5.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:12bf8e04add8bb84f9fe9117f3de6d9394eade6a5a82fe4d6bd95914fc6ef423"}, + {file = "zstd-1.5.5.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9e6a15fa4d2e65c5902ab2a4e41279ac126cb371ce6c3c75ad5789bb20dd1f54"}, + {file = "zstd-1.5.5.1-cp37-cp37m-win32.whl", hash = "sha256:a1c269243a4321beb948635b544ccbe6390846358ace620fd000ab7099011d9c"}, + {file = "zstd-1.5.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:91366e36773241cb4b049a32f4495d33dd274df1eea5b55396f5f3984a3de22e"}, + {file = "zstd-1.5.5.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:d3ce2cb310690994274d133ea7f269dd4b81799fdbce158690556209723d7d4e"}, + {file = "zstd-1.5.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e0c87bfbfa9d852f79c90bcd7426c3ba46cf3285e6984013636d4fc854ba9230"}, + {file = "zstd-1.5.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce6d829d515f272fddb3a87e1a5f32cc0f1a7b0cba24d360c89f4a165b74b"}, + {file = "zstd-1.5.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e05f81f346213b23ed1b12d84fc1f72e65eacd8978e1e88facf185c82bd3d053"}, + {file = "zstd-1.5.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43ec66c4c3a76351c672c6ef9f0ff3412fca9ede0a56d18dddaf6418a93faef8"}, + {file = "zstd-1.5.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:58e554e91e0d49f4f2b2df390cdd0f64aa9b6fd5f4dcb208c094bfd079b30f3a"}, + {file = "zstd-1.5.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:883c6d3b6f5574e1765ca97f4b6a41b69094a41be56175552faebc0e0e43b65e"}, + {file = "zstd-1.5.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d52b6932cab5419c434bccfea3e5640e755369fc9eeb51e3d17e15bf8e8cb103"}, + {file = "zstd-1.5.5.1-cp38-cp38-win32.whl", hash = "sha256:dcaf44270ec88552e969be4dd3359b34aa3065663ccd8168a257c78f150a356c"}, + {file = "zstd-1.5.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:627f12cb7035723c8f3d8d4cefcad6d950ed9cba33fd3eb46bae04ccab479234"}, + {file = "zstd-1.5.5.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:c0dab132c1a5a7cc838a7c3e4e380ad153b9d7bd1fadafabf6cfeb780b916201"}, + {file = "zstd-1.5.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4ab0a5dd9a41d3b083304beee7ada40ee36431acbeb75132032f4fe5cf0490a"}, + {file = "zstd-1.5.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f6e38f496d287020658c6b4cdb5e815ecc6998889bd0f1f9ab0825f2e3d74ef"}, + {file = "zstd-1.5.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0096c8ee0ed4bfe406bc961019f55552109e19771bfd3eb32d2af56ea27085c"}, + {file = "zstd-1.5.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a0f1527728c50b6aa8f04b47a07580f0ae13cfc6c6d9c96bb0bdf5259487559"}, + {file = "zstd-1.5.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6a64e420c904063c5c3de53c00ec0993ebc0a48cebbef97dc6c768562c5abab5"}, + {file = "zstd-1.5.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:03444e357b7632c64480a81ce7095242dab9d7f8aed317326563ef6c663263eb"}, + {file = "zstd-1.5.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:88b9a10f80d2b87bf8cc1a1fc20a815ed92b5eefdc15cbe8062021f0b5a26a10"}, + {file = "zstd-1.5.5.1-cp39-cp39-win32.whl", hash = "sha256:c91cc1606eb8b3a6fed11faaef4c6e55f1133d70cf0db0c829a2cf9c2ac1dfd9"}, + {file = "zstd-1.5.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:f462e2ebf26dcbfc2c8dddd6b5c56859683f0b77edb8f268e637f7d390a58f74"}, + {file = "zstd-1.5.5.1-pp27-pypy_73-macosx_10_14_x86_64.whl", hash = "sha256:c63f916732e3e309e49ec95e7a0af5d37ff1321f3df2aac10e507bd2b56fceda"}, + {file = "zstd-1.5.5.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:50d4850d758bb033df50722cc13ed913b2afcd5385250be4f3ffb79a26b319c3"}, + {file = "zstd-1.5.5.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:0412d666515e78a91ada7e2d78e9dd6b25ddda1b41623b145b99653275c7f3ce"}, + {file = "zstd-1.5.5.1-pp36-pypy36_pp73-macosx_10_14_x86_64.whl", hash = "sha256:0ea91f74869a3cdcb2dde08f8f30ee3da72782c5d1737afed9c703232815864e"}, + {file = "zstd-1.5.5.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:477548897dc2b8b595af7bec5f0f55dcba8e9a282335f687cc663b52b171357b"}, + {file = "zstd-1.5.5.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c518938b57a56001ee04dcf79a432152f5bd431416f3b22819ba959bc6054d89"}, + {file = "zstd-1.5.5.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:894a8fe0228d5e24dc286a8d98eb0ce2883f8e2e57f3b7e7619ebdb67967120a"}, + {file = "zstd-1.5.5.1-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:42ec0a4ae9bedd9909fa4f580f3c800469da1b631faeaa94f204e1b66c767fa2"}, + {file = "zstd-1.5.5.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d56dedaa04ab8ecc23492972b12e0bf8529f64c9bceb28c11f43c2369c9768b3"}, + {file = "zstd-1.5.5.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5b060770d796e4c01f5848b345c3cea8a177ab4e7cd95a1963a355042d429e1"}, + {file = "zstd-1.5.5.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fea04805ef6e1cb93d6e5d6bbc7a03bc75a5c733fd352d5aaa81109986fdf1ef"}, + {file = "zstd-1.5.5.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:405c28a35756e57a434bbd7ed29dc5e6490cd2fc2118cbf78b60eaebd134f5e9"}, + {file = "zstd-1.5.5.1-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:c42e630443b01a891277426365a51a2aa630b059ce675992c70c1928d30eccb4"}, + {file = "zstd-1.5.5.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1520d23f24f26cdfbcdb4dc86947446b8f694838bfce728d7fc4b3492397357c"}, + {file = "zstd-1.5.5.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4730737f63cf802321743ded6acc85e747e7f5587c5ba2e51a760bf009f7de"}, + {file = "zstd-1.5.5.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9f8c014395e89ad7f67ffe873c0fa1d8e9b4dea8b1801d24e8d9ccd8259858d"}, + {file = "zstd-1.5.5.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5d9ba4f6af0945809bfa3387c6a1208a22937a876521b9ec347e7183d623311b"}, + {file = "zstd-1.5.5.1-pp39-pypy39_pp73-macosx_10_14_x86_64.whl", hash = "sha256:04dfd9f46b0b0b1bc413884fe028b726febcb726d4f66e3cf8afc00c2d9026bf"}, + {file = "zstd-1.5.5.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af52436a2eb5caa925d95461973984cb34d472a963b6be1c0a9f2dfbafad096f"}, + {file = "zstd-1.5.5.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610928b888a2e7ae9d2018ffa814859d47ec4ba75f89a1188ab4eb9232636ee5"}, + {file = "zstd-1.5.5.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee3c9feea99c7f4ff43129a885da056b5aa0cde3f7876bf6397bfb9433f44352"}, + {file = "zstd-1.5.5.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ac9768eeb3c6b530db93de2fec9b363776075dc8a00ee4049612ba5397ca8e"}, + {file = "zstd-1.5.5.1.tar.gz", hash = "sha256:1ef980abf0e1e072b028d2d76ef95b476632651c96225cf30b619c6eef625672"}, +] [metadata] lock-version = "2.0" -python-versions = "~3.11" -content-hash = "cb63112bfe7ee2b3fc194a422479f788c9a8027d445ea51a31f4ee20299beb53" +python-versions = ">=3.11, <3.13" +content-hash = "83dbbf76ea5f3e1022233a164d6f342706ebf64ad8bbbf742a55d5f49474d863" diff --git a/pyproject.toml b/pyproject.toml index 99a8602460..5353b39887 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,12 @@ +[project] +name = "openpilot" +requires-python = ">= 3.11" +readme = "README.md" +license = {text = "MIT License"} + +[project.urls] +Homepage = "https://comma.ai" + [tool.pytest.ini_options] minversion = "6.0" addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=teleoprtc_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" @@ -11,18 +20,18 @@ markers = [ ] testpaths = [ "common", - "selfdrive/athena", - "selfdrive/boardd", + "selfdrive/pandad", "selfdrive/car", "selfdrive/controls", "selfdrive/locationd", "selfdrive/monitoring", "selfdrive/navd/tests", - "selfdrive/thermald", "selfdrive/test/longitudinal_maneuvers", "selfdrive/test/process_replay/test_fuzzy.py", + "system/updated", + "system/athena", "system/camerad", - "system/hardware/tici", + "system/hardware", "system/loggerd", "system/proclogd", "system/tests", @@ -30,7 +39,8 @@ testpaths = [ "system/webrtc", "tools/lib/tests", "tools/replay", - "tools/cabana" + "tools/cabana", + "cereal/messaging/tests", ] [tool.mypy] @@ -77,73 +87,72 @@ readme = "README.md" repository = "https://github.com/commaai/openpilot" documentation = "https://docs.comma.ai" - [tool.poetry.dependencies] -python = "~3.11" +python = ">=3.11, <3.13" + +# multiple users +sounddevice = "*" # micd + soundd +pyserial = "*" # pigeond + qcomgpsd +requests = "*" # many one-off uses +sympy = "*" # rednose + friends +crcmod = "*" # cars + qcomgpsd + +# hardwared +smbus2 = "*" # configuring amp + +# core +cffi = "*" +scons = "*" +pycapnp = "*" +Cython = "*" +numpy = "*" + +# body / webrtcd aiohttp = "*" aiortc = "*" -casadi = "==3.6.3" -cffi = "*" -crcmod = "*" -Cython = "*" -future-fstrings = "*" # for acados -json-rpc = "*" +pyaudio = "*" + +# panda libusb1 = "*" -numpy = "*" +spidev = { version = "*", platform = "linux" } + +# modeld onnx = ">=1.14.0" onnxruntime = { version = ">=1.16.3", platform = "linux", markers = "platform_machine == 'aarch64'" } onnxruntime-gpu = { version = ">=1.16.3", platform = "linux", markers = "platform_machine == 'x86_64'" } -psutil = "*" -pyaudio = "*" -pycapnp = "*" -pycryptodome = "*" -PyJWT = "*" -pyserial = "*" + +# logging pyzmq = "*" -requests = "*" -scons = "*" sentry-sdk = "*" -smbus2 = "*" -sounddevice = "*" -spidev = { version = "*", platform = "linux" } -sympy = "*" + +# athena +PyJWT = "*" +json-rpc = "*" websocket_client = "*" -# these should be removed -markdown-it-py = "*" -timezonefinder = "*" -setproctitle = "*" +# acados deps +casadi = "*" +future-fstrings = "*" -[tool.poetry.group.dev.dependencies] -av = "*" -azure-identity = "*" -azure-storage-blob = "*" -breathe = "*" -control = "*" -coverage = "*" -dictdiffer = "*" -ft4222 = "*" -flaky = "*" -hypothesis = "~6.47" -inputs = "*" +# these should be removed +psutil = "*" +timezonefinder = "*" # just used for nav ETA +pycryptodome = "*" # used in updated/casync, panda, body, and a test + +#logreader +zstd = "*" + +[tool.poetry.group.docs.dependencies] Jinja2 = "*" -lru-dict = "*" -matplotlib = "*" -metadrive-simulator = { version = "0.4.2.3", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies -mpld3 = "*" +sphinx = "*" +sphinx-rtd-theme = "*" +sphinx-sitemap = "*" + +[tool.poetry.group.testing.dependencies] +coverage = "*" +hypothesis = "~6.47" mypy = "*" -myst-parser = "*" -natsort = "*" -opencv-python-headless = "*" -parameterized = "^0.8" -pprofile = "*" -polyline = "*" pre-commit = "*" -pyautogui = "*" -pyopencl = "==2023.1.4" # 2024.1 is broken on arm64 -pygame = "*" -pywinctl = "*" -pyprof2calltree = "*" pytest = "*" pytest-cov = "*" pytest-cpp = "*" @@ -151,12 +160,37 @@ pytest-subtests = "*" pytest-xdist = "*" pytest-timeout = "*" pytest-randomly = "*" +pytest-asyncio = "*" +pytest-mock = "*" +pytest-repeat = "*" ruff = "*" -sphinx = "*" -sphinx-rtd-theme = "*" -sphinx-sitemap = "*" + +[tool.poetry.group.dev.dependencies] +av = "*" +azure-identity = "*" +azure-storage-blob = "*" +breathe = "*" +control = "*" +dictdiffer = "*" +flaky = "*" +inputs = "*" +lru-dict = "*" +matplotlib = "*" +metadrive-simulator = { git = "https://github.com/commaai/metadrive.git", branch = "python3.12", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies +mpld3 = "*" +myst-parser = "*" +natsort = "*" +opencv-python-headless = "*" +parameterized = "^0.8" +#pprofile = "*" +polyline = "*" +pyautogui = "*" +pygame = "*" +pyopencl = { version = "*", markers = "platform_machine != 'aarch64'" } # broken on arm64 +pywinctl = "*" +pyprof2calltree = "*" +rerun-sdk = "*" tabulate = "*" -tenacity = "*" types-requests = "*" types-tabulate = "*" tqdm = "*" @@ -170,11 +204,30 @@ build-backend = "poetry.core.masonry.api" # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml [tool.ruff] -lint.select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF008", "RUF100", "A", "B", "TID251"] -lint.ignore = ["E741", "E402", "C408", "ISC003", "B027", "B024"] +indent-width = 2 +lint.select = [ + "E", "F", "W", "PIE", "C4", "ISC", "A", "B", + "NPY", # numpy + "UP", # pyupgrade + "TRY302", "TRY400", "TRY401", # try/excepts + "RUF008", "RUF100", + "TID251" +] +lint.ignore = [ + "E741", + "E402", + "C408", + "ISC003", + "B027", + "B024", + "NPY002", # new numpy random syntax is worse + "UP038", # (x, y) -> x|y for isinstance +] line-length = 160 target-version="py311" exclude = [ + "body", + "cereal", "panda", "opendbc", "rednose_repo", @@ -191,6 +244,9 @@ lint.flake8-implicit-str-concat.allow-multiline=false "third_party".msg = "Use openpilot.third_party" "tools".msg = "Use openpilot.tools" "pytest.main".msg = "pytest.main requires special handling that is easy to mess up!" +"unittest".msg = "Use pytest" [tool.coverage.run] concurrency = ["multiprocessing", "thread"] +[tool.ruff.format] +quote-style = "preserve" diff --git a/rednose_repo b/rednose_repo index c5762e8bc6..72b3479bab 160000 --- a/rednose_repo +++ b/rednose_repo @@ -1 +1 @@ -Subproject commit c5762e8bc6f7338c7a30d2cd1cba8cc64e81ba19 +Subproject commit 72b3479bababc658f24cc7aa0dc8bb550f0474fc diff --git a/release/README.md b/release/README.md new file mode 100644 index 0000000000..1d3e784936 --- /dev/null +++ b/release/README.md @@ -0,0 +1,36 @@ +# openpilot releases + +## release checklist + +**Go to `devel-staging`** +- [ ] update `devel-staging`: `git reset --hard origin/master-ci` +- [ ] open a pull request from `devel-staging` to `devel` + +**Go to `devel`** +- [ ] update RELEASES.md +- [ ] close out milestone +- [ ] post on Discord dev channel +- [ ] bump version on master: `common/version.h` and `RELEASES.md` +- [ ] merge the pull request + +tests: +- [ ] update from previous release -> new release +- [ ] update from new release -> previous release +- [ ] fresh install with `openpilot-test.comma.ai` +- [ ] drive on fresh install +- [ ] comma body test +- [ ] no submodules or LFS +- [ ] check sentry, MTBF, etc. + +**Go to `release3`** +- [ ] publish the blog post +- [ ] `git reset --hard origin/release3-staging` +- [ ] tag the release +``` +git tag v0.X.X +git push origin v0.X.X +``` +- [ ] create GitHub release +- [ ] final test install on `openpilot.comma.ai` +- [ ] update production +- [ ] Post on Discord, X, etc. diff --git a/release/build_devel.sh b/release/build_devel.sh index 8b6816e423..7fce11ca72 100755 --- a/release/build_devel.sh +++ b/release/build_devel.sh @@ -1,5 +1,4 @@ #!/usr/bin/bash - set -ex DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" @@ -44,7 +43,7 @@ git clean -xdff # do the files copy echo "[-] copying files T=$SECONDS" cd $SOURCE_DIR -cp -pR --parents $(cat release/files_*) $TARGET_DIR/ +cp -pR --parents $(./release/release_files.py) $TARGET_DIR/ # in the directory cd $TARGET_DIR @@ -64,6 +63,13 @@ date: $DATETIME master commit: $GIT_HASH " +# should be no submodules or LFS files +git submodule status +if [ ! -z "$(git lfs ls-files)" ]; then + echo "LFS files detected!" + exit 1 +fi + # ensure files are within GitHub's limit BIG_FILES="$(find . -type f -not -path './.git/*' -size +95M)" if [ ! -z "$BIG_FILES" ]; then diff --git a/release/build_release.sh b/release/build_release.sh index fc15cf6cdf..510df75e51 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -1,4 +1,6 @@ -#!/usr/bin/bash -e +#!/usr/bin/bash +set -e +set -x # git diff --name-status origin/release3-staging | grep "^A" | less @@ -9,13 +11,6 @@ cd $DIR BUILD_DIR=/data/openpilot SOURCE_DIR="$(git rev-parse --show-toplevel)" -if [ -f /TICI ]; then - FILES_SRC="release/files_tici" -else - echo "no release files set" - exit 1 -fi - if [ -z "$RELEASE_BRANCH" ]; then echo "RELEASE_BRANCH is not set" exit 1 @@ -36,8 +31,7 @@ git checkout --orphan $RELEASE_BRANCH # do the files copy echo "[-] copying files T=$SECONDS" cd $SOURCE_DIR -cp -pR --parents $(cat release/files_common) $BUILD_DIR/ -cp -pR --parents $(cat $FILES_SRC) $BUILD_DIR/ +cp -pR --parents $(./release/release_files.py) $BUILD_DIR/ # in the directory cd $BUILD_DIR @@ -54,7 +48,7 @@ git commit -a -m "openpilot v$VERSION release" # Build export PYTHONPATH="$BUILD_DIR" -scons -j$(nproc) +scons -j$(nproc) --minimal # release panda fw CERT=/data/pandaextra/certs/release RELEASE=1 scons -j$(nproc) panda/ @@ -77,6 +71,10 @@ find . -name '__pycache__' -delete rm -rf .sconsign.dblite Jenkinsfile release/ rm selfdrive/modeld/models/supercombo.onnx +find third_party/ -name '*x86*' -exec rm -r {} + +find third_party/ -name '*Darwin*' -exec rm -r {} + + + # Restore third_party git checkout third_party/ @@ -92,9 +90,9 @@ TEST_FILES="tools/" cd $SOURCE_DIR cp -pR -n --parents $TEST_FILES $BUILD_DIR/ cd $BUILD_DIR -RELEASE=1 selfdrive/test/test_onroad.py -#selfdrive/manager/test/test_manager.py -selfdrive/car/tests/test_car_interfaces.py +RELEASE=1 pytest -n0 -s selfdrive/test/test_onroad.py +#system/manager/test/test_manager.py +pytest selfdrive/car/tests/test_car_interfaces.py rm -rf $TEST_FILES if [ ! -z "$RELEASE_BRANCH" ]; then diff --git a/release/check-submodules.sh b/release/check-submodules.sh index 5f4e307e49..bff8d7a28f 100755 --- a/release/check-submodules.sh +++ b/release/check-submodules.sh @@ -1,7 +1,7 @@ #!/bin/bash while read hash submodule ref; do - git -C $submodule fetch --depth 1000 origin master + git -C $submodule fetch --depth 2000 origin master git -C $submodule branch -r --contains $hash | grep "origin/master" if [ "$?" -eq 0 ]; then echo "$submodule ok" diff --git a/release/files_common b/release/files_common deleted file mode 100644 index d62ef8d1bb..0000000000 --- a/release/files_common +++ /dev/null @@ -1,561 +0,0 @@ -.gitignore -LICENSE -launch_env.sh -launch_chffrplus.sh -launch_openpilot.sh - -Jenkinsfile -SConstruct -pyproject.toml - -README.md -RELEASES.md -docs/CARS.md -docs/CONTRIBUTING.md -docs/INTEGRATION.md -docs/LIMITATIONS.md -site_scons/site_tools/cython.py - -openpilot/__init__.py -openpilot/** - -common/.gitignore -common/__init__.py -common/*.py -common/*.pyx -common/mock/* - -common/transformations/__init__.py -common/transformations/camera.py -common/transformations/model.py - -common/transformations/SConscript -common/transformations/coordinates.py -common/transformations/coordinates.cc -common/transformations/coordinates.hpp -common/transformations/orientation.py -common/transformations/orientation.cc -common/transformations/orientation.hpp -common/transformations/transformations.pxd -common/transformations/transformations.pyx - -common/api/__init__.py - -release/* - -tools/__init__.py -tools/lib/* -tools/bodyteleop/* -tools/joystick/* -tools/replay/*.cc -tools/replay/*.h - -selfdrive/__init__.py -selfdrive/sentry.py -selfdrive/tombstoned.py -selfdrive/updated.py -selfdrive/statsd.py - -system/logmessaged.py -system/micd.py -system/version.py - -selfdrive/athena/__init__.py -selfdrive/athena/athenad.py -selfdrive/athena/manage_athenad.py -selfdrive/athena/registration.py - -selfdrive/boardd/.gitignore -selfdrive/boardd/SConscript -selfdrive/boardd/__init__.py -selfdrive/boardd/boardd.cc -selfdrive/boardd/boardd.h -selfdrive/boardd/main.cc -selfdrive/boardd/boardd.py -selfdrive/boardd/boardd_api_impl.pyx -selfdrive/boardd/can_list_to_can_capnp.cc -selfdrive/boardd/panda.cc -selfdrive/boardd/panda.h -selfdrive/boardd/spi.cc -selfdrive/boardd/panda_comms.h -selfdrive/boardd/panda_comms.cc -selfdrive/boardd/set_time.py -selfdrive/boardd/pandad.py -selfdrive/boardd/tests/test_boardd_loopback.py - -selfdrive/car/__init__.py -selfdrive/car/docs_definitions.py -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 -selfdrive/car/fw_query_definitions.py -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/* - -selfdrive/car/body/*.py -selfdrive/car/chrysler/*.py -selfdrive/car/ford/*.py -selfdrive/car/gm/*.py -selfdrive/car/honda/*.py -selfdrive/car/hyundai/*.py -selfdrive/car/mazda/*.py -selfdrive/car/mock/*.py -selfdrive/car/nissan/*.py -selfdrive/car/subaru/*.py -selfdrive/car/tesla/*.py -selfdrive/car/toyota/*.py -selfdrive/car/volkswagen/*.py - -selfdrive/debug/can_printer.py -selfdrive/debug/check_freq.py -selfdrive/debug/dump.py -selfdrive/debug/filter_log_message.py -selfdrive/debug/format_fingerprints.py -selfdrive/debug/get_fingerprint.py -selfdrive/debug/uiview.py - -selfdrive/debug/hyundai_enable_radar_points.py -selfdrive/debug/vw_mqb_config.py - -common/SConscript -common/version.h - -common/*.h -common/*.cc - -selfdrive/controls/__init__.py -selfdrive/controls/controlsd.py -selfdrive/controls/plannerd.py -selfdrive/controls/radard.py -selfdrive/controls/lib/__init__.py -selfdrive/controls/lib/alertmanager.py -selfdrive/controls/lib/alerts_offroad.json -selfdrive/controls/lib/desire_helper.py -selfdrive/controls/lib/drive_helpers.py -selfdrive/controls/lib/events.py -selfdrive/controls/lib/latcontrol_angle.py -selfdrive/controls/lib/latcontrol_torque.py -selfdrive/controls/lib/latcontrol_pid.py -selfdrive/controls/lib/latcontrol.py -selfdrive/controls/lib/longcontrol.py -selfdrive/controls/lib/longitudinal_planner.py -selfdrive/controls/lib/pid.py -selfdrive/controls/lib/vehicle_model.py - -selfdrive/controls/lib/lateral_mpc_lib/.gitignore -selfdrive/controls/lib/longitudinal_mpc_lib/.gitignore -selfdrive/controls/lib/lateral_mpc_lib/* -selfdrive/controls/lib/longitudinal_mpc_lib/* - -system/__init__.py -system/*.py - -system/hardware/__init__.py -system/hardware/base.h -system/hardware/base.py -system/hardware/hw.h -system/hardware/hw.py -system/hardware/tici/__init__.py -system/hardware/tici/hardware.h -system/hardware/tici/hardware.py -system/hardware/tici/pins.py -system/hardware/tici/agnos.py -system/hardware/tici/casync.py -system/hardware/tici/agnos.json -system/hardware/tici/amplifier.py -system/hardware/tici/updater -system/hardware/tici/iwlist.py -system/hardware/tici/esim.nmconnection -system/hardware/pc/__init__.py -system/hardware/pc/hardware.h -system/hardware/pc/hardware.py - -system/ubloxd/.gitignore -system/ubloxd/SConscript -system/ubloxd/generated/* -system/ubloxd/*.h -system/ubloxd/*.cc - -selfdrive/locationd/__init__.py -selfdrive/locationd/SConscript -selfdrive/locationd/.gitignore -selfdrive/locationd/locationd.h -selfdrive/locationd/locationd.cc -selfdrive/locationd/paramsd.py -selfdrive/locationd/models/__init__.py -selfdrive/locationd/models/.gitignore -selfdrive/locationd/models/car_kf.py -selfdrive/locationd/models/live_kf.py -selfdrive/locationd/models/live_kf.h -selfdrive/locationd/models/live_kf.cc -selfdrive/locationd/models/constants.py - -selfdrive/locationd/torqued.py -selfdrive/locationd/calibrationd.py -selfdrive/locationd/helpers.py - -system/logcatd/.gitignore -system/logcatd/SConscript -system/logcatd/logcatd_systemd.cc - -system/proclogd/SConscript -system/proclogd/main.cc -system/proclogd/proclog.cc -system/proclogd/proclog.h - -system/loggerd/.gitignore -system/loggerd/SConscript -system/loggerd/encoder/encoder.cc -system/loggerd/encoder/encoder.h -system/loggerd/encoder/v4l_encoder.cc -system/loggerd/encoder/v4l_encoder.h -system/loggerd/video_writer.cc -system/loggerd/video_writer.h -system/loggerd/logger.cc -system/loggerd/logger.h -system/loggerd/loggerd.cc -system/loggerd/loggerd.h -system/loggerd/encoderd.cc -system/loggerd/bootlog.cc -system/loggerd/encoder/ffmpeg_encoder.cc -system/loggerd/encoder/ffmpeg_encoder.h - -system/loggerd/__init__.py -system/loggerd/config.py -system/loggerd/uploader.py -system/loggerd/deleter.py -system/loggerd/xattr_cache.py - -system/sensord/.gitignore -system/sensord/SConscript -system/sensord/sensors_qcom2.cc -system/sensord/sensors/*.cc -system/sensord/sensors/*.h -system/sensord/pigeond.py - -system/webrtc/__init__.py -system/webrtc/webrtcd.py -system/webrtc/schema.py -system/webrtc/device/audio.py -system/webrtc/device/video.py - -selfdrive/thermald/thermald.py -selfdrive/thermald/power_monitoring.py -selfdrive/thermald/fan_controller.py - -selfdrive/test/__init__.py -selfdrive/test/fuzzy_generation.py -selfdrive/test/helpers.py -selfdrive/test/setup_device_ci.sh -selfdrive/test/test_onroad.py -selfdrive/test/test_time_to_onroad.py - -selfdrive/ui/.gitignore -selfdrive/ui/SConscript -selfdrive/ui/*.cc -selfdrive/ui/*.h -selfdrive/ui/text -selfdrive/ui/spinner -selfdrive/ui/soundd.py -selfdrive/ui/translations/*.ts -selfdrive/ui/translations/languages.json -selfdrive/ui/update_translations.py -selfdrive/ui/tests/test_translations.py - -selfdrive/ui/qt/*.cc -selfdrive/ui/qt/*.h -selfdrive/ui/qt/network/*.cc -selfdrive/ui/qt/network/*.h -selfdrive/ui/qt/offroad/*.cc -selfdrive/ui/qt/offroad/*.h -selfdrive/ui/qt/offroad/*.qml -selfdrive/ui/qt/widgets/*.cc -selfdrive/ui/qt/widgets/*.h -selfdrive/ui/qt/maps/*.cc -selfdrive/ui/qt/maps/*.h -selfdrive/ui/qt/setup/*.cc -selfdrive/ui/qt/setup/*.h - -selfdrive/ui/installer/*.cc -selfdrive/ui/installer/*.h -selfdrive/ui/installer/*.cc - -system/camerad/SConscript -system/camerad/main.cc - -system/camerad/snapshot/* -system/camerad/cameras/camera_common.h -system/camerad/cameras/camera_common.cc -system/camerad/sensors/*.h -system/camerad/sensors/*.cc - -selfdrive/manager/__init__.py -selfdrive/manager/build.py -selfdrive/manager/helpers.py -selfdrive/manager/manager.py -selfdrive/manager/process_config.py -selfdrive/manager/process.py -selfdrive/manager/test/__init__.py -selfdrive/manager/test/test_manager.py - -selfdrive/modeld/.gitignore -selfdrive/modeld/__init__.py -selfdrive/modeld/SConscript -selfdrive/modeld/modeld.py -selfdrive/modeld/parse_model_outputs.py -selfdrive/modeld/fill_model_msg.py -selfdrive/modeld/get_model_metadata.py -selfdrive/modeld/navmodeld.py -selfdrive/modeld/dmonitoringmodeld.py -selfdrive/modeld/constants.py -selfdrive/modeld/modeld - -selfdrive/modeld/models/__init__.py -selfdrive/modeld/models/*.pxd -selfdrive/modeld/models/*.pyx - -selfdrive/modeld/models/commonmodel.cc -selfdrive/modeld/models/commonmodel.h - -selfdrive/modeld/models/supercombo.onnx - -selfdrive/modeld/models/dmonitoring_model_q.dlc - -selfdrive/modeld/models/navmodel_q.dlc - -selfdrive/modeld/transforms/loadyuv.cc -selfdrive/modeld/transforms/loadyuv.h -selfdrive/modeld/transforms/loadyuv.cl -selfdrive/modeld/transforms/transform.cc -selfdrive/modeld/transforms/transform.h -selfdrive/modeld/transforms/transform.cl - -selfdrive/modeld/thneed/*.py -selfdrive/modeld/thneed/thneed.h -selfdrive/modeld/thneed/thneed_common.cc -selfdrive/modeld/thneed/thneed_qcom2.cc -selfdrive/modeld/thneed/serialize.cc - -selfdrive/modeld/runners/__init__.py -selfdrive/modeld/runners/*.pxd -selfdrive/modeld/runners/*.pyx -selfdrive/modeld/runners/*.cc -selfdrive/modeld/runners/*.h -selfdrive/modeld/runners/*.py - -selfdrive/monitoring/dmonitoringd.py -selfdrive/monitoring/driver_monitor.py - -selfdrive/navd/.gitignore -selfdrive/navd/__init__.py -selfdrive/navd/** - -selfdrive/assets/.gitignore -selfdrive/assets/assets.qrc -selfdrive/assets/*.png -selfdrive/assets/*.svg -selfdrive/assets/body/* -selfdrive/assets/fonts/*.ttf -selfdrive/assets/icons/* -selfdrive/assets/images/* -selfdrive/assets/offroad/* -selfdrive/assets/sounds/* -selfdrive/assets/training/* -selfdrive/assets/navigation/* - -third_party/.gitignore -third_party/SConscript - -third_party/linux/** -third_party/opencl/** - -third_party/json11/json11.cpp -third_party/json11/json11.hpp - -third_party/qrcode/*.cc -third_party/qrcode/*.hpp - -third_party/kaitai/*.h -third_party/kaitai/*.cpp - -third_party/libyuv/include/** - -third_party/snpe/include/** -third_party/snpe/dsp** - -third_party/acados/.gitignore -third_party/acados/include/** -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 - -teleoprtc/** - -rednose_repo/site_scons/site_tools/rednose_filter.py -rednose/.gitignore -rednose/** - -body/.gitignore -body/board/SConscript -body/board/*.h -body/board/*.c -body/board/*.s -body/board/*.ld -body/board/inc/** -body/board/obj/ -body/board/bldc/** -body/board/drivers/** -body/certs/** -body/crypto/** - -cereal/.gitignore -cereal/__init__.py -cereal/car.capnp -cereal/custom.capnp -cereal/legacy.capnp -cereal/log.capnp -cereal/services.py -cereal/SConscript -cereal/include/** -cereal/logger/logger.h -cereal/messaging/.gitignore -cereal/messaging/__init__.py -cereal/messaging/bridge.cc -cereal/messaging/event.cc -cereal/messaging/event.h -cereal/messaging/impl_fake.cc -cereal/messaging/impl_fake.h -cereal/messaging/impl_msgq.cc -cereal/messaging/impl_msgq.h -cereal/messaging/impl_zmq.cc -cereal/messaging/impl_zmq.h -cereal/messaging/messaging.cc -cereal/messaging/messaging.h -cereal/messaging/messaging.pxd -cereal/messaging/messaging_pyx.pyx -cereal/messaging/msgq.cc -cereal/messaging/msgq.h -cereal/messaging/socketmaster.cc -cereal/visionipc/.gitignore -cereal/visionipc/__init__.py -cereal/visionipc/*.cc -cereal/visionipc/*.h -cereal/visionipc/*.pyx -cereal/visionipc/*.pxd - -panda/.gitignore -panda/__init__.py -panda/SConscript -panda/board/** -panda/certs/** -panda/crypto/** -panda/examples/query_fw_versions.py -panda/python/** - -opendbc/.gitignore -opendbc/__init__.py -opendbc/can/__init__.py -opendbc/can/SConscript -opendbc/can/can_define.py -opendbc/can/common.cc -opendbc/can/common.h -opendbc/can/common.pxd -opendbc/can/common_dbc.h -opendbc/can/dbc.cc -opendbc/can/packer.cc -opendbc/can/packer.py -opendbc/can/packer_pyx.pyx -opendbc/can/parser.cc -opendbc/can/parser.py -opendbc/can/parser_pyx.pyx - -opendbc/comma_body.dbc - -opendbc/chrysler_ram_hd_generated.dbc -opendbc/chrysler_ram_dt_generated.dbc -opendbc/chrysler_pacifica_2017_hybrid_generated.dbc -opendbc/chrysler_pacifica_2017_hybrid_private_fusion.dbc - -opendbc/gm_global_a_powertrain_generated.dbc -opendbc/gm_global_a_object.dbc -opendbc/gm_global_a_chassis.dbc - -opendbc/FORD_CADS.dbc -opendbc/ford_fusion_2018_adas.dbc -opendbc/ford_lincoln_base_pt.dbc - -opendbc/honda_accord_2018_can_generated.dbc -opendbc/acura_ilx_2016_can_generated.dbc -opendbc/acura_rdx_2018_can_generated.dbc -opendbc/acura_rdx_2020_can_generated.dbc -opendbc/honda_civic_touring_2016_can_generated.dbc -opendbc/honda_civic_hatchback_ex_2017_can_generated.dbc -opendbc/honda_crv_touring_2016_can_generated.dbc -opendbc/honda_crv_ex_2017_can_generated.dbc -opendbc/honda_crv_ex_2017_body_generated.dbc -opendbc/honda_crv_executive_2016_can_generated.dbc -opendbc/honda_fit_ex_2018_can_generated.dbc -opendbc/honda_odyssey_exl_2018_generated.dbc -opendbc/honda_odyssey_extreme_edition_2018_china_can_generated.dbc -opendbc/honda_insight_ex_2019_can_generated.dbc -opendbc/acura_ilx_2016_nidec.dbc -opendbc/honda_civic_ex_2022_can_generated.dbc - -opendbc/hyundai_canfd.dbc -opendbc/hyundai_kia_generic.dbc -opendbc/hyundai_kia_mando_front_radar_generated.dbc - -opendbc/mazda_2017.dbc - -opendbc/nissan_x_trail_2017_generated.dbc -opendbc/nissan_leaf_2018_generated.dbc - -opendbc/subaru_global_2017_generated.dbc -opendbc/subaru_global_2020_hybrid_generated.dbc -opendbc/subaru_outback_2015_generated.dbc -opendbc/subaru_outback_2019_generated.dbc -opendbc/subaru_forester_2017_generated.dbc - -opendbc/toyota_tnga_k_pt_generated.dbc -opendbc/toyota_new_mc_pt_generated.dbc -opendbc/toyota_nodsu_pt_generated.dbc -opendbc/toyota_adas.dbc -opendbc/toyota_tss2_adas.dbc - -opendbc/vw_golf_mk4.dbc -opendbc/vw_mqb_2010.dbc - -opendbc/tesla_can.dbc -opendbc/tesla_radar.dbc -opendbc/tesla_powertrain.dbc - -tinygrad_repo/openpilot/compile2.py -tinygrad_repo/extra/onnx.py -tinygrad_repo/extra/onnx_ops.py -tinygrad_repo/extra/thneed.py -tinygrad_repo/extra/utils.py -tinygrad_repo/tinygrad/codegen/kernel.py -tinygrad_repo/tinygrad/codegen/linearizer.py -tinygrad_repo/tinygrad/features/image.py -tinygrad_repo/tinygrad/features/search.py -tinygrad_repo/tinygrad/nn/* -tinygrad_repo/tinygrad/renderer/cstyle.py -tinygrad_repo/tinygrad/renderer/opencl.py -tinygrad_repo/tinygrad/runtime/lib.py -tinygrad_repo/tinygrad/runtime/ops_cpu.py -tinygrad_repo/tinygrad/runtime/ops_disk.py -tinygrad_repo/tinygrad/runtime/ops_gpu.py -tinygrad_repo/tinygrad/shape/* -tinygrad_repo/tinygrad/*.py diff --git a/release/files_pc b/release/files_pc deleted file mode 100644 index f2bf090f2c..0000000000 --- a/release/files_pc +++ /dev/null @@ -1,4 +0,0 @@ -third_party/libyuv/x86_64/** -third_party/snpe/x86_64/** -third_party/snpe/x86_64-linux-clang/** -third_party/acados/x86_64/** diff --git a/release/files_tici b/release/files_tici deleted file mode 100644 index 1771c45138..0000000000 --- a/release/files_tici +++ /dev/null @@ -1,15 +0,0 @@ -third_party/libyuv/larch64/** -third_party/snpe/larch64** -third_party/snpe/aarch64-ubuntu-gcc7.5/* -third_party/acados/larch64/** - -system/camerad/cameras/camera_qcom2.cc -system/camerad/cameras/camera_qcom2.h -system/camerad/cameras/camera_util.cc -system/camerad/cameras/camera_util.h -system/camerad/cameras/real_debayer.cl - -system/qcomgpsd/* - -selfdrive/ui/qt/spinner_larch64 -selfdrive/ui/qt/text_larch64 diff --git a/release/release_files.py b/release/release_files.py new file mode 100755 index 0000000000..031b51737d --- /dev/null +++ b/release/release_files.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +import os +import re +from pathlib import Path + +HERE = os.path.abspath(os.path.dirname(__file__)) +ROOT = HERE + "/.." + +# blacklisting is for two purposes: +# - minimizing release download size +# - keeping the diff readable +blacklist = [ + "body/STL/", + + "panda/drivers/", + "panda/examples/", + "panda/tests/safety/", + + "opendbc/.*.dbc$", + "opendbc/generator/", + + "cereal/.*test.*", + "^common/tests/", + + # particularly large text files + "poetry.lock", + "third_party/catch2", + "selfdrive/car/tests/test_models.*", + + "^tools/", + "^scripts/", + "^tinygrad_repo/", + + "matlab.*.md", + + ".git/", + ".github/", + ".devcontainer/", + "Darwin/", + ".vscode", + + # common things + "LICENSE", + "Dockerfile", + ".pre-commit", + + # no LFS or submodules in release + ".lfsconfig", + ".gitattributes", + ".git$", + ".gitmodules", +] + +# gets you through the blacklist +whitelist = [ + "tools/lib/", + "tools/bodyteleop/", + + "tinygrad_repo/openpilot/compile2.py", + "tinygrad_repo/extra/onnx.py", + "tinygrad_repo/extra/onnx_ops.py", + "tinygrad_repo/extra/thneed.py", + "tinygrad_repo/extra/utils.py", + "tinygrad_repo/tinygrad/codegen/kernel.py", + "tinygrad_repo/tinygrad/codegen/linearizer.py", + "tinygrad_repo/tinygrad/features/image.py", + "tinygrad_repo/tinygrad/features/search.py", + "tinygrad_repo/tinygrad/nn/*", + "tinygrad_repo/tinygrad/renderer/cstyle.py", + "tinygrad_repo/tinygrad/renderer/opencl.py", + "tinygrad_repo/tinygrad/runtime/lib.py", + "tinygrad_repo/tinygrad/runtime/ops_cpu.py", + "tinygrad_repo/tinygrad/runtime/ops_disk.py", + "tinygrad_repo/tinygrad/runtime/ops_gpu.py", + "tinygrad_repo/tinygrad/shape/*", + "tinygrad_repo/tinygrad/.*.py", + + # TODO: do this automatically + "opendbc/comma_body.dbc", + "opendbc/chrysler_ram_hd_generated.dbc", + "opendbc/chrysler_ram_dt_generated.dbc", + "opendbc/chrysler_pacifica_2017_hybrid_generated.dbc", + "opendbc/chrysler_pacifica_2017_hybrid_private_fusion.dbc", + "opendbc/gm_global_a_powertrain_generated.dbc", + "opendbc/gm_global_a_object.dbc", + "opendbc/gm_global_a_chassis.dbc", + "opendbc/FORD_CADS.dbc", + "opendbc/ford_fusion_2018_adas.dbc", + "opendbc/ford_lincoln_base_pt.dbc", + "opendbc/honda_accord_2018_can_generated.dbc", + "opendbc/acura_ilx_2016_can_generated.dbc", + "opendbc/acura_rdx_2018_can_generated.dbc", + "opendbc/acura_rdx_2020_can_generated.dbc", + "opendbc/honda_civic_touring_2016_can_generated.dbc", + "opendbc/honda_civic_hatchback_ex_2017_can_generated.dbc", + "opendbc/honda_crv_touring_2016_can_generated.dbc", + "opendbc/honda_crv_ex_2017_can_generated.dbc", + "opendbc/honda_crv_ex_2017_body_generated.dbc", + "opendbc/honda_crv_executive_2016_can_generated.dbc", + "opendbc/honda_fit_ex_2018_can_generated.dbc", + "opendbc/honda_odyssey_exl_2018_generated.dbc", + "opendbc/honda_odyssey_extreme_edition_2018_china_can_generated.dbc", + "opendbc/honda_insight_ex_2019_can_generated.dbc", + "opendbc/acura_ilx_2016_nidec.dbc", + "opendbc/honda_civic_ex_2022_can_generated.dbc", + "opendbc/hyundai_canfd.dbc", + "opendbc/hyundai_kia_generic.dbc", + "opendbc/hyundai_kia_mando_front_radar_generated.dbc", + "opendbc/mazda_2017.dbc", + "opendbc/nissan_x_trail_2017_generated.dbc", + "opendbc/nissan_leaf_2018_generated.dbc", + "opendbc/subaru_global_2017_generated.dbc", + "opendbc/subaru_global_2020_hybrid_generated.dbc", + "opendbc/subaru_outback_2015_generated.dbc", + "opendbc/subaru_outback_2019_generated.dbc", + "opendbc/subaru_forester_2017_generated.dbc", + "opendbc/toyota_tnga_k_pt_generated.dbc", + "opendbc/toyota_new_mc_pt_generated.dbc", + "opendbc/toyota_nodsu_pt_generated.dbc", + "opendbc/toyota_adas.dbc", + "opendbc/toyota_tss2_adas.dbc", + "opendbc/vw_golf_mk4.dbc", + "opendbc/vw_mqb_2010.dbc", + "opendbc/tesla_can.dbc", + "opendbc/tesla_radar_bosch_generated.dbc", + "opendbc/tesla_radar_continental_generated.dbc", + "opendbc/tesla_powertrain.dbc", +] + + +if __name__ == "__main__": + for f in Path(ROOT).rglob("**/*"): + if not (f.is_file() or f.is_symlink()): + continue + + rf = str(f.relative_to(ROOT)) + blacklisted = any(re.search(p, rf) for p in blacklist) + whitelisted = any(re.search(p, rf) for p in whitelist) + if blacklisted and not whitelisted: + continue + + print(rf) diff --git a/release/verify.sh b/release/verify.sh deleted file mode 100755 index ec5266bd81..0000000000 --- a/release/verify.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -e - -RED="\033[0;31m" -GREEN="\033[0;32m" -CLEAR="\033[0m" - -BRANCHES="devel release3" -for b in $BRANCHES; do - if git diff --quiet origin/$b origin/$b-staging && [ "$(git rev-parse origin/$b)" = "$(git rev-parse origin/$b-staging)" ]; then - printf "%-10s $GREEN ok $CLEAR\n" "$b" - else - printf "%-10s $RED mismatch $CLEAR\n" "$b" - fi -done diff --git a/scripts/cell.sh b/scripts/cell.sh index 3f31978af5..4ba007a532 100755 --- a/scripts/cell.sh +++ b/scripts/cell.sh @@ -1,3 +1,3 @@ #!/usr/bin/bash -nmcli connection modify --temporary lte ipv4.route-metric 1 ipv6.route-metric 1 -nmcli con up lte +nmcli connection modify --temporary esim ipv4.route-metric 1 ipv6.route-metric 1 +nmcli con up esim diff --git a/scripts/count_cars.py b/scripts/count_cars.py index 8c0892bb82..28057ddb04 100755 --- a/scripts/count_cars.py +++ b/scripts/count_cars.py @@ -2,10 +2,10 @@ from collections import Counter from pprint import pprint -from openpilot.selfdrive.car.docs import get_all_car_info +from openpilot.selfdrive.car.docs import get_all_car_docs if __name__ == "__main__": - cars = get_all_car_info() + cars = get_all_car_docs() make_count = Counter(l.make for l in cars) print("\n", "*" * 20, len(cars), "total", "*" * 20, "\n") pprint(make_count) diff --git a/scripts/launch_corolla.sh b/scripts/launch_corolla.sh index 146fbacf0a..4e5bca6ce5 100755 --- a/scripts/launch_corolla.sh +++ b/scripts/launch_corolla.sh @@ -2,6 +2,6 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -export FINGERPRINT="TOYOTA COROLLA TSS2 2019" +export FINGERPRINT="TOYOTA_COROLLA_TSS2" export SKIP_FW_QUERY="1" $DIR/../launch_openpilot.sh diff --git a/scripts/stop_updater.sh b/scripts/stop_updater.sh index 4243d30e9f..703b363928 100755 --- a/scripts/stop_updater.sh +++ b/scripts/stop_updater.sh @@ -1,7 +1,7 @@ #!/usr/bin/env sh # Stop updater -pkill -2 -f selfdrive.updated +pkill -2 -f system.updated.updated # Remove pending update rm -f /data/safe_staging/finalized/.overlay_consistent diff --git a/scripts/update_now.sh b/scripts/update_now.sh index 3f0193f081..c34228976a 100755 --- a/scripts/update_now.sh +++ b/scripts/update_now.sh @@ -1,4 +1,4 @@ #!/usr/bin/env sh # Send SIGHUP to updater -pkill -1 -f selfdrive.updated +pkill -1 -f system.updated diff --git a/scripts/waste.py b/scripts/waste.py index 0764ff77c3..bf92710fee 100755 --- a/scripts/waste.py +++ b/scripts/waste.py @@ -3,7 +3,7 @@ import os import time import numpy as np from multiprocessing import Process -from setproctitle import setproctitle +from openpilot.common.threadname import setthreadname def waste(core): os.sched_setaffinity(0, [core,]) @@ -16,7 +16,7 @@ def waste(core): j = 0 while 1: if (i % 100) == 0: - setproctitle("%3d: %8d" % (core, i)) + setthreadname("%3d: %8d" % (core, i)) lt = time.monotonic() print("%3d: %8d %f %.2f" % (core, i, lt-st, j)) st = lt diff --git a/selfdrive/SConscript b/selfdrive/SConscript new file mode 100644 index 0000000000..52898f758f --- /dev/null +++ b/selfdrive/SConscript @@ -0,0 +1,7 @@ +SConscript(['pandad/SConscript']) +SConscript(['controls/lib/lateral_mpc_lib/SConscript']) +SConscript(['controls/lib/longitudinal_mpc_lib/SConscript']) +SConscript(['locationd/SConscript']) +SConscript(['navd/SConscript']) +SConscript(['modeld/SConscript']) +SConscript(['ui/SConscript']) \ No newline at end of file diff --git a/selfdrive/athena/tests/test_registration.py b/selfdrive/athena/tests/test_registration.py deleted file mode 100755 index e7ad63a370..0000000000 --- a/selfdrive/athena/tests/test_registration.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -import json -import unittest -from Crypto.PublicKey import RSA -from pathlib import Path -from unittest import mock - -from openpilot.common.params import Params -from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID -from openpilot.selfdrive.athena.tests.helpers import MockResponse -from openpilot.system.hardware.hw import Paths - - -class TestRegistration(unittest.TestCase): - - def setUp(self): - # clear params and setup key paths - self.params = Params() - self.params.clear_all() - - persist_dir = Path(Paths.persist_root()) / "comma" - persist_dir.mkdir(parents=True, exist_ok=True) - - self.priv_key = persist_dir / "id_rsa" - self.pub_key = persist_dir / "id_rsa.pub" - - def _generate_keys(self): - self.pub_key.touch() - k = RSA.generate(2048) - with open(self.priv_key, "wb") as f: - f.write(k.export_key()) - with open(self.pub_key, "wb") as f: - f.write(k.publickey().export_key()) - - def test_valid_cache(self): - # if all params are written, return the cached dongle id - self.params.put("IMEI", "imei") - self.params.put("HardwareSerial", "serial") - self._generate_keys() - - with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m: - dongle = "DONGLE_ID_123" - self.params.put("DongleId", dongle) - self.assertEqual(register(), dongle) - self.assertFalse(m.called) - - def test_no_keys(self): - # missing pubkey - with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m: - dongle = register() - self.assertEqual(m.call_count, 0) - self.assertEqual(dongle, UNREGISTERED_DONGLE_ID) - self.assertEqual(self.params.get("DongleId", encoding='utf-8'), dongle) - - def test_missing_cache(self): - # keys exist but no dongle id - self._generate_keys() - with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m: - dongle = "DONGLE_ID_123" - m.return_value = MockResponse(json.dumps({'dongle_id': dongle}), 200) - self.assertEqual(register(), dongle) - self.assertEqual(m.call_count, 1) - - # call again, shouldn't hit the API this time - self.assertEqual(register(), dongle) - self.assertEqual(m.call_count, 1) - self.assertEqual(self.params.get("DongleId", encoding='utf-8'), dongle) - - def test_unregistered(self): - # keys exist, but unregistered - self._generate_keys() - with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m: - m.return_value = MockResponse(None, 402) - dongle = register() - self.assertEqual(m.call_count, 1) - self.assertEqual(dongle, UNREGISTERED_DONGLE_ID) - self.assertEqual(self.params.get("DongleId", encoding='utf-8'), dongle) - - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/boardd/.gitignore b/selfdrive/boardd/.gitignore deleted file mode 100644 index e8daa2ef27..0000000000 --- a/selfdrive/boardd/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -boardd -boardd_api_impl.cpp -tests/test_boardd_usbprotocol diff --git a/selfdrive/boardd/SConscript b/selfdrive/boardd/SConscript deleted file mode 100644 index 666763d9b0..0000000000 --- a/selfdrive/boardd/SConscript +++ /dev/null @@ -1,11 +0,0 @@ -Import('env', 'envCython', 'common', 'cereal', 'messaging') - -libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'] -panda = env.Library('panda', ['panda.cc', 'panda_comms.cc', 'spi.cc']) - -env.Program('boardd', ['main.cc', 'boardd.cc'], LIBS=[panda] + libs) -env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) - -envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) -if GetOption('extras'): - env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc'], LIBS=[panda] + libs) diff --git a/selfdrive/boardd/set_time.py b/selfdrive/boardd/set_time.py deleted file mode 100755 index fe17f64e82..0000000000 --- a/selfdrive/boardd/set_time.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -import os -import datetime -from panda import Panda - -from openpilot.common.time import MIN_DATE - -def set_time(logger): - sys_time = datetime.datetime.today() - if sys_time > MIN_DATE: - logger.info("System time valid") - return - - try: - ps = Panda.list() - if len(ps) == 0: - logger.error("Failed to set time, no pandas found") - return - - for s in ps: - with Panda(serial=s) as p: - if not p.is_internal(): - continue - - # Set system time from panda RTC time - panda_time = p.get_datetime() - if panda_time > MIN_DATE: - logger.info(f"adjusting time from '{sys_time}' to '{panda_time}'") - os.system(f"TZ=UTC date -s '{panda_time}'") - break - except Exception: - logger.exception("Failed to fetch time from panda") - -if __name__ == "__main__": - import logging - logging.basicConfig(level=logging.DEBUG) - - set_time(logging) diff --git a/selfdrive/boardd/tests/test_boardd_loopback.py b/selfdrive/boardd/tests/test_boardd_loopback.py deleted file mode 100755 index 148ce9a25d..0000000000 --- a/selfdrive/boardd/tests/test_boardd_loopback.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 -import os -import copy -import random -import time -import unittest -from collections import defaultdict -from pprint import pprint - -import cereal.messaging as messaging -from cereal import car, log -from openpilot.common.params import Params -from openpilot.common.timeout import Timeout -from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp -from openpilot.selfdrive.car import make_can_msg -from openpilot.system.hardware import TICI -from openpilot.selfdrive.test.helpers import phone_only, with_processes - - -class TestBoardd(unittest.TestCase): - - @classmethod - def setUpClass(cls): - os.environ['STARTED'] = '1' - os.environ['BOARDD_LOOPBACK'] = '1' - - @phone_only - @with_processes(['pandad']) - def test_loopback(self): - params = Params() - params.put_bool("IsOnroad", False) - - with Timeout(90, "boardd didn't start"): - sm = messaging.SubMaster(['pandaStates']) - 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) - - num_pandas = len(sm['pandaStates']) - expected_pandas = 2 if TICI and "SINGLE_PANDA" not in os.environ else 1 - self.assertEqual(num_pandas, expected_pandas, "connected pandas ({num_pandas}) doesn't match expected panda count ({expected_pandas}). \ - connect another panda for multipanda tests.") - - # boardd safety setting relies on these params - cp = car.CarParams.new_message() - - safety_config = car.CarParams.SafetyConfig.new_message() - safety_config.safetyModel = car.CarParams.SafetyModel.allOutput - cp.safetyConfigs = [safety_config]*num_pandas - - params.put_bool("IsOnroad", True) - params.put_bool("FirmwareQueryDone", True) - params.put_bool("ControlsReady", True) - params.put("CarParams", cp.to_bytes()) - - sendcan = messaging.pub_sock('sendcan') - can = messaging.sub_sock('can', conflate=False, timeout=100) - sm = messaging.SubMaster(['pandaStates']) - time.sleep(0.5) - - n = 200 - for i in range(n): - print(f"boardd loopback {i}/{n}") - - sent_msgs = defaultdict(set) - for _ in range(random.randrange(20, 100)): - to_send = [] - for __ in range(random.randrange(20)): - bus = random.choice([b for b in range(3*num_pandas) if b % 4 != 3]) - addr = random.randrange(1, 1<<29) - dat = bytes(random.getrandbits(8) for _ in range(random.randrange(1, 9))) - sent_msgs[bus].add((addr, dat)) - to_send.append(make_can_msg(addr, dat, bus)) - sendcan.send(can_list_to_can_capnp(to_send, msgtype='sendcan')) - - sent_loopback = copy.deepcopy(sent_msgs) - sent_loopback.update({k+128: copy.deepcopy(v) for k, v in sent_msgs.items()}) - sent_total = {k: len(v) for k, v in sent_loopback.items()} - for _ in range(100 * 5): - sm.update(0) - recvd = messaging.drain_sock(can, wait_for_one=True) - for msg in recvd: - for m in msg.can: - key = (m.address, m.dat) - assert key in sent_loopback[m.src], f"got unexpected msg: {m.src=} {m.address=} {m.dat=}" - sent_loopback[m.src].discard(key) - - if all(len(v) == 0 for v in sent_loopback.values()): - break - - # if a set isn't empty, messages got dropped - pprint(sent_msgs) - pprint(sent_loopback) - print({k: len(x) for k, x in sent_loopback.items()}) - print(sum([len(x) for x in sent_loopback.values()])) - pprint(sm['pandaStates']) # may drop messages due to RX buffer overflow - for bus in sent_loopback.keys(): - assert not len(sent_loopback[bus]), f"loop {i}: bus {bus} missing {len(sent_loopback[bus])} out of {sent_total[bus]} messages" - - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/car/CARS_template.md b/selfdrive/car/CARS_template.md index ab4d231fa7..2f49e79b81 100644 --- a/selfdrive/car/CARS_template.md +++ b/selfdrive/car/CARS_template.md @@ -12,12 +12,12 @@ 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. -# {{all_car_info | length}} Supported Cars +# {{all_car_docs | length}} Supported Cars |{{Column | map(attribute='value') | join('|') | replace(hardware_col_name, wide_hardware_col_name)}}| |---|---|---|{% for _ in range((Column | length) - 3) %}{{':---:|'}}{% endfor +%} -{% for car_info in all_car_info %} -|{% for column in Column %}{{car_info.get_column(column, star_icon, video_icon, footnote_tag)}}|{% endfor %} +{% for car_docs in all_car_docs %} +|{% for column in Column %}{{car_docs.get_column(column, star_icon, video_icon, footnote_tag)}}|{% endfor %} {% endfor %} @@ -65,7 +65,9 @@ openpilot does not yet support these Toyota models due to a new message authenti * Toyota Venza 2021+ * Toyota Sequoia 2023+ * Toyota Tundra 2022+ +* Toyota Highlander 2024+ * Toyota Corolla Cross 2022+ (only US model) +* Toyota Camry 2025+ * Lexus NX 2022+ * Toyota bZ4x 2023+ * Subaru Solterra 2023+ diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 297dc582f7..4a1df550d0 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -1,13 +1,15 @@ # functions common among cars from collections import namedtuple -from dataclasses import dataclass, field, replace -from enum import ReprEnum +from dataclasses import dataclass +from enum import IntFlag, ReprEnum, EnumType +from dataclasses import replace import capnp from cereal import car from openpilot.common.numpy_fast import clip, interp -from openpilot.selfdrive.car.docs_definitions import CarInfo +from openpilot.common.utils import Freezable +from openpilot.selfdrive.car.docs_definitions import CarDocs # kg of standard extra cargo to count for drive, gas, etc... @@ -163,41 +165,6 @@ def common_fault_avoidance(fault_condition: bool, request: bool, above_limit_fra return above_limit_frames, request -def crc8_pedal(data): - crc = 0xFF # standard init value - poly = 0xD5 # standard crc8: x8+x7+x6+x4+x2+1 - size = len(data) - for i in range(size - 1, -1, -1): - crc ^= data[i] - for _ in range(8): - if ((crc & 0x80) != 0): - crc = ((crc << 1) ^ poly) & 0xFF - else: - crc <<= 1 - return crc - - -def create_gas_interceptor_command(packer, gas_amount, idx): - # Common gas pedal msg generator - enable = gas_amount > 0.001 - - values = { - "ENABLE": enable, - "COUNTER_PEDAL": idx & 0xF, - } - - if enable: - values["GAS_COMMAND"] = gas_amount * 255. - values["GAS_COMMAND2"] = gas_amount * 255. - - dat = packer.make_can_msg("GAS_COMMAND", 0, values)[2] - - checksum = crc8_pedal(dat[:-1]) - values["CHECKSUM_PEDAL"] = checksum - - return packer.make_can_msg("GAS_COMMAND", 0, values) - - def make_can_msg(addr, dat, bus): return [addr, 0, dat, bus] @@ -243,26 +210,30 @@ class CanSignalRateCalculator: return self.rate -CarInfos = CarInfo | list[CarInfo] - - @dataclass(frozen=True, kw_only=True) class CarSpecs: - mass: float - wheelbase: float + mass: float # kg, curb weight + wheelbase: float # meters steerRatio: float - centerToFrontRatio: float = field(default=0.5) - minSteerSpeed: float = field(default=0.) - minEnableSpeed: float = field(default=-1.) + centerToFrontRatio: float = 0.5 + minSteerSpeed: float = 0.0 # m/s + minEnableSpeed: float = -1.0 # m/s + tireStiffnessFactor: float = 1.0 + + def override(self, **kwargs): + return replace(self, **kwargs) -@dataclass(frozen=True, order=True) -class PlatformConfig: - platform_str: str - car_info: CarInfos +@dataclass(order=True) +class PlatformConfig(Freezable): + car_docs: list[CarDocs] + specs: CarSpecs + dbc_dict: DbcDict - specs: CarSpecs | None = None + flags: int = 0 + + platform_str: str | None = None def __hash__(self) -> int: return hash(self.platform_str) @@ -270,8 +241,23 @@ class PlatformConfig: def override(self, **kwargs): return replace(self, **kwargs) + def init(self): + pass -class Platforms(str, ReprEnum): + def __post_init__(self): + self.init() + + +class PlatformsType(EnumType): + def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **kwds): + for key in classdict._member_names.keys(): + cfg: PlatformConfig = classdict[key] + cfg.platform_str = key + cfg.freeze() + return super().__new__(metacls, cls, bases, classdict, boundary=boundary, _simple=_simple, **kwds) + + +class Platforms(str, ReprEnum, metaclass=PlatformsType): config: PlatformConfig def __new__(cls, platform_config: PlatformConfig): @@ -280,10 +266,13 @@ class Platforms(str, ReprEnum): member._value_ = platform_config.platform_str return member + def __repr__(self): + return f"<{self.__class__.__name__}.{self.name}>" + @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} + def with_flags(cls, flags: IntFlag) -> set['Platforms']: + return {p for p in cls if p.config.flags & flags} diff --git a/selfdrive/car/body/carcontroller.py b/selfdrive/car/body/carcontroller.py index d15f11d3a4..259126c416 100644 --- a/selfdrive/car/body/carcontroller.py +++ b/selfdrive/car/body/carcontroller.py @@ -4,6 +4,7 @@ from openpilot.common.realtime import DT_CTRL from opendbc.can.packer import CANPacker from openpilot.selfdrive.car.body import bodycan from openpilot.selfdrive.car.body.values import SPEED_FROM_RPM +from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.controls.lib.pid import PIDController @@ -14,7 +15,7 @@ MAX_POS_INTEGRATOR = 0.2 # meters MAX_TURN_INTEGRATOR = 0.1 # meters -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.frame = 0 self.packer = CANPacker(dbc_name) @@ -74,7 +75,7 @@ class CarController: can_sends = [] can_sends.append(bodycan.create_control(self.packer, torque_l, torque_r)) - new_actuators = CC.actuators.copy() + new_actuators = CC.actuators.as_builder() new_actuators.accel = torque_l new_actuators.steer = torque_r new_actuators.steerOutputCan = torque_r diff --git a/selfdrive/car/body/fingerprints.py b/selfdrive/car/body/fingerprints.py index 6efaabc137..ab7a5f8d7b 100644 --- a/selfdrive/car/body/fingerprints.py +++ b/selfdrive/car/body/fingerprints.py @@ -8,13 +8,13 @@ Ecu = car.CarParams.Ecu FINGERPRINTS = { - CAR.BODY: [{ + CAR.COMMA_BODY: [{ 513: 8, 516: 8, 514: 3, 515: 4 }], } FW_VERSIONS = { - CAR.BODY: { + CAR.COMMA_BODY: { (Ecu.engine, 0x720, None): [ b'0.0.01', b'0.3.00a', diff --git a/selfdrive/car/body/interface.py b/selfdrive/car/body/interface.py index 12a2d5f304..f797a7ecf8 100644 --- a/selfdrive/car/body/interface.py +++ b/selfdrive/car/body/interface.py @@ -14,14 +14,10 @@ class CarInterface(CarInterfaceBase): ret.minSteerSpeed = -math.inf ret.maxLateralAccel = math.inf # TODO: set to a reasonable value - ret.steerRatio = 0.5 ret.steerLimitTimer = 1.0 ret.steerActuatorDelay = 0. - ret.mass = 9 - ret.wheelbase = 0.406 ret.wheelSpeedFactor = SPEED_FROM_RPM - ret.centerToFront = ret.wheelbase * 0.44 ret.radarUnavailable = True ret.openpilotLongitudinalControl = True @@ -41,6 +37,3 @@ class CarInterface(CarInterfaceBase): self.frame += 1 return ret - - def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/body/values.py b/selfdrive/car/body/values.py index 441905f28b..a1195f7cb5 100644 --- a/selfdrive/car/body/values.py +++ b/selfdrive/car/body/values.py @@ -1,6 +1,6 @@ from cereal import car -from openpilot.selfdrive.car import PlatformConfig, Platforms, dbc_dict -from openpilot.selfdrive.car.docs_definitions import CarInfo +from openpilot.selfdrive.car import CarSpecs, PlatformConfig, Platforms, dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarDocs from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu @@ -20,9 +20,9 @@ class CarControllerParams: class CAR(Platforms): - BODY = PlatformConfig( - "COMMA BODY", - CarInfo("comma body", package="All"), + COMMA_BODY = PlatformConfig( + [CarDocs("comma body", package="All")], + CarSpecs(mass=9, wheelbase=0.406, steerRatio=0.5, centerToFrontRatio=0.44), dbc_dict('comma_body', None), ) @@ -37,5 +37,4 @@ FW_QUERY_CONFIG = FwQueryConfig( ], ) -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 339be1912c..66097339b1 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -4,16 +4,15 @@ 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 from openpilot.selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN from openpilot.selfdrive.car.fw_versions import get_fw_versions_ordered, get_present_ecus, match_fw_to_car, set_obd_multiplexing +from openpilot.selfdrive.car.mock.values import CAR as MOCK from openpilot.common.swaglog import cloudlog import cereal.messaging as messaging from openpilot.selfdrive.car import gen_empty_fingerprint +from openpilot.system.version import get_build_metadata FRAME_FINGERPRINT = 100 # 1s @@ -21,7 +20,8 @@ EventName = car.CarEvent.EventName def get_startup_event(car_recognized, controller_available, fw_seen): - if is_comma_remote() and is_tested_branch(): + build_metadata = get_build_metadata() + if build_metadata.openpilot.comma_remote and build_metadata.tested_channel: event = EventName.startup else: event = EventName.startupMaster @@ -48,17 +48,8 @@ def load_interfaces(brand_names): for brand_name in brand_names: path = f'openpilot.selfdrive.car.{brand_name}' CarInterface = __import__(path + '.interface', fromlist=['CarInterface']).CarInterface - - if os.path.exists(BASEDIR + '/' + path.replace('.', '/') + '/carstate.py'): - CarState = __import__(path + '.carstate', fromlist=['CarState']).CarState - else: - CarState = None - - if os.path.exists(BASEDIR + '/' + path.replace('.', '/') + '/carcontroller.py'): - CarController = __import__(path + '.carcontroller', fromlist=['CarController']).CarController - else: - CarController = None - + CarState = __import__(path + '.carstate', fromlist=['CarState']).CarState + CarController = __import__(path + '.carcontroller', fromlist=['CarController']).CarController for model_name in brand_names[brand_name]: ret[model_name] = (CarInterface, CarController, CarState) return ret @@ -148,10 +139,10 @@ def fingerprint(logcan, sendcan, num_pandas): # 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) + car_fw = get_fw_versions_ordered(logcan, sendcan, vin, ecu_rx_addrs, num_pandas=num_pandas) cached = False - exact_fw_match, fw_candidates = match_fw_to_car(car_fw) + exact_fw_match, fw_candidates = match_fw_to_car(car_fw, vin) else: vin_rx_addr, vin_rx_bus, vin = -1, -1, VIN_UNKNOWN exact_fw_match, fw_candidates, car_fw = True, set(), [] @@ -191,9 +182,12 @@ def fingerprint(logcan, sendcan, num_pandas): fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), vin_rx_addr=vin_rx_addr, vin_rx_bus=vin_rx_bus, fingerprints=repr(finger), fw_query_time=fw_query_time, error=True) - car_platform = PLATFORMS.get(car_fingerprint, car_fingerprint) + return car_fingerprint, finger, vin, car_fw, source, exact_match - return car_platform, finger, vin, car_fw, source, exact_match + +def get_car_interface(CP): + CarInterface, CarController, CarState = interfaces[CP.carFingerprint] + return CarInterface(CP, CarController, CarState) def get_car(logcan, sendcan, experimental_long_allowed, num_pandas=1): @@ -201,25 +195,25 @@ def get_car(logcan, sendcan, experimental_long_allowed, num_pandas=1): if candidate is None: cloudlog.event("car doesn't match any fingerprints", fingerprints=repr(fingerprints), error=True) - candidate = "mock" + candidate = "MOCK" - CarInterface, CarController, CarState = interfaces[candidate] + CarInterface, _, _ = interfaces[candidate] CP = CarInterface.get_params(candidate, fingerprints, car_fw, experimental_long_allowed, docs=False) CP.carVin = vin CP.carFw = car_fw CP.fingerprintSource = source CP.fuzzyFingerprint = not exact_match - return CarInterface(CP, CarController, CarState), CP + return get_car_interface(CP), CP -def write_car_param(fingerprint="mock"): +def write_car_param(platform=MOCK.MOCK): params = Params() - CarInterface, _, _ = interfaces[fingerprint] - CP = CarInterface.get_non_essential_params(fingerprint) + CarInterface, _, _ = interfaces[platform] + CP = CarInterface.get_non_essential_params(platform) params.put("CarParams", CP.to_bytes()) def get_demo_car_params(): - fingerprint="mock" - CarInterface, _, _ = interfaces[fingerprint] - CP = CarInterface.get_non_essential_params(fingerprint) + platform = MOCK.MOCK + CarInterface, _, _ = interfaces[platform] + CP = CarInterface.get_non_essential_params(platform) return CP diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py new file mode 100755 index 0000000000..d9ee020ba4 --- /dev/null +++ b/selfdrive/car/card.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +import os +import time + +import cereal.messaging as messaging + +from cereal import car + +from panda import ALTERNATIVE_EXPERIENCE + +from openpilot.common.params import Params +from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL + +from openpilot.selfdrive.pandad import can_list_to_can_capnp +from openpilot.selfdrive.car.car_helpers import get_car, get_one_can +from openpilot.selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.controls.lib.events import Events + +REPLAY = "REPLAY" in os.environ + +EventName = car.CarEvent.EventName + + +class Car: + CI: CarInterfaceBase + + def __init__(self, CI=None): + self.can_sock = messaging.sub_sock('can', timeout=20) + self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents']) + self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput']) + + self.can_rcv_cum_timeout_counter = 0 + + self.CC_prev = car.CarControl.new_message() + self.CS_prev = car.CarState.new_message() + self.initialized_prev = False + + self.last_actuators_output = car.CarControl.Actuators.new_message() + + self.params = Params() + + if CI is None: + # wait for one pandaState and one CAN packet + print("Waiting for CAN messages...") + get_one_can(self.can_sock) + + num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates) + experimental_long_allowed = self.params.get_bool("ExperimentalLongitudinalEnabled") + self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], experimental_long_allowed, num_pandas) + else: + self.CI, self.CP = CI, CI.CP + + # set alternative experiences from parameters + self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") + self.CP.alternativeExperience = 0 + if not self.disengage_on_accelerator: + self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS + + 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 controller_available or self.CP.dashcamOnly + if self.CP.passive: + safety_config = car.CarParams.SafetyConfig.new_message() + safety_config.safetyModel = car.CarParams.SafetyModel.noOutput + self.CP.safetyConfigs = [safety_config] + + # Write previous route's CarParams + prev_cp = self.params.get("CarParamsPersistent") + if prev_cp is not None: + self.params.put("CarParamsPrevRoute", prev_cp) + + # Write CarParams for controls and radard + cp_bytes = self.CP.to_bytes() + self.params.put("CarParams", cp_bytes) + self.params.put_nonblocking("CarParamsCache", cp_bytes) + self.params.put_nonblocking("CarParamsPersistent", cp_bytes) + + self.events = Events() + + # card is driven by can recv, expected at 100Hz + self.rk = Ratekeeper(100, print_delay_threshold=None) + + def state_update(self) -> car.CarState: + """carState update loop, driven by can""" + + # Update carState from CAN + can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True) + CS = self.CI.update(self.CC_prev, 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_cum_timeout_counter += 1 + + if can_rcv_valid and REPLAY: + self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime + + return CS + + def update_events(self, CS: car.CarState) -> car.CarState: + self.events.clear() + + self.events.add_from_msg(CS.events) + + # Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0 + if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \ + (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \ + (CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)): + self.events.add(EventName.pedalPressed) + + CS.events = self.events.to_msg() + + def state_publish(self, CS: car.CarState): + """carState and carParams publish loop""" + + # 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) + + # publish new carOutput + co_send = messaging.new_message('carOutput') + co_send.valid = self.sm.all_checks(['carControl']) + co_send.carOutput.actuatorsOutput = self.last_actuators_output + self.pm.send('carOutput', co_send) + + # kick off controlsd step while we actuate the latest carControl packet + cs_send = messaging.new_message('carState') + cs_send.valid = CS.canValid + cs_send.carState = CS + cs_send.carState.canErrorCounter = self.can_rcv_cum_timeout_counter + cs_send.carState.cumLagMs = -self.rk.remaining * 1000. + self.pm.send('carState', cs_send) + + def controls_update(self, CS: car.CarState, CC: car.CarControl): + """control update loop, driven by carControl""" + + if not self.initialized_prev: + # Initialize CarInterface, once controls are ready + # TODO: this can make us miss at least a few cycles when doing an ECU knockout + self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan']) + # signal pandad to switch to car safety mode + self.params.put_bool_nonblocking("ControlsReady", True) + + if self.sm.all_alive(['carControl']): + # send car controls over can + now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9) + self.last_actuators_output, 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.CC_prev = CC + + def step(self): + CS = self.state_update() + + self.update_events(CS) + + self.state_publish(CS) + + initialized = (not any(e.name == EventName.controlsInitializing for e in self.sm['onroadEvents']) and + self.sm.seen['onroadEvents']) + if not self.CP.passive and initialized: + self.controls_update(CS, self.sm['carControl']) + + self.initialized_prev = initialized + self.CS_prev = CS.as_reader() + + def card_thread(self): + while True: + self.step() + self.rk.monitor_time() + + +def main(): + config_realtime_process(4, Priority.CTRL_HIGH) + car = Car() + car.card_thread() + + +if __name__ == "__main__": + main() diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py index 050eb41b1a..85f53f68eb 100644 --- a/selfdrive/car/chrysler/carcontroller.py +++ b/selfdrive/car/chrysler/carcontroller.py @@ -3,9 +3,10 @@ from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car import apply_meas_steer_torque_limits from openpilot.selfdrive.car.chrysler import chryslercan from openpilot.selfdrive.car.chrysler.values import RAM_CARS, CarControllerParams, ChryslerFlags +from openpilot.selfdrive.car.interfaces import CarControllerBase -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP self.apply_steer_last = 0 @@ -77,7 +78,7 @@ class CarController: self.frame += 1 - new_actuators = CC.actuators.copy() + new_actuators = CC.actuators.as_builder() new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX new_actuators.steerOutputCan = self.apply_steer_last diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index eb1cf7e7d5..91b922c596 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -21,10 +21,16 @@ class CarState(CarStateBase): else: self.shifter_values = can_define.dv["GEAR"]["PRNDL"] + self.prev_distance_button = 0 + self.distance_button = 0 + def update(self, cp, cp_cam): ret = car.CarState.new_message() + self.prev_distance_button = self.distance_button + self.distance_button = cp.vl["CRUISE_BUTTONS"]["ACC_Distance_Dec"] + # lock info ret.doorOpen = any([cp.vl["BCM_1"]["DOOR_OPEN_FL"], cp.vl["BCM_1"]["DOOR_OPEN_FR"], diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 1df514e79b..5072aad5c0 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -4,7 +4,7 @@ from openpilot.selfdrive.car.chrysler.values import CAR Ecu = car.CarParams.Ecu FW_VERSIONS = { - CAR.PACIFICA_2017_HYBRID: { + CAR.CHRYSLER_PACIFICA_2017_HYBRID: { (Ecu.combinationMeter, 0x742, None): [ b'68239262AH', b'68239262AI', @@ -33,11 +33,12 @@ FW_VERSIONS = { b'05190226AK', ], }, - CAR.PACIFICA_2018: { + CAR.CHRYSLER_PACIFICA_2018: { (Ecu.combinationMeter, 0x742, None): [ b'68227902AF', b'68227902AG', b'68227902AH', + b'68227905AG', b'68360252AC', ], (Ecu.srs, 0x744, None): [ @@ -67,10 +68,12 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'68267018AO ', b'68267020AJ ', + b'68303534AG ', b'68303534AJ ', b'68340762AD ', b'68340764AD ', b'68352652AE ', + b'68352654AE ', b'68366851AH ', b'68366853AE ', b'68372861AF ', @@ -79,6 +82,7 @@ FW_VERSIONS = { b'68277370AJ', b'68277370AM', b'68277372AD', + b'68277372AE', b'68277372AN', b'68277374AA', b'68277374AB', @@ -88,19 +92,22 @@ FW_VERSIONS = { b'68380571AB', ], }, - CAR.PACIFICA_2020: { + CAR.CHRYSLER_PACIFICA_2020: { (Ecu.combinationMeter, 0x742, None): [ b'68405327AC', b'68436233AB', b'68436233AC', + b'68436234AB', b'68436250AE', b'68529067AA', b'68594993AB', + b'68594994AB', ], (Ecu.srs, 0x744, None): [ b'68405565AB', b'68405565AC', b'68444299AC', + b'68480707AC', b'68480708AC', b'68526663AB', ], @@ -119,15 +126,18 @@ FW_VERSIONS = { b'68540436AC', b'68540436AD', b'68598670AB', + b'68598670AC', ], (Ecu.eps, 0x75a, None): [ b'68416742AA', b'68460393AA', b'68460393AB', b'68494461AB', + b'68494461AC', b'68524936AA', b'68524936AB', b'68525338AB', + b'68594337AB', b'68594340AB', ], (Ecu.engine, 0x7e0, None): [ @@ -135,30 +145,41 @@ FW_VERSIONS = { b'68413871AE ', b'68413871AH ', b'68413871AI ', + b'68413873AH ', b'68413873AI ', b'68443120AE ', b'68443123AC ', b'68443125AC ', + b'68496647AI ', + b'68496647AJ ', + b'68496650AH ', + b'68496650AI ', + b'68496652AH ', b'68526752AD ', b'68526752AE ', b'68526754AE ', b'68536264AE ', + b'68700304AB ', b'68700306AB ', ], (Ecu.transmission, 0x7e1, None): [ b'68414271AC', b'68414271AD', b'68414275AC', + b'68414275AD', b'68443154AB', b'68443155AC', b'68443158AB', b'68501050AD', + b'68501051AD', + b'68501055AD', b'68527221AB', b'68527223AB', b'68586231AD', + b'68586233AD', ], }, - CAR.PACIFICA_2018_HYBRID: { + CAR.CHRYSLER_PACIFICA_2018_HYBRID: { (Ecu.combinationMeter, 0x742, None): [ b'68358439AE', b'68358439AG', @@ -185,7 +206,7 @@ FW_VERSIONS = { b'05190226AM', ], }, - CAR.PACIFICA_2019_HYBRID: { + CAR.CHRYSLER_PACIFICA_2019_HYBRID: { (Ecu.combinationMeter, 0x742, None): [ b'68405292AC', b'68434956AC', @@ -250,27 +271,34 @@ FW_VERSIONS = { }, CAR.JEEP_GRAND_CHEROKEE: { (Ecu.combinationMeter, 0x742, None): [ + b'68243549AG', b'68302211AC', b'68302212AD', + b'68302223AC', b'68302246AC', b'68331511AC', b'68331574AC', b'68331687AC', + b'68331690AC', b'68340272AD', ], (Ecu.srs, 0x744, None): [ + b'68309533AA', b'68316742AB', b'68355363AB', ], (Ecu.abs, 0x747, None): [ + b'68252642AG', b'68306178AD', b'68336276AB', ], (Ecu.fwdRadar, 0x753, None): [ b'04672627AB', + b'68251506AF', b'68332015AB', ], (Ecu.eps, 0x75a, None): [ + b'68276201AG', b'68321644AB', b'68321644AC', b'68321646AC', @@ -278,6 +306,7 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7e0, None): [ b'05035920AE ', + b'68252272AG ', b'68284455AI ', b'68284456AI ', b'68284477AF ', @@ -288,6 +317,7 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'05035517AH', + b'68253222AF', b'68311218AC', b'68311223AF', b'68311223AG', @@ -304,6 +334,7 @@ FW_VERSIONS = { b'68402708AB', b'68402971AD', b'68454144AD', + b'68454145AB', b'68454152AB', b'68454156AB', b'68516650AB', @@ -359,7 +390,7 @@ FW_VERSIONS = { b'68503664AC', ], }, - CAR.RAM_1500: { + CAR.RAM_1500_5TH_GEN: { (Ecu.combinationMeter, 0x742, None): [ b'68294051AG', b'68294051AI', @@ -376,10 +407,13 @@ FW_VERSIONS = { b'68434859AC', b'68434860AC', b'68453483AC', + b'68453483AD', b'68453487AD', b'68453491AC', + b'68453491AD', b'68453499AD', b'68453503AC', + b'68453503AD', b'68453505AC', b'68453505AD', b'68453511AC', @@ -401,9 +435,14 @@ FW_VERSIONS = { b'68527383AD', b'68527387AE', b'68527403AC', + b'68527403AD', b'68546047AF', b'68631938AA', + b'68631939AA', + b'68631940AA', + b'68631940AB', b'68631942AA', + b'68631943AB', ], (Ecu.srs, 0x744, None): [ b'68428609AB', @@ -415,8 +454,10 @@ FW_VERSIONS = { b'68615034AA', ], (Ecu.abs, 0x747, None): [ + b'68292406AG', b'68292406AH', b'68432418AB', + b'68432418AC', b'68432418AD', b'68436004AD', b'68436004AE', @@ -455,6 +496,7 @@ FW_VERSIONS = { b'68440789AC', b'68466110AA', b'68466110AB', + b'68466113AA', b'68469901AA', b'68469907AA', b'68522583AA', @@ -466,6 +508,7 @@ FW_VERSIONS = { b'68552790AA', b'68552791AB', b'68552794AA', + b'68552794AD', b'68585106AB', b'68585107AB', b'68585108AB', @@ -474,9 +517,12 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7e0, None): [ b'05035699AG ', + b'05035841AC ', + b'05035841AD ', b'05036026AB ', b'05036065AE ', b'05036066AE ', + b'05036193AA ', b'05149368AA ', b'05149591AD ', b'05149591AE ', @@ -493,6 +539,8 @@ FW_VERSIONS = { b'68378701AI ', b'68378702AI ', b'68378710AL ', + b'68378742AI ', + b'68378742AK ', b'68378748AL ', b'68378758AM ', b'68448163AJ', @@ -506,27 +554,34 @@ FW_VERSIONS = { b'68455145AE ', b'68455146AC ', b'68467915AC ', + b'68467916AC ', b'68467936AC ', b'68500630AD', b'68500630AE', + b'68500631AE', b'68502719AC ', b'68502722AC ', + b'68502733AC ', b'68502734AF ', b'68502740AF ', b'68502741AF ', b'68502742AC ', + b'68502742AF ', b'68539650AD', b'68539650AF', b'68539651AD', b'68586101AA ', b'68586105AB ', + b'68629919AC ', b'68629922AC ', + b'68629925AC ', b'68629926AC ', ], (Ecu.transmission, 0x7e1, None): [ b'05035706AD', b'05035842AB', b'05036069AA', + b'05036181AA', b'05149536AC', b'05149537AC', b'05149543AC', @@ -536,6 +591,8 @@ FW_VERSIONS = { b'68360081AM', b'68360085AJ', b'68360085AL', + b'68360086AH', + b'68360086AK', b'68384328AD', b'68384332AD', b'68445531AC', @@ -548,15 +605,18 @@ FW_VERSIONS = { b'68484467AC', b'68484471AC', b'68502994AD', + b'68502996AD', b'68520867AE', b'68520867AF', b'68520870AC', b'68540431AB', b'68540433AB', + b'68551676AA', + b'68629935AB', b'68629936AC', ], }, - CAR.RAM_HD: { + CAR.RAM_HD_5TH_GEN: { (Ecu.combinationMeter, 0x742, None): [ b'68361606AH', b'68437735AC', diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index eb40bc6f6e..217a1a756c 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags from openpilot.selfdrive.car.interfaces import CarInterfaceBase +ButtonType = car.CarState.ButtonEvent.Type + class CarInterface(CarInterfaceBase): @staticmethod @@ -27,13 +29,14 @@ 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, CAR.DODGE_DURANGO) + new_eps_platform = candidate in (CAR.CHRYSLER_PACIFICA_2019_HYBRID, CAR.CHRYSLER_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, CAR.DODGE_DURANGO): + if candidate in (CAR.CHRYSLER_PACIFICA_2017_HYBRID, CAR.CHRYSLER_PACIFICA_2018, CAR.CHRYSLER_PACIFICA_2018_HYBRID, \ + CAR.CHRYSLER_PACIFICA_2019_HYBRID, CAR.CHRYSLER_PACIFICA_2020, CAR.DODGE_DURANGO): ret.lateralTuning.init('pid') ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] @@ -49,14 +52,14 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 0.00006 # Ram - elif candidate == CAR.RAM_1500: + elif candidate == CAR.RAM_1500_5TH_GEN: ret.steerActuatorDelay = 0.2 ret.wheelbase = 3.88 # Older EPS FW allow steer to zero if any(fw.ecu == 'eps' and b"68" < fw.fwVersion[:4] <= b"6831" for fw in car_fw): ret.minSteerSpeed = 0. - elif candidate == CAR.RAM_HD: + elif candidate == CAR.RAM_HD_5TH_GEN: ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, 1.0, False) @@ -76,6 +79,8 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) + ret.buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) + # events events = self.create_common_events(ret, extra_gears=[car.CarState.GearShifter.low]) @@ -90,6 +95,3 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() return ret - - def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 1cc51753d4..42ea94cf86 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -4,24 +4,25 @@ from dataclasses import dataclass, field from cereal import car from panda.python import uds from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict -from openpilot.selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts +from openpilot.selfdrive.car.docs_definitions import CarHarness, CarDocs, CarParts from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu class ChryslerFlags(IntFlag): + # Detected flags HIGHER_MIN_STEERING_SPEED = 1 @dataclass -class ChryslerCarInfo(CarInfo): +class ChryslerCarDocs(CarDocs): package: str = "Adaptive Cruise Control (ACC)" car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.fca])) -@dataclass(frozen=True) +@dataclass class ChryslerPlatformConfig(PlatformConfig): - dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('chrysler_pacifica_2017_hybrid_generated', None)) + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion')) @dataclass(frozen=True) @@ -31,70 +32,60 @@ class ChryslerCarSpecs(CarSpecs): class CAR(Platforms): # Chrysler - PACIFICA_2017_HYBRID = ChryslerPlatformConfig( - "CHRYSLER PACIFICA HYBRID 2017", - ChryslerCarInfo("Chrysler Pacifica Hybrid 2017"), - specs=ChryslerCarSpecs(mass=2242., wheelbase=3.089, steerRatio=16.2), + CHRYSLER_PACIFICA_2017_HYBRID = ChryslerPlatformConfig( + [ChryslerCarDocs("Chrysler Pacifica Hybrid 2017")], + ChryslerCarSpecs(mass=2242., wheelbase=3.089, steerRatio=16.2), ) - PACIFICA_2018_HYBRID = ChryslerPlatformConfig( - "CHRYSLER PACIFICA HYBRID 2018", - ChryslerCarInfo("Chrysler Pacifica Hybrid 2018"), - specs=PACIFICA_2017_HYBRID.specs, + CHRYSLER_PACIFICA_2018_HYBRID = ChryslerPlatformConfig( + [ChryslerCarDocs("Chrysler Pacifica Hybrid 2018")], + CHRYSLER_PACIFICA_2017_HYBRID.specs, ) - PACIFICA_2019_HYBRID = ChryslerPlatformConfig( - "CHRYSLER PACIFICA HYBRID 2019", - ChryslerCarInfo("Chrysler Pacifica Hybrid 2019-23"), - specs=PACIFICA_2017_HYBRID.specs, + CHRYSLER_PACIFICA_2019_HYBRID = ChryslerPlatformConfig( + [ChryslerCarDocs("Chrysler Pacifica Hybrid 2019-23")], + CHRYSLER_PACIFICA_2017_HYBRID.specs, ) - PACIFICA_2018 = ChryslerPlatformConfig( - "CHRYSLER PACIFICA 2018", - ChryslerCarInfo("Chrysler Pacifica 2017-18"), - specs=PACIFICA_2017_HYBRID.specs, + CHRYSLER_PACIFICA_2018 = ChryslerPlatformConfig( + [ChryslerCarDocs("Chrysler Pacifica 2017-18")], + CHRYSLER_PACIFICA_2017_HYBRID.specs, ) - PACIFICA_2020 = ChryslerPlatformConfig( - "CHRYSLER PACIFICA 2020", + CHRYSLER_PACIFICA_2020 = ChryslerPlatformConfig( [ - ChryslerCarInfo("Chrysler Pacifica 2019-20"), - ChryslerCarInfo("Chrysler Pacifica 2021-23", package="All"), + ChryslerCarDocs("Chrysler Pacifica 2019-20"), + ChryslerCarDocs("Chrysler Pacifica 2021-23", package="All"), ], - specs=PACIFICA_2017_HYBRID.specs, + CHRYSLER_PACIFICA_2017_HYBRID.specs, ) # Dodge DODGE_DURANGO = ChryslerPlatformConfig( - "DODGE DURANGO 2021", - ChryslerCarInfo("Dodge Durango 2020-21"), - specs=PACIFICA_2017_HYBRID.specs, + [ChryslerCarDocs("Dodge Durango 2020-21")], + CHRYSLER_PACIFICA_2017_HYBRID.specs, ) # Jeep JEEP_GRAND_CHEROKEE = ChryslerPlatformConfig( # includes 2017 Trailhawk - "JEEP GRAND CHEROKEE V6 2018", - ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"), - specs=ChryslerCarSpecs(mass=1778., wheelbase=2.71, steerRatio=16.7), + [ChryslerCarDocs("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk")], + ChryslerCarSpecs(mass=1778., wheelbase=2.71, steerRatio=16.7), ) JEEP_GRAND_CHEROKEE_2019 = ChryslerPlatformConfig( # includes 2020 Trailhawk - "JEEP GRAND CHEROKEE 2019", - ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), - specs=JEEP_GRAND_CHEROKEE.specs, + [ChryslerCarDocs("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4")], + JEEP_GRAND_CHEROKEE.specs, ) # Ram - RAM_1500 = ChryslerPlatformConfig( - "RAM 1500 5TH GEN", - ChryslerCarInfo("Ram 1500 2019-24", car_parts=CarParts.common([CarHarness.ram])), + RAM_1500_5TH_GEN = ChryslerPlatformConfig( + [ChryslerCarDocs("Ram 1500 2019-24", car_parts=CarParts.common([CarHarness.ram]))], + ChryslerCarSpecs(mass=2493., wheelbase=3.88, steerRatio=16.3, minSteerSpeed=14.5), dbc_dict('chrysler_ram_dt_generated', None), - specs=ChryslerCarSpecs(mass=2493., wheelbase=3.88, steerRatio=16.3, minSteerSpeed=14.5), ) - RAM_HD = ChryslerPlatformConfig( - "RAM HD 5TH GEN", + RAM_HD_5TH_GEN = ChryslerPlatformConfig( [ - ChryslerCarInfo("Ram 2500 2020-24", car_parts=CarParts.common([CarHarness.ram])), - ChryslerCarInfo("Ram 3500 2019-22", car_parts=CarParts.common([CarHarness.ram])), + ChryslerCarDocs("Ram 2500 2020-24", car_parts=CarParts.common([CarHarness.ram])), + ChryslerCarDocs("Ram 3500 2019-22", car_parts=CarParts.common([CarHarness.ram])), ], + ChryslerCarSpecs(mass=3405., wheelbase=3.785, steerRatio=15.61, minSteerSpeed=16.), dbc_dict('chrysler_ram_hd_generated', None), - specs=ChryslerCarSpecs(mass=3405., wheelbase=3.785, steerRatio=15.61, minSteerSpeed=16.), ) @@ -118,8 +109,8 @@ class CarControllerParams: STEER_THRESHOLD = 120 -RAM_DT = {CAR.RAM_1500, } -RAM_HD = {CAR.RAM_HD, } +RAM_DT = {CAR.RAM_1500_5TH_GEN, } +RAM_HD = {CAR.RAM_HD_5TH_GEN, } RAM_CARS = RAM_DT | RAM_HD @@ -162,5 +153,4 @@ FW_QUERY_CONFIG = FwQueryConfig( ], ) -CAR_INFO = CAR.create_carinfo_map() DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py index caa79a8f87..7bf6a6ad22 100755 --- a/selfdrive/car/docs.py +++ b/selfdrive/car/docs.py @@ -9,8 +9,9 @@ from natsort import natsorted from cereal import car from openpilot.common.basedir import BASEDIR from openpilot.selfdrive.car import gen_empty_fingerprint -from openpilot.selfdrive.car.docs_definitions import CarInfo, Column, CommonFootnote, PartType +from openpilot.selfdrive.car.docs_definitions import CarDocs, Column, CommonFootnote, PartType from openpilot.selfdrive.car.car_helpers import interfaces, get_interface_attr +from openpilot.selfdrive.car.values import PLATFORMS def get_all_footnotes() -> dict[Enum, int]: @@ -24,45 +25,43 @@ 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_docs() -> list[CarDocs]: + all_car_docs: list[CarDocs] = [] footnotes = get_all_footnotes() - for model, car_info in get_interface_attr("CAR_INFO", combine_brands=True).items(): + for model, platform in PLATFORMS.items(): + car_docs = platform.config.car_docs # If available, uses experimental longitudinal limits for the docs - CP = interfaces[model][0].get_params(model, fingerprint=gen_empty_fingerprint(), + CP = interfaces[model][0].get_params(platform, fingerprint=gen_empty_fingerprint(), car_fw=[car.CarParams.CarFw(ecu="unknown")], experimental_long=True, docs=True) - if CP.dashcamOnly or car_info is None: + if CP.dashcamOnly or not len(car_docs): continue # A platform can include multiple car models - if not isinstance(car_info, list): - car_info = (car_info,) - - for _car_info in car_info: - if not hasattr(_car_info, "row"): - _car_info.init_make(CP) - _car_info.init(CP, footnotes) - all_car_info.append(_car_info) + for _car_docs in car_docs: + if not hasattr(_car_docs, "row"): + _car_docs.init_make(CP) + _car_docs.init(CP, footnotes) + all_car_docs.append(_car_docs) # Sort cars by make and model + year - sorted_cars: list[CarInfo] = natsorted(all_car_info, key=lambda car: car.name.lower()) + sorted_cars: list[CarDocs] = natsorted(all_car_docs, key=lambda car: car.name.lower()) return sorted_cars -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 group_by_make(all_car_docs: list[CarDocs]) -> dict[str, list[CarDocs]]: + sorted_car_docs = defaultdict(list) + for car_docs in all_car_docs: + sorted_car_docs[car_docs.make].append(car_docs) + return dict(sorted_car_docs) -def generate_cars_md(all_car_info: list[CarInfo], template_fn: str) -> str: +def generate_cars_md(all_car_docs: list[CarDocs], 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()] - cars_md: str = template.render(all_car_info=all_car_info, PartType=PartType, + cars_md: str = template.render(all_car_docs=all_car_docs, PartType=PartType, group_by_make=group_by_make, footnotes=footnotes, Column=Column) return cars_md @@ -77,5 +76,5 @@ if __name__ == "__main__": args = parser.parse_args() with open(args.out, 'w') as f: - f.write(generate_cars_md(get_all_car_info(), args.template)) + f.write(generate_cars_md(get_all_car_docs(), args.template)) print(f"Generated and written to {args.out}") diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index 841cc3af2f..971338e9b5 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -118,7 +118,8 @@ class CarHarness(EnumBase): nissan_b = BaseCarHarness("Nissan B connector", parts=[Accessory.harness_box, Cable.rj45_cable_7ft, Cable.long_obdc_cable, Cable.usbc_coupler]) mazda = BaseCarHarness("Mazda connector") ford_q3 = BaseCarHarness("Ford Q3 connector") - ford_q4 = BaseCarHarness("Ford Q4 connector") + ford_q4 = BaseCarHarness("Ford Q4 connector", parts=[Accessory.harness_box, Accessory.comma_power_v2, Cable.rj45_cable_7ft, Cable.long_obdc_cable, + Cable.usbc_coupler]) class Device(EnumBase): @@ -219,7 +220,7 @@ def split_name(name: str) -> tuple[str, str, str]: @dataclass -class CarInfo: +class CarDocs: # make + model + model years name: str @@ -265,7 +266,7 @@ class CarInfo: # min steer & enable speed columns # TODO: set all the min steer speeds in carParams and remove this if self.min_steer_speed is not None: - assert CP.minSteerSpeed == 0, f"{CP.carFingerprint}: Minimum steer speed set in both CarInfo and CarParams" + assert CP.minSteerSpeed < 0.5, f"{CP.carFingerprint}: Minimum steer speed set in both CarDocs and CarParams" else: self.min_steer_speed = CP.minSteerSpeed @@ -274,7 +275,7 @@ class CarInfo: self.min_enable_speed = CP.minEnableSpeed if self.auto_resume is None: - self.auto_resume = CP.autoResumeSng + self.auto_resume = CP.autoResumeSng and self.min_enable_speed <= 0 # hardware column hardware_col = "None" @@ -316,7 +317,7 @@ class CarInfo: return self def init_make(self, CP: car.CarParams): - """CarInfo subclasses can add make-specific logic for harness selection, footnotes, etc.""" + """CarDocs subclasses can add make-specific logic for harness selection, footnotes, etc.""" def get_detail_sentence(self, CP): if not CP.notCar: @@ -339,13 +340,13 @@ class CarInfo: # experimental mode exp_link = "Experimental mode" - if CP.openpilotLongitudinalControl or CP.experimentalLongitudinalAvailable: + if CP.openpilotLongitudinalControl and not CP.experimentalLongitudinalAvailable: sentence_builder += f" Traffic light and stop sign handling is also available in {exp_link}." return sentence_builder.format(car_model=f"{self.make} {self.model}", alc=alc, acc=acc) else: - if CP.carFingerprint == "COMMA BODY": + if CP.carFingerprint == "COMMA_BODY": return "The body is a robotics dev kit that can run openpilot. Learn more." else: raise Exception(f"This notCar does not have a detail sentence: {CP.carFingerprint}") diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py index da5e7b4612..756cd7f963 100755 --- a/selfdrive/car/ecu_addrs.py +++ b/selfdrive/car/ecu_addrs.py @@ -6,7 +6,7 @@ import cereal.messaging as messaging from panda.python.uds import SERVICE_TYPE from openpilot.selfdrive.car import make_can_msg from openpilot.selfdrive.car.fw_query_definitions import EcuAddrBusType -from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp +from openpilot.selfdrive.pandad import can_list_to_can_capnp from openpilot.common.swaglog import cloudlog @@ -71,25 +71,35 @@ def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, que if __name__ == "__main__": import argparse + from openpilot.common.params import Params + from openpilot.selfdrive.car.fw_versions import set_obd_multiplexing parser = argparse.ArgumentParser(description='Get addresses of all ECUs') parser.add_argument('--debug', action='store_true') parser.add_argument('--bus', type=int, default=1) + parser.add_argument('--no-obd', action='store_true') parser.add_argument('--timeout', type=float, default=1.0) args = parser.parse_args() logcan = messaging.sub_sock('can') sendcan = messaging.pub_sock('sendcan') - time.sleep(1.0) + # Set up params for pandad + params = Params() + params.remove("FirmwareQueryDone") + params.put_bool("IsOnroad", False) + time.sleep(0.2) # thread is 10 Hz + params.put_bool("IsOnroad", True) + + set_obd_multiplexing(params, not args.no_obd) print("Getting ECU addresses ...") ecu_addrs = get_all_ecu_addrs(logcan, sendcan, args.bus, args.timeout, debug=args.debug) print() - print("Found ECUs on addresses:") + print("Found ECUs on rx addresses:") for addr, subaddr, _ in ecu_addrs: - msg = f" 0x{hex(addr)}" + msg = f" {hex(addr)}" if subaddr is not None: msg += f" (sub-address: {hex(subaddr)})" print(msg) diff --git a/selfdrive/car/fingerprints.py b/selfdrive/car/fingerprints.py index 6a7c3c75be..1128a31c29 100644 --- a/selfdrive/car/fingerprints.py +++ b/selfdrive/car/fingerprints.py @@ -1,4 +1,17 @@ from openpilot.selfdrive.car.interfaces import get_interface_attr +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.mock.values import CAR as MOCK +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 VW FW_VERSIONS = get_interface_attr('FW_VERSIONS', combine_brands=True, ignore_none=True) _FINGERPRINTS = get_interface_attr('FINGERPRINTS', combine_brands=True, ignore_none=True) @@ -44,3 +57,291 @@ def all_known_cars(): def all_legacy_fingerprint_cars(): """Returns a list of all known car strings, FPv1 only.""" return list(_FINGERPRINTS.keys()) + + +# A dict that maps old platform strings to their latest representations +MIGRATION = { + "ACURA ILX 2016 ACURAWATCH PLUS": HONDA.ACURA_ILX, + "ACURA RDX 2018 ACURAWATCH PLUS": HONDA.ACURA_RDX, + "ACURA RDX 2020 TECH": HONDA.ACURA_RDX_3G, + "AUDI A3": VW.AUDI_A3_MK3, + "HONDA ACCORD 2018 HYBRID TOURING": HONDA.HONDA_ACCORD, + "HONDA ACCORD 1.5T 2018": HONDA.HONDA_ACCORD, + "HONDA ACCORD 2018 LX 1.5T": HONDA.HONDA_ACCORD, + "HONDA ACCORD 2018 SPORT 2T": HONDA.HONDA_ACCORD, + "HONDA ACCORD 2T 2018": HONDA.HONDA_ACCORD, + "HONDA ACCORD HYBRID 2018": HONDA.HONDA_ACCORD, + "HONDA CIVIC 2016 TOURING": HONDA.HONDA_CIVIC, + "HONDA CIVIC HATCHBACK 2017 SEDAN/COUPE 2019": HONDA.HONDA_CIVIC_BOSCH, + "HONDA CIVIC SEDAN 1.6 DIESEL": HONDA.HONDA_CIVIC_BOSCH_DIESEL, + "HONDA CR-V 2016 EXECUTIVE": HONDA.HONDA_CRV_EU, + "HONDA CR-V 2016 TOURING": HONDA.HONDA_CRV, + "HONDA CR-V 2017 EX": HONDA.HONDA_CRV_5G, + "HONDA CR-V 2019 HYBRID": HONDA.HONDA_CRV_HYBRID, + "HONDA FIT 2018 EX": HONDA.HONDA_FIT, + "HONDA HRV 2019 TOURING": HONDA.HONDA_HRV, + "HONDA INSIGHT 2019 TOURING": HONDA.HONDA_INSIGHT, + "HONDA ODYSSEY 2018 EX-L": HONDA.HONDA_ODYSSEY, + "HONDA ODYSSEY 2019 EXCLUSIVE CHN": HONDA.HONDA_ODYSSEY_CHN, + "HONDA PILOT 2017 TOURING": HONDA.HONDA_PILOT, + "HONDA PILOT 2019 ELITE": HONDA.HONDA_PILOT, + "HONDA PILOT 2019": HONDA.HONDA_PILOT, + "HONDA PASSPORT 2021": HONDA.HONDA_PILOT, + "HONDA RIDGELINE 2017 BLACK EDITION": HONDA.HONDA_RIDGELINE, + "HYUNDAI ELANTRA LIMITED ULTIMATE 2017": HYUNDAI.HYUNDAI_ELANTRA, + "HYUNDAI SANTA FE LIMITED 2019": HYUNDAI.HYUNDAI_SANTA_FE, + "HYUNDAI TUCSON DIESEL 2019": HYUNDAI.HYUNDAI_TUCSON, + "KIA OPTIMA 2016": HYUNDAI.KIA_OPTIMA_G4, + "KIA OPTIMA 2019": HYUNDAI.KIA_OPTIMA_G4_FL, + "KIA OPTIMA SX 2019 & 2016": HYUNDAI.KIA_OPTIMA_G4_FL, + "LEXUS CT 200H 2018": TOYOTA.LEXUS_CTH, + "LEXUS ES 300H 2018": TOYOTA.LEXUS_ES, + "LEXUS ES 300H 2019": TOYOTA.LEXUS_ES_TSS2, + "LEXUS IS300 2018": TOYOTA.LEXUS_IS, + "LEXUS NX300 2018": TOYOTA.LEXUS_NX, + "LEXUS NX300H 2018": TOYOTA.LEXUS_NX, + "LEXUS RX 350 2016": TOYOTA.LEXUS_RX, + "LEXUS RX350 2020": TOYOTA.LEXUS_RX_TSS2, + "LEXUS RX450 HYBRID 2020": TOYOTA.LEXUS_RX_TSS2, + "TOYOTA SIENNA XLE 2018": TOYOTA.TOYOTA_SIENNA, + "TOYOTA C-HR HYBRID 2018": TOYOTA.TOYOTA_CHR, + "TOYOTA COROLLA HYBRID TSS2 2019": TOYOTA.TOYOTA_COROLLA_TSS2, + "TOYOTA RAV4 HYBRID 2019": TOYOTA.TOYOTA_RAV4_TSS2, + "LEXUS ES HYBRID 2019": TOYOTA.LEXUS_ES_TSS2, + "LEXUS NX HYBRID 2018": TOYOTA.LEXUS_NX, + "LEXUS NX HYBRID 2020": TOYOTA.LEXUS_NX_TSS2, + "LEXUS RX HYBRID 2020": TOYOTA.LEXUS_RX_TSS2, + "TOYOTA ALPHARD HYBRID 2021": TOYOTA.TOYOTA_ALPHARD_TSS2, + "TOYOTA AVALON HYBRID 2019": TOYOTA.TOYOTA_AVALON_2019, + "TOYOTA AVALON HYBRID 2022": TOYOTA.TOYOTA_AVALON_TSS2, + "TOYOTA CAMRY HYBRID 2018": TOYOTA.TOYOTA_CAMRY, + "TOYOTA CAMRY HYBRID 2021": TOYOTA.TOYOTA_CAMRY_TSS2, + "TOYOTA C-HR HYBRID 2022": TOYOTA.TOYOTA_CHR_TSS2, + "TOYOTA HIGHLANDER HYBRID 2020": TOYOTA.TOYOTA_HIGHLANDER_TSS2, + "TOYOTA RAV4 HYBRID 2022": TOYOTA.TOYOTA_RAV4_TSS2_2022, + "TOYOTA RAV4 HYBRID 2023": TOYOTA.TOYOTA_RAV4_TSS2_2023, + "TOYOTA HIGHLANDER HYBRID 2018": TOYOTA.TOYOTA_HIGHLANDER, + "LEXUS ES HYBRID 2018": TOYOTA.LEXUS_ES, + "LEXUS RX HYBRID 2017": TOYOTA.LEXUS_RX, + "HYUNDAI TUCSON HYBRID 4TH GEN": HYUNDAI.HYUNDAI_TUCSON_4TH_GEN, + "KIA SPORTAGE HYBRID 5TH GEN": HYUNDAI.KIA_SPORTAGE_5TH_GEN, + "KIA SORENTO PLUG-IN HYBRID 4TH GEN": HYUNDAI.KIA_SORENTO_HEV_4TH_GEN, + "CADILLAC ESCALADE ESV PLATINUM 2019": GM.CADILLAC_ESCALADE_ESV_2019, + + # Removal of platform_str, see https://github.com/commaai/openpilot/pull/31868/ + "COMMA BODY": BODY.COMMA_BODY, + "CHRYSLER PACIFICA HYBRID 2017": CHRYSLER.CHRYSLER_PACIFICA_2017_HYBRID, + "CHRYSLER PACIFICA HYBRID 2018": CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID, + "CHRYSLER PACIFICA HYBRID 2019": CHRYSLER.CHRYSLER_PACIFICA_2019_HYBRID, + "CHRYSLER PACIFICA 2018": CHRYSLER.CHRYSLER_PACIFICA_2018, + "CHRYSLER PACIFICA 2020": CHRYSLER.CHRYSLER_PACIFICA_2020, + "DODGE DURANGO 2021": CHRYSLER.DODGE_DURANGO, + "JEEP GRAND CHEROKEE V6 2018": CHRYSLER.JEEP_GRAND_CHEROKEE, + "JEEP GRAND CHEROKEE 2019": CHRYSLER.JEEP_GRAND_CHEROKEE_2019, + "RAM 1500 5TH GEN": CHRYSLER.RAM_1500_5TH_GEN, + "RAM HD 5TH GEN": CHRYSLER.RAM_HD_5TH_GEN, + "FORD BRONCO SPORT 1ST GEN": FORD.FORD_BRONCO_SPORT_MK1, + "FORD ESCAPE 4TH GEN": FORD.FORD_ESCAPE_MK4, + "FORD EXPLORER 6TH GEN": FORD.FORD_EXPLORER_MK6, + "FORD F-150 14TH GEN": FORD.FORD_F_150_MK14, + "FORD F-150 LIGHTNING 1ST GEN": FORD.FORD_F_150_LIGHTNING_MK1, + "FORD FOCUS 4TH GEN": FORD.FORD_FOCUS_MK4, + "FORD MAVERICK 1ST GEN": FORD.FORD_MAVERICK_MK1, + "FORD MUSTANG MACH-E 1ST GEN": FORD.FORD_MUSTANG_MACH_E_MK1, + "HOLDEN ASTRA RS-V BK 2017": GM.HOLDEN_ASTRA, + "CHEVROLET VOLT PREMIER 2017": GM.CHEVROLET_VOLT, + "CADILLAC ATS Premium Performance 2018": GM.CADILLAC_ATS, + "CHEVROLET MALIBU PREMIER 2017": GM.CHEVROLET_MALIBU, + "GMC ACADIA DENALI 2018": GM.GMC_ACADIA, + "BUICK LACROSSE 2017": GM.BUICK_LACROSSE, + "BUICK REGAL ESSENCE 2018": GM.BUICK_REGAL, + "CADILLAC ESCALADE 2017": GM.CADILLAC_ESCALADE, + "CADILLAC ESCALADE ESV 2016": GM.CADILLAC_ESCALADE_ESV, + "CADILLAC ESCALADE ESV 2019": GM.CADILLAC_ESCALADE_ESV_2019, + "CHEVROLET BOLT EUV 2022": GM.CHEVROLET_BOLT_EUV, + "CHEVROLET SILVERADO 1500 2020": GM.CHEVROLET_SILVERADO, + "CHEVROLET EQUINOX 2019": GM.CHEVROLET_EQUINOX, + "CHEVROLET TRAILBLAZER 2021": GM.CHEVROLET_TRAILBLAZER, + "HONDA ACCORD 2018": HONDA.HONDA_ACCORD, + "HONDA CIVIC (BOSCH) 2019": HONDA.HONDA_CIVIC_BOSCH, + "HONDA CIVIC SEDAN 1.6 DIESEL 2019": HONDA.HONDA_CIVIC_BOSCH_DIESEL, + "HONDA CIVIC 2022": HONDA.HONDA_CIVIC_2022, + "HONDA CR-V 2017": HONDA.HONDA_CRV_5G, + "HONDA CR-V HYBRID 2019": HONDA.HONDA_CRV_HYBRID, + "HONDA HR-V 2023": HONDA.HONDA_HRV_3G, + "ACURA RDX 2020": HONDA.ACURA_RDX_3G, + "HONDA INSIGHT 2019": HONDA.HONDA_INSIGHT, + "HONDA E 2020": HONDA.HONDA_E, + "ACURA ILX 2016": HONDA.ACURA_ILX, + "HONDA CR-V 2016": HONDA.HONDA_CRV, + "HONDA CR-V EU 2016": HONDA.HONDA_CRV_EU, + "HONDA FIT 2018": HONDA.HONDA_FIT, + "HONDA FREED 2020": HONDA.HONDA_FREED, + "HONDA HRV 2019": HONDA.HONDA_HRV, + "HONDA ODYSSEY 2018": HONDA.HONDA_ODYSSEY, + "HONDA ODYSSEY CHN 2019": HONDA.HONDA_ODYSSEY_CHN, + "ACURA RDX 2018": HONDA.ACURA_RDX, + "HONDA PILOT 2017": HONDA.HONDA_PILOT, + "HONDA RIDGELINE 2017": HONDA.HONDA_RIDGELINE, + "HONDA CIVIC 2016": HONDA.HONDA_CIVIC, + "HYUNDAI AZERA 6TH GEN": HYUNDAI.HYUNDAI_AZERA_6TH_GEN, + "HYUNDAI AZERA HYBRID 6TH GEN": HYUNDAI.HYUNDAI_AZERA_HEV_6TH_GEN, + "HYUNDAI ELANTRA 2017": HYUNDAI.HYUNDAI_ELANTRA, + "HYUNDAI I30 N LINE 2019 & GT 2018 DCT": HYUNDAI.HYUNDAI_ELANTRA_GT_I30, + "HYUNDAI ELANTRA 2021": HYUNDAI.HYUNDAI_ELANTRA_2021, + "HYUNDAI ELANTRA HYBRID 2021": HYUNDAI.HYUNDAI_ELANTRA_HEV_2021, + "HYUNDAI GENESIS 2015-2016": HYUNDAI.HYUNDAI_GENESIS, + "HYUNDAI IONIQ HYBRID 2017-2019": HYUNDAI.HYUNDAI_IONIQ, + "HYUNDAI IONIQ HYBRID 2020-2022": HYUNDAI.HYUNDAI_IONIQ_HEV_2022, + "HYUNDAI IONIQ ELECTRIC LIMITED 2019": HYUNDAI.HYUNDAI_IONIQ_EV_LTD, + "HYUNDAI IONIQ ELECTRIC 2020": HYUNDAI.HYUNDAI_IONIQ_EV_2020, + "HYUNDAI IONIQ PLUG-IN HYBRID 2019": HYUNDAI.HYUNDAI_IONIQ_PHEV_2019, + "HYUNDAI IONIQ PHEV 2020": HYUNDAI.HYUNDAI_IONIQ_PHEV, + "HYUNDAI KONA 2020": HYUNDAI.HYUNDAI_KONA, + "HYUNDAI KONA ELECTRIC 2019": HYUNDAI.HYUNDAI_KONA_EV, + "HYUNDAI KONA ELECTRIC 2022": HYUNDAI.HYUNDAI_KONA_EV_2022, + "HYUNDAI KONA ELECTRIC 2ND GEN": HYUNDAI.HYUNDAI_KONA_EV_2ND_GEN, + "HYUNDAI KONA HYBRID 2020": HYUNDAI.HYUNDAI_KONA_HEV, + "HYUNDAI SANTA FE 2019": HYUNDAI.HYUNDAI_SANTA_FE, + "HYUNDAI SANTA FE 2022": HYUNDAI.HYUNDAI_SANTA_FE_2022, + "HYUNDAI SANTA FE HYBRID 2022": HYUNDAI.HYUNDAI_SANTA_FE_HEV_2022, + "HYUNDAI SANTA FE PlUG-IN HYBRID 2022": HYUNDAI.HYUNDAI_SANTA_FE_PHEV_2022, + "HYUNDAI SONATA 2020": HYUNDAI.HYUNDAI_SONATA, + "HYUNDAI SONATA 2019": HYUNDAI.HYUNDAI_SONATA_LF, + "HYUNDAI STARIA 4TH GEN": HYUNDAI.HYUNDAI_STARIA_4TH_GEN, + "HYUNDAI TUCSON 2019": HYUNDAI.HYUNDAI_TUCSON, + "HYUNDAI PALISADE 2020": HYUNDAI.HYUNDAI_PALISADE, + "HYUNDAI VELOSTER 2019": HYUNDAI.HYUNDAI_VELOSTER, + "HYUNDAI SONATA HYBRID 2021": HYUNDAI.HYUNDAI_SONATA_HYBRID, + "HYUNDAI IONIQ 5 2022": HYUNDAI.HYUNDAI_IONIQ_5, + "HYUNDAI IONIQ 6 2023": HYUNDAI.HYUNDAI_IONIQ_6, + "HYUNDAI TUCSON 4TH GEN": HYUNDAI.HYUNDAI_TUCSON_4TH_GEN, + "HYUNDAI SANTA CRUZ 1ST GEN": HYUNDAI.HYUNDAI_SANTA_CRUZ_1ST_GEN, + "HYUNDAI CUSTIN 1ST GEN": HYUNDAI.HYUNDAI_CUSTIN_1ST_GEN, + "KIA FORTE E 2018 & GT 2021": HYUNDAI.KIA_FORTE, + "KIA K5 2021": HYUNDAI.KIA_K5_2021, + "KIA K5 HYBRID 2020": HYUNDAI.KIA_K5_HEV_2020, + "KIA K8 HYBRID 1ST GEN": HYUNDAI.KIA_K8_HEV_1ST_GEN, + "KIA NIRO EV 2020": HYUNDAI.KIA_NIRO_EV, + "KIA NIRO EV 2ND GEN": HYUNDAI.KIA_NIRO_EV_2ND_GEN, + "KIA NIRO HYBRID 2019": HYUNDAI.KIA_NIRO_PHEV, + "KIA NIRO PLUG-IN HYBRID 2022": HYUNDAI.KIA_NIRO_PHEV_2022, + "KIA NIRO HYBRID 2021": HYUNDAI.KIA_NIRO_HEV_2021, + "KIA NIRO HYBRID 2ND GEN": HYUNDAI.KIA_NIRO_HEV_2ND_GEN, + "KIA OPTIMA 4TH GEN": HYUNDAI.KIA_OPTIMA_G4, + "KIA OPTIMA 4TH GEN FACELIFT": HYUNDAI.KIA_OPTIMA_G4_FL, + "KIA OPTIMA HYBRID 2017 & SPORTS 2019": HYUNDAI.KIA_OPTIMA_H, + "KIA OPTIMA HYBRID 4TH GEN FACELIFT": HYUNDAI.KIA_OPTIMA_H_G4_FL, + "KIA SELTOS 2021": HYUNDAI.KIA_SELTOS, + "KIA SPORTAGE 5TH GEN": HYUNDAI.KIA_SPORTAGE_5TH_GEN, + "KIA SORENTO GT LINE 2018": HYUNDAI.KIA_SORENTO, + "KIA SORENTO 4TH GEN": HYUNDAI.KIA_SORENTO_4TH_GEN, + "KIA SORENTO HYBRID 4TH GEN": HYUNDAI.KIA_SORENTO_HEV_4TH_GEN, + "KIA STINGER GT2 2018": HYUNDAI.KIA_STINGER, + "KIA STINGER 2022": HYUNDAI.KIA_STINGER_2022, + "KIA CEED INTRO ED 2019": HYUNDAI.KIA_CEED, + "KIA EV6 2022": HYUNDAI.KIA_EV6, + "KIA CARNIVAL 4TH GEN": HYUNDAI.KIA_CARNIVAL_4TH_GEN, + "GENESIS GV60 ELECTRIC 1ST GEN": HYUNDAI.GENESIS_GV60_EV_1ST_GEN, + "GENESIS G70 2018": HYUNDAI.GENESIS_G70, + "GENESIS G70 2020": HYUNDAI.GENESIS_G70_2020, + "GENESIS GV70 1ST GEN": HYUNDAI.GENESIS_GV70_1ST_GEN, + "GENESIS G80 2017": HYUNDAI.GENESIS_G80, + "GENESIS G90 2017": HYUNDAI.GENESIS_G90, + "GENESIS GV80 2023": HYUNDAI.GENESIS_GV80, + "MAZDA CX-5": MAZDA.MAZDA_CX5, + "MAZDA CX-9": MAZDA.MAZDA_CX9, + "MAZDA 3": MAZDA.MAZDA_3, + "MAZDA 6": MAZDA.MAZDA_6, + "MAZDA CX-9 2021": MAZDA.MAZDA_CX9_2021, + "MAZDA CX-5 2022": MAZDA.MAZDA_CX5_2022, + "NISSAN X-TRAIL 2017": NISSAN.NISSAN_XTRAIL, + "NISSAN LEAF 2018": NISSAN.NISSAN_LEAF, + "NISSAN LEAF 2018 Instrument Cluster": NISSAN.NISSAN_LEAF_IC, + "NISSAN ROGUE 2019": NISSAN.NISSAN_ROGUE, + "NISSAN ALTIMA 2020": NISSAN.NISSAN_ALTIMA, + "SUBARU ASCENT LIMITED 2019": SUBARU.SUBARU_ASCENT, + "SUBARU OUTBACK 6TH GEN": SUBARU.SUBARU_OUTBACK, + "SUBARU LEGACY 7TH GEN": SUBARU.SUBARU_LEGACY, + "SUBARU IMPREZA LIMITED 2019": SUBARU.SUBARU_IMPREZA, + "SUBARU IMPREZA SPORT 2020": SUBARU.SUBARU_IMPREZA_2020, + "SUBARU CROSSTREK HYBRID 2020": SUBARU.SUBARU_CROSSTREK_HYBRID, + "SUBARU FORESTER 2019": SUBARU.SUBARU_FORESTER, + "SUBARU FORESTER HYBRID 2020": SUBARU.SUBARU_FORESTER_HYBRID, + "SUBARU FORESTER 2017 - 2018": SUBARU.SUBARU_FORESTER_PREGLOBAL, + "SUBARU LEGACY 2015 - 2018": SUBARU.SUBARU_LEGACY_PREGLOBAL, + "SUBARU OUTBACK 2015 - 2017": SUBARU.SUBARU_OUTBACK_PREGLOBAL, + "SUBARU OUTBACK 2018 - 2019": SUBARU.SUBARU_OUTBACK_PREGLOBAL_2018, + "SUBARU FORESTER 2022": SUBARU.SUBARU_FORESTER_2022, + "SUBARU OUTBACK 7TH GEN": SUBARU.SUBARU_OUTBACK_2023, + "SUBARU ASCENT 2023": SUBARU.SUBARU_ASCENT_2023, + 'TESLA AP1 MODEL S': TESLA.TESLA_AP1_MODELS, + 'TESLA AP2 MODEL S': TESLA.TESLA_AP2_MODELS, + 'TESLA MODEL S RAVEN': TESLA.TESLA_MODELS_RAVEN, + "TOYOTA ALPHARD 2020": TOYOTA.TOYOTA_ALPHARD_TSS2, + "TOYOTA AVALON 2016": TOYOTA.TOYOTA_AVALON, + "TOYOTA AVALON 2019": TOYOTA.TOYOTA_AVALON_2019, + "TOYOTA AVALON 2022": TOYOTA.TOYOTA_AVALON_TSS2, + "TOYOTA CAMRY 2018": TOYOTA.TOYOTA_CAMRY, + "TOYOTA CAMRY 2021": TOYOTA.TOYOTA_CAMRY_TSS2, + "TOYOTA C-HR 2018": TOYOTA.TOYOTA_CHR, + "TOYOTA C-HR 2021": TOYOTA.TOYOTA_CHR_TSS2, + "TOYOTA COROLLA 2017": TOYOTA.TOYOTA_COROLLA, + "TOYOTA COROLLA TSS2 2019": TOYOTA.TOYOTA_COROLLA_TSS2, + "TOYOTA HIGHLANDER 2017": TOYOTA.TOYOTA_HIGHLANDER, + "TOYOTA HIGHLANDER 2020": TOYOTA.TOYOTA_HIGHLANDER_TSS2, + "TOYOTA PRIUS 2017": TOYOTA.TOYOTA_PRIUS, + "TOYOTA PRIUS v 2017": TOYOTA.TOYOTA_PRIUS_V, + "TOYOTA PRIUS TSS2 2021": TOYOTA.TOYOTA_PRIUS_TSS2, + "TOYOTA RAV4 2017": TOYOTA.TOYOTA_RAV4, + "TOYOTA RAV4 HYBRID 2017": TOYOTA.TOYOTA_RAV4H, + "TOYOTA RAV4 2019": TOYOTA.TOYOTA_RAV4_TSS2, + "TOYOTA RAV4 2022": TOYOTA.TOYOTA_RAV4_TSS2_2022, + "TOYOTA RAV4 2023": TOYOTA.TOYOTA_RAV4_TSS2_2023, + "TOYOTA MIRAI 2021": TOYOTA.TOYOTA_MIRAI, + "TOYOTA SIENNA 2018": TOYOTA.TOYOTA_SIENNA, + "LEXUS CT HYBRID 2018": TOYOTA.LEXUS_CTH, + "LEXUS ES 2018": TOYOTA.LEXUS_ES, + "LEXUS ES 2019": TOYOTA.LEXUS_ES_TSS2, + "LEXUS IS 2018": TOYOTA.LEXUS_IS, + "LEXUS IS 2023": TOYOTA.LEXUS_IS_TSS2, + "LEXUS NX 2018": TOYOTA.LEXUS_NX, + "LEXUS NX 2020": TOYOTA.LEXUS_NX_TSS2, + "LEXUS LC 2024": TOYOTA.LEXUS_LC_TSS2, + "LEXUS RC 2020": TOYOTA.LEXUS_RC, + "LEXUS RX 2016": TOYOTA.LEXUS_RX, + "LEXUS RX 2020": TOYOTA.LEXUS_RX_TSS2, + "LEXUS GS F 2016": TOYOTA.LEXUS_GS_F, + "VOLKSWAGEN ARTEON 1ST GEN": VW.VOLKSWAGEN_ARTEON_MK1, + "VOLKSWAGEN ATLAS 1ST GEN": VW.VOLKSWAGEN_ATLAS_MK1, + "VOLKSWAGEN CADDY 3RD GEN": VW.VOLKSWAGEN_CADDY_MK3, + "VOLKSWAGEN CRAFTER 2ND GEN": VW.VOLKSWAGEN_CRAFTER_MK2, + "VOLKSWAGEN GOLF 7TH GEN": VW.VOLKSWAGEN_GOLF_MK7, + "VOLKSWAGEN JETTA 7TH GEN": VW.VOLKSWAGEN_JETTA_MK7, + "VOLKSWAGEN PASSAT 8TH GEN": VW.VOLKSWAGEN_PASSAT_MK8, + "VOLKSWAGEN PASSAT NMS": VW.VOLKSWAGEN_PASSAT_NMS, + "VOLKSWAGEN POLO 6TH GEN": VW.VOLKSWAGEN_POLO_MK6, + "VOLKSWAGEN SHARAN 2ND GEN": VW.VOLKSWAGEN_SHARAN_MK2, + "VOLKSWAGEN TAOS 1ST GEN": VW.VOLKSWAGEN_TAOS_MK1, + "VOLKSWAGEN T-CROSS 1ST GEN": VW.VOLKSWAGEN_TCROSS_MK1, + "VOLKSWAGEN TIGUAN 2ND GEN": VW.VOLKSWAGEN_TIGUAN_MK2, + "VOLKSWAGEN TOURAN 2ND GEN": VW.VOLKSWAGEN_TOURAN_MK2, + "VOLKSWAGEN TRANSPORTER T6.1": VW.VOLKSWAGEN_TRANSPORTER_T61, + "VOLKSWAGEN T-ROC 1ST GEN": VW.VOLKSWAGEN_TROC_MK1, + "AUDI A3 3RD GEN": VW.AUDI_A3_MK3, + "AUDI Q2 1ST GEN": VW.AUDI_Q2_MK1, + "AUDI Q3 2ND GEN": VW.AUDI_Q3_MK2, + "SEAT ATECA 1ST GEN": VW.SEAT_ATECA_MK1, + "SEAT LEON 3RD GEN": VW.SEAT_ATECA_MK1, + "SEAT_LEON_MK3": VW.SEAT_ATECA_MK1, + "SKODA FABIA 4TH GEN": VW.SKODA_FABIA_MK4, + "SKODA KAMIQ 1ST GEN": VW.SKODA_KAMIQ_MK1, + "SKODA KAROQ 1ST GEN": VW.SKODA_KAROQ_MK1, + "SKODA KODIAQ 1ST GEN": VW.SKODA_KODIAQ_MK1, + "SKODA OCTAVIA 3RD GEN": VW.SKODA_OCTAVIA_MK3, + "SKODA SCALA 1ST GEN": VW.SKODA_KAMIQ_MK1, + "SKODA_SCALA_MK1": VW.SKODA_KAMIQ_MK1, + "SKODA SUPERB 3RD GEN": VW.SKODA_SUPERB_MK3, + + "mock": MOCK.MOCK, +} diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index 45516d6035..7be3b2ebe9 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -1,9 +1,10 @@ from cereal import car -from openpilot.common.numpy_fast import clip from opendbc.can.packer import CANPacker +from openpilot.common.numpy_fast import clip from openpilot.selfdrive.car import apply_std_steer_angle_limits from openpilot.selfdrive.car.ford import fordcan -from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams +from openpilot.selfdrive.car.ford.values import CarControllerParams, FordFlags +from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX LongCtrlState = car.CarControl.Actuators.LongControlState @@ -22,7 +23,7 @@ def apply_ford_curvature_limits(apply_curvature, apply_curvature_last, current_c return clip(apply_curvature, -CarControllerParams.CURVATURE_MAX, CarControllerParams.CURVATURE_MAX) -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP self.VM = VM @@ -34,6 +35,7 @@ class CarController: self.main_on_last = False self.lkas_enabled_last = False self.steer_alert_last = False + self.lead_distance_bars_last = None def update(self, CC, CS, now_nanos): can_sends = [] @@ -69,10 +71,10 @@ class CarController: self.apply_curvature_last = apply_curvature - if self.CP.carFingerprint in CANFD_CAR: + if self.CP.flags & FordFlags.CANFD: # TODO: extended mode mode = 1 if CC.latActive else 0 - counter = (self.frame // CarControllerParams.STEER_STEP) % 0xF + counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10 can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -apply_curvature, 0., counter)) else: can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -apply_curvature, 0.)) @@ -97,17 +99,21 @@ class CarController: # send lkas ui msg at 1Hz or if ui state changes if (self.frame % CarControllerParams.LKAS_UI_STEP) == 0 or send_ui: can_sends.append(fordcan.create_lkas_ui_msg(self.packer, self.CAN, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values)) + # send acc ui msg at 5Hz or if ui state changes + if hud_control.leadDistanceBars != self.lead_distance_bars_last: + send_ui = True if (self.frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui: can_sends.append(fordcan.create_acc_ui_msg(self.packer, self.CAN, self.CP, main_on, CC.latActive, - fcw_alert, CS.out.cruiseState.standstill, hud_control, - CS.acc_tja_status_stock_values)) + fcw_alert, CS.out.cruiseState.standstill, hud_control, + CS.acc_tja_status_stock_values)) self.main_on_last = main_on self.lkas_enabled_last = CC.latActive self.steer_alert_last = steer_alert + self.lead_distance_bars_last = hud_control.leadDistanceBars - new_actuators = actuators.copy() + new_actuators = actuators.as_builder() new_actuators.curvature = self.apply_curvature_last self.frame += 1 diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 34006e8da4..78f48ec5c4 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -1,10 +1,10 @@ from cereal import car -from openpilot.common.conversions import Conversions as CV from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser -from openpilot.selfdrive.car.interfaces import CarStateBase +from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car.ford.fordcan import CanBus -from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams, DBC +from openpilot.selfdrive.car.ford.values import DBC, CarControllerParams, FordFlags +from openpilot.selfdrive.car.interfaces import CarStateBase GearShifter = car.CarState.GearShifter TransmissionType = car.CarParams.TransmissionType @@ -19,6 +19,9 @@ class CarState(CarStateBase): self.vehicle_sensors_valid = False + self.prev_distance_button = 0 + self.distance_button = 0 + def update(self, cp, cp_cam): ret = car.CarState.new_message() @@ -49,12 +52,13 @@ class CarState(CarStateBase): ret.steerFaultPermanent = cp.vl["EPAS_INFO"]["EPAS_Failure"] in (2, 3) ret.espDisabled = cp.vl["Cluster_Info1_FD1"]["DrvSlipCtlMde_D_Rq"] != 0 # 0 is default mode - if self.CP.carFingerprint in CANFD_CAR: + if self.CP.flags & FordFlags.CANFD: # this signal is always 0 on non-CAN FD cars ret.steerFaultTemporary |= cp.vl["Lane_Assist_Data3_FD1"]["LatCtlSte_D_Stat"] not in (1, 2, 3) # cruise state - ret.cruiseState.speed = cp.vl["EngBrakeData"]["Veh_V_DsplyCcSet"] * CV.MPH_TO_MS + is_metric = cp.vl["INSTRUMENT_PANEL"]["METRIC_UNITS"] == 1 if not self.CP.flags & FordFlags.CANFD else False + ret.cruiseState.speed = cp.vl["EngBrakeData"]["Veh_V_DsplyCcSet"] * (CV.KPH_TO_MS if is_metric else CV.MPH_TO_MS) ret.cruiseState.enabled = cp.vl["EngBrakeData"]["CcStat_D_Actl"] in (4, 5) ret.cruiseState.available = cp.vl["EngBrakeData"]["CcStat_D_Actl"] in (3, 4, 5) ret.cruiseState.nonAdaptive = cp.vl["Cluster_Info1_FD1"]["AccEnbl_B_RqDrv"] == 0 @@ -83,6 +87,8 @@ class CarState(CarStateBase): ret.rightBlinker = cp.vl["Steering_Data_FD1"]["TurnLghtSwtch_D_Stat"] == 2 # TODO: block this going to the camera otherwise it will enable stock TJA ret.genericToggle = bool(cp.vl["Steering_Data_FD1"]["TjaButtnOnOffPress"]) + self.prev_distance_button = self.distance_button + self.distance_button = cp.vl["Steering_Data_FD1"]["AccButtnGapTogglePress"] # lock info ret.doorOpen = any([cp.vl["BodyInfo_3_FD1"]["DrStatDrv_B_Actl"], cp.vl["BodyInfo_3_FD1"]["DrStatPsngr_B_Actl"], @@ -91,7 +97,7 @@ class CarState(CarStateBase): # blindspot sensors if self.CP.enableBsm: - cp_bsm = cp_cam if self.CP.carFingerprint in CANFD_CAR else cp + cp_bsm = cp_cam if self.CP.flags & FordFlags.CANFD else cp ret.leftBlindspot = cp_bsm.vl["Side_Detect_L_Stat"]["SodDetctLeft_D_Stat"] != 0 ret.rightBlindspot = cp_bsm.vl["Side_Detect_R_Stat"]["SodDetctRight_D_Stat"] != 0 @@ -122,10 +128,14 @@ class CarState(CarStateBase): ("RCMStatusMessage2_FD1", 10), ] - if CP.carFingerprint in CANFD_CAR: + if CP.flags & FordFlags.CANFD: messages += [ ("Lane_Assist_Data3_FD1", 33), ] + else: + messages += [ + ("INSTRUMENT_PANEL", 1), + ] if CP.transmissionType == TransmissionType.automatic: messages += [ @@ -137,7 +147,7 @@ class CarState(CarStateBase): ("BCM_Lamp_Stat_FD1", 1), ] - if CP.enableBsm and CP.carFingerprint not in CANFD_CAR: + if CP.enableBsm and not (CP.flags & FordFlags.CANFD): messages += [ ("Side_Detect_L_Stat", 5), ("Side_Detect_R_Stat", 5), @@ -155,7 +165,7 @@ class CarState(CarStateBase): ("IPMA_Data", 1), ] - if CP.enableBsm and CP.carFingerprint in CANFD_CAR: + if CP.enableBsm and CP.flags & FordFlags.CANFD: messages += [ ("Side_Detect_L_Stat", 5), ("Side_Detect_R_Stat", 5), diff --git a/selfdrive/car/ford/fingerprints.py b/selfdrive/car/ford/fingerprints.py index a5d465849a..2201072fa3 100644 --- a/selfdrive/car/ford/fingerprints.py +++ b/selfdrive/car/ford/fingerprints.py @@ -4,23 +4,26 @@ from openpilot.selfdrive.car.ford.values import CAR Ecu = car.CarParams.Ecu FW_VERSIONS = { - CAR.BRONCO_SPORT_MK1: { + CAR.FORD_BRONCO_SPORT_MK1: { (Ecu.eps, 0x730, None): [ b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6C-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x760, None): [ b'LX6C-2D053-RD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6C-2D053-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-RF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x706, None): [ b'M1PT-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'M1PT-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.ESCAPE_MK4: { + CAR.FORD_ESCAPE_MK4: { (Ecu.eps, 0x730, None): [ b'LX6C-14D003-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -43,7 +46,7 @@ FW_VERSIONS = { b'LV4T-14F397-GG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.EXPLORER_MK6: { + CAR.FORD_EXPLORER_MK6: { (Ecu.eps, 0x730, None): [ b'L1MC-14D003-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -58,6 +61,7 @@ FW_VERSIONS = { b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-2D053-BJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ @@ -71,7 +75,7 @@ FW_VERSIONS = { b'LC5T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.F_150_MK14: { + CAR.FORD_F_150_MK14: { (Ecu.eps, 0x730, None): [ b'ML3V-14D003-BC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -82,10 +86,11 @@ FW_VERSIONS = { b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x706, None): [ + b'ML3T-14H102-ABR\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.F_150_LIGHTNING_MK1: { + CAR.FORD_F_150_LIGHTNING_MK1: { (Ecu.abs, 0x760, None): [ b'PL38-2D053-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -96,7 +101,7 @@ FW_VERSIONS = { b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.MUSTANG_MACH_E_MK1: { + CAR.FORD_MUSTANG_MACH_E_MK1: { (Ecu.eps, 0x730, None): [ b'LJ9C-14D003-AM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LJ9C-14D003-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -111,7 +116,7 @@ FW_VERSIONS = { b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.FOCUS_MK4: { + CAR.FORD_FOCUS_MK4: { (Ecu.eps, 0x730, None): [ b'JX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -125,14 +130,17 @@ FW_VERSIONS = { b'JX7T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.MAVERICK_MK1: { + CAR.FORD_MAVERICK_MK1: { (Ecu.eps, 0x730, None): [ + b'NZ6C-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6C-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x760, None): [ + b'NZ6C-2D053-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6C-2D053-AG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PZ6C-2D053-ED\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PZ6C-2D053-EE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PZ6C-2D053-EF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'NZ6T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -141,4 +149,18 @@ FW_VERSIONS = { b'NZ6T-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, + CAR.FORD_RANGER_MK2: { + (Ecu.eps, 0x730, None): [ + b'NL14-14D003-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'PB3C-2D053-ZD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, } diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py index c5ef0900f3..2cfd61a191 100644 --- a/selfdrive/car/ford/fordcan.py +++ b/selfdrive/car/ford/fordcan.py @@ -212,7 +212,7 @@ def create_acc_ui_msg(packer, CAN: CanBus, CP, main_on: bool, enabled: bool, fcw "AccFllwMde_B_Dsply": 1 if hud_control.leadVisible else 0, # Lead indicator "AccStopMde_B_Dsply": 1 if standstill else 0, "AccWarn_D_Dsply": 0, # ACC warning - "AccTGap_D_Dsply": 4, # Fixed time gap in UI + "AccTGap_D_Dsply": hud_control.leadDistanceBars, # Time gap }) # Forwards FCW alert from IPMA diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index fd4c381f88..54cf865109 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -1,11 +1,12 @@ 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 import create_button_events, get_safety_config from openpilot.selfdrive.car.ford.fordcan import CanBus -from openpilot.selfdrive.car.ford.values import CANFD_CAR, Ecu +from openpilot.selfdrive.car.ford.values import Ecu, FordFlags from openpilot.selfdrive.car.interfaces import CarInterfaceBase +ButtonType = car.CarState.ButtonEvent.Type TransmissionType = car.CarParams.TransmissionType GearShifter = car.CarState.GearShifter @@ -14,17 +15,13 @@ class CarInterface(CarInterfaceBase): @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "ford" - ret.dashcamOnly = candidate in CANFD_CAR + ret.dashcamOnly = bool(ret.flags & FordFlags.CANFD) ret.radarUnavailable = True ret.steerControlType = car.CarParams.SteerControlType.angle ret.steerActuatorDelay = 0.2 ret.steerLimitTimer = 1.0 - ret.longitudinalTuning.kpBP = [0.] - ret.longitudinalTuning.kpV = [0.5] - ret.longitudinalTuning.kiV = [0.] - CAN = CanBus(fingerprint=fingerprint) cfgs = [get_safety_config(car.CarParams.SafetyModel.ford)] if CAN.main >= 4: @@ -36,8 +33,20 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_LONG_CONTROL ret.openpilotLongitudinalControl = True - if candidate in CANFD_CAR: + if ret.flags & FordFlags.CANFD: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_CANFD + else: + # Lock out if the car does not have needed lateral and longitudinal control APIs. + # Note that we also check CAN for adaptive cruise, but no known signal for LCA exists + pscm_config = next((fw for fw in car_fw if fw.ecu == Ecu.eps and b'\x22\xDE\x01' in fw.request), None) + if pscm_config: + if len(pscm_config.fwVersion) != 24: + ret.dashcamOnly = True + else: + config_tja = pscm_config.fwVersion[7] # Traffic Jam Assist + config_lca = pscm_config.fwVersion[8] # Lane Centering Assist + if config_tja != 0xFF or config_lca != 0xFF: + ret.dashcamOnly = True # Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1 found_ecus = [fw.ecu for fw in car_fw] @@ -61,6 +70,8 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) + ret.buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) + events = self.create_common_events(ret, extra_gears=[GearShifter.manumatic]) if not self.CS.vehicle_sensors_valid: events.add(car.CarEvent.EventName.vehicleSensorsInvalid) @@ -68,6 +79,3 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() return ret - - def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/ford/tests/print_platform_codes.py b/selfdrive/car/ford/tests/print_platform_codes.py new file mode 100755 index 0000000000..670199980a --- /dev/null +++ b/selfdrive/car/ford/tests/print_platform_codes.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +from collections import defaultdict + +from cereal import car +from openpilot.selfdrive.car.ford.values import get_platform_codes +from openpilot.selfdrive.car.ford.fingerprints import FW_VERSIONS + +Ecu = car.CarParams.Ecu +ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} + + +if __name__ == "__main__": + cars_for_code: defaultdict = defaultdict(lambda: defaultdict(set)) + + for car_model, ecus in FW_VERSIONS.items(): + print(car_model) + for ecu in sorted(ecus, key=lambda x: int(x[0])): + platform_codes = get_platform_codes(ecus[ecu]) + for code in platform_codes: + cars_for_code[ecu][code].add(car_model) + + print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):') + print(f' Codes: {sorted(platform_codes)}') + print() + + print('\nCar models vs. platform codes:') + for ecu, codes in cars_for_code.items(): + print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):') + for code, cars in codes.items(): + print(f' {code!r}: {sorted(map(str, cars))}') diff --git a/selfdrive/car/ford/tests/test_ford.py b/selfdrive/car/ford/tests/test_ford.py old mode 100755 new mode 100644 index 2ad3f5db1b..b1a19017d4 --- a/selfdrive/car/ford/tests/test_ford.py +++ b/selfdrive/car/ford/tests/test_ford.py @@ -1,12 +1,13 @@ -#!/usr/bin/env python3 -import unittest -from parameterized import parameterized +import random from collections.abc import Iterable import capnp +from hypothesis import settings, given, strategies as st +from parameterized import parameterized from cereal import car -from openpilot.selfdrive.car.ford.values import FW_QUERY_CONFIG +from openpilot.selfdrive.car.fw_versions import build_fw_dict +from openpilot.selfdrive.car.ford.values import CAR, FW_QUERY_CONFIG, FW_PATTERN, get_platform_codes from openpilot.selfdrive.car.ford.fingerprints import FW_VERSIONS Ecu = car.CarParams.Ecu @@ -23,7 +24,7 @@ ECU_ADDRESSES = { } -ECU_FW_CORE = { +ECU_PART_NUMBER = { Ecu.eps: [ b"14D003", ], @@ -37,46 +38,106 @@ ECU_FW_CORE = { b"14F397", # Ford Q3 b"14H102", # Ford Q4 ], - Ecu.engine: [ - b"14C204", - ], } -class TestFordFW(unittest.TestCase): +class TestFordFW: def test_fw_query_config(self): for (ecu, addr, subaddr) in FW_QUERY_CONFIG.extra_ecus: - self.assertIn(ecu, ECU_ADDRESSES, "Unknown ECU") - self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch") - self.assertIsNone(subaddr, "Unexpected ECU subaddress") + assert ecu in ECU_ADDRESSES, "Unknown ECU" + assert addr == ECU_ADDRESSES[ecu], "ECU address mismatch" + assert subaddr is None, "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, 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") - self.assertIsNone(subaddr, "Unexpected ECU subaddress") + assert ecu in ECU_PART_NUMBER, "Unexpected ECU" + assert addr == ECU_ADDRESSES[ecu], "ECU address mismatch" + assert subaddr is None, "Unexpected ECU subaddress" - # Software part number takes the form: PREFIX-CORE-SUFFIX - # Prefix changes based on the family of part. It includes the model year - # and likely the platform. - # Core identifies the type of the item (e.g. 14D003 = PSCM, 14C204 = PCM). - # Suffix specifies the version of the part. -AA would be followed by -AB. - # Small increments in the suffix are usually compatible. - # Details: https://forscan.org/forum/viewtopic.php?p=70008#p70008 for fw in fws: - self.assertEqual(len(fw), 24, "Expected ECU response to be 24 bytes") + assert len(fw) == 24, "Expected ECU response to be 24 bytes" - # TODO: parse with regex, don't need detailed error message - fw_parts = fw.rstrip(b'\x00').split(b'-') - self.assertEqual(len(fw_parts), 3, "Expected FW to be in format: prefix-core-suffix") + match = FW_PATTERN.match(fw) + assert match is not None, f"Unable to parse FW: {fw!r}" + if match: + part_number = match.group("part_number") + assert part_number in ECU_PART_NUMBER[ecu], f"Unexpected part number for {fw!r}" - prefix, core, suffix = fw_parts - self.assertEqual(len(prefix), 4, "Expected FW prefix to be 4 characters") - self.assertIn(len(core), (5, 6), "Expected FW core to be 5-6 characters") - self.assertIn(core, ECU_FW_CORE[ecu], f"Unexpected FW core for {ecu}") - self.assertIn(len(suffix), (2, 3), "Expected FW suffix to be 2-3 characters") + codes = get_platform_codes([fw]) + assert 1 == len(codes), f"Unable to parse FW: {fw!r}" + @settings(max_examples=100) + @given(data=st.data()) + def test_platform_codes_fuzzy_fw(self, data): + """Ensure function doesn't raise an exception""" + fw_strategy = st.lists(st.binary()) + fws = data.draw(fw_strategy) + get_platform_codes(fws) -if __name__ == "__main__": - unittest.main() + def test_platform_codes_spot_check(self): + # Asserts basic platform code parsing behavior for a few cases + results = get_platform_codes([ + b"JX6A-14C204-BPL\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"NZ6T-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ]) + assert results == {(b"X6A", b"J"), (b"Z6T", b"N"), (b"J6T", b"P"), (b"B5A", b"L")} + + def test_fuzzy_match(self): + for platform, fw_by_addr in FW_VERSIONS.items(): + # Ensure there's no overlaps in platform codes + for _ in range(20): + car_fw = [] + for ecu, fw_versions in fw_by_addr.items(): + ecu_name, addr, sub_addr = ecu + fw = random.choice(fw_versions) + car_fw.append({"ecu": ecu_name, "fwVersion": fw, "address": addr, + "subAddress": 0 if sub_addr is None else sub_addr}) + + CP = car.CarParams.new_message(carFw=car_fw) + matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, FW_VERSIONS) + assert matches == {platform} + + def test_match_fw_fuzzy(self): + offline_fw = { + (Ecu.eps, 0x730, None): [ + b"L1MC-14D003-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ], + (Ecu.abs, 0x760, None): [ + b"L1MC-2D053-BA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"L1MC-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ], + (Ecu.fwdRadar, 0x764, None): [ + b"LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"LB5T-14D049-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ], + # We consider all model year hints for ECU, even with different platform codes + (Ecu.fwdCamera, 0x706, None): [ + b"LB5T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"NC5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + ], + } + expected_fingerprint = CAR.FORD_EXPLORER_MK6 + + # ensure that we fuzzy match on all non-exact FW with changed revisions + live_fw = { + (0x730, None): {b"L1MC-14D003-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}, + (0x760, None): {b"L1MC-2D053-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}, + (0x764, None): {b"LB5T-14D049-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}, + (0x706, None): {b"LB5T-14F397-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}, + } + candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw}) + assert candidates == {expected_fingerprint} + + # model year hint in between the range should match + live_fw[(0x706, None)] = {b"MB5T-14F397-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"} + candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw,}) + assert candidates == {expected_fingerprint} + + # unseen model year hint should not match + live_fw[(0x760, None)] = {b"M1MC-2D053-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"} + candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw}) + assert len(candidates) == 0, "Should not match new model year hint" diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 1008d68b98..b1868bfa9b 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -1,12 +1,14 @@ -from dataclasses import dataclass, field -from enum import Enum +import copy +import re +from dataclasses import dataclass, field, replace +from enum import Enum, IntFlag import panda.python.uds as uds from cereal import car from openpilot.selfdrive.car import AngleRateLimit, CarSpecs, dbc_dict, DbcDict, PlatformConfig, Platforms -from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column, \ Device -from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, LiveFwVersions, OfflineFwVersions, Request, StdQueries, p16 Ecu = car.CarParams.Ecu @@ -39,6 +41,11 @@ class CarControllerParams: pass +class FordFlags(IntFlag): + # Static flags + CANFD = 1 + + class RADAR: DELPHI_ESR = 'ford_fusion_2018_adas' DELPHI_MRR = 'FORD_CADS' @@ -53,94 +60,160 @@ class Footnote(Enum): @dataclass -class FordCarInfo(CarInfo): +class FordCarDocs(CarDocs): package: str = "Co-Pilot360 Assist+" + hybrid: bool = False + plug_in_hybrid: bool = False def init_make(self, CP: car.CarParams): - harness = CarHarness.ford_q4 if CP.carFingerprint in CANFD_CAR else CarHarness.ford_q3 - if CP.carFingerprint in (CAR.BRONCO_SPORT_MK1, CAR.MAVERICK_MK1, CAR.F_150_MK14): + harness = CarHarness.ford_q4 if CP.flags & FordFlags.CANFD else CarHarness.ford_q3 + if CP.carFingerprint in (CAR.FORD_BRONCO_SPORT_MK1, CAR.FORD_MAVERICK_MK1, CAR.FORD_F_150_MK14, CAR.FORD_F_150_LIGHTNING_MK1): self.car_parts = CarParts([Device.threex_angled_mount, harness]) else: self.car_parts = CarParts([Device.threex, harness]) -@dataclass(frozen=True) +@dataclass class FordPlatformConfig(PlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR)) + def init(self): + for car_docs in list(self.car_docs): + if car_docs.hybrid: + name = f"{car_docs.make} {car_docs.model} Hybrid {car_docs.years}" + self.car_docs.append(replace(copy.deepcopy(car_docs), name=name)) + if car_docs.plug_in_hybrid: + name = f"{car_docs.make} {car_docs.model} Plug-in Hybrid {car_docs.years}" + self.car_docs.append(replace(copy.deepcopy(car_docs), name=name)) + + +@dataclass +class FordCANFDPlatformConfig(FordPlatformConfig): + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', None)) + + def init(self): + super().init() + self.flags |= FordFlags.CANFD + 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), + FORD_BRONCO_SPORT_MK1 = FordPlatformConfig( + [FordCarDocs("Ford Bronco Sport 2021-23")], + CarSpecs(mass=1625, wheelbase=2.67, steerRatio=17.7), ) - ESCAPE_MK4 = FordPlatformConfig( - "FORD ESCAPE 4TH GEN", + FORD_ESCAPE_MK4 = FordPlatformConfig( [ - 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"), + FordCarDocs("Ford Escape 2020-22", hybrid=True, plug_in_hybrid=True), + FordCarDocs("Ford Kuga 2020-22", "Adaptive Cruise Control with Lane Centering", hybrid=True, plug_in_hybrid=True), ], - specs=CarSpecs(mass=1750, wheelbase=2.71, steerRatio=16.7), + CarSpecs(mass=1750, wheelbase=2.71, steerRatio=16.7), ) - EXPLORER_MK6 = FordPlatformConfig( - "FORD EXPLORER 6TH GEN", + FORD_EXPLORER_MK6 = FordPlatformConfig( [ - 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 + FordCarDocs("Ford Explorer 2020-23", hybrid=True), # Hybrid: Limited and Platinum only + FordCarDocs("Lincoln Aviator 2020-23", "Co-Pilot360 Plus", plug_in_hybrid=True), # Hybrid: Grand Touring only ], - specs=CarSpecs(mass=2050, wheelbase=3.025, steerRatio=16.8), + CarSpecs(mass=2050, wheelbase=3.025, steerRatio=16.8), ) - F_150_MK14 = FordPlatformConfig( - "FORD F-150 14TH GEN", + FORD_F_150_MK14 = FordCANFDPlatformConfig( + [FordCarDocs("Ford F-150 2022-23", "Co-Pilot360 Active 2.0", hybrid=True)], + CarSpecs(mass=2000, wheelbase=3.69, steerRatio=17.0), + ) + FORD_F_150_LIGHTNING_MK1 = FordCANFDPlatformConfig( + [FordCarDocs("Ford F-150 Lightning 2021-23", "Co-Pilot360 Active 2.0")], + CarSpecs(mass=2948, wheelbase=3.70, steerRatio=16.9), + ) + FORD_FOCUS_MK4 = FordPlatformConfig( + [FordCarDocs("Ford Focus 2018", "Adaptive Cruise Control with Lane Centering", footnotes=[Footnote.FOCUS], hybrid=True)], # mHEV only + CarSpecs(mass=1350, wheelbase=2.7, steerRatio=15.0), + ) + FORD_MAVERICK_MK1 = FordPlatformConfig( [ - FordCarInfo("Ford F-150 2023", "Co-Pilot360 Active 2.0"), - FordCarInfo("Ford F-150 Hybrid 2023", "Co-Pilot360 Active 2.0"), + FordCarDocs("Ford Maverick 2022", "LARIAT Luxury", hybrid=True), + FordCarDocs("Ford Maverick 2023-24", "Co-Pilot360 Assist", hybrid=True), ], - dbc_dict=dbc_dict('ford_lincoln_base_pt', None), - specs=CarSpecs(mass=2000, wheelbase=3.69, steerRatio=17.0), + CarSpecs(mass=1650, wheelbase=3.076, 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), + FORD_MUSTANG_MACH_E_MK1 = FordCANFDPlatformConfig( + [FordCarDocs("Ford Mustang Mach-E 2021-23", "Co-Pilot360 Active 2.0")], + CarSpecs(mass=2200, wheelbase=2.984, steerRatio=17.0), # TODO: check steer ratio ) - 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 + FORD_RANGER_MK2 = FordCANFDPlatformConfig( + [FordCarDocs("Ford Ranger 2024", "Adaptive Cruise Control with Lane Centering")], + CarSpecs(mass=2000, wheelbase=3.27, steerRatio=17.0), ) -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 +# A-Z except no I, O or W +# e.g. NZ6A-14C204-AAA +# 1222-333333-444 +# 1 = Model year hint (approximates model year/generation) +# 2 = Platform hint +# 3 = Part number +# 4 = Software version +FW_ALPHABET = b'A-HJ-NP-VX-Z' +FW_PATTERN = re.compile(b'^(?P[' + FW_ALPHABET + b'])' + + b'(?P[0-9' + FW_ALPHABET + b']{3})-' + + b'(?P[0-9' + FW_ALPHABET + b']{5,6})-' + + b'(?P[' + FW_ALPHABET + b']{2,})\x00*$') +def get_platform_codes(fw_versions: list[bytes] | set[bytes]) -> set[tuple[bytes, bytes]]: + codes = set() + for fw in fw_versions: + match = FW_PATTERN.match(fw) + if match is not None: + codes.add((match.group('platform_hint'), match.group('model_year_hint'))) + + return codes + + +def match_fw_to_car_fuzzy(live_fw_versions: LiveFwVersions, vin: str, offline_fw_versions: OfflineFwVersions) -> set[str]: + candidates: set[str] = set() + + for candidate, fws in offline_fw_versions.items(): + # Keep track of ECUs which pass all checks (platform hint, within model year hint range) + valid_found_ecus = set() + valid_expected_ecus = {ecu[1:] for ecu in fws if ecu[0] in PLATFORM_CODE_ECUS} + for ecu, expected_versions in fws.items(): + addr = ecu[1:] + # Only check ECUs expected to have platform codes + if ecu[0] not in PLATFORM_CODE_ECUS: + continue + + # Expected platform codes & model year hints + codes = get_platform_codes(expected_versions) + expected_platform_codes = {code for code, _ in codes} + expected_model_year_hints = {model_year_hint for _, model_year_hint in codes} + + # Found platform codes & model year hints + codes = get_platform_codes(live_fw_versions.get(addr, set())) + found_platform_codes = {code for code, _ in codes} + found_model_year_hints = {model_year_hint for _, model_year_hint in codes} + + # Check platform code matches for any found versions + if not any(found_platform_code in expected_platform_codes for found_platform_code in found_platform_codes): + break + + # Check any model year hint within range in the database. Note that some models have more than one + # platform code per ECU which we don't consider as separate ranges + if not any(min(expected_model_year_hints) <= found_model_year_hint <= max(expected_model_year_hints) for + found_model_year_hint in found_model_year_hints): + break + + valid_found_ecus.add(addr) + + # If all live ECUs pass all checks for candidate, add it as a match + if valid_expected_ecus.issubset(valid_found_ecus): + candidates.add(candidate) + + return candidates + + +# All of these ECUs must be present and are expected to have platform codes we can match +PLATFORM_CODE_ECUS = (Ecu.abs, Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps) + DATA_IDENTIFIER_FORD_ASBUILT = 0xDE00 ASBUILT_BLOCKS: list[tuple[int, list]] = [ @@ -199,7 +272,8 @@ FW_QUERY_CONFIG = FwQueryConfig( (Ecu.shiftByWire, 0x732, None), # Gear Shift Module (Ecu.debug, 0x7d0, None), # Accessory Protocol Interface Module ], + # Custom fuzzy fingerprinting function using platform and model year hints + 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 80492b4177..bb2827571c 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -47,6 +47,11 @@ class StdQueries: MANUFACTURER_SOFTWARE_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER) + SUPPLIER_SOFTWARE_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.SYSTEM_SUPPLIER_ECU_SOFTWARE_VERSION_NUMBER) + SUPPLIER_SOFTWARE_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.SYSTEM_SUPPLIER_ECU_SOFTWARE_VERSION_NUMBER) + UDS_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) UDS_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ @@ -80,7 +85,7 @@ class Request: auxiliary: bool = False # FW responses from these queries will not be used for fingerprinting logging: bool = False - # boardd toggles OBD multiplexing on/off as needed + # pandad toggles OBD multiplexing on/off as needed obd_multiplexing: bool = True @@ -92,9 +97,9 @@ class FwQueryConfig: 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, int | None]] = field(default_factory=list) - # Function a brand can implement to provide better fuzzy matching. Takes in FW versions, + # Function a brand can implement to provide better fuzzy matching. Takes in FW versions and VIN, # returns set of candidates. Only will match if one candidate is returned - match_fw_to_car_fuzzy: Callable[[LiveFwVersions, OfflineFwVersions], set[str]] | None = None + match_fw_to_car_fuzzy: Callable[[LiveFwVersions, str, OfflineFwVersions], set[str]] | None = None def __post_init__(self): for i in range(len(self.requests)): diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 7673814195..4bb6413d01 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -1,19 +1,20 @@ #!/usr/bin/env python3 from collections import defaultdict -from typing import Any, TypeVar from collections.abc import Iterator +from typing import Any, Protocol, TypeVar + from tqdm import tqdm import capnp 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, 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 from openpilot.common.swaglog import cloudlog +from openpilot.selfdrive.car.ecu_addrs import get_ecu_addrs +from openpilot.selfdrive.car.fingerprints import FW_VERSIONS +from openpilot.selfdrive.car.fw_query_definitions import AddrType, EcuAddrBusType, FwQueryConfig, LiveFwVersions, OfflineFwVersions +from openpilot.selfdrive.car.interfaces import get_interface_attr +from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery Ecu = car.CarParams.Ecu ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] @@ -38,8 +39,7 @@ def is_brand(brand: str, filter_brand: str | None) -> bool: return filter_brand is None or brand == filter_brand -def build_fw_dict(fw_versions: list[capnp.lib.capnp._DynamicStructBuilder], - filter_brand: str = None) -> dict[AddrType, set[bytes]]: +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: @@ -48,7 +48,12 @@ def build_fw_dict(fw_versions: list[capnp.lib.capnp._DynamicStructBuilder], return dict(fw_versions_dict) -def match_fw_to_car_fuzzy(live_fw_versions, match_brand=None, log=True, exclude=None): +class MatchFwToCar(Protocol): + def __call__(self, live_fw_versions: LiveFwVersions, match_brand: str = None, log: bool = True) -> set[str]: + ... + + +def match_fw_to_car_fuzzy(live_fw_versions: LiveFwVersions, match_brand: str = None, log: bool = True, exclude: str = None) -> set[str]: """Do a fuzzy FW match. This function will return a match, and the number of firmware version that were matched uniquely to that specific car. If multiple ECUs uniquely match to different cars the match is rejected.""" @@ -73,7 +78,7 @@ def match_fw_to_car_fuzzy(live_fw_versions, match_brand=None, log=True, exclude= all_fw_versions[(addr[1], addr[2], f)].append(candidate) matched_ecus = set() - candidate = None + match: str | None = None for addr, versions in live_fw_versions.items(): ecu_key = (addr[0], addr[1]) for version in versions: @@ -82,23 +87,23 @@ def match_fw_to_car_fuzzy(live_fw_versions, match_brand=None, log=True, exclude= if len(candidates) == 1: matched_ecus.add(ecu_key) - if candidate is None: - candidate = candidates[0] + if match is None: + match = candidates[0] # We uniquely matched two different cars. No fuzzy match possible - elif candidate != candidates[0]: + elif match != candidates[0]: return set() # Note that it is possible to match to a candidate without all its ECUs being present # if there are enough matches. FIXME: parameterize this or require all ECUs to exist like exact matching - if len(matched_ecus) >= 2: + if match and len(matched_ecus) >= 2: if log: - cloudlog.error(f"Fingerprinted {candidate} using fuzzy match. {len(matched_ecus)} matching ECUs") - return {candidate} + cloudlog.error(f"Fingerprinted {match} using fuzzy match. {len(matched_ecus)} matching ECUs") + return {match} else: 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: LiveFwVersions, match_brand: str = None, log: bool = True, extra_fw_versions: dict = 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 @@ -139,9 +144,10 @@ def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True, extra_fw return set(candidates.keys()) - invalid -def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True, log=True): +def match_fw_to_car(fw_versions: list[capnp.lib.capnp._DynamicStructBuilder], vin: str, + allow_exact: bool = True, allow_fuzzy: bool = True, log: bool = True) -> tuple[bool, set[str]]: # Try exact matching first - exact_matches = [] + exact_matches: list[tuple[bool, MatchFwToCar]] = [] if allow_exact: exact_matches = [(True, match_fw_to_car_exact)] if allow_fuzzy: @@ -149,7 +155,7 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True, log=True): for exact_match, match_func in exact_matches: # For each brand, attempt to fingerprint using all FW returned from its queries - matches = set() + matches: set[str] = set() for brand in VERSIONS.keys(): fw_versions_dict = build_fw_dict(fw_versions, filter_brand=brand) matches |= match_func(fw_versions_dict, match_brand=brand, log=log) @@ -157,7 +163,7 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True, log=True): # If specified and no matches so far, fall back to brand's fuzzy fingerprinting function config = FW_QUERY_CONFIGS[brand] if not exact_match and not len(matches) and config.match_fw_to_car_fuzzy is not None: - matches |= config.match_fw_to_car_fuzzy(fw_versions_dict, VERSIONS[brand]) + matches |= config.match_fw_to_car_fuzzy(fw_versions_dict, vin, VERSIONS[brand]) if len(matches): return exact_match, matches @@ -165,12 +171,12 @@ 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: int = 1) -> set[EcuAddrBusType]: params = Params() # queries are split by OBD multiplexing mode queries: dict[bool, list[list[EcuAddrBusType]]] = {True: [], False: []} parallel_queries: dict[bool, list[EcuAddrBusType]] = {True: [], False: []} - responses = set() + responses: set[EcuAddrBusType] = set() for brand, config, r in REQUESTS: # Skip query if no panda available @@ -231,8 +237,8 @@ def set_obd_multiplexing(params: Params, obd_multiplexing: bool): cloudlog.warning("OBD multiplexing set successfully") -def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pandas=1, debug=False, progress=False) -> \ - list[capnp.lib.capnp._DynamicStructBuilder]: +def get_fw_versions_ordered(logcan, sendcan, vin: str, ecu_rx_addrs: set[EcuAddrBusType], timeout: float = 0.1, num_pandas: int = 1, + debug: bool = False, progress: bool = False) -> list[capnp.lib.capnp._DynamicStructBuilder]: """Queries for FW versions ordering brands by likelihood, breaks when exact match is found""" all_car_fw = [] @@ -247,15 +253,15 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pand all_car_fw.extend(car_fw) # If there is a match using this brand's FW alone, finish querying early - _, matches = match_fw_to_car(car_fw, log=False) + _, matches = match_fw_to_car(car_fw, vin, log=False) if len(matches) == 1: break return all_car_fw -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]: +def get_fw_versions(logcan, sendcan, query_brand: str = None, extra: OfflineFwVersions = None, timeout: float = 0.1, num_pandas: int = 1, + debug: bool = False, progress: bool = False) -> list[capnp.lib.capnp._DynamicStructBuilder]: versions = VERSIONS.copy() params = Params() @@ -346,7 +352,7 @@ if __name__ == "__main__": pandaStates_sock = messaging.sub_sock('pandaStates') sendcan = messaging.pub_sock('sendcan') - # Set up params for boardd + # Set up params for pandad params = Params() params.remove("FirmwareQueryDone") params.put_bool("IsOnroad", False) @@ -367,14 +373,15 @@ if __name__ == "__main__": t = time.time() print("Getting vin...") - vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (0, 1), retry=10, debug=args.debug) + set_obd_multiplexing(params, True) + vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (0, 1), debug=args.debug) print(f'RX: {hex(vin_rx_addr)}, BUS: {vin_rx_bus}, VIN: {vin}') print(f"Getting VIN took {time.time() - t:.3f} s") print() t = time.time() fw_vers = get_fw_versions(logcan, sendcan, query_brand=args.brand, extra=extra, num_pandas=num_pandas, debug=args.debug, progress=True) - _, candidates = match_fw_to_car(fw_vers) + _, candidates = match_fw_to_car(fw_vers, vin) print() print("Found FW versions") @@ -382,7 +389,8 @@ if __name__ == "__main__": padding = max([len(fw.brand) for fw in fw_vers] or [0]) for version in fw_vers: subaddr = None if version.subAddress == 0 else hex(version.subAddress) - print(f" Brand: {version.brand:{padding}}, bus: {version.bus} - (Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}]") + print(f" Brand: {version.brand:{padding}}, bus: {version.bus}, OBD: {version.obdMultiplexing} - " + + f"(Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}]") print("}") print() diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 6c7e8007f2..b204d3b80f 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -6,6 +6,7 @@ from opendbc.can.packer import CANPacker from openpilot.selfdrive.car import apply_driver_steer_torque_limits from openpilot.selfdrive.car.gm import gmcan from openpilot.selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons +from openpilot.selfdrive.car.interfaces import CarControllerBase VisualAlert = car.CarControl.HUDControl.VisualAlert NetworkLocation = car.CarParams.NetworkLocation @@ -17,7 +18,7 @@ CAMERA_CANCEL_DELAY_FRAMES = 10 MIN_STEER_MSG_INTERVAL_MS = 15 -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP self.start_time = 0. @@ -117,7 +118,7 @@ class CarController: # Send dashboard UI commands (ACC status) send_fcw = hud_alert == VisualAlert.fcw can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, CC.enabled, - hud_v_cruise * CV.MS_TO_KPH, hud_control.leadVisible, send_fcw)) + hud_v_cruise * CV.MS_TO_KPH, hud_control, send_fcw)) # Radar needs to know current speed and yaw rate (50hz), # and that ADAS is alive (10hz) @@ -154,7 +155,7 @@ class CarController: if self.frame % 10 == 0: can_sends.append(gmcan.create_pscm_status(self.packer_pt, CanBus.CAMERA, CS.pscm_status)) - new_actuators = actuators.copy() + new_actuators = actuators.as_builder() new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX new_actuators.steerOutputCan = self.apply_steer_last new_actuators.gas = self.apply_gas diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index c3d061de78..a1129c59c8 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -26,11 +26,16 @@ class CarState(CarStateBase): self.cam_lka_steering_cmd_counter = 0 self.buttons_counter = 0 + self.prev_distance_button = 0 + self.distance_button = 0 + def update(self, pt_cp, cam_cp, loopback_cp): ret = car.CarState.new_message() self.prev_cruise_buttons = self.cruise_buttons + self.prev_distance_button = self.distance_button self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"] + self.distance_button = pt_cp.vl["ASCMSteeringButton"]["DistanceButton"] self.buttons_counter = pt_cp.vl["ASCMSteeringButton"]["RollingCounter"] self.pscm_status = copy.copy(pt_cp.vl["PSCMStatus"]) # This is to avoid a fault where you engage while still moving backwards after shifting to D. diff --git a/selfdrive/car/gm/fingerprints.py b/selfdrive/car/gm/fingerprints.py index 73a205a250..3752fbb8d3 100644 --- a/selfdrive/car/gm/fingerprints.py +++ b/selfdrive/car/gm/fingerprints.py @@ -9,7 +9,7 @@ FINGERPRINTS = { CAR.HOLDEN_ASTRA: [{ 190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 401: 8, 413: 8, 417: 8, 419: 8, 422: 1, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 458: 5, 479: 8, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 647: 5, 707: 8, 715: 8, 723: 8, 753: 5, 761: 7, 806: 1, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1009: 8, 1011: 6, 1017: 8, 1019: 3, 1020: 8, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 8, 1280: 4, 1300: 8, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1908: 7, 1912: 7, 1919: 7 }], - CAR.VOLT: [{ + CAR.CHEVROLET_VOLT: [{ 170: 8, 171: 8, 189: 7, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 289: 8, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 528: 4, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 577: 8, 647: 3, 707: 8, 711: 6, 715: 8, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 961: 8, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1928: 7, 2016: 8, 2020: 8, 2024: 8, 2028: 8 }, { @@ -27,31 +27,31 @@ FINGERPRINTS = { CAR.CADILLAC_ATS: [{ 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 368: 3, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 401: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 462: 4, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 491: 2, 493: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 723: 2, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 882: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1233: 8, 1241: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1916: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8 }], - CAR.MALIBU: [{ + CAR.CHEVROLET_MALIBU: [{ 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1919: 7, 1930: 7, 2016: 8, 2024: 8 }], - CAR.ACADIA: [{ + CAR.GMC_ACADIA: [{ 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 1, 290: 1, 298: 8, 304: 8, 309: 8, 313: 8, 320: 8, 322: 7, 328: 1, 352: 7, 368: 8, 381: 8, 384: 8, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 458: 8, 460: 4, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 5, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 512: 3, 530: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 568: 2, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 801: 8, 803: 8, 804: 3, 805: 8, 832: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7 }, { 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 313: 8, 320: 3, 322: 7, 328: 1, 338: 6, 340: 6, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8 }], - CAR.ESCALADE: [{ + CAR.CADILLAC_ESCALADE: [{ 170: 8, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 4, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 460: 5, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 }], - CAR.ESCALADE_ESV: [{ + CAR.CADILLAC_ESCALADE_ESV: [{ 309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8 }], - CAR.ESCALADE_ESV_2019: [{ + CAR.CADILLAC_ESCALADE_ESV_2019: [{ 715: 8, 840: 5, 717: 5, 869: 4, 880: 6, 289: 8, 454: 8, 842: 5, 460: 5, 463: 3, 801: 8, 170: 8, 190: 6, 241: 6, 201: 8, 417: 7, 211: 2, 419: 1, 398: 8, 426: 7, 487: 8, 442: 8, 451: 8, 452: 8, 453: 6, 479: 3, 311: 8, 500: 6, 647: 6, 193: 8, 707: 8, 197: 8, 209: 7, 199: 4, 455: 7, 313: 8, 481: 7, 485: 8, 489: 8, 249: 8, 393: 7, 407: 7, 413: 8, 422: 4, 431: 8, 501: 8, 499: 3, 810: 8, 508: 8, 381: 8, 462: 4, 532: 6, 562: 8, 386: 8, 761: 7, 573: 1, 554: 3, 719: 5, 560: 8, 1279: 4, 388: 8, 288: 5, 1005: 6, 497: 8, 844: 8, 961: 8, 967: 4, 977: 8, 979: 8, 985: 5, 1001: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 510: 8, 866: 4, 304: 1, 969: 8, 384: 4, 1033: 7, 1009: 8, 1034: 7, 1296: 4, 1930: 7, 1105: 5, 1013: 5, 1225: 7, 1919: 7, 320: 3, 534: 2, 352: 5, 298: 8, 1223: 2, 1233: 8, 608: 8, 1265: 8, 609: 6, 1267: 1, 1417: 8, 610: 6, 1906: 7, 611: 6, 612: 8, 613: 8, 208: 8, 564: 5, 309: 8, 1221: 5, 1280: 4, 1249: 8, 1907: 7, 1257: 6, 1300: 8, 1920: 7, 563: 5, 1322: 6, 1323: 4, 1328: 4, 1917: 7, 328: 1, 1912: 7, 1914: 7, 804: 3, 1918: 7 }], - CAR.BOLT_EUV: [{ + CAR.CHEVROLET_BOLT_EUV: [{ 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 8, 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, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 }], - CAR.SILVERADO: [{ + CAR.CHEVROLET_SILVERADO: [{ 190: 6, 193: 8, 197: 8, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 460: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 534: 2, 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, 761: 7, 789: 5, 800: 6, 801: 8, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 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 }], - CAR.EQUINOX: [{ + CAR.CHEVROLET_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 }, { diff --git a/selfdrive/car/gm/gmcan.py b/selfdrive/car/gm/gmcan.py index bd1e29ce3b..e833e77636 100644 --- a/selfdrive/car/gm/gmcan.py +++ b/selfdrive/car/gm/gmcan.py @@ -76,7 +76,7 @@ def create_friction_brake_command(packer, bus, apply_brake, idx, enabled, near_s mode = 0x1 # TODO: Understand this better. Volts and ICE Camera ACC cars are 0x1 when enabled with no brake - if enabled and CP.carFingerprint in (CAR.BOLT_EUV,): + if enabled and CP.carFingerprint in (CAR.CHEVROLET_BOLT_EUV,): mode = 0x9 if apply_brake > 0: @@ -102,17 +102,17 @@ def create_friction_brake_command(packer, bus, apply_brake, idx, enabled, near_s return packer.make_can_msg("EBCMFrictionBrakeCmd", bus, values) -def create_acc_dashboard_command(packer, bus, enabled, target_speed_kph, lead_car_in_sight, fcw): +def create_acc_dashboard_command(packer, bus, enabled, target_speed_kph, hud_control, fcw): target_speed = min(target_speed_kph, 255) values = { "ACCAlwaysOne": 1, "ACCResumeButton": 0, "ACCSpeedSetpoint": target_speed, - "ACCGapLevel": 3 * enabled, # 3 "far", 0 "inactive" + "ACCGapLevel": hud_control.leadDistanceBars * enabled, # 3 "far", 0 "inactive" "ACCCmdActive": enabled, "ACCAlwaysOne2": 1, - "ACCLeadCar": lead_car_in_sight, + "ACCLeadCar": hud_control.leadVisible, "FCWAlert": 0x3 if fcw else 0 } diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index e0dde4d0e9..5ea2b19891 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -22,9 +22,9 @@ BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.D NON_LINEAR_TORQUE_PARAMS = { - CAR.BOLT_EUV: [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178], - CAR.ACADIA: [4.78003305, 1.0, 0.3122, 0.05591772], - CAR.SILVERADO: [3.29974374, 1.0, 0.25571356, 0.0465122] + CAR.CHEVROLET_BOLT_EUV: [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178], + CAR.GMC_ACADIA: [4.78003305, 1.0, 0.3122, 0.05591772], + CAR.CHEVROLET_SILVERADO: [3.29974374, 1.0, 0.25571356, 0.0465122] } NEURAL_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/neural_ff_weights.json') @@ -43,7 +43,7 @@ class CarInterface(CarInterfaceBase): return 0.10006696 * sigmoid * (v_ego + 3.12485927) def get_steer_feedforward_function(self): - if self.CP.carFingerprint == CAR.VOLT: + if self.CP.carFingerprint == CAR.CHEVROLET_VOLT: return self.get_steer_feedforward_volt else: return CarInterfaceBase.get_steer_feedforward_default @@ -74,7 +74,7 @@ class CarInterface(CarInterfaceBase): return float(self.neural_ff_model.predict(inputs)) + friction def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: - if self.CP.carFingerprint == CAR.BOLT_EUV: + if self.CP.carFingerprint == CAR.CHEVROLET_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: @@ -94,11 +94,7 @@ class CarInterface(CarInterfaceBase): else: ret.transmissionType = TransmissionType.automatic - ret.longitudinalTuning.deadzoneBP = [0.] - ret.longitudinalTuning.deadzoneV = [0.15] - - ret.longitudinalTuning.kpBP = [5., 35.] - ret.longitudinalTuning.kiBP = [0.] + ret.longitudinalTuning.kiBP = [5., 35.] if candidate in CAMERA_ACC_CAR: ret.experimentalLongitudinalAvailable = True @@ -110,8 +106,7 @@ class CarInterface(CarInterfaceBase): ret.minSteerSpeed = 10 * CV.KPH_TO_MS # Tuning for experimental long - ret.longitudinalTuning.kpV = [2.0, 1.5] - ret.longitudinalTuning.kiV = [0.72] + ret.longitudinalTuning.kiV = [2.0, 1.5] ret.stoppingDecelRate = 2.0 # reach brake quickly after enabling ret.vEgoStopping = 0.25 ret.vEgoStarting = 0.25 @@ -131,13 +126,12 @@ class CarInterface(CarInterfaceBase): ret.minSteerSpeed = 7 * CV.MPH_TO_MS # Tuning - ret.longitudinalTuning.kpV = [2.4, 1.5] - ret.longitudinalTuning.kiV = [0.36] + ret.longitudinalTuning.kiV = [2.4, 1.5] # 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} or \ + ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.CHEVROLET_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. @@ -145,15 +139,12 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.00]] ret.lateralTuning.pid.kf = 0.00004 # full torque for 20 deg at 80mph means 0.00007818594 ret.steerActuatorDelay = 0.1 # Default delay, not measured yet - ret.tireStiffnessFactor = 0.444 # not optimized yet ret.steerLimitTimer = 0.4 ret.radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz - ret.longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking - - if candidate == CAR.VOLT: - ret.tireStiffnessFactor = 0.469 # Stock Michelin Energy Saver A/S, LiveParameters + ret.longitudinalActuatorDelay = 0.5 # large delay to initially start braking + if candidate == CAR.CHEVROLET_VOLT: ret.lateralTuning.pid.kpBP = [0., 40.] ret.lateralTuning.pid.kpV = [0., 0.17] ret.lateralTuning.pid.kiBP = [0.] @@ -161,7 +152,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_volt() ret.steerActuatorDelay = 0.2 - elif candidate == CAR.ACADIA: + elif candidate == CAR.GMC_ACADIA: ret.minEnableSpeed = -1. # engage speed is decided by pcm ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) @@ -169,15 +160,14 @@ class CarInterface(CarInterfaceBase): elif candidate == CAR.BUICK_LACROSSE: CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - elif candidate == CAR.ESCALADE: + elif candidate == CAR.CADILLAC_ESCALADE: ret.minEnableSpeed = -1. # engage speed is decided by pcm CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - elif candidate in (CAR.ESCALADE_ESV, CAR.ESCALADE_ESV_2019): + elif candidate in (CAR.CADILLAC_ESCALADE_ESV, CAR.CADILLAC_ESCALADE_ESV_2019): ret.minEnableSpeed = -1. # engage speed is decided by pcm - ret.tireStiffnessFactor = 1.0 - if candidate == CAR.ESCALADE_ESV: + if candidate == CAR.CADILLAC_ESCALADE_ESV: ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[10., 41.0], [10., 41.0]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.13, 0.24], [0.01, 0.02]] ret.lateralTuning.pid.kf = 0.000045 @@ -185,13 +175,11 @@ class CarInterface(CarInterfaceBase): ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - elif candidate == CAR.BOLT_EUV: - ret.tireStiffnessFactor = 1.0 + elif candidate == CAR.CHEVROLET_BOLT_EUV: ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - elif candidate == CAR.SILVERADO: - ret.tireStiffnessFactor = 1.0 + elif candidate == CAR.CHEVROLET_SILVERADO: # 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. # TODO: check if this is split by EV/ICE with more platforms in the future @@ -199,11 +187,10 @@ class CarInterface(CarInterfaceBase): ret.minEnableSpeed = -1. CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - elif candidate == CAR.EQUINOX: + elif candidate == CAR.CHEVROLET_EQUINOX: CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - elif candidate == CAR.TRAILBLAZER: - ret.tireStiffnessFactor = 1.0 + elif candidate == CAR.CHEVROLET_TRAILBLAZER: ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) @@ -215,8 +202,12 @@ class CarInterface(CarInterfaceBase): # Don't add event if transitioning from INIT, unless it's to an actual button if self.CS.cruise_buttons != CruiseButtons.UNPRESS or self.CS.prev_cruise_buttons != CruiseButtons.INIT: - ret.buttonEvents = create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, - unpressed_btn=CruiseButtons.UNPRESS) + ret.buttonEvents = [ + *create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, + unpressed_btn=CruiseButtons.UNPRESS), + *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, + {1: ButtonType.gapAdjustCruise}) + ] # The ECM allows enabling on falling edge of set, but only rising edge of resume events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low, @@ -240,6 +231,3 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() return ret - - def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/gm/tests/test_gm.py b/selfdrive/car/gm/tests/test_gm.py old mode 100755 new mode 100644 index 2aea5b231b..a0a4a94ffa --- a/selfdrive/car/gm/tests/test_gm.py +++ b/selfdrive/car/gm/tests/test_gm.py @@ -1,28 +1,20 @@ -#!/usr/bin/env python3 from parameterized import parameterized -import unittest from openpilot.selfdrive.car.gm.fingerprints import FINGERPRINTS -from openpilot.selfdrive.car.gm.values import CAMERA_ACC_CAR, CAR, GM_RX_OFFSET +from openpilot.selfdrive.car.gm.values import CAMERA_ACC_CAR, GM_RX_OFFSET CAMERA_DIAGNOSTIC_ADDRESS = 0x24b -class TestGMFingerprint(unittest.TestCase): +class TestGMFingerprint: @parameterized.expand(FINGERPRINTS.items()) def test_can_fingerprints(self, car_model, fingerprints): - self.assertGreater(len(fingerprints), 0) + assert len(fingerprints) > 0 - # Trailblazer is in dashcam - if car_model != CAR.TRAILBLAZER: - self.assertTrue(all(len(finger) for finger in fingerprints)) + assert all(len(finger) for finger in fingerprints) # The camera can sometimes be communicating on startup - if car_model in CAMERA_ACC_CAR - {CAR.TRAILBLAZER}: + if car_model in CAMERA_ACC_CAR: for finger in fingerprints: for required_addr in (CAMERA_DIAGNOSTIC_ADDRESS, CAMERA_DIAGNOSTIC_ADDRESS + GM_RX_OFFSET): - self.assertEqual(finger.get(required_addr), 8, required_addr) - - -if __name__ == "__main__": - unittest.main() + assert finger.get(required_addr) == 8, required_addr diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 35fdf3fb92..53a4621d27 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -1,9 +1,8 @@ from dataclasses import dataclass, field -from enum import Enum from cereal import car 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.docs_definitions import CarHarness, CarDocs, CarParts from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu @@ -60,15 +59,8 @@ class CarControllerParams: self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.] -class Footnote(Enum): - OBD_II = CarFootnote( - 'Requires a community built ASCM harness. ' + - 'NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).', - Column.MODEL) - - @dataclass -class GMCarInfo(CarInfo): +class GMCarDocs(CarDocs): package: str = "Adaptive Cruise Control (ACC)" def init_make(self, CP: car.CarParams): @@ -76,90 +68,87 @@ class GMCarInfo(CarInfo): self.car_parts = CarParts.common([CarHarness.gm]) else: self.car_parts = CarParts.common([CarHarness.obd_ii]) - self.footnotes.append(Footnote.OBD_II) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) +class GMCarSpecs(CarSpecs): + tireStiffnessFactor: float = 0.444 # not optimized yet + + +@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')) +@dataclass +class GMASCMPlatformConfig(GMPlatformConfig): + def init(self): + # ASCM is supported, but due to a janky install and hardware configuration, we are not showing in the car docs + self.car_docs = [] + + 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), + HOLDEN_ASTRA = GMASCMPlatformConfig( + [GMCarDocs("Holden Astra 2017")], + GMCarSpecs(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), + CHEVROLET_VOLT = GMASCMPlatformConfig( + [GMCarDocs("Chevrolet Volt 2017-18", min_enable_speed=0, video_link="https://youtu.be/QeMCN_4TFfQ")], + GMCarSpecs(mass=1607, wheelbase=2.69, steerRatio=17.7, centerToFrontRatio=0.45, tireStiffnessFactor=0.469), ) - CADILLAC_ATS = GMPlatformConfig( - "CADILLAC ATS Premium Performance 2018", - GMCarInfo("Cadillac ATS Premium Performance 2018"), - specs=CarSpecs(mass=1601, wheelbase=2.78, steerRatio=15.3), + CADILLAC_ATS = GMASCMPlatformConfig( + [GMCarDocs("Cadillac ATS Premium Performance 2018")], + GMCarSpecs(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), + CHEVROLET_MALIBU = GMASCMPlatformConfig( + [GMCarDocs("Chevrolet Malibu Premier 2017")], + GMCarSpecs(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), + GMC_ACADIA = GMASCMPlatformConfig( + [GMCarDocs("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo")], + GMCarSpecs(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_LACROSSE = GMASCMPlatformConfig( + [GMCarDocs("Buick LaCrosse 2017-19", "Driver Confidence Package 2")], + GMCarSpecs(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), + BUICK_REGAL = GMASCMPlatformConfig( + [GMCarDocs("Buick Regal Essence 2018")], + GMCarSpecs(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), + CADILLAC_ESCALADE = GMASCMPlatformConfig( + [GMCarDocs("Cadillac Escalade 2017", "Driver Assist Package")], + GMCarSpecs(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), + CADILLAC_ESCALADE_ESV = GMASCMPlatformConfig( + [GMCarDocs("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS")], + GMCarSpecs(mass=2739, wheelbase=3.302, steerRatio=17.3, tireStiffnessFactor=1.0), ) - ESCALADE_ESV_2019 = GMPlatformConfig( - "CADILLAC ESCALADE ESV 2019", - GMCarInfo("Cadillac Escalade ESV 2019", "Adaptive Cruise Control (ACC) & LKAS"), - specs=ESCALADE_ESV.specs, + CADILLAC_ESCALADE_ESV_2019 = GMASCMPlatformConfig( + [GMCarDocs("Cadillac Escalade ESV 2019", "Adaptive Cruise Control (ACC) & LKAS")], + CADILLAC_ESCALADE_ESV.specs, ) - BOLT_EUV = GMPlatformConfig( - "CHEVROLET BOLT EUV 2022", + CHEVROLET_BOLT_EUV = GMPlatformConfig( [ - 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"), + GMCarDocs("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video_link="https://youtu.be/xvwzGMUA210"), + GMCarDocs("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), + GMCarSpecs(mass=1669, wheelbase=2.63779, steerRatio=16.8, centerToFrontRatio=0.4, tireStiffnessFactor=1.0), ) - SILVERADO = GMPlatformConfig( - "CHEVROLET SILVERADO 1500 2020", + CHEVROLET_SILVERADO = GMPlatformConfig( [ - 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"), + GMCarDocs("Chevrolet Silverado 1500 2020-21", "Safety Package II"), + GMCarDocs("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), + GMCarSpecs(mass=2450, wheelbase=3.75, steerRatio=16.3, tireStiffnessFactor=1.0), ) - EQUINOX = GMPlatformConfig( - "CHEVROLET EQUINOX 2019", - GMCarInfo("Chevrolet Equinox 2019-22"), - specs=CarSpecs(mass=1588, wheelbase=2.72, steerRatio=14.4, centerToFrontRatio=0.4), + CHEVROLET_EQUINOX = GMPlatformConfig( + [GMCarDocs("Chevrolet Equinox 2019-22")], + GMCarSpecs(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), + CHEVROLET_TRAILBLAZER = GMPlatformConfig( + [GMCarDocs("Chevrolet Trailblazer 2021-22")], + GMCarSpecs(mass=1345, wheelbase=2.64, steerRatio=16.8, centerToFrontRatio=0.4, tireStiffnessFactor=1.0), ) @@ -189,22 +178,35 @@ class CanBus: # In a Data Module, an identifier is a string used to recognize an object, # either by itself or together with the identifiers of parent objects. # Each returns a 4 byte hex representation of the decimal part number. `b"\x02\x8c\xf0'"` -> 42790951 +GM_BOOT_SOFTWARE_PART_NUMER_REQUEST = b'\x1a\xc0' # likely does not contain anything useful GM_SOFTWARE_MODULE_1_REQUEST = b'\x1a\xc1' GM_SOFTWARE_MODULE_2_REQUEST = b'\x1a\xc2' GM_SOFTWARE_MODULE_3_REQUEST = b'\x1a\xc3' + +# Part number of XML data file that is used to configure ECU +GM_XML_DATA_FILE_PART_NUMBER = b'\x1a\x9c' +GM_XML_CONFIG_COMPAT_ID = b'\x1a\x9b' # used to know if XML file is compatible with the ECU software/hardware + # This DID is for identifying the part number that reflects the mix of hardware, # software, and calibrations in the ECU when it first arrives at the vehicle assembly plant. # If there's an Alpha Code, it's associated with this part number and stored in the DID $DB. GM_END_MODEL_PART_NUMBER_REQUEST = b'\x1a\xcb' +GM_END_MODEL_PART_NUMBER_ALPHA_CODE_REQUEST = b'\x1a\xdb' GM_BASE_MODEL_PART_NUMBER_REQUEST = b'\x1a\xcc' +GM_BASE_MODEL_PART_NUMBER_ALPHA_CODE_REQUEST = b'\x1a\xdc' GM_FW_RESPONSE = b'\x5a' GM_FW_REQUESTS = [ + GM_BOOT_SOFTWARE_PART_NUMER_REQUEST, GM_SOFTWARE_MODULE_1_REQUEST, GM_SOFTWARE_MODULE_2_REQUEST, GM_SOFTWARE_MODULE_3_REQUEST, + GM_XML_DATA_FILE_PART_NUMBER, + GM_XML_CONFIG_COMPAT_ID, GM_END_MODEL_PART_NUMBER_REQUEST, + GM_END_MODEL_PART_NUMBER_ALPHA_CODE_REQUEST, GM_BASE_MODEL_PART_NUMBER_REQUEST, + GM_BASE_MODEL_PART_NUMBER_ALPHA_CODE_REQUEST, ] GM_RX_OFFSET = 0x400 @@ -222,12 +224,11 @@ FW_QUERY_CONFIG = FwQueryConfig( extra_ecus=[(Ecu.fwdCamera, 0x24b, None)], ) -EV_CAR = {CAR.VOLT, CAR.BOLT_EUV} +EV_CAR = {CAR.CHEVROLET_VOLT, CAR.CHEVROLET_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} +CAMERA_ACC_CAR = {CAR.CHEVROLET_BOLT_EUV, CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_EQUINOX, CAR.CHEVROLET_TRAILBLAZER} STEER_THRESHOLD = 1.0 -CAR_INFO = CAR.create_carinfo_map() DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index 056b47c4b3..fe023ea17d 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -4,9 +4,9 @@ from cereal import car from openpilot.common.numpy_fast import clip, interp from openpilot.common.realtime import DT_CTRL from opendbc.can.packer import CANPacker -from openpilot.selfdrive.car import create_gas_interceptor_command from openpilot.selfdrive.car.honda import hondacan from openpilot.selfdrive.car.honda.values import CruiseButtons, VISUAL_HUD, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams +from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.controls.lib.drive_helpers import rate_limit VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -95,7 +95,7 @@ def process_hud_alert(hud_alert): HUDData = namedtuple("HUDData", ["pcm_accel", "v_cruise", "lead_visible", - "lanes_visible", "fcw", "acc_alert", "steer_required"]) + "lanes_visible", "fcw", "acc_alert", "steer_required", "lead_distance_bars"]) def rate_limit_steer(new_steer, last_steer): @@ -104,11 +104,12 @@ def rate_limit_steer(new_steer, last_steer): return clip(new_steer, last_steer - MAX_DELTA, last_steer + MAX_DELTA) -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP self.packer = CANPacker(dbc_name) self.params = CarControllerParams(CP) + self.CAN = hondacan.CanBus(CP) self.frame = 0 self.braking = False @@ -167,7 +168,7 @@ class CarController: can_sends.append((0x18DAB0F1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 1)) # Send steering command. - can_sends.append(hondacan.create_steering_control(self.packer, apply_steer, CC.latActive, self.CP.carFingerprint, + can_sends.append(hondacan.create_steering_control(self.packer, self.CAN, apply_steer, CC.latActive, self.CP.carFingerprint, CS.CP.openpilotLongitudinalControl)) # wind brake from air resistance decel at high speed @@ -181,7 +182,7 @@ class CarController: 0.5] # The Honda ODYSSEY seems to have different PCM_ACCEL # msgs, is it other cars too? - if self.CP.enableGasInterceptor or not CC.longActive: + if not CC.longActive: pcm_speed = 0.0 pcm_accel = int(0.0) elif self.CP.carFingerprint in HONDA_NIDEC_ALT_PCM_ACCEL: @@ -201,12 +202,12 @@ class CarController: if not self.CP.openpilotLongitudinalControl: if self.frame % 2 == 0 and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: # radarless cars don't have supplemental message - can_sends.append(hondacan.create_bosch_supplemental_1(self.packer, self.CP.carFingerprint)) + can_sends.append(hondacan.create_bosch_supplemental_1(self.packer, self.CAN, self.CP.carFingerprint)) # If using stock ACC, spam cancel command to kill gas when OP disengages. if pcm_cancel_cmd: - can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.CANCEL, self.CP.carFingerprint)) + can_sends.append(hondacan.spam_buttons_command(self.packer, self.CAN, CruiseButtons.CANCEL, self.CP.carFingerprint)) elif CC.cruiseControl.resume: - can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.RES_ACCEL, self.CP.carFingerprint)) + can_sends.append(hondacan.spam_buttons_command(self.packer, self.CAN, CruiseButtons.RES_ACCEL, self.CP.carFingerprint)) else: # Send gas and brake commands. @@ -219,7 +220,7 @@ class CarController: stopping = actuators.longControlState == LongCtrlState.stopping self.stopping_counter = self.stopping_counter + 1 if stopping else 0 - can_sends.extend(hondacan.create_acc_commands(self.packer, CC.enabled, CC.longActive, self.accel, self.gas, + can_sends.extend(hondacan.create_acc_commands(self.packer, self.CAN, CC.enabled, CC.longActive, self.accel, self.gas, self.stopping_counter, self.CP.carFingerprint)) else: apply_brake = clip(self.brake_last - wind_brake, 0.0, 1.0) @@ -227,38 +228,23 @@ class CarController: pump_on, self.last_pump_ts = brake_pump_hysteresis(apply_brake, self.apply_brake_last, self.last_pump_ts, ts) pcm_override = True - can_sends.append(hondacan.create_brake_command(self.packer, apply_brake, pump_on, + can_sends.append(hondacan.create_brake_command(self.packer, self.CAN, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw_display, self.CP.carFingerprint, CS.stock_brake)) self.apply_brake_last = apply_brake self.brake = apply_brake / self.params.NIDEC_BRAKE_MAX - if self.CP.enableGasInterceptor: - # way too aggressive at low speed without this - gas_mult = interp(CS.out.vEgo, [0., 10.], [0.4, 1.0]) - # send exactly zero if apply_gas is zero. Interceptor will send the max between read value and apply_gas. - # This prevents unexpected pedal range rescaling - # Sending non-zero gas when OP is not enabled will cause the PCM not to respond to throttle as expected - # when you do enable. - if CC.longActive: - self.gas = clip(gas_mult * (gas - brake + wind_brake * 3 / 4), 0., 1.) - else: - self.gas = 0.0 - can_sends.append(create_gas_interceptor_command(self.packer, self.gas, self.frame // 2)) - # Send dashboard UI commands. if self.frame % 10 == 0: hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_control.leadVisible, - hud_control.lanesVisible, fcw_display, acc_alert, steer_required) - can_sends.extend(hondacan.create_ui_commands(self.packer, self.CP, CC.enabled, pcm_speed, hud, CS.is_metric, CS.acc_hud, CS.lkas_hud)) + hud_control.lanesVisible, fcw_display, acc_alert, steer_required, hud_control.leadDistanceBars) + can_sends.extend(hondacan.create_ui_commands(self.packer, self.CAN, self.CP, CC.enabled, pcm_speed, hud, CS.is_metric, CS.acc_hud, CS.lkas_hud)) if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint not in HONDA_BOSCH: self.speed = pcm_speed + self.gas = pcm_accel / self.params.NIDEC_GAS_MAX - if not self.CP.enableGasInterceptor: - self.gas = pcm_accel / self.params.NIDEC_GAS_MAX - - new_actuators = actuators.copy() + new_actuators = actuators.as_builder() new_actuators.speed = self.speed new_actuators.accel = self.accel new_actuators.gas = self.gas diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index 9025f72397..c98d1a72d3 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -5,7 +5,7 @@ from openpilot.common.conversions import Conversions as CV from openpilot.common.numpy_fast import interp 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.hondacan import CanBus, get_cruise_speed_conversion from openpilot.selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, \ HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, \ HondaFlags @@ -28,7 +28,7 @@ def get_can_messages(CP, gearbox_msg): ("STEER_MOTOR_TORQUE", 0), # TODO: not on every car ] - if CP.carFingerprint == CAR.ODYSSEY_CHN: + if CP.carFingerprint == CAR.HONDA_ODYSSEY_CHN: messages += [ ("SCM_FEEDBACK", 25), ("SCM_BUTTONS", 50), @@ -39,7 +39,7 @@ def get_can_messages(CP, gearbox_msg): ("SCM_BUTTONS", 25), ] - if CP.carFingerprint in (CAR.CRV_HYBRID, CAR.CIVIC_BOSCH_DIESEL, CAR.ACURA_RDX_3G, CAR.HONDA_E): + if CP.carFingerprint in (CAR.HONDA_CRV_HYBRID, CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.ACURA_RDX_3G, CAR.HONDA_E): messages.append((gearbox_msg, 50)) else: messages.append((gearbox_msg, 100)) @@ -47,7 +47,7 @@ def get_can_messages(CP, gearbox_msg): 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}): + if CP.carFingerprint in (HONDA_BOSCH | {CAR.HONDA_CIVIC, CAR.HONDA_ODYSSEY, CAR.HONDA_ODYSSEY_CHN}): messages.append(("EPB_STATUS", 50)) if CP.carFingerprint in HONDA_BOSCH: @@ -58,24 +58,20 @@ def get_can_messages(CP, gearbox_msg): ("ACC_CONTROL", 50), ] else: # Nidec signals - if CP.carFingerprint == CAR.ODYSSEY_CHN: + if CP.carFingerprint == CAR.HONDA_ODYSSEY_CHN: messages.append(("CRUISE_PARAMS", 10)) else: messages.append(("CRUISE_PARAMS", 50)) # TODO: clean this up - if CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, - CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022, CAR.HRV_3G): + if CP.carFingerprint in (CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.HONDA_CRV_HYBRID, CAR.HONDA_INSIGHT, + CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.HONDA_CIVIC_2022, CAR.HONDA_HRV_3G): pass - elif CP.carFingerprint in (CAR.ODYSSEY_CHN, CAR.FREED, CAR.HRV): + elif CP.carFingerprint in (CAR.HONDA_ODYSSEY_CHN, CAR.HONDA_FREED, CAR.HONDA_HRV): pass else: messages.append(("DOORS_STATUS", 3)) - # add gas interceptor reading if we are using it - if CP.enableGasInterceptor: - messages.append(("GAS_SENSOR", 50)) - if CP.carFingerprint in HONDA_BOSCH_RADARLESS: messages.append(("CRUISE_FAULT_STATUS", 50)) elif CP.openpilotLongitudinalControl: @@ -89,7 +85,7 @@ class CarState(CarStateBase): super().__init__(CP) can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) self.gearbox_msg = "GEARBOX" - if CP.carFingerprint == CAR.ACCORD and CP.transmissionType == TransmissionType.cvt: + if CP.carFingerprint == CAR.HONDA_ACCORD and CP.transmissionType == TransmissionType.cvt: self.gearbox_msg = "GEARBOX_15T" self.main_on_sig_msg = "SCM_FEEDBACK" @@ -129,10 +125,10 @@ class CarState(CarStateBase): # panda checks if the signal is non-zero ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 1e-5 # TODO: find a common signal across all cars - if self.CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, - CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022, CAR.HRV_3G): + if self.CP.carFingerprint in (CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.HONDA_CRV_HYBRID, CAR.HONDA_INSIGHT, + CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.HONDA_CIVIC_2022, CAR.HONDA_HRV_3G): ret.doorOpen = bool(cp.vl["SCM_FEEDBACK"]["DRIVERS_DOOR_OPEN"]) - elif self.CP.carFingerprint in (CAR.ODYSSEY_CHN, CAR.FREED, CAR.HRV): + elif self.CP.carFingerprint in (CAR.HONDA_ODYSSEY_CHN, CAR.HONDA_FREED, CAR.HONDA_HRV): ret.doorOpen = bool(cp.vl["SCM_BUTTONS"]["DRIVERS_DOOR_OPEN"]) else: ret.doorOpen = any([cp.vl["DOORS_STATUS"]["DOOR_OPEN_FL"], cp.vl["DOORS_STATUS"]["DOOR_OPEN_FR"], @@ -185,25 +181,24 @@ class CarState(CarStateBase): ret.brakeHoldActive = cp.vl["VSA_STATUS"]["BRAKE_HOLD_ACTIVE"] == 1 # TODO: set for all cars - if self.CP.carFingerprint in (HONDA_BOSCH | {CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN}): + if self.CP.carFingerprint in (HONDA_BOSCH | {CAR.HONDA_CIVIC, CAR.HONDA_ODYSSEY, CAR.HONDA_ODYSSEY_CHN}): ret.parkingBrake = cp.vl["EPB_STATUS"]["EPB_STATE"] != 0 gear = int(cp.vl[self.gearbox_msg]["GEAR_SHIFTER"]) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear, None)) - if self.CP.enableGasInterceptor: - # Same threshold as panda, equivalent to 1e-5 with previous DBC scaling - ret.gas = (cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) // 2 - ret.gasPressed = ret.gas > 492 - else: - ret.gas = cp.vl["POWERTRAIN_DATA"]["PEDAL_GAS"] - ret.gasPressed = ret.gas > 1e-5 + ret.gas = cp.vl["POWERTRAIN_DATA"]["PEDAL_GAS"] + ret.gasPressed = ret.gas > 1e-5 ret.steeringTorque = cp.vl["STEER_STATUS"]["STEER_TORQUE_SENSOR"] ret.steeringTorqueEps = cp.vl["STEER_MOTOR_TORQUE"]["MOTOR_TORQUE"] ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD.get(self.CP.carFingerprint, 1200) if self.CP.carFingerprint in HONDA_BOSCH: + # The PCM always manages its own cruise control state, but doesn't publish it + if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS: + ret.cruiseState.nonAdaptive = cp_cam.vl["ACC_HUD"]["CRUISE_CONTROL_LABEL"] != 0 + if not self.CP.openpilotLongitudinalControl: # ACC_HUD is on camera bus on radarless cars acc_hud = cp_cam.vl["ACC_HUD"] if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS else cp.vl["ACC_HUD"] @@ -237,7 +232,7 @@ class CarState(CarStateBase): ret.cruiseState.available = bool(cp.vl[self.main_on_sig_msg]["MAIN_ON"]) # Gets rid of Pedal Grinding noise when brake is pressed at slow speeds for some models - if self.CP.carFingerprint in (CAR.PILOT, CAR.RIDGELINE): + if self.CP.carFingerprint in (CAR.HONDA_PILOT, CAR.HONDA_RIDGELINE): if ret.brake > 0.1: ret.brakePressed = True @@ -267,7 +262,7 @@ class CarState(CarStateBase): def get_can_parser(self, CP): messages = get_can_messages(CP, self.gearbox_msg) - return CANParser(DBC[CP.carFingerprint]["pt"], messages, get_pt_bus(CP.carFingerprint)) + return CANParser(DBC[CP.carFingerprint]["pt"], messages, CanBus(CP).pt) @staticmethod def get_cam_can_parser(CP): @@ -276,9 +271,10 @@ class CarState(CarStateBase): ] if CP.carFingerprint in HONDA_BOSCH_RADARLESS: - messages.append(("LKAS_HUD", 10)) - if not CP.openpilotLongitudinalControl: - messages.append(("ACC_HUD", 10)) + messages += [ + ("ACC_HUD", 10), + ("LKAS_HUD", 10), + ] elif CP.carFingerprint not in HONDA_BOSCH: messages += [ @@ -287,7 +283,7 @@ class CarState(CarStateBase): ("BRAKE_COMMAND", 50), ] - return CANParser(DBC[CP.carFingerprint]["pt"], messages, 2) + return CANParser(DBC[CP.carFingerprint]["pt"], messages, CanBus(CP).camera) @staticmethod def get_body_can_parser(CP): @@ -296,6 +292,6 @@ class CarState(CarStateBase): ("BSM_STATUS_LEFT", 3), ("BSM_STATUS_RIGHT", 3), ] - bus_body = 0 # B-CAN is forwarded to ACC-CAN radar side (CAN 0 on fake ethernet port) + bus_body = CanBus(CP).radar # B-CAN is forwarded to ACC-CAN radar side (CAN 0 on fake ethernet port) return CANParser(DBC[CP.carFingerprint]["body"], messages, bus_body) return None diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 359ae83b15..8a5b79b41e 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -10,44 +10,10 @@ Ecu = car.CarParams.Ecu FW_VERSIONS = { - CAR.ACCORD: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-6A0-8720\x00\x00', - b'37805-6A0-9520\x00\x00', - b'37805-6A0-9620\x00\x00', - b'37805-6A0-9720\x00\x00', - b'37805-6A0-A540\x00\x00', - b'37805-6A0-A550\x00\x00', - b'37805-6A0-A640\x00\x00', - b'37805-6A0-A650\x00\x00', - b'37805-6A0-A740\x00\x00', - b'37805-6A0-A750\x00\x00', - b'37805-6A0-A840\x00\x00', - b'37805-6A0-A850\x00\x00', - b'37805-6A0-A930\x00\x00', - b'37805-6A0-AF30\x00\x00', - b'37805-6A0-AG30\x00\x00', - b'37805-6A0-AJ10\x00\x00', - b'37805-6A0-C540\x00\x00', - b'37805-6A0-CG20\x00\x00', - b'37805-6A1-H650\x00\x00', - b'37805-6B2-A550\x00\x00', - b'37805-6B2-A560\x00\x00', - b'37805-6B2-A650\x00\x00', - b'37805-6B2-A660\x00\x00', - b'37805-6B2-A720\x00\x00', - b'37805-6B2-A810\x00\x00', - b'37805-6B2-A820\x00\x00', - b'37805-6B2-A920\x00\x00', - b'37805-6B2-AA10\x00\x00', - b'37805-6B2-C520\x00\x00', - b'37805-6B2-C540\x00\x00', - b'37805-6B2-M520\x00\x00', - b'37805-6B2-Y810\x00\x00', - b'37805-6M4-B730\x00\x00', - ], + CAR.HONDA_ACCORD: { (Ecu.shiftByWire, 0x18da0bf1, None): [ b'54008-TVC-A910\x00\x00', + b'54008-TWA-A910\x00\x00', ], (Ecu.transmission, 0x18da1ef1, None): [ b'28101-6A7-A220\x00\x00', @@ -89,6 +55,12 @@ FW_VERSIONS = { b'57114-TVA-C530\x00\x00', b'57114-TVA-E520\x00\x00', b'57114-TVE-H250\x00\x00', + b'57114-TWA-A040\x00\x00', + b'57114-TWA-A050\x00\x00', + b'57114-TWA-A530\x00\x00', + b'57114-TWA-B520\x00\x00', + b'57114-TWA-C510\x00\x00', + b'57114-TWB-H030\x00\x00', ], (Ecu.eps, 0x18da30f1, None): [ b'39990-TBX-H120\x00\x00', @@ -100,6 +72,7 @@ FW_VERSIONS = { b'39990-TVA-X030\x00\x00', b'39990-TVA-X040\x00\x00', b'39990-TVE-H130\x00\x00', + b'39990-TWB-H120\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TBX-H230\x00\x00', @@ -108,40 +81,9 @@ FW_VERSIONS = { b'77959-TVA-H230\x00\x00', b'77959-TVA-L420\x00\x00', b'77959-TVA-X330\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TBX-H310\x00\x00', - b'78109-TVA-A010\x00\x00', - b'78109-TVA-A020\x00\x00', - b'78109-TVA-A030\x00\x00', - b'78109-TVA-A110\x00\x00', - b'78109-TVA-A120\x00\x00', - b'78109-TVA-A130\x00\x00', - b'78109-TVA-A210\x00\x00', - b'78109-TVA-A220\x00\x00', - b'78109-TVA-A230\x00\x00', - b'78109-TVA-A310\x00\x00', - b'78109-TVA-C010\x00\x00', - b'78109-TVA-C130\x00\x00', - b'78109-TVA-L010\x00\x00', - b'78109-TVA-L210\x00\x00', - b'78109-TVA-R310\x00\x00', - b'78109-TVC-A010\x00\x00', - b'78109-TVC-A020\x00\x00', - b'78109-TVC-A030\x00\x00', - b'78109-TVC-A110\x00\x00', - b'78109-TVC-A130\x00\x00', - b'78109-TVC-A210\x00\x00', - b'78109-TVC-A220\x00\x00', - b'78109-TVC-A230\x00\x00', - b'78109-TVC-C010\x00\x00', - b'78109-TVC-C110\x00\x00', - b'78109-TVC-L010\x00\x00', - b'78109-TVC-L210\x00\x00', - b'78109-TVC-M510\x00\x00', - b'78109-TVC-YF10\x00\x00', - b'78109-TVE-H610\x00\x00', - b'78109-TWA-A210\x00\x00', + b'77959-TWA-A440\x00\x00', + b'77959-TWA-L420\x00\x00', + b'77959-TWB-H220\x00\x00', ], (Ecu.hud, 0x18da61f1, None): [ b'78209-TVA-A010\x00\x00', @@ -158,6 +100,9 @@ FW_VERSIONS = { b'36802-TVE-H070\x00\x00', b'36802-TWA-A070\x00\x00', b'36802-TWA-A080\x00\x00', + b'36802-TWA-A210\x00\x00', + b'36802-TWA-A330\x00\x00', + b'36802-TWB-H060\x00\x00', ], (Ecu.fwdCamera, 0x18dab5f1, None): [ b'36161-TBX-H130\x00\x00', @@ -166,106 +111,19 @@ FW_VERSIONS = { b'36161-TVC-A330\x00\x00', b'36161-TVE-H050\x00\x00', b'36161-TWA-A070\x00\x00', + b'36161-TWA-A330\x00\x00', + b'36161-TWB-H040\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-TVA-A010\x00\x00', b'38897-TVA-A020\x00\x00', b'38897-TVA-A230\x00\x00', b'38897-TVA-A240\x00\x00', - ], - }, - CAR.ACCORDH: { - (Ecu.gateway, 0x18daeff1, None): [ b'38897-TWA-A120\x00\x00', b'38897-TWD-J020\x00\x00', ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TWA-A040\x00\x00', - b'57114-TWA-A050\x00\x00', - b'57114-TWA-A530\x00\x00', - b'57114-TWA-B520\x00\x00', - b'57114-TWA-C510\x00\x00', - b'57114-TWB-H030\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TWA-A440\x00\x00', - b'77959-TWA-L420\x00\x00', - b'77959-TWB-H220\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TWA-A010\x00\x00', - b'78109-TWA-A020\x00\x00', - b'78109-TWA-A030\x00\x00', - b'78109-TWA-A110\x00\x00', - b'78109-TWA-A120\x00\x00', - b'78109-TWA-A130\x00\x00', - b'78109-TWA-A210\x00\x00', - b'78109-TWA-A220\x00\x00', - b'78109-TWA-A230\x00\x00', - b'78109-TWA-A610\x00\x00', - b'78109-TWA-H210\x00\x00', - b'78109-TWA-L010\x00\x00', - b'78109-TWA-L210\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TWA-A910\x00\x00', - ], - (Ecu.hud, 0x18da61f1, None): [ - b'78209-TVA-A010\x00\x00', - b'78209-TVA-A110\x00\x00', - ], - (Ecu.fwdCamera, 0x18dab5f1, None): [ - b'36161-TWA-A070\x00\x00', - b'36161-TWA-A330\x00\x00', - b'36161-TWB-H040\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TWA-A070\x00\x00', - b'36802-TWA-A080\x00\x00', - b'36802-TWA-A210\x00\x00', - b'36802-TWA-A330\x00\x00', - b'36802-TWB-H060\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TVA-A150\x00\x00', - b'39990-TVA-A160\x00\x00', - b'39990-TVA-A340\x00\x00', - b'39990-TWB-H120\x00\x00', - ], }, - CAR.CIVIC: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-5AA-A640\x00\x00', - b'37805-5AA-A650\x00\x00', - b'37805-5AA-A670\x00\x00', - b'37805-5AA-A680\x00\x00', - b'37805-5AA-A810\x00\x00', - b'37805-5AA-C640\x00\x00', - b'37805-5AA-C680\x00\x00', - b'37805-5AA-C820\x00\x00', - b'37805-5AA-L650\x00\x00', - b'37805-5AA-L660\x00\x00', - b'37805-5AA-L680\x00\x00', - b'37805-5AA-L690\x00\x00', - b'37805-5AA-L810\x00\x00', - b'37805-5AG-Q710\x00\x00', - b'37805-5AJ-A610\x00\x00', - b'37805-5AJ-A620\x00\x00', - b'37805-5AJ-L610\x00\x00', - b'37805-5BA-A310\x00\x00', - b'37805-5BA-A510\x00\x00', - b'37805-5BA-A740\x00\x00', - b'37805-5BA-A760\x00\x00', - b'37805-5BA-A930\x00\x00', - b'37805-5BA-A960\x00\x00', - b'37805-5BA-C640\x00\x00', - b'37805-5BA-C860\x00\x00', - b'37805-5BA-L410\x00\x00', - b'37805-5BA-L760\x00\x00', - b'37805-5BA-L930\x00\x00', - b'37805-5BA-L940\x00\x00', - b'37805-5BA-L960\x00\x00', - ], + CAR.HONDA_CIVIC: { (Ecu.transmission, 0x18da1ef1, None): [ b'28101-5CG-A040\x00\x00', b'28101-5CG-A050\x00\x00', @@ -299,25 +157,6 @@ FW_VERSIONS = { b'77959-TBG-A030\x00\x00', b'77959-TEA-Q820\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TBA-A510\x00\x00', - b'78109-TBA-A520\x00\x00', - b'78109-TBA-A530\x00\x00', - b'78109-TBA-C310\x00\x00', - b'78109-TBA-C520\x00\x00', - b'78109-TBC-A310\x00\x00', - b'78109-TBC-A320\x00\x00', - b'78109-TBC-A510\x00\x00', - b'78109-TBC-A520\x00\x00', - b'78109-TBC-A530\x00\x00', - b'78109-TBC-C510\x00\x00', - b'78109-TBC-C520\x00\x00', - b'78109-TBC-C530\x00\x00', - b'78109-TBH-A510\x00\x00', - b'78109-TBH-A530\x00\x00', - b'78109-TED-Q510\x00\x00', - b'78109-TEG-A310\x00\x00', - ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-TBA-A020\x00\x00', b'36161-TBA-A030\x00\x00', @@ -333,61 +172,7 @@ FW_VERSIONS = { b'38897-TBA-A020\x00\x00', ], }, - CAR.CIVIC_BOSCH: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-5AA-A940\x00\x00', - b'37805-5AA-A950\x00\x00', - b'37805-5AA-C950\x00\x00', - b'37805-5AA-L940\x00\x00', - b'37805-5AA-L950\x00\x00', - b'37805-5AG-Z910\x00\x00', - b'37805-5AJ-A750\x00\x00', - b'37805-5AJ-L750\x00\x00', - b'37805-5AK-T530\x00\x00', - b'37805-5AN-A750\x00\x00', - b'37805-5AN-A830\x00\x00', - b'37805-5AN-A840\x00\x00', - b'37805-5AN-A930\x00\x00', - b'37805-5AN-A940\x00\x00', - b'37805-5AN-A950\x00\x00', - b'37805-5AN-AG20\x00\x00', - b'37805-5AN-AH20\x00\x00', - b'37805-5AN-AJ30\x00\x00', - b'37805-5AN-AK10\x00\x00', - b'37805-5AN-AK20\x00\x00', - b'37805-5AN-AR10\x00\x00', - b'37805-5AN-AR20\x00\x00', - b'37805-5AN-C650\x00\x00', - b'37805-5AN-CH20\x00\x00', - b'37805-5AN-E630\x00\x00', - b'37805-5AN-E720\x00\x00', - b'37805-5AN-E820\x00\x00', - b'37805-5AN-J820\x00\x00', - b'37805-5AN-L840\x00\x00', - b'37805-5AN-L930\x00\x00', - b'37805-5AN-L940\x00\x00', - b'37805-5AN-LF20\x00\x00', - b'37805-5AN-LH20\x00\x00', - b'37805-5AN-LJ20\x00\x00', - b'37805-5AN-LR20\x00\x00', - b'37805-5AN-LS20\x00\x00', - b'37805-5AW-G720\x00\x00', - b'37805-5AZ-E850\x00\x00', - b'37805-5AZ-G540\x00\x00', - b'37805-5AZ-G740\x00\x00', - b'37805-5AZ-G840\x00\x00', - b'37805-5BB-A530\x00\x00', - b'37805-5BB-A540\x00\x00', - b'37805-5BB-A620\x00\x00', - b'37805-5BB-A630\x00\x00', - b'37805-5BB-A640\x00\x00', - b'37805-5BB-C540\x00\x00', - b'37805-5BB-C630\x00\x00', - b'37805-5BB-C640\x00\x00', - b'37805-5BB-L540\x00\x00', - b'37805-5BB-L630\x00\x00', - b'37805-5BB-L640\x00\x00', - ], + CAR.HONDA_CIVIC_BOSCH: { (Ecu.transmission, 0x18da1ef1, None): [ b'28101-5CG-A920\x00\x00', b'28101-5CG-AB10\x00\x00', @@ -420,6 +205,7 @@ FW_VERSIONS = { b'57114-TGG-G320\x00\x00', b'57114-TGG-L320\x00\x00', b'57114-TGG-L330\x00\x00', + b'57114-TGH-L130\x00\x00', b'57114-TGK-T320\x00\x00', b'57114-TGL-G330\x00\x00', ], @@ -433,6 +219,7 @@ FW_VERSIONS = { b'39990-TGG-A020\x00\x00', b'39990-TGG-A120\x00\x00', b'39990-TGG-J510\x00\x00', + b'39990-TGH-J530\x00\x00', b'39990-TGL-E130\x00\x00', b'39990-TGN-E120\x00\x00', ], @@ -447,40 +234,7 @@ FW_VERSIONS = { b'77959-TGG-G110\x00\x00', b'77959-TGG-J320\x00\x00', b'77959-TGG-Z820\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TBA-A110\x00\x00', - b'78109-TBA-A910\x00\x00', - b'78109-TBA-C340\x00\x00', - b'78109-TBA-C910\x00\x00', - b'78109-TBC-A740\x00\x00', - b'78109-TBC-C540\x00\x00', - b'78109-TBG-A110\x00\x00', - b'78109-TBH-A710\x00\x00', - b'78109-TEG-A720\x00\x00', - b'78109-TFJ-G020\x00\x00', - b'78109-TGG-9020\x00\x00', - b'78109-TGG-A210\x00\x00', - b'78109-TGG-A220\x00\x00', - b'78109-TGG-A310\x00\x00', - b'78109-TGG-A320\x00\x00', - b'78109-TGG-A330\x00\x00', - b'78109-TGG-A610\x00\x00', - b'78109-TGG-A620\x00\x00', - b'78109-TGG-A810\x00\x00', - b'78109-TGG-A820\x00\x00', - b'78109-TGG-C010\x00\x00', - b'78109-TGG-C220\x00\x00', - b'78109-TGG-E110\x00\x00', - b'78109-TGG-G030\x00\x00', - b'78109-TGG-G230\x00\x00', - b'78109-TGG-G410\x00\x00', - b'78109-TGK-Z410\x00\x00', - b'78109-TGL-G120\x00\x00', - b'78109-TGL-G130\x00\x00', - b'78109-TGL-G210\x00\x00', - b'78109-TGL-G230\x00\x00', - b'78109-TGL-GM10\x00\x00', + b'77959-TGH-J110\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TBA-A150\x00\x00', @@ -492,6 +246,7 @@ FW_VERSIONS = { b'36802-TGG-A130\x00\x00', b'36802-TGG-G040\x00\x00', b'36802-TGG-G130\x00\x00', + b'36802-TGH-A140\x00\x00', b'36802-TGK-Q120\x00\x00', b'36802-TGL-G040\x00\x00', ], @@ -506,6 +261,7 @@ FW_VERSIONS = { b'36161-TGG-G070\x00\x00', b'36161-TGG-G130\x00\x00', b'36161-TGG-G140\x00\x00', + b'36161-TGH-A140\x00\x00', b'36161-TGK-Q120\x00\x00', b'36161-TGL-G050\x00\x00', b'36161-TGL-G070\x00\x00', @@ -513,16 +269,13 @@ FW_VERSIONS = { (Ecu.gateway, 0x18daeff1, None): [ b'38897-TBA-A020\x00\x00', b'38897-TBA-A110\x00\x00', + b'38897-TGH-A010\x00\x00', ], (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ b'39494-TGL-G030\x00\x00', ], }, - CAR.CIVIC_BOSCH_DIESEL: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-59N-G630\x00\x00', - b'37805-59N-G830\x00\x00', - ], + CAR.HONDA_CIVIC_BOSCH_DIESEL: { (Ecu.transmission, 0x18da1ef1, None): [ b'28101-59Y-G220\x00\x00', b'28101-59Y-G620\x00\x00', @@ -537,10 +290,6 @@ FW_VERSIONS = { b'77959-TFK-G210\x00\x00', b'77959-TGN-G220\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TFK-G020\x00\x00', - b'78109-TGN-G120\x00\x00', - ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TFK-G130\x00\x00', b'36802-TGN-G130\x00\x00', @@ -556,7 +305,7 @@ FW_VERSIONS = { b'38897-TBA-A020\x00\x00', ], }, - CAR.CRV: { + CAR.HONDA_CRV: { (Ecu.vsa, 0x18da28f1, None): [ b'57114-T1W-A230\x00\x00', b'57114-T1W-A240\x00\x00', @@ -565,52 +314,13 @@ FW_VERSIONS = { (Ecu.srs, 0x18da53f1, None): [ b'77959-T0A-A230\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T1W-A210\x00\x00', - b'78109-T1W-C210\x00\x00', - b'78109-T1X-A210\x00\x00', - ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-T1W-A830\x00\x00', b'36161-T1W-C830\x00\x00', b'36161-T1X-A830\x00\x00', ], }, - CAR.CRV_5G: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-5PA-3060\x00\x00', - b'37805-5PA-3080\x00\x00', - b'37805-5PA-3180\x00\x00', - b'37805-5PA-4050\x00\x00', - b'37805-5PA-4150\x00\x00', - b'37805-5PA-6520\x00\x00', - b'37805-5PA-6530\x00\x00', - b'37805-5PA-6630\x00\x00', - b'37805-5PA-6640\x00\x00', - b'37805-5PA-7630\x00\x00', - b'37805-5PA-9530\x00\x00', - b'37805-5PA-9630\x00\x00', - b'37805-5PA-9640\x00\x00', - b'37805-5PA-9730\x00\x00', - b'37805-5PA-9830\x00\x00', - b'37805-5PA-9840\x00\x00', - b'37805-5PA-A650\x00\x00', - b'37805-5PA-A670\x00\x00', - b'37805-5PA-A680\x00\x00', - b'37805-5PA-A850\x00\x00', - b'37805-5PA-A870\x00\x00', - b'37805-5PA-A880\x00\x00', - b'37805-5PA-A890\x00\x00', - b'37805-5PA-AB10\x00\x00', - b'37805-5PA-AD10\x00\x00', - b'37805-5PA-AF20\x00\x00', - b'37805-5PA-AH20\x00\x00', - b'37805-5PA-BF10\x00\x00', - b'37805-5PA-C680\x00\x00', - b'37805-5PD-Q630\x00\x00', - b'37805-5PF-F730\x00\x00', - b'37805-5PF-M630\x00\x00', - ], + CAR.HONDA_CRV_5G: { (Ecu.transmission, 0x18da1ef1, None): [ b'28101-5RG-A020\x00\x00', b'28101-5RG-A030\x00\x00', @@ -649,25 +359,6 @@ FW_VERSIONS = { b'46114-TLA-A930\x00\x00', b'46114-TMC-U020\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TLA-A020\x00\x00', - b'78109-TLA-A110\x00\x00', - b'78109-TLA-A120\x00\x00', - b'78109-TLA-A210\x00\x00', - b'78109-TLA-A220\x00\x00', - b'78109-TLA-C020\x00\x00', - b'78109-TLA-C110\x00\x00', - b'78109-TLA-C210\x00\x00', - b'78109-TLA-C310\x00\x00', - b'78109-TLB-A020\x00\x00', - b'78109-TLB-A110\x00\x00', - b'78109-TLB-A120\x00\x00', - b'78109-TLB-A210\x00\x00', - b'78109-TLB-A220\x00\x00', - b'78109-TMC-Q210\x00\x00', - b'78109-TMM-F210\x00\x00', - b'78109-TMM-M110\x00\x00', - ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-TLA-A010\x00\x00', b'38897-TLA-A110\x00\x00', @@ -704,11 +395,7 @@ FW_VERSIONS = { b'77959-TMM-F040\x00\x00', ], }, - CAR.CRV_EU: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-R5Z-G740\x00\x00', - b'37805-R5Z-G780\x00\x00', - ], + CAR.HONDA_CRV_EU: { (Ecu.vsa, 0x18da28f1, None): [ b'57114-T1V-G920\x00\x00', ], @@ -722,15 +409,11 @@ FW_VERSIONS = { b'28101-5LH-E120\x00\x00', b'28103-5LH-E100\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T1B-3050\x00\x00', - b'78109-T1V-G020\x00\x00', - ], (Ecu.srs, 0x18da53f1, None): [ b'77959-T1G-G940\x00\x00', ], }, - CAR.CRV_HYBRID: { + CAR.HONDA_CRV_HYBRID: { (Ecu.vsa, 0x18da28f1, None): [ b'57114-TMB-H030\x00\x00', b'57114-TPA-G020\x00\x00', @@ -756,12 +439,6 @@ FW_VERSIONS = { b'36161-TPG-A030\x00\x00', b'36161-TPG-A040\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TMB-H220\x00\x00', - b'78109-TPA-G520\x00\x00', - b'78109-TPG-A110\x00\x00', - b'78109-TPG-A210\x00\x00', - ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TMB-H040\x00\x00', b'36802-TPA-E040\x00\x00', @@ -775,7 +452,7 @@ FW_VERSIONS = { b'77959-TLA-H240\x00\x00', ], }, - CAR.FIT: { + CAR.HONDA_FIT: { (Ecu.vsa, 0x18da28f1, None): [ b'57114-T5R-L020\x00\x00', b'57114-T5R-L220\x00\x00', @@ -787,12 +464,6 @@ FW_VERSIONS = { (Ecu.gateway, 0x18daeff1, None): [ b'38897-T5A-J010\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T5A-A210\x00\x00', - b'78109-T5A-A410\x00\x00', - b'78109-T5A-A420\x00\x00', - b'78109-T5A-A910\x00\x00', - ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-T5R-A040\x00\x00', b'36161-T5R-A240\x00\x00', @@ -802,7 +473,7 @@ FW_VERSIONS = { b'77959-T5R-A230\x00\x00', ], }, - CAR.FREED: { + CAR.HONDA_FREED: { (Ecu.gateway, 0x18daeff1, None): [ b'38897-TDK-J010\x00\x00', ], @@ -814,37 +485,17 @@ FW_VERSIONS = { b'57114-TDK-J120\x00\x00', b'57114-TDK-J330\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TDK-J310\x00\x00', - b'78109-TDK-J320\x00\x00', - ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-TDK-J070\x00\x00', b'36161-TDK-J080\x00\x00', b'36161-TDK-J530\x00\x00', ], }, - CAR.ODYSSEY: { + CAR.HONDA_ODYSSEY: { (Ecu.gateway, 0x18daeff1, None): [ b'38897-THR-A010\x00\x00', b'38897-THR-A020\x00\x00', ], - (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', - b'37805-5MR-A250\x00\x00', - b'37805-5MR-A310\x00\x00', - b'37805-5MR-A740\x00\x00', - b'37805-5MR-A750\x00\x00', - b'37805-5MR-A840\x00\x00', - b'37805-5MR-C620\x00\x00', - b'37805-5MR-D530\x00\x00', - b'37805-5MR-K730\x00\x00', - ], (Ecu.eps, 0x18da30f1, None): [ b'39990-THR-A020\x00\x00', b'39990-THR-A030\x00\x00', @@ -889,53 +540,17 @@ FW_VERSIONS = { b'57114-THR-A040\x00\x00', b'57114-THR-A110\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-THR-A220\x00\x00', - b'78109-THR-A230\x00\x00', - b'78109-THR-A420\x00\x00', - b'78109-THR-A430\x00\x00', - b'78109-THR-A720\x00\x00', - b'78109-THR-A730\x00\x00', - b'78109-THR-A820\x00\x00', - b'78109-THR-A830\x00\x00', - b'78109-THR-AB20\x00\x00', - b'78109-THR-AB30\x00\x00', - b'78109-THR-AB40\x00\x00', - b'78109-THR-AC20\x00\x00', - b'78109-THR-AC30\x00\x00', - b'78109-THR-AC40\x00\x00', - b'78109-THR-AC50\x00\x00', - b'78109-THR-AD30\x00\x00', - b'78109-THR-AE20\x00\x00', - b'78109-THR-AE30\x00\x00', - b'78109-THR-AE40\x00\x00', - b'78109-THR-AK10\x00\x00', - b'78109-THR-AL10\x00\x00', - b'78109-THR-AN10\x00\x00', - b'78109-THR-C220\x00\x00', - 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', - b'78109-THR-K120\x00\x00', - ], (Ecu.shiftByWire, 0x18da0bf1, None): [ b'54008-THR-A020\x00\x00', ], }, - CAR.ODYSSEY_CHN: { + CAR.HONDA_ODYSSEY_CHN: { (Ecu.eps, 0x18da30f1, None): [ b'39990-T6D-H220\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-T6A-J010\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T6A-F310\x00\x00', - ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-T6A-P040\x00\x00', ], @@ -943,12 +558,13 @@ FW_VERSIONS = { b'77959-T6A-P110\x00\x00', ], }, - CAR.PILOT: { + CAR.HONDA_PILOT: { (Ecu.shiftByWire, 0x18da0bf1, None): [ b'54008-TG7-A520\x00\x00', b'54008-TG7-A530\x00\x00', ], (Ecu.transmission, 0x18da1ef1, None): [ + b'28101-5EY-A040\x00\x00', b'28101-5EY-A050\x00\x00', b'28101-5EY-A100\x00\x00', b'28101-5EY-A430\x00\x00', @@ -964,36 +580,6 @@ FW_VERSIONS = { b'28101-5EZ-A700\x00\x00', b'28103-5EY-A110\x00\x00', ], - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-RLV-4060\x00\x00', - b'37805-RLV-4070\x00\x00', - b'37805-RLV-5140\x00\x00', - b'37805-RLV-5230\x00\x00', - b'37805-RLV-A830\x00\x00', - b'37805-RLV-A840\x00\x00', - b'37805-RLV-B210\x00\x00', - b'37805-RLV-B220\x00\x00', - b'37805-RLV-B420\x00\x00', - b'37805-RLV-B430\x00\x00', - b'37805-RLV-B620\x00\x00', - b'37805-RLV-B710\x00\x00', - b'37805-RLV-B720\x00\x00', - b'37805-RLV-C430\x00\x00', - b'37805-RLV-C510\x00\x00', - b'37805-RLV-C520\x00\x00', - b'37805-RLV-C530\x00\x00', - b'37805-RLV-C910\x00\x00', - 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', - b'37805-RLV-L410\x00\x00', - b'37805-RLV-L430\x00\x00', - b'37805-RLV-L850\x00\x00', - ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-TG7-A030\x00\x00', b'38897-TG7-A040\x00\x00', @@ -1039,43 +625,6 @@ FW_VERSIONS = { b'77959-TGS-A010\x00\x00', b'77959-TGS-A110\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TG7-A040\x00\x00', - b'78109-TG7-A050\x00\x00', - b'78109-TG7-A420\x00\x00', - b'78109-TG7-A520\x00\x00', - b'78109-TG7-A720\x00\x00', - b'78109-TG7-AJ10\x00\x00', - b'78109-TG7-AJ20\x00\x00', - b'78109-TG7-AK10\x00\x00', - b'78109-TG7-AK20\x00\x00', - b'78109-TG7-AM20\x00\x00', - b'78109-TG7-AP10\x00\x00', - b'78109-TG7-AP20\x00\x00', - b'78109-TG7-AS20\x00\x00', - b'78109-TG7-AT20\x00\x00', - b'78109-TG7-AU20\x00\x00', - b'78109-TG7-AX20\x00\x00', - b'78109-TG7-D020\x00\x00', - b'78109-TG7-DJ10\x00\x00', - b'78109-TG7-YK20\x00\x00', - b'78109-TG8-A420\x00\x00', - b'78109-TG8-A520\x00\x00', - b'78109-TG8-AJ10\x00\x00', - b'78109-TG8-AJ20\x00\x00', - b'78109-TG8-AK20\x00\x00', - b'78109-TG8-AS20\x00\x00', - b'78109-TGS-AB10\x00\x00', - b'78109-TGS-AC10\x00\x00', - b'78109-TGS-AD10\x00\x00', - b'78109-TGS-AJ20\x00\x00', - b'78109-TGS-AK20\x00\x00', - b'78109-TGS-AP20\x00\x00', - b'78109-TGS-AT20\x00\x00', - b'78109-TGS-AX20\x00\x00', - b'78109-TGT-AJ20\x00\x00', - b'78109-TGT-AK30\x00\x00', - ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-TG7-A130\x00\x00', b'57114-TG7-A140\x00\x00', @@ -1084,6 +633,7 @@ FW_VERSIONS = { b'57114-TG7-A630\x00\x00', b'57114-TG7-A730\x00\x00', b'57114-TG8-A140\x00\x00', + b'57114-TG8-A230\x00\x00', b'57114-TG8-A240\x00\x00', b'57114-TG8-A630\x00\x00', b'57114-TG8-A730\x00\x00', @@ -1105,32 +655,8 @@ FW_VERSIONS = { b'77959-TX4-C010\x00\x00', b'77959-TX4-C020\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TX4-A210\x00\x00', - b'78109-TX4-A310\x00\x00', - b'78109-TX5-A210\x00\x00', - b'78109-TX5-A310\x00\x00', - ], }, CAR.ACURA_RDX_3G: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-5YF-A130\x00\x00', - b'37805-5YF-A230\x00\x00', - b'37805-5YF-A320\x00\x00', - b'37805-5YF-A330\x00\x00', - b'37805-5YF-A420\x00\x00', - b'37805-5YF-A430\x00\x00', - b'37805-5YF-A750\x00\x00', - b'37805-5YF-A760\x00\x00', - b'37805-5YF-A850\x00\x00', - b'37805-5YF-A870\x00\x00', - b'37805-5YF-AD20\x00\x00', - b'37805-5YF-C210\x00\x00', - b'37805-5YF-C220\x00\x00', - b'37805-5YF-C410\x00\x00', - b'37805-5YF-C420\x00\x00', - b'37805-5YF-C430\x00\x00', - ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-TJB-A030\x00\x00', b'57114-TJB-A040\x00\x00', @@ -1160,24 +686,6 @@ FW_VERSIONS = { b'28102-5YL-A700\x00\x00', b'28102-5YL-A711\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TJB-A140\x00\x00', - b'78109-TJB-A240\x00\x00', - b'78109-TJB-A420\x00\x00', - b'78109-TJB-AB10\x00\x00', - b'78109-TJB-AD10\x00\x00', - b'78109-TJB-AF10\x00\x00', - b'78109-TJB-AQ20\x00\x00', - b'78109-TJB-AR10\x00\x00', - b'78109-TJB-AS10\x00\x00', - b'78109-TJB-AU10\x00\x00', - b'78109-TJB-AW10\x00\x00', - b'78109-TJC-A240\x00\x00', - b'78109-TJC-A420\x00\x00', - b'78109-TJC-AA10\x00\x00', - b'78109-TJC-AD10\x00\x00', - b'78109-TJC-AF10\x00\x00', - ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TJB-A040\x00\x00', b'77959-TJB-A120\x00\x00', @@ -1202,7 +710,7 @@ FW_VERSIONS = { b'39990-TJB-A130\x00\x00', ], }, - CAR.RIDGELINE: { + CAR.HONDA_RIDGELINE: { (Ecu.eps, 0x18da30f1, None): [ b'39990-T6Z-A020\x00\x00', b'39990-T6Z-A030\x00\x00', @@ -1222,18 +730,6 @@ FW_VERSIONS = { b'38897-T6Z-A010\x00\x00', 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', - b'78109-T6Z-A810\x00\x00', - b'78109-T6Z-A910\x00\x00', - b'78109-T6Z-AA10\x00\x00', - b'78109-T6Z-C620\x00\x00', - b'78109-T6Z-C910\x00\x00', - b'78109-TJZ-A510\x00\x00', - ], (Ecu.srs, 0x18da53f1, None): [ b'77959-T6Z-A020\x00\x00', ], @@ -1245,7 +741,7 @@ FW_VERSIONS = { b'57114-TJZ-A520\x00\x00', ], }, - CAR.INSIGHT: { + CAR.HONDA_INSIGHT: { (Ecu.eps, 0x18da30f1, None): [ b'39990-TXM-A040\x00\x00', ], @@ -1270,15 +766,8 @@ FW_VERSIONS = { (Ecu.gateway, 0x18daeff1, None): [ b'38897-TXM-A020\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TXM-A010\x00\x00', - b'78109-TXM-A020\x00\x00', - b'78109-TXM-A030\x00\x00', - b'78109-TXM-A110\x00\x00', - b'78109-TXM-C010\x00\x00', - ], }, - CAR.HRV: { + CAR.HONDA_HRV: { (Ecu.gateway, 0x18daeff1, None): [ b'38897-T7A-A010\x00\x00', b'38897-T7A-A110\x00\x00', @@ -1295,36 +784,32 @@ FW_VERSIONS = { (Ecu.srs, 0x18da53f1, None): [ b'77959-T7A-A230\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-THW-A110\x00\x00', - b'78109-THX-A110\x00\x00', - b'78109-THX-A120\x00\x00', - b'78109-THX-A210\x00\x00', - b'78109-THX-A220\x00\x00', - b'78109-THX-C220\x00\x00', - ], }, - CAR.HRV_3G: { + CAR.HONDA_HRV_3G: { (Ecu.eps, 0x18da30f1, None): [ + b'39990-3M0-G110\x00\x00', b'39990-3W0-A030\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ + b'38897-3M0-M110\x00\x00', b'38897-3W1-A010\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ + b'77959-3M0-K840\x00\x00', b'77959-3V0-A820\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78108-3V1-A220\x00\x00', + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'8S102-3M6-P030\x00\x00', + b'8S102-3W0-A060\x00\x00', + b'8S102-3W0-AB10\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ + b'57114-3M6-M010\x00\x00', b'57114-3W0-A040\x00\x00', ], (Ecu.transmission, 0x18da1ef1, None): [ b'28101-6EH-A010\x00\x00', - ], - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-6CT-A710\x00\x00', + b'28101-6JC-M310\x00\x00', ], (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ b'46114-3W0-A020\x00\x00', @@ -1342,11 +827,6 @@ FW_VERSIONS = { b'77959-TX6-A230\x00\x00', b'77959-TX6-C210\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T3R-A120\x00\x00', - b'78109-T3R-A410\x00\x00', - b'78109-TV9-A510\x00\x00', - ], }, CAR.HONDA_E: { (Ecu.eps, 0x18da30f1, None): [ @@ -1361,9 +841,6 @@ FW_VERSIONS = { (Ecu.srs, 0x18da53f1, None): [ b'77959-TYF-G430\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78108-TYF-G610\x00\x00', - ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TYF-E030\x00\x00', ], @@ -1374,7 +851,7 @@ FW_VERSIONS = { b'57114-TYF-E030\x00\x00', ], }, - CAR.CIVIC_2022: { + CAR.HONDA_CIVIC_2022: { (Ecu.eps, 0x18da30f1, None): [ b'39990-T24-T120\x00\x00', b'39990-T39-A130\x00\x00', @@ -1395,15 +872,6 @@ FW_VERSIONS = { b'77959-T47-A940\x00\x00', b'77959-T47-A950\x00\x00', ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78108-T21-A220\x00\x00', - b'78108-T21-A230\x00\x00', - b'78108-T21-A620\x00\x00', - b'78108-T21-A740\x00\x00', - b'78108-T21-MB10\x00\x00', - b'78108-T22-A020\x00\x00', - b'78108-T23-A110\x00\x00', - ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-T20-A060\x00\x00', b'36161-T20-A070\x00\x00', @@ -1423,14 +891,5 @@ FW_VERSIONS = { b'28101-65H-A120\x00\x00', b'28101-65J-N010\x00\x00', ], - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-64A-A540\x00\x00', - b'37805-64A-A620\x00\x00', - b'37805-64D-P510\x00\x00', - b'37805-64L-A540\x00\x00', - b'37805-64S-A540\x00\x00', - b'37805-64S-A720\x00\x00', - b'37805-64S-AA10\x00\x00', - ], }, } diff --git a/selfdrive/car/honda/hondacan.py b/selfdrive/car/honda/hondacan.py index a8cbad78ce..1be496d951 100644 --- a/selfdrive/car/honda/hondacan.py +++ b/selfdrive/car/honda/hondacan.py @@ -1,4 +1,5 @@ from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car import CanBusBase from openpilot.selfdrive.car.honda.values import HondaFlags, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, CAR, CarControllerParams # CAN bus layout with relay @@ -8,15 +9,34 @@ from openpilot.selfdrive.car.honda.values import HondaFlags, HONDA_BOSCH, HONDA_ # 3 = F-CAN A - OBDII port -def get_pt_bus(car_fingerprint): - return 1 if car_fingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) else 0 +class CanBus(CanBusBase): + def __init__(self, CP=None, fingerprint=None) -> None: + # use fingerprint if specified + super().__init__(CP if fingerprint is None else None, fingerprint) + + if CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS): + self._pt, self._radar = self.offset + 1, self.offset + else: + self._pt, self._radar = self.offset, self.offset + 1 + + @property + def pt(self) -> int: + return self._pt + + @property + def radar(self) -> int: + return self._radar + + @property + def camera(self) -> int: + return self.offset + 2 -def get_lkas_cmd_bus(car_fingerprint, radar_disabled=False): +def get_lkas_cmd_bus(CAN, car_fingerprint, radar_disabled=False): no_radar = car_fingerprint in HONDA_BOSCH_RADARLESS if radar_disabled or no_radar: # when radar is disabled, steering commands are sent directly to powertrain bus - return get_pt_bus(car_fingerprint) + return CAN.pt # normally steering commands are sent to radar, which forwards them to powertrain bus return 0 @@ -26,7 +46,7 @@ def get_cruise_speed_conversion(car_fingerprint: str, is_metric: bool) -> float: return CV.MPH_TO_MS if car_fingerprint in HONDA_BOSCH_RADARLESS and not is_metric else CV.KPH_TO_MS -def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw, car_fingerprint, stock_brake): +def create_brake_command(packer, CAN, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw, car_fingerprint, stock_brake): # TODO: do we loose pressure if we keep pump off for long? brakelights = apply_brake > 0 brake_rq = apply_brake > 0 @@ -47,13 +67,11 @@ def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_ "AEB_REQ_2": 0, "AEB_STATUS": 0, } - bus = get_pt_bus(car_fingerprint) - return packer.make_can_msg("BRAKE_COMMAND", bus, values) + return packer.make_can_msg("BRAKE_COMMAND", CAN.pt, values) -def create_acc_commands(packer, enabled, active, accel, gas, stopping_counter, car_fingerprint): +def create_acc_commands(packer, CAN, enabled, active, accel, gas, stopping_counter, car_fingerprint): commands = [] - bus = get_pt_bus(car_fingerprint) min_gas_accel = CarControllerParams.BOSCH_GAS_LOOKUP_BP[0] control_on = 5 if enabled else 0 @@ -90,43 +108,43 @@ def create_acc_commands(packer, enabled, active, accel, gas, stopping_counter, c "SET_TO_75": 0x75, "SET_TO_30": 0x30, } - commands.append(packer.make_can_msg("ACC_CONTROL_ON", bus, acc_control_on_values)) + commands.append(packer.make_can_msg("ACC_CONTROL_ON", CAN.pt, acc_control_on_values)) - commands.append(packer.make_can_msg("ACC_CONTROL", bus, acc_control_values)) + commands.append(packer.make_can_msg("ACC_CONTROL", CAN.pt, acc_control_values)) return commands -def create_steering_control(packer, apply_steer, lkas_active, car_fingerprint, radar_disabled): +def create_steering_control(packer, CAN, apply_steer, lkas_active, car_fingerprint, radar_disabled): values = { "STEER_TORQUE": apply_steer if lkas_active else 0, "STEER_TORQUE_REQUEST": lkas_active, } - bus = get_lkas_cmd_bus(car_fingerprint, radar_disabled) + bus = get_lkas_cmd_bus(CAN, car_fingerprint, radar_disabled) return packer.make_can_msg("STEERING_CONTROL", bus, values) -def create_bosch_supplemental_1(packer, car_fingerprint): +def create_bosch_supplemental_1(packer, CAN, car_fingerprint): # non-active params values = { "SET_ME_X04": 0x04, "SET_ME_X80": 0x80, "SET_ME_X10": 0x10, } - bus = get_lkas_cmd_bus(car_fingerprint) + bus = get_lkas_cmd_bus(CAN, car_fingerprint) return packer.make_can_msg("BOSCH_SUPPLEMENTAL_1", bus, values) -def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, lkas_hud): +def create_ui_commands(packer, CAN, CP, enabled, pcm_speed, hud, is_metric, acc_hud, lkas_hud): commands = [] - bus_pt = get_pt_bus(CP.carFingerprint) radar_disabled = CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and CP.openpilotLongitudinalControl - bus_lkas = get_lkas_cmd_bus(CP.carFingerprint, radar_disabled) + bus_lkas = get_lkas_cmd_bus(CAN, CP.carFingerprint, radar_disabled) if CP.openpilotLongitudinalControl: acc_hud_values = { 'CRUISE_SPEED': hud.v_cruise, 'ENABLE_MINI_CAR': 1 if enabled else 0, - 'HUD_DISTANCE': 0, # max distance setting on display + # only moves the lead car without ACC_ON + 'HUD_DISTANCE': (hud.lead_distance_bars + 1) % 4, # wraps to 0 at 4 bars 'IMPERIAL_UNIT': int(not is_metric), 'HUD_LEAD': 2 if enabled and hud.lead_visible else 1 if enabled else 0, 'SET_ME_X01_2': 1, @@ -137,6 +155,8 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, acc_hud_values['FCM_OFF'] = 1 acc_hud_values['FCM_OFF_2'] = 1 else: + # Shows the distance bars, TODO: stock camera shows updates temporarily while disabled + acc_hud_values['ACC_ON'] = int(enabled) acc_hud_values['PCM_SPEED'] = pcm_speed * CV.MS_TO_KPH acc_hud_values['PCM_GAS'] = hud.pcm_accel acc_hud_values['SET_ME_X01'] = 1 @@ -144,7 +164,7 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, acc_hud_values['FCM_OFF_2'] = acc_hud['FCM_OFF_2'] acc_hud_values['FCM_PROBLEM'] = acc_hud['FCM_PROBLEM'] acc_hud_values['ICONS'] = acc_hud['ICONS'] - commands.append(packer.make_can_msg("ACC_HUD", bus_pt, acc_hud_values)) + commands.append(packer.make_can_msg("ACC_HUD", CAN.pt, acc_hud_values)) lkas_hud_values = { 'SET_ME_X41': 0x41, @@ -173,19 +193,19 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, 'CMBS_OFF': 0x01, 'SET_TO_1': 0x01, } - commands.append(packer.make_can_msg('RADAR_HUD', bus_pt, radar_hud_values)) + commands.append(packer.make_can_msg('RADAR_HUD', CAN.pt, radar_hud_values)) - if CP.carFingerprint == CAR.CIVIC_BOSCH: - commands.append(packer.make_can_msg("LEGACY_BRAKE_COMMAND", bus_pt, {})) + if CP.carFingerprint == CAR.HONDA_CIVIC_BOSCH: + commands.append(packer.make_can_msg("LEGACY_BRAKE_COMMAND", CAN.pt, {})) return commands -def spam_buttons_command(packer, button_val, car_fingerprint): +def spam_buttons_command(packer, CAN, button_val, car_fingerprint): values = { 'CRUISE_BUTTONS': button_val, 'CRUISE_SETTING': 0, } # send buttons to camera on radarless cars - bus = 2 if car_fingerprint in HONDA_BOSCH_RADARLESS else get_pt_bus(car_fingerprint) + bus = CAN.camera if car_fingerprint in HONDA_BOSCH_RADARLESS else CAN.pt return packer.make_can_msg("SCM_BUTTONS", bus, values) diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 153fa1e635..fdcba8bb81 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -3,9 +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_RADARLESS +from openpilot.selfdrive.car.honda.hondacan import CanBus +from openpilot.selfdrive.car.honda.values import CarControllerParams, CruiseButtons, CruiseSettings, HondaFlags, CAR, HONDA_BOSCH, \ + HONDA_NIDEC_ALT_SCM_MESSAGES, 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 @@ -16,6 +16,7 @@ EventName = car.CarEvent.EventName TransmissionType = car.CarParams.TransmissionType BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise, CruiseButtons.MAIN: ButtonType.altButton3, CruiseButtons.CANCEL: ButtonType.cancel} +SETTINGS_BUTTONS_DICT = {CruiseSettings.DISTANCE: ButtonType.gapAdjustCruise, CruiseSettings.LKAS: ButtonType.altButton1} class CarInterface(CarInterfaceBase): @@ -23,8 +24,6 @@ class CarInterface(CarInterfaceBase): def get_pid_accel_limits(CP, current_speed, cruise_speed): if CP.carFingerprint in HONDA_BOSCH: return CarControllerParams.BOSCH_ACCEL_MIN, CarControllerParams.BOSCH_ACCEL_MAX - elif CP.enableGasInterceptor: - return CarControllerParams.NIDEC_ACCEL_MIN, CarControllerParams.NIDEC_ACCEL_MAX else: # NIDECs don't allow acceleration near cruise_speed, # so limit limits of pid to prevent windup @@ -36,6 +35,8 @@ class CarInterface(CarInterfaceBase): def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "honda" + CAN = CanBus(ret, fingerprint) + if candidate in HONDA_BOSCH: ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaBosch)] ret.radarUnavailable = True @@ -47,20 +48,19 @@ class CarInterface(CarInterfaceBase): ret.pcmCruise = not ret.openpilotLongitudinalControl else: ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaNidec)] - ret.enableGasInterceptor = 0x201 in fingerprint[0] ret.openpilotLongitudinalControl = True - ret.pcmCruise = not ret.enableGasInterceptor + ret.pcmCruise = True - if candidate == CAR.CRV_5G: - ret.enableBsm = 0x12f8bfa7 in fingerprint[0] + if candidate == CAR.HONDA_CRV_5G: + ret.enableBsm = 0x12f8bfa7 in fingerprint[CAN.radar] # Detect Bosch cars with new HUD msgs if any(0x33DA in f for f in fingerprint.values()): ret.flags |= HondaFlags.BOSCH_EXT_HUD.value - # Accord 1.5T CVT has different gearbox message - if candidate == CAR.ACCORD and 0x191 in fingerprint[1]: + # Accord ICE 1.5T CVT has different gearbox message + if candidate == CAR.HONDA_ACCORD and 0x191 in fingerprint[CAN.pt]: ret.transmissionType = TransmissionType.cvt # Certain Hondas have an extra steering sensor at the bottom of the steering rack, @@ -72,28 +72,20 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 0.00006 # conservative feed-forward if candidate in HONDA_BOSCH: - ret.longitudinalTuning.kpV = [0.25] - ret.longitudinalTuning.kiV = [0.05] - ret.longitudinalActuatorDelayUpperBound = 0.5 # s + ret.longitudinalActuatorDelay = 0.5 # s if candidate in HONDA_BOSCH_RADARLESS: ret.stopAccel = CarControllerParams.BOSCH_ACCEL_MIN # stock uses -4.0 m/s^2 once stopped but limited by safety model else: # default longitudinal tuning for all hondas - ret.longitudinalTuning.kpBP = [0., 5., 35.] - ret.longitudinalTuning.kpV = [1.2, 0.8, 0.5] - ret.longitudinalTuning.kiBP = [0., 35.] - ret.longitudinalTuning.kiV = [0.18, 0.12] + ret.longitudinalTuning.kiBP = [0., 5., 35.] + ret.longitudinalTuning.kiV = [1.2, 0.8, 0.5] eps_modified = False for fw in car_fw: if fw.ecu == "eps" and b"," in fw.fwVersion: eps_modified = True - if candidate == CAR.CIVIC: - ret.mass = 1326. - ret.wheelbase = 2.70 - ret.centerToFront = ret.wheelbase * 0.4 - ret.steerRatio = 15.38 # 10.93 is end-to-end spec + if candidate == CAR.HONDA_CIVIC: if eps_modified: # stock request input values: 0x0000, 0x00DE, 0x014D, 0x01EF, 0x0290, 0x0377, 0x0454, 0x0610, 0x06EE # stock request output values: 0x0000, 0x0917, 0x0DC5, 0x1017, 0x119F, 0x140B, 0x1680, 0x1680, 0x1680 @@ -107,21 +99,12 @@ class CarInterface(CarInterfaceBase): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[1.1], [0.33]] - elif candidate in (CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CIVIC_2022): - ret.mass = 1326. - ret.wheelbase = 2.70 - ret.centerToFront = ret.wheelbase * 0.4 - ret.steerRatio = 15.38 # 10.93 is end-to-end spec + elif candidate in (CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.HONDA_CIVIC_2022): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] - elif candidate in (CAR.ACCORD, CAR.ACCORDH): - ret.mass = 3279. * CV.LB_TO_KG - ret.wheelbase = 2.83 - ret.centerToFront = ret.wheelbase * 0.39 - ret.steerRatio = 16.33 # 11.82 is spec end-to-end + elif candidate == CAR.HONDA_ACCORD: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - ret.tireStiffnessFactor = 0.8467 if eps_modified: ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.09]] @@ -129,29 +112,15 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] elif candidate == CAR.ACURA_ILX: - ret.mass = 3095. * CV.LB_TO_KG - ret.wheelbase = 2.67 - ret.centerToFront = ret.wheelbase * 0.37 - ret.steerRatio = 18.61 # 15.3 is spec end-to-end ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] # TODO: determine if there is a dead zone at the top end - ret.tireStiffnessFactor = 0.72 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] - elif candidate in (CAR.CRV, CAR.CRV_EU): - ret.mass = 3572. * CV.LB_TO_KG - ret.wheelbase = 2.62 - ret.centerToFront = ret.wheelbase * 0.41 - ret.steerRatio = 16.89 # as spec + elif candidate in (CAR.HONDA_CRV, CAR.HONDA_CRV_EU): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 1000], [0, 1000]] # TODO: determine if there is a dead zone at the top end - ret.tireStiffnessFactor = 0.444 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] ret.wheelSpeedFactor = 1.025 - elif candidate == CAR.CRV_5G: - ret.mass = 3410. * CV.LB_TO_KG - ret.wheelbase = 2.66 - ret.centerToFront = ret.wheelbase * 0.41 - ret.steerRatio = 16.0 # 12.3 is spec end-to-end + elif candidate == CAR.HONDA_CRV_5G: if eps_modified: # stock request input values: 0x0000, 0x00DB, 0x01BB, 0x0296, 0x0377, 0x0454, 0x0532, 0x0610, 0x067F # stock request output values: 0x0000, 0x0500, 0x0A15, 0x0E6D, 0x1100, 0x1200, 0x129A, 0x134D, 0x1400 @@ -161,123 +130,69 @@ class CarInterface(CarInterfaceBase): else: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.64], [0.192]] - ret.tireStiffnessFactor = 0.677 ret.wheelSpeedFactor = 1.025 - elif candidate == CAR.CRV_HYBRID: - ret.mass = 1667. # mean of 4 models in kg - ret.wheelbase = 2.66 - ret.centerToFront = ret.wheelbase * 0.41 - ret.steerRatio = 16.0 # 12.3 is spec end-to-end + elif candidate == CAR.HONDA_CRV_HYBRID: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - ret.tireStiffnessFactor = 0.677 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] ret.wheelSpeedFactor = 1.025 - elif candidate == CAR.FIT: - ret.mass = 2644. * CV.LB_TO_KG - ret.wheelbase = 2.53 - ret.centerToFront = ret.wheelbase * 0.39 - ret.steerRatio = 13.06 + elif candidate == CAR.HONDA_FIT: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - ret.tireStiffnessFactor = 0.75 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.05]] - elif candidate == CAR.FREED: - ret.mass = 3086. * CV.LB_TO_KG - ret.wheelbase = 2.74 - # the remaining parameters were copied from FIT - ret.centerToFront = ret.wheelbase * 0.39 - ret.steerRatio = 13.06 + elif candidate == CAR.HONDA_FREED: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] - ret.tireStiffnessFactor = 0.75 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.05]] - elif candidate in (CAR.HRV, CAR.HRV_3G): - ret.mass = 3125 * CV.LB_TO_KG - ret.wheelbase = 2.61 - ret.centerToFront = ret.wheelbase * 0.41 - ret.steerRatio = 15.2 + elif candidate in (CAR.HONDA_HRV, CAR.HONDA_HRV_3G): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] - ret.tireStiffnessFactor = 0.5 - if candidate == CAR.HRV: + if candidate == CAR.HONDA_HRV: ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.025]] ret.wheelSpeedFactor = 1.025 else: ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] # TODO: can probably use some tuning elif candidate == CAR.ACURA_RDX: - ret.mass = 3935. * CV.LB_TO_KG - ret.wheelbase = 2.68 - ret.centerToFront = ret.wheelbase * 0.38 - ret.steerRatio = 15.0 # as spec - ret.tireStiffnessFactor = 0.444 ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 1000], [0, 1000]] # TODO: determine if there is a dead zone at the top end ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] elif candidate == CAR.ACURA_RDX_3G: - ret.mass = 4068. * CV.LB_TO_KG - ret.wheelbase = 2.75 - ret.centerToFront = ret.wheelbase * 0.41 - ret.steerRatio = 11.95 # as spec ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]] - ret.tireStiffnessFactor = 0.677 - elif candidate in (CAR.ODYSSEY, CAR.ODYSSEY_CHN): - ret.mass = 1900. - ret.wheelbase = 3.00 - ret.centerToFront = ret.wheelbase * 0.41 - ret.steerRatio = 14.35 # as spec - ret.tireStiffnessFactor = 0.82 + elif candidate in (CAR.HONDA_ODYSSEY, CAR.HONDA_ODYSSEY_CHN): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]] - if candidate == CAR.ODYSSEY_CHN: + if candidate == CAR.HONDA_ODYSSEY_CHN: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end else: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - elif candidate == CAR.PILOT: - ret.mass = 4278. * CV.LB_TO_KG # average weight - ret.wheelbase = 2.86 - ret.centerToFront = ret.wheelbase * 0.428 - ret.steerRatio = 16.0 # as spec + elif candidate == CAR.HONDA_PILOT: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - ret.tireStiffnessFactor = 0.444 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]] - elif candidate == CAR.RIDGELINE: - ret.mass = 4515. * CV.LB_TO_KG - ret.wheelbase = 3.18 - ret.centerToFront = ret.wheelbase * 0.41 - ret.steerRatio = 15.59 # as spec + elif candidate == CAR.HONDA_RIDGELINE: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - ret.tireStiffnessFactor = 0.444 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]] - elif candidate == CAR.INSIGHT: - ret.mass = 2987. * CV.LB_TO_KG - ret.wheelbase = 2.7 - ret.centerToFront = ret.wheelbase * 0.39 - ret.steerRatio = 15.0 # 12.58 is spec end-to-end + elif candidate == CAR.HONDA_INSIGHT: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - ret.tireStiffnessFactor = 0.82 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] elif candidate == CAR.HONDA_E: - ret.mass = 3338.8 * CV.LB_TO_KG - ret.wheelbase = 2.5 - ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 16.71 ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - ret.tireStiffnessFactor = 0.82 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] # TODO: can probably use some tuning else: raise ValueError(f"unsupported car {candidate}") # These cars use alternate user brake msg (0x1BE) - if 0x1BE in fingerprint[get_pt_bus(candidate)] and candidate in HONDA_BOSCH: + # TODO: Only detect feature for Accord/Accord Hybrid, not all Bosch DBCs have BRAKE_MODULE + if 0x1BE in fingerprint[CAN.pt] and candidate in (CAR.HONDA_ACCORD, CAR.HONDA_HRV_3G): ret.flags |= HondaFlags.BOSCH_ALT_BRAKE.value + + if ret.flags & HondaFlags.BOSCH_ALT_BRAKE: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_ALT_BRAKE # These cars use alternate SCM messages (SCM_FEEDBACK AND SCM_BUTTON) @@ -287,16 +202,13 @@ class CarInterface(CarInterfaceBase): if ret.openpilotLongitudinalControl and candidate in HONDA_BOSCH: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_BOSCH_LONG - if ret.enableGasInterceptor and candidate not in HONDA_BOSCH: - ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_GAS_INTERCEPTOR - if candidate in HONDA_BOSCH_RADARLESS: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_RADARLESS # 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. Otherwise, add 0.5 mph margin to not # conflict with PCM acc - ret.autoResumeSng = candidate in (HONDA_BOSCH | {CAR.CIVIC}) or ret.enableGasInterceptor + ret.autoResumeSng = candidate in (HONDA_BOSCH | {CAR.HONDA_CIVIC}) ret.minEnableSpeed = -1. if ret.autoResumeSng else 25.5 * CV.MPH_TO_MS ret.steerActuatorDelay = 0.1 @@ -315,7 +227,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT), - *create_button_events(self.CS.cruise_setting, self.CS.prev_cruise_setting, {1: ButtonType.altButton1}), + *create_button_events(self.CS.cruise_setting, self.CS.prev_cruise_setting, SETTINGS_BUTTONS_DICT), ] # events @@ -341,8 +253,3 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() return ret - - # pass in a car.CarControl - # to be called @ 100hz - def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/honda/tests/test_honda.py b/selfdrive/car/honda/tests/test_honda.py old mode 100755 new mode 100644 index 4e3f9182ea..b8ad7872b2 --- a/selfdrive/car/honda/tests/test_honda.py +++ b/selfdrive/car/honda/tests/test_honda.py @@ -1,20 +1,14 @@ -#!/usr/bin/env python3 import re -import unittest from openpilot.selfdrive.car.honda.fingerprints import FW_VERSIONS -HONDA_FW_VERSION_RE = br"\d{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$" +HONDA_FW_VERSION_RE = br"[A-Z0-9]{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$" -class TestHondaFingerprint(unittest.TestCase): +class TestHondaFingerprint: def test_fw_version_format(self): # Asserts all FW versions follow an expected format for fw_by_ecu in FW_VERSIONS.values(): for fws in fw_by_ecu.values(): for fw in fws: - self.assertTrue(re.match(HONDA_FW_VERSION_RE, fw) is not None, fw) - - -if __name__ == "__main__": - unittest.main() + assert re.match(HONDA_FW_VERSION_RE, fw) is not None, fw diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index a2ef757d15..c3005c667c 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -1,11 +1,11 @@ from dataclasses import dataclass -from enum import Enum, IntFlag, StrEnum +from enum import Enum, IntFlag from cereal import car from openpilot.common.conversions import Conversions as CV from panda.python import uds -from openpilot.selfdrive.car import dbc_dict -from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column +from openpilot.selfdrive.car import CarSpecs, PlatformConfig, Platforms, dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 Ecu = car.CarParams.Ecu @@ -46,10 +46,19 @@ class CarControllerParams: class HondaFlags(IntFlag): + # Detected flags # Bosch models with alternate set of LKAS_HUD messages BOSCH_EXT_HUD = 1 BOSCH_ALT_BRAKE = 2 + # Static flags + BOSCH = 4 + BOSCH_RADARLESS = 8 + + NIDEC = 16 + NIDEC_ALT_PCM_ACCEL = 32 + NIDEC_ALT_SCM_MESSAGES = 64 + # Car button codes class CruiseButtons: @@ -59,6 +68,11 @@ class CruiseButtons: MAIN = 1 +class CruiseSettings: + DISTANCE = 3 + LKAS = 1 + + # See dbc files for info on values VISUAL_HUD = { VisualAlert.none: 0, @@ -72,30 +86,15 @@ VISUAL_HUD = { } -class CAR(StrEnum): - ACCORD = "HONDA ACCORD 2018" - ACCORDH = "HONDA ACCORD HYBRID 2018" - CIVIC = "HONDA CIVIC 2016" - CIVIC_BOSCH = "HONDA CIVIC (BOSCH) 2019" - CIVIC_BOSCH_DIESEL = "HONDA CIVIC SEDAN 1.6 DIESEL 2019" - CIVIC_2022 = "HONDA CIVIC 2022" - ACURA_ILX = "ACURA ILX 2016" - CRV = "HONDA CR-V 2016" - CRV_5G = "HONDA CR-V 2017" - CRV_EU = "HONDA CR-V EU 2016" - CRV_HYBRID = "HONDA CR-V HYBRID 2019" - FIT = "HONDA FIT 2018" - FREED = "HONDA FREED 2020" - HRV = "HONDA HRV 2019" - HRV_3G = "HONDA HR-V 2023" - ODYSSEY = "HONDA ODYSSEY 2018" - ODYSSEY_CHN = "HONDA ODYSSEY CHN 2019" - ACURA_RDX = "ACURA RDX 2018" - ACURA_RDX_3G = "ACURA RDX 2020" - PILOT = "HONDA PILOT 2017" - RIDGELINE = "HONDA RIDGELINE 2017" - INSIGHT = "HONDA INSIGHT 2019" - HONDA_E = "HONDA E 2020" +@dataclass +class HondaCarDocs(CarDocs): + package: str = "Honda Sensing" + + def init_make(self, CP: car.CarParams): + if CP.flags & HondaFlags.BOSCH: + self.car_parts = CarParts.common([CarHarness.bosch_b]) if CP.flags & HondaFlags.BOSCH_RADARLESS else CarParts.common([CarHarness.bosch_a]) + else: + self.car_parts = CarParts.common([CarHarness.nidec]) class Footnote(Enum): @@ -104,59 +103,167 @@ class Footnote(Enum): Column.FSR_STEERING) -@dataclass -class HondaCarInfo(CarInfo): - package: str = "Honda Sensing" - - def init_make(self, CP: car.CarParams): - if CP.carFingerprint in HONDA_BOSCH: - self.car_parts = CarParts.common([CarHarness.bosch_b]) if CP.carFingerprint in HONDA_BOSCH_RADARLESS else CarParts.common([CarHarness.bosch_a]) - else: - self.car_parts = CarParts.common([CarHarness.nidec]) +class HondaBoschPlatformConfig(PlatformConfig): + def init(self): + self.flags |= HondaFlags.BOSCH -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), - ], - CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), - CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, video_link="https://youtu.be/-IkImTe1NYE"), - CAR.CIVIC_BOSCH: [ - HondaCarInfo("Honda Civic 2019-21", "All", video_link="https://www.youtube.com/watch?v=4Iz1Mz5LGF8", - footnotes=[Footnote.CIVIC_DIESEL], min_steer_speed=2. * CV.MPH_TO_MS), - HondaCarInfo("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS), - ], - CAR.CIVIC_BOSCH_DIESEL: None, # same platform - CAR.CIVIC_2022: [ - HondaCarInfo("Honda Civic 2022-23", "All", video_link="https://youtu.be/ytiOT5lcp6Q"), - HondaCarInfo("Honda Civic Hatchback 2022-23", "All", video_link="https://youtu.be/ytiOT5lcp6Q"), - ], - CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS), - CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS), - CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS), - CAR.CRV_EU: None, # HondaCarInfo("Honda CR-V EU", "Touring"), # Euro version of CRV Touring - CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-20", min_steer_speed=12. * CV.MPH_TO_MS), - CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS), - CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS), - CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS), - CAR.HRV_3G: HondaCarInfo("Honda HR-V 2023", "All"), - CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20"), - CAR.ODYSSEY_CHN: None, # Chinese version of Odyssey - CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS), - CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), - CAR.PILOT: [ - HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS), - HondaCarInfo("Honda Passport 2019-23", "All", 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), -} +class HondaNidecPlatformConfig(PlatformConfig): + def init(self): + self.flags |= HondaFlags.NIDEC -HONDA_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + +class CAR(Platforms): + # Bosch Cars + HONDA_ACCORD = HondaBoschPlatformConfig( + [ + HondaCarDocs("Honda Accord 2018-22", "All", video_link="https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS), + HondaCarDocs("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS), + HondaCarDocs("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), + ], + # steerRatio: 11.82 is spec end-to-end + CarSpecs(mass=3279 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=16.33, centerToFrontRatio=0.39, tireStiffnessFactor=0.8467), + dbc_dict('honda_accord_2018_can_generated', None), + ) + HONDA_CIVIC_BOSCH = HondaBoschPlatformConfig( + [ + HondaCarDocs("Honda Civic 2019-21", "All", video_link="https://www.youtube.com/watch?v=4Iz1Mz5LGF8", + footnotes=[Footnote.CIVIC_DIESEL], min_steer_speed=2. * CV.MPH_TO_MS), + HondaCarDocs("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS), + ], + CarSpecs(mass=1326, wheelbase=2.7, steerRatio=15.38, centerToFrontRatio=0.4), # steerRatio: 10.93 is end-to-end spec + dbc_dict('honda_civic_hatchback_ex_2017_can_generated', None), + ) + HONDA_CIVIC_BOSCH_DIESEL = HondaBoschPlatformConfig( + [], # don't show in docs + HONDA_CIVIC_BOSCH.specs, + dbc_dict('honda_accord_2018_can_generated', None), + ) + HONDA_CIVIC_2022 = HondaBoschPlatformConfig( + [ + HondaCarDocs("Honda Civic 2022-23", "All", video_link="https://youtu.be/ytiOT5lcp6Q"), + HondaCarDocs("Honda Civic Hatchback 2022-23", "All", video_link="https://youtu.be/ytiOT5lcp6Q"), + ], + HONDA_CIVIC_BOSCH.specs, + dbc_dict('honda_civic_ex_2022_can_generated', None), + flags=HondaFlags.BOSCH_RADARLESS, + ) + HONDA_CRV_5G = HondaBoschPlatformConfig( + [HondaCarDocs("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS)], + # steerRatio: 12.3 is spec end-to-end + CarSpecs(mass=3410 * CV.LB_TO_KG, wheelbase=2.66, steerRatio=16.0, centerToFrontRatio=0.41, tireStiffnessFactor=0.677), + dbc_dict('honda_crv_ex_2017_can_generated', None, body_dbc='honda_crv_ex_2017_body_generated'), + flags=HondaFlags.BOSCH_ALT_BRAKE, + ) + HONDA_CRV_HYBRID = HondaBoschPlatformConfig( + [HondaCarDocs("Honda CR-V Hybrid 2017-21", min_steer_speed=12. * CV.MPH_TO_MS)], + # mass: mean of 4 models in kg, steerRatio: 12.3 is spec end-to-end + CarSpecs(mass=1667, wheelbase=2.66, steerRatio=16, centerToFrontRatio=0.41, tireStiffnessFactor=0.677), + dbc_dict('honda_accord_2018_can_generated', None), + ) + HONDA_HRV_3G = HondaBoschPlatformConfig( + [HondaCarDocs("Honda HR-V 2023", "All")], + CarSpecs(mass=3125 * CV.LB_TO_KG, wheelbase=2.61, steerRatio=15.2, centerToFrontRatio=0.41, tireStiffnessFactor=0.5), + dbc_dict('honda_civic_ex_2022_can_generated', None), + flags=HondaFlags.BOSCH_RADARLESS, + ) + ACURA_RDX_3G = HondaBoschPlatformConfig( + [HondaCarDocs("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS)], + CarSpecs(mass=4068 * CV.LB_TO_KG, wheelbase=2.75, steerRatio=11.95, centerToFrontRatio=0.41, tireStiffnessFactor=0.677), # as spec + dbc_dict('acura_rdx_2020_can_generated', None), + flags=HondaFlags.BOSCH_ALT_BRAKE, + ) + HONDA_INSIGHT = HondaBoschPlatformConfig( + [HondaCarDocs("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS)], + CarSpecs(mass=2987 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.0, centerToFrontRatio=0.39, tireStiffnessFactor=0.82), # as spec + dbc_dict('honda_insight_ex_2019_can_generated', None), + ) + HONDA_E = HondaBoschPlatformConfig( + [HondaCarDocs("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS)], + CarSpecs(mass=3338.8 * CV.LB_TO_KG, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82), + dbc_dict('acura_rdx_2020_can_generated', None), + ) + + # Nidec Cars + ACURA_ILX = HondaNidecPlatformConfig( + [HondaCarDocs("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS)], + CarSpecs(mass=3095 * CV.LB_TO_KG, wheelbase=2.67, steerRatio=18.61, centerToFrontRatio=0.37, tireStiffnessFactor=0.72), # 15.3 is spec end-to-end + dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + HONDA_CRV = HondaNidecPlatformConfig( + [HondaCarDocs("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS)], + CarSpecs(mass=3572 * CV.LB_TO_KG, wheelbase=2.62, steerRatio=16.89, centerToFrontRatio=0.41, tireStiffnessFactor=0.444), # as spec + dbc_dict('honda_crv_touring_2016_can_generated', 'acura_ilx_2016_nidec'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + HONDA_CRV_EU = HondaNidecPlatformConfig( + [], # Euro version of CRV Touring, don't show in docs + HONDA_CRV.specs, + dbc_dict('honda_crv_executive_2016_can_generated', 'acura_ilx_2016_nidec'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + HONDA_FIT = HondaNidecPlatformConfig( + [HondaCarDocs("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS)], + CarSpecs(mass=2644 * CV.LB_TO_KG, wheelbase=2.53, steerRatio=13.06, centerToFrontRatio=0.39, tireStiffnessFactor=0.75), + dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + HONDA_FREED = HondaNidecPlatformConfig( + [HondaCarDocs("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS)], + CarSpecs(mass=3086. * CV.LB_TO_KG, wheelbase=2.74, steerRatio=13.06, centerToFrontRatio=0.39, tireStiffnessFactor=0.75), # mostly copied from FIT + dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + HONDA_HRV = HondaNidecPlatformConfig( + [HondaCarDocs("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS)], + HONDA_HRV_3G.specs, + dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + HONDA_ODYSSEY = HondaNidecPlatformConfig( + [HondaCarDocs("Honda Odyssey 2018-20")], + CarSpecs(mass=1900, wheelbase=3.0, steerRatio=14.35, centerToFrontRatio=0.41, tireStiffnessFactor=0.82), + dbc_dict('honda_odyssey_exl_2018_generated', 'acura_ilx_2016_nidec'), + flags=HondaFlags.NIDEC_ALT_PCM_ACCEL, + ) + HONDA_ODYSSEY_CHN = HondaNidecPlatformConfig( + [], # Chinese version of Odyssey, don't show in docs + HONDA_ODYSSEY.specs, + dbc_dict('honda_odyssey_extreme_edition_2018_china_can_generated', 'acura_ilx_2016_nidec'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + ACURA_RDX = HondaNidecPlatformConfig( + [HondaCarDocs("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS)], + CarSpecs(mass=3925 * CV.LB_TO_KG, wheelbase=2.68, steerRatio=15.0, centerToFrontRatio=0.38, tireStiffnessFactor=0.444), # as spec + dbc_dict('acura_rdx_2018_can_generated', 'acura_ilx_2016_nidec'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + HONDA_PILOT = HondaNidecPlatformConfig( + [ + HondaCarDocs("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS), + HondaCarDocs("Honda Passport 2019-23", "All", min_steer_speed=12. * CV.MPH_TO_MS), + ], + CarSpecs(mass=4278 * CV.LB_TO_KG, wheelbase=2.86, centerToFrontRatio=0.428, steerRatio=16.0, tireStiffnessFactor=0.444), # as spec + dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + HONDA_RIDGELINE = HondaNidecPlatformConfig( + [HondaCarDocs("Honda Ridgeline 2017-24", min_steer_speed=12. * CV.MPH_TO_MS)], + CarSpecs(mass=4515 * CV.LB_TO_KG, wheelbase=3.18, centerToFrontRatio=0.41, steerRatio=15.59, tireStiffnessFactor=0.444), # as spec + dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + HONDA_CIVIC = HondaNidecPlatformConfig( + [HondaCarDocs("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, video_link="https://youtu.be/-IkImTe1NYE")], + CarSpecs(mass=1326, wheelbase=2.70, centerToFrontRatio=0.4, steerRatio=15.38), # 10.93 is end-to-end spec + dbc_dict('honda_civic_touring_2016_can_generated', 'acura_ilx_2016_nidec'), + ) + + +HONDA_ALT_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(0xF112) -HONDA_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ +HONDA_ALT_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ p16(0xF112) FW_QUERY_CONFIG = FwQueryConfig( @@ -169,17 +276,10 @@ FW_QUERY_CONFIG = FwQueryConfig( ), # Data collection requests: - # Attempt to get the radarless Civic 2022+ camera FW + # Log manufacturer-specific identifier for current ECUs Request( - [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST], - [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], - bus=0, - logging=True - ), - # Log extra identifiers for current ECUs - Request( - [HONDA_VERSION_REQUEST], - [HONDA_VERSION_RESPONSE], + [HONDA_ALT_VERSION_REQUEST], + [HONDA_ALT_VERSION_RESPONSE], bus=1, logging=True, ), @@ -188,7 +288,6 @@ FW_QUERY_CONFIG = FwQueryConfig( [StdQueries.UDS_VERSION_REQUEST], [StdQueries.UDS_VERSION_RESPONSE], bus=0, - logging=True, ), # Bosch PT bus Request( @@ -200,56 +299,33 @@ FW_QUERY_CONFIG = FwQueryConfig( ], # We lose these ECUs without the comma power on these cars. # Note that we still attempt to match with them when they are present + # This is or'd with (ALL_ECUS - ESSENTIAL_ECUS) from fw_versions.py 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], + Ecu.eps: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_2022, CAR.HONDA_E, CAR.HONDA_HRV_3G], + Ecu.vsa: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_CIVIC, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_2022, CAR.HONDA_CRV_5G, CAR.HONDA_CRV_HYBRID, + CAR.HONDA_E, CAR.HONDA_HRV_3G, CAR.HONDA_INSIGHT], }, extra_ecus=[ + (Ecu.combinationMeter, 0x18da60f1, None), + (Ecu.programmedFuelInjection, 0x18da10f1, None), # The only other ECU on PT bus accessible by camera on radarless Civic - (Ecu.unknown, 0x18DAB3F1, None), + # This is likely a manufacturer-specific sub-address implementation: the camera responds to this and 0x18dab0f1 + # Unclear what the part number refers to: 8S103 is 'Camera Set Mono', while 36160 is 'Camera Monocular - Honda' + # TODO: add query back, camera does not support querying both in parallel and 0x18dab0f1 often fails to respond + # (Ecu.unknown, 0x18DAB3F1, None), ], ) - -DBC = { - CAR.ACCORD: dbc_dict('honda_accord_2018_can_generated', None), - CAR.ACCORDH: dbc_dict('honda_accord_2018_can_generated', None), - CAR.ACURA_ILX: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), - CAR.ACURA_RDX: dbc_dict('acura_rdx_2018_can_generated', 'acura_ilx_2016_nidec'), - CAR.ACURA_RDX_3G: dbc_dict('acura_rdx_2020_can_generated', None), - CAR.CIVIC: dbc_dict('honda_civic_touring_2016_can_generated', 'acura_ilx_2016_nidec'), - CAR.CIVIC_BOSCH: dbc_dict('honda_civic_hatchback_ex_2017_can_generated', None), - CAR.CIVIC_BOSCH_DIESEL: dbc_dict('honda_accord_2018_can_generated', None), - CAR.CRV: dbc_dict('honda_crv_touring_2016_can_generated', 'acura_ilx_2016_nidec'), - CAR.CRV_5G: dbc_dict('honda_crv_ex_2017_can_generated', None, body_dbc='honda_crv_ex_2017_body_generated'), - CAR.CRV_EU: dbc_dict('honda_crv_executive_2016_can_generated', 'acura_ilx_2016_nidec'), - CAR.CRV_HYBRID: dbc_dict('honda_accord_2018_can_generated', None), - CAR.FIT: dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'), - CAR.FREED: dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'), - CAR.HRV: dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'), - CAR.HRV_3G: dbc_dict('honda_civic_ex_2022_can_generated', None), - CAR.ODYSSEY: dbc_dict('honda_odyssey_exl_2018_generated', 'acura_ilx_2016_nidec'), - CAR.ODYSSEY_CHN: dbc_dict('honda_odyssey_extreme_edition_2018_china_can_generated', 'acura_ilx_2016_nidec'), - CAR.PILOT: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), - CAR.RIDGELINE: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), - CAR.INSIGHT: dbc_dict('honda_insight_ex_2019_can_generated', None), - CAR.HONDA_E: dbc_dict('acura_rdx_2020_can_generated', None), - CAR.CIVIC_2022: dbc_dict('honda_civic_ex_2022_can_generated', None), -} - STEER_THRESHOLD = { # default is 1200, overrides go here CAR.ACURA_RDX: 400, - CAR.CRV_EU: 400, + CAR.HONDA_CRV_EU: 400, } -HONDA_NIDEC_ALT_PCM_ACCEL = {CAR.ODYSSEY} -HONDA_NIDEC_ALT_SCM_MESSAGES = {CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_EU, CAR.FIT, CAR.FREED, CAR.HRV, CAR.ODYSSEY_CHN, - CAR.PILOT, CAR.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_RADARLESS = {CAR.CIVIC_2022, CAR.HRV_3G} +HONDA_NIDEC_ALT_PCM_ACCEL = CAR.with_flags(HondaFlags.NIDEC_ALT_PCM_ACCEL) +HONDA_NIDEC_ALT_SCM_MESSAGES = CAR.with_flags(HondaFlags.NIDEC_ALT_SCM_MESSAGES) +HONDA_BOSCH = CAR.with_flags(HondaFlags.BOSCH) +HONDA_BOSCH_RADARLESS = CAR.with_flags(HondaFlags.BOSCH_RADARLESS) + + +DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 0b5f724ab9..4038ddcca9 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -7,6 +7,7 @@ from openpilot.selfdrive.car import apply_driver_steer_torque_limits, common_fau from openpilot.selfdrive.car.hyundai import hyundaicanfd, hyundaican from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CANFD_CAR, CAR +from openpilot.selfdrive.car.interfaces import CarControllerBase VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState @@ -42,7 +43,7 @@ def process_hud_alert(enabled, fingerprint, hud_control): return sys_warning, sys_state, left_lane_warning, right_lane_warning -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP self.CAN = CanBus(CP) @@ -128,13 +129,13 @@ class CarController: can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame)) if self.frame % 2 == 0: can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, - set_speed_in_units)) + set_speed_in_units, hud_control)) self.accel_last = accel else: # button presses can_sends.extend(self.create_button_messages(CC, CS, use_clu11=False)) else: - can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.car_fingerprint, apply_steer, apply_steer_req, + can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_steer, apply_steer_req, torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled, hud_control.leftLaneVisible, hud_control.rightLaneVisible, left_lane_warning, right_lane_warning)) @@ -147,7 +148,7 @@ class CarController: jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0 use_fca = self.CP.flags & HyundaiFlags.USE_FCA.value can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2), - hud_control.leadVisible, set_speed_in_units, stopping, + hud_control, set_speed_in_units, stopping, CC.cruiseControl.override, use_fca)) # 20 Hz LFA MFA message @@ -162,7 +163,7 @@ class CarController: if self.frame % 50 == 0 and self.CP.openpilotLongitudinalControl: can_sends.append(hyundaican.create_frt_radar_opt(self.packer)) - new_actuators = actuators.copy() + new_actuators = actuators.as_builder() new_actuators.steer = apply_steer / self.params.STEER_MAX new_actuators.steerOutputCan = apply_steer new_actuators.accel = accel @@ -174,12 +175,12 @@ class CarController: can_sends = [] if use_clu11: if CC.cruiseControl.cancel: - can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP.carFingerprint)) + can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP)) elif CC.cruiseControl.resume: # send resume at a max freq of 10Hz if (self.frame - self.last_button_frame) * DT_CTRL > 0.1: # send 25 messages at a time to increases the likelihood of resume being accepted - can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP.carFingerprint)] * 25) + can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP)] * 25) if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15: self.last_button_frame = self.frame else: diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 64a9fdf2ce..92c489cf34 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -118,6 +118,7 @@ class CarState(CarStateBase): ret.brakePressed = cp.vl["TCS13"]["DriverOverride"] == 2 # 2 includes regen braking by user on HEV/EV ret.brakeHoldActive = cp.vl["TCS15"]["AVH_LAMP"] == 2 # 0 OFF, 1 ERROR, 2 ACTIVE, 3 READY ret.parkingBrake = cp.vl["TCS13"]["PBRAKE_ACT"] == 1 + ret.espDisabled = cp.vl["TCS11"]["TCS_PAS"] == 1 ret.accFaulted = cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV): @@ -207,7 +208,7 @@ class CarState(CarStateBase): # TODO: alt signal usage may be described by cp.vl['BLINKERS']['USE_ALT_LAMP'] left_blinker_sig, right_blinker_sig = "LEFT_LAMP", "RIGHT_LAMP" - if self.CP.carFingerprint == CAR.KONA_EV_2ND_GEN: + if self.CP.carFingerprint == CAR.HYUNDAI_KONA_EV_2ND_GEN: left_blinker_sig, right_blinker_sig = "LEFT_LAMP_ALT", "RIGHT_LAMP_ALT" ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"][left_blinker_sig], cp.vl["BLINKERS"][right_blinker_sig]) @@ -255,6 +256,7 @@ class CarState(CarStateBase): messages = [ # address, frequency ("MDPS12", 50), + ("TCS11", 100), ("TCS13", 50), ("TCS15", 10), ("CLU11", 50), diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index fe594db297..a611fc4d54 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -2,25 +2,13 @@ from cereal import car from openpilot.selfdrive.car.hyundai.values import CAR +# The existence of SCC or RDR in the fwdRadar FW usually determines the radar's function, +# i.e. if it sends the SCC messages or if another ECU like the camera or ADAS Driving ECU does + Ecu = car.CarParams.Ecu FINGERPRINTS = { - CAR.HYUNDAI_GENESIS: [{ - 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1342: 6, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4 - }, - { - 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1378: 4, 1379: 8, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4 - }, - { - 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 912: 7, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1268: 8, 1280: 1, 1281: 3, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1437: 8, 1456: 4 - }, - { - 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1378: 4, 1379: 8, 1384: 5, 1407: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4 - }, - { - 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4 - }], - CAR.SANTA_FE: [{ + CAR.HYUNDAI_SANTA_FE: [{ 67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1162: 8, 1164: 8, 1168: 7, 1170: 8, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1227: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1379: 8, 1384: 8, 1407: 8, 1414: 3, 1419: 8, 1427: 6, 1456: 4, 1470: 8 }, { @@ -29,40 +17,19 @@ FINGERPRINTS = { { 67: 8, 68: 8, 80: 4, 160: 8, 161: 8, 272: 8, 288: 4, 339: 8, 356: 8, 357: 8, 399: 8, 544: 8, 608: 8, 672: 8, 688: 5, 704: 1, 790: 8, 809: 8, 848: 8, 880: 8, 898: 8, 900: 8, 901: 8, 904: 8, 1056: 8, 1064: 8, 1065: 8, 1072: 8, 1075: 8, 1087: 8, 1088: 8, 1151: 8, 1200: 8, 1201: 8, 1232: 4, 1264: 8, 1265: 8, 1266: 8, 1296: 8, 1306: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1348: 8, 1349: 8, 1369: 8, 1370: 8, 1371: 8, 1407: 8, 1415: 8, 1419: 8, 1440: 8, 1442: 4, 1461: 8, 1470: 8 }], - CAR.SONATA: [{ + CAR.HYUNDAI_SONATA: [{ 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 546: 8, 549: 8, 550: 8, 576: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 8, 865: 8, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 908: 8, 909: 8, 912: 7, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1089: 5, 1096: 8, 1107: 5, 1108: 8, 1114: 8, 1136: 8, 1145: 8, 1151: 8, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 8, 1170: 8, 1173: 8, 1180: 8, 1183: 8, 1184: 8, 1186: 2, 1191: 2, 1193: 8, 1210: 8, 1225: 8, 1227: 8, 1265: 4, 1268: 8, 1280: 8, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1330: 8, 1339: 8, 1342: 6, 1343: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1379: 8, 1384: 8, 1394: 8, 1407: 8, 1419: 8, 1427: 6, 1446: 8, 1456: 4, 1460: 8, 1470: 8, 1485: 8, 1504: 3, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 2015: 8 }], - CAR.SONATA_LF: [{ - 66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 447: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1253: 8, 1254: 8, 1255: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1397: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2014: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }], - CAR.KIA_SORENTO: [{ - 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1384: 8, 1407: 8, 1411: 8, 1419: 8, 1425: 2, 1427: 6, 1444: 8, 1456: 4, 1470: 8, 1489: 1 - }], CAR.KIA_STINGER: [{ 67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 576: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 4, 1379: 8, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1456: 4, 1470: 8 }], - CAR.GENESIS_G80: [{ - 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1024: 2, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1191: 2, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1456: 4, 1470: 8 - }, - { - 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 546: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1437: 8, 1456: 4, 1470: 8 - }, - { - 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1162: 8, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1193: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4, 1470: 8 - }], CAR.GENESIS_G90: [{ 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1162: 4, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1456: 4, 1470: 8, 1988: 8, 2000: 8, 2003: 8, 2004: 8, 2005: 8, 2008: 8, 2011: 8, 2012: 8, 2013: 8 }], - CAR.IONIQ_EV_2020: [{ - 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 524: 8, 544: 7, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1164: 8, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8, 2013: 8 - }], - CAR.IONIQ: [{ - 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 524: 8, 544: 8, 576: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1164: 8, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1473: 8, 1476: 8, 1507: 8, 1535: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8, 2013: 8 - }], - CAR.KONA_EV: [{ + CAR.HYUNDAI_KONA_EV: [{ 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 549: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1307: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1378: 4, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 1157: 4, 1193: 8, 1379: 8, 1988: 8, 1996: 8 }], - CAR.KONA_EV_2022: [{ + CAR.HYUNDAI_KONA_EV_2022: [{ 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1069: 8, 1078: 4, 1136: 8, 1145: 8, 1151: 8, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 8, 1173: 8, 1183: 8, 1188: 8, 1191: 2, 1193: 8, 1225: 8, 1227: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1339: 8, 1342: 8, 1343: 8, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1446: 8, 1456: 4, 1470: 8, 1473: 8, 1485: 8, 1507: 8, 1535: 8, 1990: 8, 1998: 8 }], CAR.KIA_NIRO_EV: [{ @@ -74,13 +41,10 @@ FINGERPRINTS = { { 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 576: 8, 593: 8, 688: 5, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 909: 8, 912: 7, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1168: 7, 1173: 8, 1180: 8, 1186: 2, 1191: 2, 1265: 4, 1268: 8, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1371: 8, 1407: 8, 1419: 8, 1420: 8, 1425: 2, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1476: 8, 1535: 8 }], - CAR.PALISADE: [{ - 67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 546: 8, 547: 8, 548: 8, 549: 8, 576: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1123: 8, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 7, 1170: 8, 1173: 8, 1180: 8, 1186: 2, 1191: 2, 1193: 8, 1210: 8, 1225: 8, 1227: 8, 1265: 4, 1280: 8, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1384: 8, 1407: 8, 1419: 8, 1427: 6, 1456: 4, 1470: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8 - }], } FW_VERSIONS = { - CAR.AZERA_6TH_GEN: { + CAR.HYUNDAI_AZERA_6TH_GEN: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00IG__ SCC F-CU- 1.00 1.00 99110-G8100 ', ], @@ -90,33 +54,22 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00IG MFC AT MES LHD 1.00 1.04 99211-G8100 200511', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 U912\x00\x00\x00\x00\x00\x00SIG0M35MH0\xa4 |.', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81641KA051\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, - CAR.AZERA_HEV_6TH_GEN: { + CAR.HYUNDAI_AZERA_HEV_6TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.00 99211-G8000 180903', + b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.01 99211-G8000 181109', b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.02 99211-G8100 191029', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00IG MDPS C 1.00 1.00 56310M9600\x00 4IHSC100', b'\xf1\x00IG MDPS C 1.00 1.01 56310M9350\x00 4IH8C101', + b'\xf1\x00IG MDPS C 1.00 1.02 56310M9350\x00 4IH8C102', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00IGhe SCC FHCUP 1.00 1.00 99110-M9100 ', b'\xf1\x00IGhe SCC FHCUP 1.00 1.01 99110-M9000 ', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006T7N0_C2\x00\x006T7Q2051\x00\x00TIG2H24KA2\x12@\x11\xb7', - b'\xf1\x006T7N0_C2\x00\x006T7VA051\x00\x00TIGSH24KA1\xc7\x85\xe2`', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H570051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816H590051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x00IGhe SCC FHCUP 1.00 1.02 99110-M9000 ', ], }, CAR.HYUNDAI_GENESIS: { @@ -126,42 +79,36 @@ FW_VERSIONS = { b'\xf1\x00DH LKAS 1.5 -140425', ], }, - CAR.IONIQ: { + CAR.HYUNDAI_IONIQ: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ', + b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2100 ', ], (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00AE MDPS C 1.00 1.05 56310/G2501 4AEHC105', b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2301 4AEHC107', + b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2501 4AEHC107', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00AEH MFC AT EUR LHD 1.00 1.00 95740-G2400 180222', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6F2051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3H1051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H1051\x00\x00HAE0G16US2\x00\x00\x00\x00', + b'\xf1\x00AEH MFC AT USA LHD 1.00 1.00 95740-G2400 180222', ], }, - CAR.IONIQ_PHEV_2019: { + CAR.HYUNDAI_IONIQ_PHEV_2019: { (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ', b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2100 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2501 4AEHC107', + b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2551 4AEHC107', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00AEP MFC AT AUS RHD 1.00 1.00 95740-G2400 180222', b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2400 180222', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PAE0G16NS1\x00\x00\x00\x00', - b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PAE0G16NS1\xdbD\r\x81', - ], }, - CAR.IONIQ_PHEV: { + CAR.HYUNDAI_IONIQ_PHEV: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2200 ', b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', @@ -170,6 +117,7 @@ FW_VERSIONS = { b'\xf1\x00AEhe SCC FHCUP 1.00 1.02 99110-G2100 ', ], (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2210 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2310 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2510 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2560 4APHC101', @@ -181,28 +129,18 @@ FW_VERSIONS = { b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2700 201027', b'\xf1\x00AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', - 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\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', - b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x00\x00\x00\x00', - b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt', - b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x82zT\xd2', - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00', - ], }, - CAR.IONIQ_EV_2020: { + CAR.HYUNDAI_IONIQ_EV_2020: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEev SCC F-CUP 1.00 1.00 99110-G7200 ', b'\xf1\x00AEev SCC F-CUP 1.00 1.00 99110-G7500 ', b'\xf1\x00AEev SCC F-CUP 1.00 1.01 99110-G7000 ', + b'\xf1\x00AEev SCC F-CUP 1.00 1.01 99110-G7100 ', + b'\xf1\x00AEev SCC FHCUP 1.00 1.01 99110-G7100 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00AE MDPS C 1.00 1.01 56310/G7310 4APEC101', + b'\xf1\x00AE MDPS C 1.00 1.01 56310/G7510 4APEC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310/G7560 4APEC101', ], (Ecu.fwdCamera, 0x7c4, None): [ @@ -211,9 +149,10 @@ FW_VERSIONS = { b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.03 95740-G2500 190516', b'\xf1\x00AEE MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', + b'\xf1\x00AEE MFC AT USA LHD 1.00 1.01 95740-G2600 190819', ], }, - CAR.IONIQ_EV_LTD: { + CAR.HYUNDAI_IONIQ_EV_LTD: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEev SCC F-CUP 1.00 1.00 96400-G7000 ', b'\xf1\x00AEev SCC F-CUP 1.00 1.00 96400-G7100 ', @@ -232,7 +171,7 @@ FW_VERSIONS = { b'\xf1\x00AEE MFC AT USA LHD 1.00 1.00 95740-G2400 180222', ], }, - CAR.IONIQ_HEV_2022: { + CAR.HYUNDAI_IONIQ_HEV_2022: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ', @@ -243,15 +182,8 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00AEH MFC AT USA LHD 1.00 1.00 95740-G2700 201027', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x96\xda\xd4\xee', - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x00\x00\x00\x00', - ], }, - CAR.SONATA: { + CAR.HYUNDAI_SONATA: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DN8_ SCC F-CU- 1.00 1.00 99110-L0000 ', b'\xf1\x00DN8_ SCC F-CUP 1.00 1.00 99110-L0000 ', @@ -266,55 +198,26 @@ FW_VERSIONS = { b'\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100', b'\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100', b'\xf1\x00DN ESC \x06 107 \x07\x03 58910-L1300', + b'\xf1\x00DN ESC \x06 107"\x08\x07 58910-L0100', b'\xf1\x00DN ESC \x07 104\x19\x08\x01 58910-L0100', b'\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', b'\xf1\x00DN ESC \x07 107"\x08\x07 58910-L0100', b'\xf1\x00DN ESC \x08 103\x19\x06\x01 58910-L1300', - b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100', - b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100', - b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 104\x19\x08\x01 58910-L0100', - b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', - ], - (Ecu.engine, 0x7e0, None): [ - b'HM6M1_0a0_F00', - b'HM6M1_0a0_G20', - b'HM6M2_0a0_BD0', - b'\xf1\x81HM6M1_0a0_F00', - b'\xf1\x81HM6M1_0a0_G20', - b'\xf1\x82DNBVN5GMCCXXXDCA', - b'\xf1\x82DNBVN5GMCCXXXG2F', - b'\xf1\x82DNBWN5TMDCXXXG2E', - b'\xf1\x82DNCVN5GMCCXXXF0A', - b'\xf1\x82DNCVN5GMCCXXXG2B', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DNDWN5TMDCXXXJ1A', - b'\xf1\x8739110-2S041\xf1\x81HM6M1_0a0_M00', - b'\xf1\x8739110-2S041\xf1\x81HM6M1_0a0_M10', - b'\xf1\x8739110-2S042\xf1\x81HM6M1_0a0_M00', - b'\xf1\x8739110-2S278\xf1\x82DNDVD5GMCCXXXL5B', - b'\xf1\x87391162M003', - b'\xf1\x87391162M010', - b'\xf1\x87391162M013', - b'\xf1\x87391162M023', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101', + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC101', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC102', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0200\x00 4DNAC102', + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC102', + b'\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1010 4DNDC103', + b'\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1030 4DNDC103', b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101', b'\xf1\x00DN8 MDPS R 1.00 1.02 57700-L1000 4DNDP105', - b'\xf1\x8756310-L0010\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101', - b'\xf1\x8756310-L0210\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC101', - b'\xf1\x8756310-L1010\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1010 4DNDC103', - b'\xf1\x8756310-L1030\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1030 4DNDC103', - b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', - b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', - b'\xf1\x8756310L0210\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101', - b'\xf1\x8757700-L0000\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422', @@ -327,74 +230,8 @@ FW_VERSIONS = { b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.06 99211-L1000 210325', b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.07 99211-L1000 211223', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00HT6TA260BLHT6TA800A1TDN8C20KS4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x00HT6TA260BLHT6TA810A1TDN8M25GS0\x00\x00\x00\x00\x00\x00\xaa\x8c\xd9p', - b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x96\xa1\xf1\x92', - b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:', - b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB4\x00\x00\x00\x00\x00\x00g!l[', - b'\xf1\x00HT6WA280BLHT6WAE10A1SDN8G25NB5\x00\x00\x00\x00\x00\x00\xe0t\xa9\xba', - b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', - b'\xf1\x00T02601BL T02832A1 VDN8T25XXX832NS8G\x0e\xfeE', - b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSA\xb9\x13\xf9p', - b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSCF\xe4!Y', - b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16KB05\x95h%', - b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87954A02N060\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', - b'\xf1\x87SAKFBA2926554GJ2VefVww\x87xwwwww\x88\x87xww\x87wTo\xfb\xffvUo\xff\x8d\x16\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SAKFBA3030524GJ2UVugww\x97yx\x88\x87\x88vw\x87gww\x87wto\xf9\xfffUo\xff\xa2\x0c\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SAKFBA3356084GJ2\x86fvgUUuWgw\x86www\x87wffvf\xb6\xcf\xfc\xffeUO\xff\x12\x19\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SAKFBA3474944GJ2ffvgwwwwg\x88\x86x\x88\x88\x98\x88ffvfeo\xfa\xff\x86fo\xff\t\xae\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SAKFBA3475714GJ2Vfvgvg\x96yx\x88\x97\x88ww\x87ww\x88\x87xs_\xfb\xffvUO\xff\x0f\xff\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALDBA3510954GJ3ww\x87xUUuWx\x88\x87\x88\x87w\x88wvfwfc_\xf9\xff\x98wO\xffl\xe0\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA3573534GJ3\x89\x98\x89\x88EUuWgwvwwwwww\x88\x87xTo\xfa\xff\x86f\x7f\xffo\x0e\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA3601464GJ3\x88\x88\x88\x88ffvggwvwvw\x87gww\x87wvo\xfb\xff\x98\x88\x7f\xffjJ\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA3753044GJ3UUeVff\x86hwwwwvwwgvfgfvo\xf9\xfffU_\xffC\xae\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA3862294GJ3vfvgvefVxw\x87\x87w\x88\x87xwwwwc_\xf9\xff\x87w\x9f\xff\xd5\xdc\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA3873834GJ3fefVwuwWx\x88\x97\x88w\x88\x97xww\x87wU_\xfb\xff\x86f\x8f\xffN\x04\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA4525334GJ3\x89\x99\x99\x99fevWh\x88\x86\x88fwvgw\x88\x87xfo\xfa\xffuDo\xff\xd1>\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA4626804GJ3wwww\x88\x87\x88xx\x88\x87\x88wwgw\x88\x88\x98\x88\x95_\xf9\xffuDo\xff|\xe7\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA4803224GJ3wwwwwvwg\x88\x88\x98\x88wwww\x87\x88\x88xu\x9f\xfc\xff\x87f\x8f\xff\xea\xea\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA6212564GJ3\x87wwwUTuGg\x88\x86xx\x88\x87\x88\x87\x88\x98xu?\xf9\xff\x97f\x7f\xff\xb8\n\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA6347404GJ3wwwwff\x86hx\x88\x97\x88\x88\x88\x88\x88vfgf\x88?\xfc\xff\x86Uo\xff\xec/\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA6901634GJ3UUuWVeVUww\x87wwwwwvUge\x86/\xfb\xff\xbb\x99\x7f\xff]2\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA7077724GJ3\x98\x88\x88\x88ww\x97ygwvwww\x87ww\x88\x87x\x87_\xfd\xff\xba\x99o\xff\x99\x01\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALFBA3525114GJ2wvwgvfvggw\x86wffvffw\x86g\x85_\xf9\xff\xa8wo\xffv\xcd\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA3624024GJ2\x88\x88\x88\x88wv\x87hx\x88\x97\x88x\x88\x97\x88ww\x87w\x86o\xfa\xffvU\x7f\xff\xd1\xec\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA3960824GJ2wwwwff\x86hffvfffffvfwfg_\xf9\xff\xa9\x88\x8f\xffb\x99\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA4011074GJ2fgvwwv\x87hw\x88\x87xww\x87wwfgvu_\xfa\xffefo\xff\x87\xc0\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA4121304GJ2x\x87xwff\x86hwwwwww\x87wwwww\x84_\xfc\xff\x98\x88\x9f\xffi\xa6\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA4195874GJ2EVugvf\x86hgwvwww\x87wgw\x86wc_\xfb\xff\x98\x88\x8f\xff\xe23\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA4625294GJ2eVefeUeVx\x88\x97\x88wwwwwwww\xa7o\xfb\xffvw\x9f\xff\xee.\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA4728774GJ2vfvg\x87vwgww\x87ww\x88\x97xww\x87w\x86_\xfb\xffeD?\xffk0\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA5129064GJ2vfvgwv\x87hx\x88\x87\x88ww\x87www\x87wd_\xfa\xffvfo\xff\x1d\x00\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA5454914GJ2\x98\x88\x88\x88\x87vwgx\x88\x87\x88xww\x87ffvf\xa7\x7f\xf9\xff\xa8w\x7f\xff\x1b\x90\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA5987784GJ2UVugDDtGx\x88\x87\x88w\x88\x87xwwwwd/\xfb\xff\x97fO\xff\xb0h\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA5987864GJ2fgvwUUuWgwvw\x87wxwwwww\x84/\xfc\xff\x97w\x7f\xff\xdf\x1d\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA6337644GJ2vgvwwv\x87hgffvwwwwwwww\x85O\xfa\xff\xa7w\x7f\xff\xc5\xfc\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA6802004GJ2UUuWUUuWgw\x86www\x87www\x87w\x96?\xf9\xff\xa9\x88\x7f\xff\x9fK\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA6892284GJ233S5\x87w\x87xx\x88\x87\x88vwwgww\x87w\x84?\xfb\xff\x98\x88\x8f\xff*\x9e\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA7005534GJ2eUuWfg\x86xxww\x87x\x88\x87\x88\x88w\x88\x87\x87O\xfc\xffuUO\xff\xa3k\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', - b'\xf1\x87SALFBA7152454GJ2gvwgFf\x86hx\x88\x87\x88vfWfffffd?\xfa\xff\xba\x88o\xff,\xcf\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', - b'\xf1\x87SALFBA7460044GJ2gx\x87\x88Vf\x86hx\x88\x87\x88wwwwgw\x86wd?\xfa\xff\x86U_\xff\xaf\x1f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SALFBA7485034GJ2ww\x87xww\x87xfwvgwwwwvfgf\xa5/\xfc\xff\xa9w_\xff40\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMDBA7743924GJ3wwwwww\x87xgwvw\x88\x88\x88\x88wwww\x85_\xfa\xff\x86f\x7f\xff0\x9d\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SAMDBA7817334GJ3Vgvwvfvgww\x87wwwwwwfgv\x97O\xfd\xff\x88\x88o\xff\x8e\xeb\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SAMDBA8054504GJ3gw\x87xffvgffffwwwweUVUf?\xfc\xffvU_\xff\xddl\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SAMFB41553621GC7ww\x87xUU\x85Xvwwg\x88\x88\x88\x88wwgw\x86\xaf\xfb\xffuDo\xff\xaa\x8f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMFB42555421GC7\x88\x88\x88\x88wvwgx\x88\x87\x88wwgw\x87wxw3\x8f\xfc\xff\x98f\x8f\xffga\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMFBA7978674GJ2gw\x87xgw\x97ywwwwvUGeUUeU\x87O\xfb\xff\x98w\x8f\xfffF\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMFBA8105254GJ2wx\x87\x88Vf\x86hx\x88\x87\x88wwwwwwww\x86O\xfa\xff\x99\x88\x7f\xffZG\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMFBA9283024GJ2wwwwEUuWwwgwwwwwwwww\x87/\xfb\xff\x98w\x8f\xff<\xd3\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMFBA9708354GJ2wwwwVf\x86h\x88wx\x87xww\x87\x88\x88\x88\x88w/\xfa\xff\x97w\x8f\xff\x86\xa0\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SANDB45316691GC6\x99\x99\x99\x99\x88\x88\xa8\x8avfwfwwww\x87wxwT\x9f\xfd\xff\x88wo\xff\x1c\xfa\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB3\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SANFB45889451GC7wx\x87\x88gw\x87x\x88\x88x\x88\x87wxw\x87wxw\x87\x8f\xfc\xffeU\x8f\xff+Q\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - ], }, - CAR.SONATA_LF: { + CAR.HYUNDAI_SONATA_LF: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00LF__ SCC F-CUP 1.00 1.00 96401-C2200 ', ], @@ -402,44 +239,22 @@ FW_VERSIONS = { b'\xf1\x00LF ESC \t 11 \x17\x01\x13 58920-C2610', b'\xf1\x00LF ESC \x0c 11 \x17\x01\x13 58920-C2610', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81606D5051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81606D5K51\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81606G1051\x00\x00\x00\x00\x00\x00\x00\x00', - ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00LFF LKAS AT USA LHD 1.00 1.01 95740-C1000 E51', b'\xf1\x00LFF LKAS AT USA LHD 1.01 1.02 95740-C1000 E52', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\xb0\x9f\xee\xf5', - b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2\x00\x00\x00\x00', - b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm', - b'\xf1\x87LAJSG49645724HF0\x87x\x87\x88\x87www\x88\x99\xa8\x89\x88\x99\xa8\x89\x88\x99\xa8\x89S_\xfb\xff\x87f\x7f\xff^2\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm', - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\x00\x00\x00\x00', - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\xb0\x9f\xee\xf5', - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24SL2n\x8d\xbe\xd8', - ], }, - CAR.TUCSON: { + CAR.HYUNDAI_TUCSON: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00TL__ FCA F-CUP 1.00 1.01 99110-D3500 ', b'\xf1\x00TL__ FCA F-CUP 1.00 1.02 99110-D3510 ', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81606G3051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x8971TLC2NAIDDIR002\xf1\x8271TLC2NAIDDIR002', - ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00TL MFC AT KOR LHD 1.00 1.02 95895-D3800 180719', b'\xf1\x00TL MFC AT USA LHD 1.00 1.06 95895-D3800 190107', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87KMLDCU585233TJ20wx\x87\x88x\x88\x98\x89vfwfwwww\x87f\x9f\xff\x98\xff\x7f\xf9\xf7s\xf1\x816T6G4051\x00\x00\xf1\x006T6J0_C2\x00\x006T6G4051\x00\x00TTL4G24NH2\x00\x00\x00\x00', - b'\xf1\x87LBJXAN202299KF22\x87x\x87\x88ww\x87xx\x88\x97\x88\x87\x88\x98x\x88\x99\x98\x89\x87o\xf6\xff\x87w\x7f\xff\x12\x9a\xf1\x81U083\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U083\x00\x00\x00\x00\x00\x00TTL2V20KL1\x8fRn\x8a', - ], }, - CAR.SANTA_FE: { + CAR.HYUNDAI_SANTA_FE: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1210 ', b'\xf1\x00TM__ SCC F-CUP 1.00 1.01 99110-S2000 ', @@ -458,11 +273,6 @@ FW_VERSIONS = { b'\xf1\x00TM ESC \r 104\x19\x07\x08 58910-S2650', b'\xf1\x00TM ESC \r 105\x19\x05# 58910-S1500', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81606EA051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81606G1051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81606G3051\x00\x00\x00\x00\x00\x00\x00\x00', - ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409', b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8A12', @@ -473,66 +283,26 @@ FW_VERSIONS = { b'\xf1\x00TM MFC AT EUR LHD 1.00 1.01 99211-S1010 181207', b'\xf1\x00TM MFC AT USA LHD 1.00 1.00 99211-S2000 180409', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', - b'\xf1\x00bcsh8p54 U833\x00\x00\x00\x00\x00\x00TTM4V22US3_<]\xf1', - b'\xf1\x87LBJSGA7082574HG0\x87www\x98\x88\x88\x88\x99\xaa\xb9\x9afw\x86gx\x99\xa7\x89co\xf8\xffvU_\xffR\xaf\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\xa6\xe0\x91', - b'\xf1\x87LBKSGA0458404HG0vfvg\x87www\x89\x99\xa8\x99y\xaa\xa7\x9ax\x88\xa7\x88t_\xf9\xff\x86w\x8f\xff\x15x\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\x00\x00\x00', - b'\xf1\x87LDJUEA6010814HG1\x87w\x87x\x86gvw\x88\x88\x98\x88gw\x86wx\x88\x97\x88\x85o\xf8\xff\x86f_\xff\xd37\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS0\xf8\x19\x92g', - b'\xf1\x87LDJUEA6458264HG1ww\x87x\x97x\x87\x88\x88\x99\x98\x89g\x88\x86xw\x88\x97x\x86o\xf7\xffvw\x8f\xff3\x9a\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS0\xf8\x19\x92g', - b'\xf1\x87LDKUEA2045844HG1wwww\x98\x88x\x87\x88\x88\xa8\x88x\x99\x97\x89x\x88\xa7\x88U\x7f\xf8\xffvfO\xffC\x1e\xf1\x816W3E0051\x00\x00\xf1\x006W351_C2\x00\x006W3E0051\x00\x00TTM4T20NS3\x00\x00\x00\x00', - b'\xf1\x87LDKUEA9993304HG1\x87www\x97x\x87\x88\x99\x99\xa9\x99x\x99\xa7\x89w\x88\x97x\x86_\xf7\xffwwO\xffl#\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS1R\x7f\x90\n', - b'\xf1\x87LDLUEA6061564HG1\xa9\x99\x89\x98\x87wwwx\x88\x97\x88x\x99\xa7\x89x\x99\xa7\x89sO\xf9\xffvU_\xff<\xde\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS50\xcb\xc3\xed', - b'\xf1\x87LDLUEA6159884HG1\x88\x87hv\x99\x99y\x97\x89\xaa\xb8\x9ax\x99\x87\x89y\x99\xb7\x99\xa7?\xf7\xff\x97wo\xff\xf3\x05\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', - b'\xf1\x87LDLUEA6852664HG1\x97wWu\x97www\x89\xaa\xc8\x9ax\x99\x97\x89x\x99\xa7\x89SO\xf7\xff\xa8\x88\x7f\xff\x03z\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS50\xcb\xc3\xed', - b'\xf1\x87LDLUEA6898374HG1fevW\x87wwwx\x88\x97\x88h\x88\x96\x88x\x88\xa7\x88ao\xf9\xff\x98\x99\x7f\xffD\xe2\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', - b'\xf1\x87LDLUEA6898374HG1fevW\x87wwwx\x88\x97\x88h\x88\x96\x88x\x88\xa7\x88ao\xf9\xff\x98\x99\x7f\xffD\xe2\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS50\xcb\xc3\xed', - b'\xf1\x87SBJWAA5842214GG0\x88\x87\x88xww\x87x\x89\x99\xa8\x99\x88\x99\x98\x89w\x88\x87xw_\xfa\xfffU_\xff\xd1\x8d\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x98{|\xe3', - b'\xf1\x87SBJWAA5890864GG0\xa9\x99\x89\x98\x98\x87\x98y\x89\x99\xa8\x99w\x88\x87xww\x87wvo\xfb\xffuD_\xff\x9f\xb5\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x98{|\xe3', - b'\xf1\x87SBJWAA6562474GG0ffvgeTeFx\x88\x97\x88ww\x87www\x87w\x84o\xfa\xff\x87fO\xff\xc2 \xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x00\x00\x00\x00', - b'\xf1\x87SBJWAA6562474GG0ffvgeTeFx\x88\x97\x88ww\x87www\x87w\x84o\xfa\xff\x87fO\xff\xc2 \xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x98{|\xe3', - b'\xf1\x87SBJWAA7780564GG0wvwgUUeVwwwwx\x88\x87\x88wwwwd_\xfc\xff\x86f\x7f\xff\xd7*\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS2F\x84<\xc0', - b'\xf1\x87SBJWAA8278284GG0ffvgUU\x85Xx\x88\x87\x88x\x88w\x88ww\x87w\x96o\xfd\xff\xa7U_\xff\xf2\xa0\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS2F\x84<\xc0', - b'\xf1\x87SBLWAA4363244GG0wvwgwv\x87hgw\x86ww\x88\x87xww\x87wdo\xfb\xff\x86f\x7f\xff3$\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS6\x00\x00\x00\x00', - b'\xf1\x87SBLWAA4363244GG0wvwgwv\x87hgw\x86ww\x88\x87xww\x87wdo\xfb\xff\x86f\x7f\xff3$\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS6x0\x17\xfe', - b'\xf1\x87SBLWAA4899564GG0VfvgUU\x85Xx\x88\x87\x88vfgf\x87wxwvO\xfb\xff\x97f\xb1\xffSB\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS7\x00\x00\x00\x00', - b'\xf1\x87SBLWAA6622844GG0wwwwff\x86hwwwwx\x88\x87\x88\x88\x88\x88\x88\x98?\xfd\xff\xa9\x88\x7f\xffn\xe5\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS7u\x1e{\x1c', - b'\xf1\x87SDJXAA7656854GG1DEtWUU\x85X\x88\x88\x98\x88w\x88\x87xx\x88\x87\x88\x96o\xfb\xff\x86f\x7f\xff.\xca\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4G24NS2\x00\x00\x00\x00', - b'\xf1\x87SDJXAA7656854GG1DEtWUU\x85X\x88\x88\x98\x88w\x88\x87xx\x88\x87\x88\x96o\xfb\xff\x86f\x7f\xff.\xca\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4G24NS2K\xdaV0', - b'\xf1\x87SDKXAA2443414GG1vfvgwv\x87h\x88\x88\x88\x88ww\x87wwwww\x99_\xfc\xffvD?\xffl\xd2\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4G24NS6\x00\x00\x00\x00', - ], }, - CAR.SANTA_FE_2022: { + CAR.HYUNDAI_SANTA_FE_2022: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1500 ', + b'\xf1\x00TM__ SCC F-CUP 1.00 1.01 99110-S1500 ', b'\xf1\x00TM__ SCC FHCUP 1.00 1.00 99110-S1500 ', + b'\xf1\x00TM__ SCC FHCUP 1.00 1.01 99110-S1500 ', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00TM ESC \x01 102!\x04\x03 58910-S2DA0', + b'\xf1\x00TM ESC \x01 104"\x10\x07 58910-S2DA0', b'\xf1\x00TM ESC \x02 101 \x08\x04 58910-S2GA0', b'\xf1\x00TM ESC \x02 103"\x07\x08 58910-S2GA0', b'\xf1\x00TM ESC \x03 101 \x08\x02 58910-S2DA0', + b'\xf1\x00TM ESC \x03 102!\x04\x03 58910-S2DA0', b'\xf1\x00TM ESC \x04 101 \x08\x04 58910-S2GA0', b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', + b'\xf1\x00TM ESC \x04 103"\x07\x08 58910-S2GA0', + b'\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0', b'\xf1\x00TM ESC 103!\x030 58910-S1MA0', - b'\xf1\x8758910-S1DA0\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0', - b'\xf1\x8758910-S2DA0\xf1\x00TM ESC \x03 101 \x08\x02 58910-S2DA0', - b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x02 101 \x08\x04 58910-S2GA0', - b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81HM6M1_0a0_G20', - b'\xf1\x81HM6M1_0a0_H00', - b'\xf1\x81HM6M2_0a0_G00', - b'\xf1\x82TACVN5GMI3XXXH0A', - b'\xf1\x82TACVN5GSI3XXXH0A', - b'\xf1\x82TMBZN5TMD3XXXG2E', - b'\xf1\x82TMCFD5MMCXXXXG0A', - b'\xf1\x87 \xf1\x81 ', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_L50', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82TMDWN5TMD3TXXJ1A', - b'\xf1\x8739101-2STN8\xf1\x81HM6M1_0a0_M00', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00TM MDPS C 1.00 1.01 56310-S1AB0 4TSDC101', @@ -547,54 +317,33 @@ FW_VERSIONS = { b'\xf1\x00TMA MFC AT USA LHD 1.00 1.01 99211-S2500 210205', b'\xf1\x00TMA MFC AT USA LHD 1.00 1.03 99211-S2500 220414', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00HT6TA290BLHT6TAF00A1STM0M25GS1\x00\x00\x00\x00\x00\x006\xd8\x97\x15', - b'\xf1\x00HT6WA280BLHT6WAD00A1STM2G25NH2\x00\x00\x00\x00\x00\x00\xf8\xc0\xc3\xaa', - b'\xf1\x00HT6WA280BLHT6WAD00A1STM4G25NH1\x00\x00\x00\x00\x00\x00\x9cl\x04\xbc', - b'\xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7', - b'\xf1\x00T02601BL T02800A1 VTMPT25XXX800NS4\xed\xaf\xed\xf5', - b'\xf1\x00T02601BL T02900A1 VTMPT25XXW900NS1c\x918\xc5', - b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', - b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NSA\xf3\xf4Uj', - b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6', - b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7', - b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02900A1 \xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', - b'\xf1\x87KMMYBU034207SB72x\x89\x88\x98h\x88\x98\x89\x87fhvvfWf33_\xff\x87\xff\x8f\xfa\x81\xe5\xf1\x89HT6TAF00A1\xf1\x82STM0M25GS1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SDMXCA8653204GN1EVugEUuWwwwwww\x87wwwwwv/\xfb\xff\xa8\x88\x9f\xff\xa5\x9c\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SDMXCA9087684GN1VfvgUUeVwwgwwwwwffffU?\xfb\xff\x97\x88\x7f\xff+\xa4\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', - ], }, - CAR.SANTA_FE_HEV_2022: { + CAR.HYUNDAI_SANTA_FE_HEV_2022: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', + b'\xf1\x00TMhe SCC FHCUP 1.00 1.01 99110-CL500 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102', b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLEC0 4TSHC102', b'\xf1\x00TM MDPS C 1.00 1.02 56310-GA000 4TSHA100', b'\xf1\x00TM MDPS R 1.00 1.05 57700-CL000 4TSHP105', + b'\xf1\x00TM MDPS R 1.00 1.06 57700-CL000 4TSHP106', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00TMA MFC AT USA LHD 1.00 1.03 99211-S2500 220414', b'\xf1\x00TMH MFC AT EUR LHD 1.00 1.06 99211-S1500 220727', + b'\xf1\x00TMH MFC AT KOR LHD 1.00 1.06 99211-S1500 220727', b'\xf1\x00TMH MFC AT USA LHD 1.00 1.03 99211-S1500 210224', + b'\xf1\x00TMH MFC AT USA LHD 1.00 1.05 99211-S1500 220126', b'\xf1\x00TMH MFC AT USA LHD 1.00 1.06 99211-S1500 220727', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16SA3\xa3\x1b\xe14', - b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16UA3I\x94\xac\x8f', - b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TTM2H16SA2\x80\xd7l\xb2', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x87391312MTC1', - b'\xf1\x87391312MTE0', - b'\xf1\x87391312MTL0', - ], }, - CAR.SANTA_FE_PHEV_2022: { + CAR.HYUNDAI_SANTA_FE_PHEV_2022: { (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00TMhe SCC F-CUP 1.00 1.00 99110-CL500 ', + b'\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', b'\xf1\x00TMhe SCC FHCUP 1.00 1.01 99110-CL500 ', - b'\xf1\x8799110CL500\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102', @@ -603,19 +352,11 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00TMP MFC AT USA LHD 1.00 1.03 99211-S1500 210224', + b'\xf1\x00TMP MFC AT USA LHD 1.00 1.05 99211-S1500 220126', b'\xf1\x00TMP MFC AT USA LHD 1.00 1.06 99211-S1500 220727', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea', - b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA0o\x88^\xbe', - b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x87391312MTF0', - b'\xf1\x87391312MTF1', - ], }, - CAR.CUSTIN_1ST_GEN: { + CAR.HYUNDAI_CUSTIN_1ST_GEN: { (Ecu.abs, 0x7d1, None): [ b'\xf1\x00KU ESC \x01 101!\x02\x03 58910-O3200', ], @@ -628,12 +369,6 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00KU2 MFC AT CHN LHD 1.00 1.02 99211-O3000 220923', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x87391212MEC0', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 U928\x00\x00\x00\x00\x00\x00SKU0T15KB2\x92U\xf9M', - ], }, CAR.KIA_STINGER: { (Ecu.fwdRadar, 0x7d0, None): [ @@ -642,14 +377,6 @@ FW_VERSIONS = { b'\xf1\x00CK__ SCC F_CUP 1.00 1.02 96400-J5100 ', b'\xf1\x00CK__ SCC F_CUP 1.00 1.03 96400-J5100 ', ], - (Ecu.engine, 0x7e0, None): [ - b'\xe0\x19\xff\xe7\xe7g\x01\xa2\x00\x0f\x00\x9e\x00\x06\x00\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x0f\x0e\x0f\x0f\x0e\r\x00\x00\x7f\x02.\xff\x00\x00~p\x00\x00\x00\x00u\xff\xf9\xff\x00\x00\x00\x00V\t\xd5\x01\xc0\x00\x00\x00\x007\xfb\xfc\x0b\x8d\x00', - b'\xf1\x81606DE051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640E0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x82CKJN3TMSDE0B\x00\x00\x00\x00', - b'\xf1\x82CKKN3TMD_H0A\x00\x00\x00\x00', - ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5200 4C2CL104', b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5220 4C2VL104', @@ -663,20 +390,6 @@ FW_VERSIONS = { b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 95740-J5000 170822', b'\xf1\x00CK MFC AT USA LHD 1.00 1.04 95740-J5000 180504', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', - b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', - b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SCK0T33NB2\xb3\xee\xba\xdc', - b'\xf1\x87VCJLE17622572DK0vd6D\x99\x98y\x97vwVffUfvfC%CuT&Dx\x87o\xff{\x1c\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', - b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', - b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', - b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x89E21\x00\x00\x00\x00\x00\x00\x00\xf1\x82SCK0T33NB0', - b'\xf1\x87VDHLG17034412DK2vD6DfVvVTD$D\x99w\x88\x98EDEDeT6DgfO\xff\xc3=\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', - b'\xf1\x87VDHLG17118862DK2\x8awWwgu\x96wVfUVwv\x97xWvfvUTGTx\x87o\xff\xc9\xed\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', - b'\xf1\x87VDHLG17274082DK2wfFf\x89x\x98wUT5T\x88v\x97xgeGefTGTVvO\xff\x1c\x14\xf1\x81E19\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E19\x00\x00\x00\x00\x00\x00\x00SCK0T33UB2\xee[\x97S', - b'\xf1\x87VDKLJ18675252DK6\x89vhgwwwwveVU\x88w\x87w\x99vgf\x97vXfgw_\xff\xc2\xfb\xf1\x89E25\x00\x00\x00\x00\x00\x00\x00\xf1\x82TCK0T33NB2', - b'\xf1\x87WAJTE17552812CH4vfFffvfVeT5DwvvVVdFeegeg\x88\x88o\xff\x1a]\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00TCK2T20NB1\x19\xd2\x00\x94', - ], }, CAR.KIA_STINGER_2022: { (Ecu.fwdRadar, 0x7d0, None): [ @@ -685,15 +398,11 @@ FW_VERSIONS = { b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5600 ', b'\xf1\x00CK__ SCC FHCUP 1.00 1.01 99110-J5100 ', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640N2051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81HM6M1_0a0_H00', - ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5300 4C2CL503', b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5320 4C2VL503', b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', + b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5520 4C4VL503', b'\xf1\x00CK MDPS R 1.00 5.04 57700-J5520 4C4VL504', ], (Ecu.fwdCamera, 0x7c4, None): [ @@ -702,29 +411,29 @@ FW_VERSIONS = { b'\xf1\x00CK MFC AT USA LHD 1.00 1.00 99211-J5500 210622', b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 99211-J5000 201209', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T25KH2B\xfbI\xe2', - b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T33NH07\xdf\xf0\xc1', - b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00TCK0T33NH0%g~\xd3', - b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6', - ], }, - CAR.PALISADE: { + CAR.HYUNDAI_PALISADE: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00LX2 SCC FHCUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC F-CUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC F-CUP 1.00 1.05 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCU- 1.00 1.05 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCUP 1.00 1.00 99110-S8110 ', + b'\xf1\x00LX2_ SCC FHCUP 1.00 1.03 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCUP 1.00 1.05 99110-S8100 ', + b'\xf1\x00ON__ FCA FHCUP 1.00 1.00 99110-S9110 ', b'\xf1\x00ON__ FCA FHCUP 1.00 1.01 99110-S9110 ', b'\xf1\x00ON__ FCA FHCUP 1.00 1.02 99110-S9100 ', + b'\xf1\x00ON__ FCA FHCUP 1.00 1.03 99110-S9100 ', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00LX ESC \x01 103\x19\t\x10 58910-S8360', b'\xf1\x00LX ESC \x01 1031\t\x10 58910-S8360', + b'\xf1\x00LX ESC \x01 104 \x10\x15 58910-S8350', + b'\xf1\x00LX ESC \x01 104 \x10\x16 58910-S8360', b'\xf1\x00LX ESC \x0b 101\x19\x03\x17 58910-S8330', + b'\xf1\x00LX ESC \x0b 101\x19\x03 58910-S8360', b'\xf1\x00LX ESC \x0b 102\x19\x05\x07 58910-S8330', b'\xf1\x00LX ESC \x0b 103\x19\t\x07 58910-S8330', b'\xf1\x00LX ESC \x0b 103\x19\t\t 58910-S8350', @@ -735,20 +444,19 @@ FW_VERSIONS = { b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320', b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640K0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640S1051\x00\x00\x00\x00\x00\x00\x00\x00', - ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00LX2 MDPS C 1,00 1,03 56310-S8020 4LXDC103', b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8000 4LXDC103', b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8020 4LXDC103', + b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-XX000 4LXDC103', b'\xf1\x00LX2 MDPS C 1.00 1.04 56310-S8020 4LXDC104', + b'\xf1\x00LX2 MDPS C 1.00 1.04 56310-S8420 4LXDC104', + b'\xf1\x00LX2 MDPS R 1.00 1.02 56370-S8300 9318', b'\xf1\x00ON MDPS C 1.00 1.00 56340-S9000 8B13', b'\xf1\x00ON MDPS C 1.00 1.01 56340-S9000 9201', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00LX2 MFC AT KOR LHD 1.00 1.08 99211-S8100 200903', b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.00 99211-S8110 210226', b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.03 99211-S8100 190125', b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.05 99211-S8100 190909', @@ -758,74 +466,12 @@ FW_VERSIONS = { b'\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 200720', b'\xf1\x00ON MFC AT USA LHD 1.00 1.04 99211-S9100 211227', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', - b'\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00TON2G38NB5j\x94.\xde', - b'\xf1\x87LBLUFN591307KF25vgvw\x97wwwy\x99\xa7\x99\x99\xaa\xa9\x9af\x88\x96h\x95o\xf7\xff\x99f/\xff\xe4c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB2\xd7\xc1/\xd1', - b"\xf1\x87LBLUFN622950KF36\xa8\x88\x88\x88\x87w\x87xh\x99\x96\x89\x88\x99\x98\x89\x88\x99\x98\x89\x87o\xf6\xff\x98\x88o\xffx'\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8", - b'\xf1\x87LBLUFN650868KF36\xa9\x98\x89\x88\xa8\x88\x88\x88h\x99\xa6\x89fw\x86gw\x88\x97x\xaa\x7f\xf6\xff\xbb\xbb\x8f\xff+\x82\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', - b'\xf1\x87LBLUFN655162KF36\x98\x88\x88\x88\x98\x88\x88\x88x\x99\xa7\x89x\x99\xa7\x89x\x99\x97\x89g\x7f\xf7\xffwU_\xff\xe9!\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', - b'\xf1\x87LBLUFN731381KF36\xb9\x99\x89\x98\x98\x88\x88\x88\x89\x99\xa8\x99\x88\x99\xa8\x89\x88\x88\x98\x88V\x7f\xf6\xff\x99w\x8f\xff\xad\xd8\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', - b'\xf1\x87LDKVAA0028604HH1\xa8\x88x\x87vgvw\x88\x99\xa8\x89gw\x86ww\x88\x97x\x97o\xf9\xff\x97w\x7f\xffo\x02\xf1\x81U872\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', - b'\xf1\x87LDKVAA3068374HH1wwww\x87xw\x87y\x99\xa7\x99w\x88\x87xw\x88\x97x\x85\xaf\xfa\xffvU/\xffU\xdc\xf1\x81U872\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', - b'\xf1\x87LDKVBN382172KF26\x98\x88\x88\x88\xa8\x88\x88\x88x\x99\xa7\x89\x87\x88\x98x\x98\x99\xa9\x89\xa5_\xf6\xffDDO\xff\xcd\x16\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', - b'\xf1\x87LDKVBN424201KF26\xba\xaa\x9a\xa9\x99\x99\x89\x98\x89\x99\xa8\x99\x88\x99\x98\x89\x88\x99\xa8\x89v\x7f\xf7\xffwf_\xffq\xa6\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', - b'\xf1\x87LDKVBN540766KF37\x87wgv\x87w\x87xx\x99\x97\x89v\x88\x97h\x88\x88\x88\x88x\x7f\xf6\xffvUo\xff\xd3\x01\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', - b'\xf1\x87LDLVAA4225634HH1\x98\x88\x88\x88eUeVx\x88\x87\x88g\x88\x86xx\x88\x87\x88\x86o\xf9\xff\x87w\x7f\xff\xf2\xf7\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x87LDLVAA4478824HH1\x87wwwvfvg\x89\x99\xa8\x99w\x88\x87x\x89\x99\xa8\x99\xa6o\xfa\xfffU/\xffu\x92\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x87LDLVAA4777834HH1\x98\x88x\x87\x87wwwx\x88\x87\x88x\x99\x97\x89x\x88\x97\x88\x86o\xfa\xff\x86fO\xff\x1d9\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x87LDLVAA5194534HH1ffvguUUUx\x88\xa7\x88h\x99\x96\x89x\x88\x97\x88ro\xf9\xff\x98wo\xff\xaaM\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x87LDLVAA5949924HH1\xa9\x99y\x97\x87wwwx\x99\x97\x89x\x99\xa7\x89x\x99\xa7\x89\x87_\xfa\xffeD?\xff\xf1\xfd\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x87LDLVBN560098KF26\x86fff\x87vgfg\x88\x96xfw\x86gfw\x86g\x95\xf6\xffeU_\xff\x92c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', - b'\xf1\x87LDLVBN602045KF26\xb9\x99\x89\x98\x97vwgy\xaa\xb7\x9af\x88\x96hw\x99\xa7y\xa9\x7f\xf5\xff\x99w\x7f\xff,\xd3\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN628911KF26\xa9\x99\x89\x98\x98\x88\x88\x88y\x99\xa7\x99fw\x86gw\x88\x87x\x83\x7f\xf6\xff\x98wo\xff2\xda\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN645817KF37\x87www\x98\x87xwx\x99\x97\x89\x99\x99\x99\x99g\x88\x96x\xb6_\xf7\xff\x98fo\xff\xe2\x86\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN662115KF37\x98\x88\x88\x88\xa8\x88\x88\x88x\x99\x97\x89x\x99\xa7\x89\x88\x99\xa8\x89\x88\x7f\xf7\xfffD_\xff\xdc\x84\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN667933KF37\xb9\x99\x89\x98\xb9\x99\x99\x99x\x88\x87\x88w\x88\x87x\x88\x88\x98\x88\xcbo\xf7\xffe3/\xffQ!\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN673087KF37\x97www\x86fvgx\x99\x97\x89\x99\xaa\xa9\x9ag\x88\x86x\xe9_\xf8\xff\x98w\x7f\xff"\xad\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN673841KF37\x98\x88x\x87\x86g\x86xy\x99\xa7\x99\x88\x99\xa8\x89w\x88\x97xdo\xf5\xff\x98\x88\x8f\xffT\xec\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN681363KF37\x98\x88\x88\x88\x97x\x87\x88y\xaa\xa7\x9a\x88\x88\x98\x88\x88\x88\x88\x88vo\xf6\xffvD\x7f\xff%v\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN713782KF37\x99\x99y\x97\x98\x88\x88\x88x\x88\x97\x88\x88\x99\x98\x89\x88\x99\xa8\x89\x87o\xf7\xffeU?\xff7,\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN713890KF26\xb9\x99\x89\x98\xa9\x99\x99\x99x\x99\x97\x89\x88\x99\xa8\x89\x88\x99\xb8\x89Do\xf7\xff\xa9\x88o\xffs\r\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN733215KF37\x99\x98y\x87\x97wwwi\x99\xa6\x99x\x99\xa7\x89V\x88\x95h\x86o\xf7\xffeDO\xff\x12\xe7\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN750044KF37\xca\xa9\x8a\x98\xa7wwwy\xaa\xb7\x9ag\x88\x96x\x88\x99\xa8\x89\xb9\x7f\xf6\xff\xa8w\x7f\xff\xbe\xde\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN752612KF37\xba\xaa\x8a\xa8\x87w\x87xy\xaa\xa7\x9a\x88\x99\x98\x89x\x88\x97\x88\x96o\xf6\xffvU_\xffh\x1b\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN755553KF37\x87xw\x87\x97w\x87xy\x99\xa7\x99\x99\x99\xa9\x99Vw\x95gwo\xf6\xffwUO\xff\xb5T\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN757883KF37\x98\x87xw\x98\x87\x88xy\xaa\xb7\x9ag\x88\x96x\x89\x99\xa8\x99e\x7f\xf6\xff\xa9\x88o\xff5\x15\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN778156KF37\x87vWe\xa9\x99\x99\x99y\x99\xb7\x99\x99\x99\x99\x99x\x99\x97\x89\xa8\x7f\xf8\xffwf\x7f\xff\x82_\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN780576KF37\x98\x87hv\x97x\x97\x89x\x99\xa7\x89\x88\x99\x98\x89w\x88\x97x\x98\x7f\xf7\xff\xba\x88\x8f\xff\x1e0\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN783485KF37\x87www\x87vwgy\x99\xa7\x99\x99\x99\xa9\x99Vw\x95g\x89_\xf6\xff\xa9w_\xff\xc5\xd6\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN811844KF37\x87vwgvfffx\x99\xa7\x89Vw\x95gg\x88\xa6xe\x8f\xf6\xff\x97wO\xff\t\x80\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN830601KF37\xa7www\xa8\x87xwx\x99\xa7\x89Uw\x85Ww\x88\x97x\x88o\xf6\xff\x8a\xaa\x7f\xff\xe2:\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN848789KF37\x87w\x87x\x87w\x87xy\x99\xb7\x99\x87\x88\x98x\x88\x99\xa8\x89\x87\x7f\xf6\xfffUo\xff\xe3!\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN851595KF37\x97wgvvfffx\x99\xb7\x89\x88\x99\x98\x89\x87\x88\x98x\x99\x7f\xf7\xff\x97w\x7f\xff@\xf3\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN871852KF37\xb9\x99\x99\x99\xa8\x88\x88\x88y\x99\xa7\x99x\x99\xa7\x89\x88\x88\x98\x88\x89o\xf7\xff\xaa\x88o\xff\x0e\xed\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN873175KF26\xa8\x88\x88\x88vfVex\x99\xb7\x89\x88\x99\x98\x89x\x88\x97\x88f\x7f\xf7\xff\xbb\xaa\x8f\xff,\x04\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN879401KF26veVU\xa8\x88\x88\x88g\x88\xa6xVw\x95gx\x88\xa7\x88v\x8f\xf9\xff\xdd\xbb\xbf\xff\xb3\x99\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN881314KF37\xa8\x88h\x86\x97www\x89\x99\xa8\x99w\x88\x97xx\x99\xa7\x89\xca\x7f\xf8\xff\xba\x99\x8f\xff\xd8v\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN888651KF37\xa9\x99\x89\x98vfff\x88\x99\x98\x89w\x99\xa7y\x88\x88\x98\x88D\x8f\xf9\xff\xcb\x99\x8f\xff\xa5\x1e\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN889419KF37\xa9\x99y\x97\x87w\x87xx\x88\x97\x88w\x88\x97x\x88\x99\x98\x89e\x9f\xf9\xffeUo\xff\x901\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN895969KF37vefV\x87vgfx\x99\xa7\x89\x99\x99\xb9\x99f\x88\x96he_\xf7\xffxwo\xff\x14\xf9\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN899222KF37\xa8\x88x\x87\x97www\x98\x99\x99\x89\x88\x99\x98\x89f\x88\x96hdo\xf7\xff\xbb\xaa\x9f\xff\xe2U\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN950669KF37\x97www\x96fffy\x99\xa7\x99\xa9\x99\xaa\x99g\x88\x96x\xb8\x8f\xf9\xffTD/\xff\xa7\xcb\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - ], }, - CAR.VELOSTER: { + CAR.HYUNDAI_VELOSTER: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00JS__ SCC H-CUP 1.00 1.02 95650-J3200 ', b'\xf1\x00JS__ SCC HNCUP 1.00 1.02 95650-J3100 ', ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816V8RAC00121.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x01TJS-JDK06F200H0A', - b'\x01TJS-JNU06F200H0A', - b'391282BJF5 ', - ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00JSL MDPS C 1.00 1.03 56340-J3000 8308', ], @@ -833,119 +479,77 @@ FW_VERSIONS = { b'\xf1\x00JS LKAS AT KOR LHD 1.00 1.03 95740-J3000 K33', b'\xf1\x00JS LKAS AT USA LHD 1.00 1.02 95740-J3000 K32', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16KS2\x0e\xba\x1e\xa2', - b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\x00\x00\x00\x00', - b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\xba\x02\xb8\x80', - ], }, CAR.GENESIS_G70: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00IK__ SCC F-CUP 1.00 1.01 96400-G9100 ', b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', - ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00IK MDPS R 1.00 1.06 57700-G9420 4I4VL106', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', - b'\xf1\x87VDJLT17895112DN4\x88fVf\x99\x88\x88\x88\x87fVe\x88vhwwUFU\x97eFex\x99\x7f\xff\xb7\x82\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', - ], }, CAR.GENESIS_G70_2020: { (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00IK MDPS R 1.00 1.06 57700-G9220 4I2VL106', b'\xf1\x00IK MDPS R 1.00 1.07 57700-G9220 4I2VL107', b'\xf1\x00IK MDPS R 1.00 1.07 57700-G9420 4I4VL107', b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9200 4I2CL108', b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9420 4I4VL108', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL', - b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T20KB3Wuvz', - b'\xf1\x87VCJLP18407832DN3\x88vXfvUVT\x97eFU\x87d7v\x88eVeveFU\x89\x98\x7f\xff\xb2\xb0\xf1\x81E25\x00\x00\x00', - b'\xf1\x87VDJLC18480772DK9\x88eHfwfff\x87eFUeDEU\x98eFe\x86T5DVyo\xff\x87s\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33KB5\x9f\xa5&\x81', - b'\xf1\x87VDKLT18912362DN4wfVfwefeveVUwfvw\x88vWfvUFU\x89\xa9\x8f\xff\x87w\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL', + b'\xf1\x00IK MDPS R 1.00 5.09 57700-G9520 4I4VL509', ], (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00IK__ SCC F-CUP 1.00 1.01 96400-G9100 ', b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ', b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 \xf1\xa01.02', + b'\xf1\x00IK__ SCC FHCUP 1.00 1.00 99110-G9300 ', b'\xf1\x00IK__ SCC FHCUP 1.00 1.02 96400-G9000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00IK MFC AT KOR LHD 1.00 1.01 95740-G9000 170920', b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81606G2051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x00IK MFC AT USA LHD 1.00 1.04 99211-G9000 220401', ], }, CAR.GENESIS_G80: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DH__ SCC F-CUP 1.00 1.01 96400-B1120 ', + b'\xf1\x00DH__ SCC F-CUP 1.00 1.02 96400-B1120 ', b'\xf1\x00DH__ SCC FHCUP 1.00 1.01 96400-B1110 ', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DH LKAS AT KOR LHD 1.01 1.01 95895-B1500 161014', b'\xf1\x00DH LKAS AT KOR LHD 1.01 1.02 95895-B1500 170810', b'\xf1\x00DH LKAS AT USA LHD 1.01 1.01 95895-B1500 161014', b'\xf1\x00DH LKAS AT USA LHD 1.01 1.02 95895-B1500 170810', b'\xf1\x00DH LKAS AT USA LHD 1.01 1.03 95895-B1500 180713', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G33KH2\xae\xde\xd5!', - b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G38NH2j\x9dA\x1c', - b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0T33NH3\x97\xe6\xbc\xb8', - b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00TDH0G38NH3:-\xa9n', - b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SDH0T33NH4\xd7O\x9e\xc9', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640A4051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.04 95895-B1500 181213', ], }, CAR.GENESIS_G90: { - (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 ', + b'\xf1\x00HI__ SCC FHCUP 1.00 1.02 99110-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', + b'\xf1\x00HI MFC AT USA LHD 1.00 1.03 99211-D2000 190831', ], }, - CAR.KONA: { + CAR.HYUNDAI_KONA: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00OS__ SCC F-CUP 1.00 1.00 95655-J9200 ', ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x816V5RAK00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'"\x01TOS-0NU06F301J02', - ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00OS MDPS C 1.00 1.05 56310J9030\x00 4OSDC105', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00OS9 LKAS AT USA LHD 1.00 1.00 95740-J9300 g21', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U2VE051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VE051\x00\x00DOS4T16NS3\x00\x00\x00\x00', - ], }, CAR.KIA_CEED: { (Ecu.fwdRadar, 0x7d0, None): [ @@ -957,13 +561,6 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CD LKAS AT EUR LHD 1.00 1.01 99211-J7000 B40', ], - (Ecu.engine, 0x7e0, None): [ - b'\x01TCD-JECU4F202H0K', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U2V7051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V7051\x00\x00DCD0T14US1\x00\x00\x00\x00', - b'\xf1\x816U2V7051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V7051\x00\x00DCD0T14US1U\x867Z', - ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00CD ESC \x03 102\x18\x08\x05 58920-J7350', ], @@ -984,22 +581,6 @@ FW_VERSIONS = { b'\xf1\x00BDPE_SCC FHCUPC 1.00 1.04 99110-M6500\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x00BD__ SCC H-CUP 1.00 1.02 99110-M6000 ', ], - (Ecu.engine, 0x7e0, None): [ - b'\x01TBDM1NU06F200H01', - b'391182B945\x00', - 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", - ], }, CAR.KIA_K5_2021: { (Ecu.fwdRadar, 0x7d0, None): [ @@ -1007,17 +588,13 @@ FW_VERSIONS = { b'\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', b'\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', b'\xf1\x00DL3_ SCC FHCUP 1.00 1.04 99110-L2100 ', - b'\xf1\x8799110L2000\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', - b'\xf1\x8799110L2100\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', - b'\xf1\x8799110L2100\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', ], (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', b'\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L2220 4DLDC102', b'\xf1\x00DL3 MDPS C 1.00 1.02 56310L3220\x00 4DLAC102', - b'\xf1\x8756310-L3110\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', - b'\xf1\x8756310-L3220\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', - b'\xf1\x8757700-L3000\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102', + b'\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DL3 MFC AT KOR LHD 1.00 1.04 99210-L2000 210527', @@ -1027,29 +604,12 @@ FW_VERSIONS = { ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00DL ESC \x01 104 \x07\x12 58910-L2200', + b'\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600', b'\xf1\x00DL ESC \x06 101 \x04\x02 58910-L3200', b'\xf1\x00DL ESC \x06 103"\x08\x06 58910-L3200', b'\xf1\x00DL ESC \t 100 \x06\x02 58910-L3800', - b'\xf1\x8758910-L3200\xf1\x00DL ESC \x06 101 \x04\x02 58910-L3200', - b'\xf1\x8758910-L3600\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600', - b'\xf1\x8758910-L3800\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81HM6M2_0a0_DQ0', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DLDWN5TMDCXXXJ1B', - b'\xf1\x87391212MKT0', - b'\xf1\x87391212MKT3', - b'\xf1\x87391212MKV0', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00HT6TA261BLHT6TAB00A1SDL0C20KS0\x00\x00\x00\x00\x00\x00\\\x9f\xa5\x15', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB2.\x13\xf6\xed', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', - b'\xf1\x87954A02N300\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 WDL3T25XXX730NS2b\x1f\xb8%', - b'\xf1\x87SALFEA5652514GK2UUeV\x88\x87\x88xxwg\x87ww\x87wwfwvd/\xfb\xffvU_\xff\x93\xd3\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', - b'\xf1\x87SALFEA6046104GK2wvwgeTeFg\x88\x96xwwwwffvfe?\xfd\xff\x86fo\xff\x97A\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', - b'\xf1\x87SCMSAA8572454GK1\x87x\x87\x88Vf\x86hgwvwvwwgvwwgT?\xfb\xff\x97fo\xffH\xb8\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', + b'\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800', + b'\xf1\x00DL ESC \t 102"\x08\x10 58910-L3800', ], }, CAR.KIA_K5_HEV_2020: { @@ -1064,17 +624,11 @@ FW_VERSIONS = { b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.02 99210-L2000 200309', b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.04 99210-L2000 210527', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x87391162JLA0', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00PSBG2323 E08\x00\x00\x00\x00\x00\x00\x00TDL2H20KA2\xe3\xc6cz', - b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDL2H20KA5T\xf2\xc9\xc2', - ], }, - CAR.KONA_EV: { + CAR.HYUNDAI_KONA_EV: { (Ecu.abs, 0x7d1, None): [ b'\xf1\x00OS IEB \x01 212 \x11\x13 58520-K4000', + b'\xf1\x00OS IEB \x02 210 \x02\x14 58520-K4000', b'\xf1\x00OS IEB \x02 212 \x11\x13 58520-K4000', b'\xf1\x00OS IEB \x03 210 \x02\x14 58520-K4000', b'\xf1\x00OS IEB \x03 212 \x11\x13 58520-K4000', @@ -1085,10 +639,12 @@ FW_VERSIONS = { b'\xf1\x00OSE LKAS AT EUR LHD 1.00 1.00 95740-K4100 W40', b'\xf1\x00OSE LKAS AT EUR RHD 1.00 1.00 95740-K4100 W40', b'\xf1\x00OSE LKAS AT KOR LHD 1.00 1.00 95740-K4100 W40', + b'\xf1\x00OSE LKAS AT USA LHD 1.00 1.00 95740-K4100 W40', b'\xf1\x00OSE LKAS AT USA LHD 1.00 1.00 95740-K4300 W50', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00OS MDPS C 1.00 1.03 56310/K4550 4OEDC103', + b'\xf1\x00OS MDPS C 1.00 1.04 56310-XX000 4OEDC104', b'\xf1\x00OS MDPS C 1.00 1.04 56310K4000\x00 4OEDC104', b'\xf1\x00OS MDPS C 1.00 1.04 56310K4050\x00 4OEDC104', ], @@ -1099,14 +655,12 @@ FW_VERSIONS = { b'\xf1\x00OSev SCC FNCUP 1.00 1.01 99110-K4000 ', ], }, - CAR.KONA_EV_2022: { + CAR.HYUNDAI_KONA_EV_2022: { (Ecu.abs, 0x7d1, None): [ b'\xf1\x00OS IEB \x02 102"\x05\x16 58520-K4010', + b'\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', b'\xf1\x00OS IEB \x03 102"\x05\x16 58520-K4010', b'\xf1\x00OS IEB \r 102"\x05\x16 58520-K4010', - b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010', - b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', - b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00OSP LKA AT AUS RHD 1.00 1.04 99211-J9200 904', @@ -1128,7 +682,7 @@ FW_VERSIONS = { b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00', ], }, - CAR.KONA_EV_2ND_GEN: { + CAR.HYUNDAI_KONA_EV_2ND_GEN: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00SXev RDR ----- 1.00 1.00 99110-BF000 ', ], @@ -1139,16 +693,15 @@ FW_VERSIONS = { CAR.KIA_NIRO_EV: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', + b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4100 ', + b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4500 ', + b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4600 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4000 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4100 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.03 96400-Q4100 ', + b'\xf1\x00DEev SCC FHCUP 1.00 1.00 99110-Q4600 ', b'\xf1\x00DEev SCC FHCUP 1.00 1.03 96400-Q4000 ', - b'\xf1\x8799110Q4000\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', - b'\xf1\x8799110Q4100\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4100 ', - b'\xf1\x8799110Q4500\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4500 ', - b'\xf1\x8799110Q4600\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4600 ', - b'\xf1\x8799110Q4600\xf1\x00DEev SCC FHCUP 1.00 1.00 99110-Q4600 ', - b'\xf1\x8799110Q4600\xf1\x00DEev SCC FNCUP 1.00 1.00 99110-Q4600 ', + b'\xf1\x00DEev SCC FNCUP 1.00 1.00 99110-Q4600 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00DE MDPS C 1.00 1.04 56310Q4100\x00 4DEEC104', @@ -1175,23 +728,12 @@ FW_VERSIONS = { ], }, CAR.KIA_NIRO_PHEV: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816H6F4051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL2&[\xc3\x01', - b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x00\x00\x00\x00', - b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x13\xcd\x88\x92', - b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\x00\x00\x00\x00', - b"\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\xf4'\\\x91", - ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DEH MFC AT USA LHD 1.00 1.00 95740-G5010 170117', b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 95740-G5010 170117', b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424', b'\xf1\x00DEP MFC AT USA LHD 1.00 1.05 99211-G5000 190826', @@ -1202,14 +744,6 @@ 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', ], @@ -1219,40 +753,30 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DEhe SCC F-CUP 1.00 1.00 99110-G5600 ', + b'\xf1\x00DEhe SCC FHCUP 1.00 1.00 99110-G5600 ', ], }, CAR.KIA_NIRO_HEV_2021: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HDE0G16NL3\x00\x00\x00\x00', - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HDE0G16NL3\xb9\xd3\xfaW', - ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DEH MFC AT KOR LHD 1.00 1.04 99211-G5000 190516', b'\xf1\x00DEH MFC AT USA LHD 1.00 1.00 99211-G5500 210428', b'\xf1\x00DEH MFC AT USA LHD 1.00 1.07 99211-G5000 201221', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DEhe SCC FHCUP 1.00 1.00 99110-G5600 ', + b'\xf1\x00DEhe SCC FHCUP 1.00 1.01 99110-G5000 ', ], }, CAR.KIA_SELTOS: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x8799110Q5100\xf1\x00SP2_ SCC FHCUP 1.01 1.05 99110-Q5100 ', + b'\xf1\x00SP2_ SCC FHCUP 1.01 1.05 99110-Q5100 ', ], (Ecu.abs, 0x7d1, None): [ - b'\xf1\x8758910-Q5450\xf1\x00SP ESC \x07 101\x19\t\x05 58910-Q5450', - b'\xf1\x8758910-Q5450\xf1\x00SP ESC \t 101\x19\t\x05 58910-Q5450', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x01TSP2KNL06F100J0K', - b'\x01TSP2KNL06F200J0K', - b'\xf1\x81616D2051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81616D5051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x00SP ESC \x07 101\x19\t\x05 58910-Q5450', + b'\xf1\x00SP ESC \t 101\x19\t\x05 58910-Q5450', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00SP2 MDPS C 1.00 1.04 56300Q5200 ', @@ -1262,11 +786,6 @@ FW_VERSIONS = { b'\xf1\x00SP2 MFC AT USA LHD 1.00 1.04 99210-Q5000 191114', b'\xf1\x00SP2 MFC AT USA LHD 1.00 1.05 99210-Q5000 201012', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\x00T0190XBL T01950A1 DSP2T16X4X950NS6\xd30\xa5\xb9', - b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\x00T0190XBL T01950A1 DSP2T16X4X950NS8\r\xfe\x9c\x8b', - b'\xf1\x87CZLUB49370612JF7h\xa8y\x87\x99\xa7hv\x99\x97fv\x88\x87x\x89x\x96O\xff\x88\xff\xff\xff.@\xf1\x816V2C2051\x00\x00\xf1\x006V2B0_C2\x00\x006V2C2051\x00\x00CSP4N20NS3\x00\x00\x00\x00', - ], }, CAR.KIA_OPTIMA_G4: { (Ecu.fwdRadar, 0x7d0, None): [ @@ -1278,9 +797,6 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00JFWGN LDWS AT USA LHD 1.00 1.02 95895-D4100 G21', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6J0051\x00\x00\xf1\x006T6J0_C2\x00\x006T6J0051\x00\x00TJF0T20NSB\x00\x00\x00\x00', - ], }, CAR.KIA_OPTIMA_G4_FL: { (Ecu.fwdRadar, 0x7d0, None): [ @@ -1294,17 +810,6 @@ FW_VERSIONS = { b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5001 h32', b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5100 h32', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW', - b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', - b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.', - b'\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01', - b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW', - b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', - b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.', - b'\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01', - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B8051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B8051\x00\x00TJFSG24NH27\xa7\xc2\xb4', - ], }, CAR.KIA_OPTIMA_H: { (Ecu.fwdRadar, 0x7d0, None): [ @@ -1321,28 +826,12 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00JFH MFC AT KOR LHD 1.00 1.01 95895-A8200 180323', ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, - CAR.ELANTRA: { + CAR.HYUNDAI_ELANTRA: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00AD LKAS AT USA LHD 1.01 1.01 95895-F2000 251', b'\xf1\x00ADP LKAS AT USA LHD 1.00 1.03 99211-F2000 X31', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\x00\x00\x00\x00', - b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\xc5\x92\x9e\x8a', - b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20SS2.~\x90\x87', - b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD\x00\x00\x00\x00', - b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD(\xfcA\x9d', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8161657051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816165D051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816165E051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x8161698051\x00\x00\x00\x00\x00\x00\x00\x00', - ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00AD ESC \x11 11 \x18\x05\x06 58910-F2840', b'\xf1\x00AD ESC \x11 12 \x15\t\t 58920-F2810', @@ -1352,18 +841,12 @@ FW_VERSIONS = { b'\xf1\x00AD__ SCC H-CUP 1.00 1.01 96400-F2100 ', ], }, - CAR.ELANTRA_GT_I30: { + CAR.HYUNDAI_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', @@ -1380,49 +863,35 @@ FW_VERSIONS = { b'\xf1\x00PD__ SCC FNCUP 1.01 1.00 96400-G3000 ', ], }, - CAR.ELANTRA_2021: { + CAR.HYUNDAI_ELANTRA_2021: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', b'\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', b'\xf1\x00CN7_ SCC FNCUP 1.00 1.01 99110-AA000 ', - b'\xf1\x8799110AA000\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', - b'\xf1\x8799110AA000\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', ], (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA050 4CNDC106', b'\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106', b'\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106', + b'\xf1\x00CN7 MDPS C 1.00 1.07 56310AA050\x00 4CNDC107', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.01 99210-AB000 210205', + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.02 99210-AB000 220111', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AA000 200819', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AB000 220426', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.06 99210-AA000 220111', + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.07 99210-AA000 220426', + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.08 99210-AA000 220728', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800', - b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800', - b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800', - b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800\xf1\xa01.01', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\xe8\xba\xce\xfa', - b'\xf1\x87CXLQF40189012JL2f\x88\x86\x88\x88vUex\xb8\x88\x88\x88\x87\x88\x89fh?\xffz\xff\xff\xff\x08z\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', - b'\xf1\x87CXMQFM1916035JB2\x88vvgg\x87Wuwgev\xa9\x98\x88\x98h\x99\x9f\xffh\xff\xff\xff\xa5\xee\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', - b'\xf1\x87CXMQFM2135005JB2E\xb9\x89\x98W\xa9y\x97h\xa9\x98\x99wxvwh\x87\x7f\xffx\xff\xff\xff,,\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', - b'\xf1\x87CXMQFM2728305JB2E\x97\x87xw\x87vwgw\x84x\x88\x88w\x89EI\xbf\xff{\xff\xff\xff\xe6\x0e\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', - b'\xf1\x87CXMQFM3806705JB2\x89\x87wwx\x88g\x86\x99\x87\x86xwwv\x88yv\x7f\xffz\xff\xff\xffV\x15\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81HM6M2_0a0_FF0', - b'\xf1\x82CNCVD0AMFCXCSFFB', - b'\xf1\x82CNCWD0AMFCXCSFFA', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_G80', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_HC0', + b'\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800', + b'\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800', ], }, - CAR.ELANTRA_HEV_2021: { + CAR.HYUNDAI_ELANTRA_HEV_2021: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930', @@ -1432,28 +901,16 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', - b'\xf1\x8799110BY000\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', ], (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', + b'\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103', + b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY050\x00 4CNHC103', b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY0500 4CNHC103', b'\xf1\x00CN7 MDPS C 1.00 1.04 56310BY050\x00 4CNHC104', - b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', - b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', - b'\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\xb9?A\xaa', - b'\xf1\x006U3L0_C2\x00\x006U3K9051\x00\x00HCN0G16NS1\x00\x00\x00\x00', - b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', - b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\xb9?A\xaa', - ], - (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', - b'\xf1\x816H6G8051\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.KONA_HEV: { + CAR.HYUNDAI_KONA_HEV: { (Ecu.abs, 0x7d1, None): [ b'\xf1\x00OS IEB \x01 104 \x11 58520-CM000', ], @@ -1466,64 +923,43 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00OSH LKAS AT KOR LHD 1.00 1.01 95740-CM000 l31', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HOS0G16DS1\x16\xc7\xb0\xd9', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, - CAR.SONATA_HYBRID: { + CAR.HYUNDAI_SONATA_HYBRID: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', + b'\xf1\x00DNhe SCC FHCUP 1.00 1.00 99110-L5000 ', b'\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', - b'\xf1\x8799110L5000\xf1\x00DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', - b'\xf1\x8799110L5000\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', ], (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L5000 4DNHC101', + b'\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102', + b'\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102', + b'\xf1\x00DN8 MDPS C 1.00 1.03 56310-L5450 4DNHC103', b'\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104', - b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102', - b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.03 56310-L5450 4DNHC103', - b'\xf1\x8756310-L5500\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102', - b'\xf1\x8756310L5450\x00\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DN8HMFC AT KOR LHD 1.00 1.03 99211-L1000 190705', b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.04 99211-L1000 191016', b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.05 99211-L1000 201109', b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.06 99211-L1000 210325', b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.07 99211-L1000 211223', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00PSBG2323 E09\x00\x00\x00\x00\x00\x00\x00TDN2H20SA5\x97R\x88\x9e', - b'\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', - b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab', - b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E09\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2323 E09\x00\x00\x00\x00\x00\x00\x00TDN2H20SA5\x97R\x88\x9e', - b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', - b'\xf1\x87PCU\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x87391062J002', - b'\xf1\x87391162J012', - b'\xf1\x87391162J013', - b'\xf1\x87391162J014', - ], }, CAR.KIA_SORENTO: { (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00UMP LKAS AT KOR LHD 1.00 1.00 95740-C5550 S30', + b'\xf1\x00UMP LKAS AT USA LHD 1.00 1.00 95740-C6550 d00', b'\xf1\x00UMP LKAS AT USA LHD 1.01 1.01 95740-C6550 d01', ], (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00UM ESC \x02 12 \x18\x05\x05 58910-C6300', b'\xf1\x00UM ESC \x0c 12 \x18\x05\x06 58910-C6330', + b'\xf1\x00UM ESC \x13 12 \x17\x07\x05 58910-C5320', ], (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00UM__ SCC F-CUP 1.00 1.00 96400-C5500 ', b'\xf1\x00UM__ SCC F-CUP 1.00 1.00 96400-C6500 ', ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87LDKUAA0348164HE3\x87www\x87www\x88\x88\xa8\x88w\x88\x97xw\x88\x97x\x86o\xf8\xff\x87f\x7f\xff\x15\xe0\xf1\x81U811\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U811\x00\x00\x00\x00\x00\x00TUM4G33NL3V|DG', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, CAR.KIA_EV6: { (Ecu.fwdRadar, 0x7d0, None): [ @@ -1537,16 +973,21 @@ FW_VERSIONS = { b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.05 99210-CV000 211027', b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.06 99210-CV000 220328', b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.00 99210-CV100 220630', + b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.00 99210-CV200 230510', b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027', b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328', ], }, - CAR.IONIQ_5: { + CAR.HYUNDAI_IONIQ_5: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NE1 MFC AT CAN LHD 1.00 1.01 99211-GI010 211007', + b'\xf1\x00NE1 MFC AT CAN LHD 1.00 1.05 99211-GI010 220614', + b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.01 99211-GI010 211007', b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813', + b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI010 230110', 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', @@ -1557,19 +998,25 @@ FW_VERSIONS = { b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.06 99211-GI010 230110', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.00 99211-GI100 230915', + b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.00 99211-GI100 230915', ], }, - CAR.IONIQ_6: { + CAR.HYUNDAI_IONIQ_6: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CE__ RDR ----- 1.00 1.01 99110-KL000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CE MFC AT CAN LHD 1.00 1.04 99211-KL000 221213', b'\xf1\x00CE MFC AT EUR LHD 1.00 1.03 99211-KL000 221011', + b'\xf1\x00CE MFC AT EUR LHD 1.00 1.04 99211-KL000 221213', b'\xf1\x00CE MFC AT USA LHD 1.00 1.04 99211-KL000 221213', ], }, - CAR.TUCSON_4TH_GEN: { + CAR.HYUNDAI_TUCSON_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.00 99211-N9260 14Y', + b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.01 99211-N9100 14A', 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', @@ -1587,10 +1034,11 @@ FW_VERSIONS = { b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', ], }, - CAR.SANTA_CRUZ_1ST_GEN: { + CAR.HYUNDAI_SANTA_CRUZ_1ST_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW000 14M', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW010 14X', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW020 14Z', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NX4__ 1.00 1.00 99110-K5000 ', @@ -1600,13 +1048,16 @@ FW_VERSIONS = { CAR.KIA_SPORTAGE_5TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NQ5 FR_CMR AT AUS RHD 1.00 1.00 99211-P1040 663', + b'\xf1\x00NQ5 FR_CMR AT EUR LHD 1.00 1.00 99211-P1040 663', b'\xf1\x00NQ5 FR_CMR AT GEN LHD 1.00 1.00 99211-P1060 665', b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1030 662', b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1040 663', b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1060 665', + b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1070 690', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NQ5__ 1.00 1.02 99110-P1000 ', + b'\xf1\x00NQ5__ 1.00 1.03 99110-CH000 ', b'\xf1\x00NQ5__ 1.00 1.03 99110-P1000 ', b'\xf1\x00NQ5__ 1.01 1.03 99110-CH000 ', b'\xf1\x00NQ5__ 1.01 1.03 99110-P1000 ', @@ -1679,13 +1130,16 @@ FW_VERSIONS = { }, CAR.KIA_CARNIVAL_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00KA4 MFC AT EUR LHD 1.00 1.06 99210-R0000 220221', b'\xf1\x00KA4 MFC AT KOR LHD 1.00 1.06 99210-R0000 220221', b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.00 99210-R0100 230105', + b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.01 99210-R0100 230710', b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.05 99210-R0000 201221', b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.06 99210-R0000 220221', b'\xf1\x00KA4CMFC AT CHN LHD 1.00 1.01 99211-I4000 210525', ], (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00KA4_ SCC F-CUP 1.00 1.03 99110-R0000 ', b'\xf1\x00KA4_ SCC FHCUP 1.00 1.00 99110-R0100 ', b'\xf1\x00KA4_ SCC FHCUP 1.00 1.03 99110-R0000 ', b'\xf1\x00KA4c SCC FHCUP 1.00 1.01 99110-I4000 ', @@ -1694,12 +1148,13 @@ FW_VERSIONS = { CAR.KIA_K8_HEV_1ST_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00GL3HMFC AT KOR LHD 1.00 1.03 99211-L8000 210907', + b'\xf1\x00GL3HMFC AT KOR LHD 1.00 1.04 99211-L8000 230207', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00GL3_ RDR ----- 1.00 1.02 99110-L8000 ', ], }, - CAR.STARIA_4TH_GEN: { + CAR.HYUNDAI_STARIA_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00US4 MFC AT KOR LHD 1.00 1.06 99211-CG000 230524', ], diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index 3fe4f3abed..37bad5924e 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -1,9 +1,9 @@ import crcmod -from openpilot.selfdrive.car.hyundai.values import CAR, CHECKSUM, CAMERA_SCC_CAR +from openpilot.selfdrive.car.hyundai.values import CAR, HyundaiFlags hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf) -def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, +def create_lkas11(packer, frame, CP, apply_steer, steer_req, torque_fault, lkas11, sys_warning, sys_state, enabled, left_lane, right_lane, left_lane_depart, right_lane_depart): @@ -33,12 +33,12 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, values["CF_Lkas_ToiFlt"] = torque_fault # seems to allow actuation on CR_Lkas_StrToqReq values["CF_Lkas_MsgCount"] = frame % 0x10 - if car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE, - CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020, - CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, - CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, - CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED, - CAR.AZERA_6TH_GEN, CAR.AZERA_HEV_6TH_GEN, CAR.CUSTIN_1ST_GEN, CAR.GENESIS_G70_1ST_GEN_FL): + if CP.carFingerprint in (CAR.HYUNDAI_SONATA, CAR.HYUNDAI_PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.HYUNDAI_SANTA_FE, + CAR.HYUNDAI_IONIQ_EV_2020, CAR.HYUNDAI_IONIQ_PHEV, CAR.KIA_SELTOS, CAR.HYUNDAI_ELANTRA_2021, CAR.GENESIS_G70_2020, + CAR.HYUNDAI_ELANTRA_HEV_2021, CAR.HYUNDAI_SONATA_HYBRID, CAR.HYUNDAI_KONA_EV, CAR.HYUNDAI_KONA_HEV, CAR.HYUNDAI_KONA_EV_2022, + CAR.HYUNDAI_SANTA_FE_2022, CAR.KIA_K5_2021, CAR.HYUNDAI_IONIQ_HEV_2022, CAR.HYUNDAI_SANTA_FE_HEV_2022, + CAR.HYUNDAI_SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED, + CAR.HYUNDAI_AZERA_6TH_GEN, CAR.HYUNDAI_AZERA_HEV_6TH_GEN, CAR.HYUNDAI_CUSTIN_1ST_GEN, CAR.GENESIS_G70_1ST_GEN_FL): values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsOpt_USM"] = 2 @@ -57,7 +57,7 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0 # Likely cars lacking the ability to show individual lane lines in the dash - elif car_fingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL): + elif CP.carFingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL): # SysWarning 4 = keep hands on wheel + beep values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0 @@ -72,18 +72,18 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, values["CF_Lkas_LdwsActivemode"] = 0 values["CF_Lkas_FcwOpt_USM"] = 0 - elif car_fingerprint == CAR.HYUNDAI_GENESIS: + elif CP.carFingerprint == CAR.HYUNDAI_GENESIS: # This field is actually LdwsActivemode # Genesis and Optima fault when forwarding while engaged values["CF_Lkas_LdwsActivemode"] = 2 dat = packer.make_can_msg("LKAS11", 0, values)[2] - if car_fingerprint in CHECKSUM["crc8"]: + if CP.flags & HyundaiFlags.CHECKSUM_CRC8: # CRC Checksum as seen on 2019 Hyundai Santa Fe dat = dat[:6] + dat[7:8] checksum = hyundai_checksum(dat) - elif car_fingerprint in CHECKSUM["6B"]: + elif CP.flags & HyundaiFlags.CHECKSUM_6B: # Checksum of first 6 Bytes, as seen on 2018 Kia Sorento checksum = sum(dat[:6]) % 256 else: @@ -95,7 +95,7 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, return packer.make_can_msg("LKAS11", 0, values) -def create_clu11(packer, frame, clu11, button, car_fingerprint): +def create_clu11(packer, frame, clu11, button, CP): values = {s: clu11[s] for s in [ "CF_Clu_CruiseSwState", "CF_Clu_CruiseSwMain", @@ -113,7 +113,7 @@ def create_clu11(packer, frame, clu11, button, car_fingerprint): values["CF_Clu_CruiseSwState"] = button values["CF_Clu_AliveCnt1"] = frame % 0x10 # send buttons to camera on camera-scc based cars - bus = 2 if car_fingerprint in CAMERA_SCC_CAR else 0 + bus = 2 if CP.flags & HyundaiFlags.CAMERA_SCC else 0 return packer.make_can_msg("CLU11", bus, values) @@ -126,12 +126,12 @@ def create_lfahda_mfc(packer, enabled, hda_set_speed=0): } return packer.make_can_msg("LFAHDA_MFC", 0, values) -def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, set_speed, stopping, long_override, use_fca): +def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca): commands = [] scc11_values = { "MainMode_ACC": 1, - "TauGapSet": 4, + "TauGapSet": hud_control.leadDistanceBars, "VSetDis": set_speed if enabled else 0, "AliveCounterACC": idx % 0x10, "ObjValid": 1, # close lead makes controls tighter @@ -167,7 +167,7 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, s "JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values "JerkLowerLimit": 5.0, # stock usually is 0.5 but sometimes uses higher values "ACCMode": 2 if enabled and long_override else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage - "ObjGap": 2 if lead_visible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead + "ObjGap": 2 if hud_control.leadVisible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead } commands.append(packer.make_can_msg("SCC14", 0, scc14_values)) diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index a35fcb7779..54804f94fd 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -121,7 +121,7 @@ def create_lfahda_cluster(packer, CAN, enabled): return packer.make_can_msg("LFAHDA_CLUSTER", CAN.ECAN, values) -def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed): +def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control): jerk = 5 jn = jerk / 50 if not enabled or gas_override: @@ -146,7 +146,7 @@ def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_ov "SET_ME_2": 0x4, "SET_ME_3": 0x3, "SET_ME_TMP_64": 0x64, - "DISTANCE_SETTING": 4, + "DISTANCE_SETTING": hud_control.leadDistanceBars, } return packer.make_can_msg("SCC_CONTROL", CAN.ECAN, values) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index d933de8881..8c270a7e2b 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -1,6 +1,5 @@ from cereal import car from panda import Panda -from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, \ CANFD_UNSUPPORTED_LONGITUDINAL_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, \ @@ -76,208 +75,13 @@ class CarInterface(CarInterfaceBase): ret.steerLimitTimer = 0.4 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - if candidate in (CAR.AZERA_6TH_GEN, CAR.AZERA_HEV_6TH_GEN): - ret.mass = 1600. if candidate == CAR.AZERA_6TH_GEN else 1675. # ICE is ~average of 2.5L and 3.5L - ret.wheelbase = 2.885 - ret.steerRatio = 14.5 - elif candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): - ret.mass = 3982. * CV.LB_TO_KG - ret.wheelbase = 2.766 - # Values from optimizer - ret.steerRatio = 16.55 # 13.8 is spec end-to-end - ret.tireStiffnessFactor = 0.82 - elif candidate in (CAR.SONATA, CAR.SONATA_HYBRID): - ret.mass = 1513. - ret.wheelbase = 2.84 - ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable - ret.tireStiffnessFactor = 0.65 - elif candidate == CAR.SONATA_LF: - ret.mass = 1536. - ret.wheelbase = 2.804 - ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable - elif candidate == CAR.PALISADE: - ret.mass = 1999. - ret.wheelbase = 2.90 - ret.steerRatio = 15.6 * 1.15 - ret.tireStiffnessFactor = 0.63 - elif candidate in (CAR.ELANTRA, CAR.ELANTRA_GT_I30): - ret.mass = 1275. - ret.wheelbase = 2.7 - ret.steerRatio = 15.4 # 14 is Stock | Settled Params Learner values are steerRatio: 15.401566348670535 - ret.tireStiffnessFactor = 0.385 # stiffnessFactor settled on 1.0081302973865127 - ret.minSteerSpeed = 32 * CV.MPH_TO_MS - elif candidate == CAR.ELANTRA_2021: - ret.mass = 2800. * CV.LB_TO_KG - ret.wheelbase = 2.72 - ret.steerRatio = 12.9 - ret.tireStiffnessFactor = 0.65 - elif candidate == CAR.ELANTRA_HEV_2021: - ret.mass = 3017. * CV.LB_TO_KG - ret.wheelbase = 2.72 - ret.steerRatio = 12.9 - ret.tireStiffnessFactor = 0.65 - elif candidate == CAR.HYUNDAI_GENESIS: - ret.mass = 2060. - ret.wheelbase = 3.01 - ret.steerRatio = 16.5 - ret.minSteerSpeed = 60 * CV.KPH_TO_MS - elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, CAR.KONA_EV_2ND_GEN): - ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425., CAR.KONA_EV_2022: 1743., CAR.KONA_EV_2ND_GEN: 1740.}.get(candidate, 1275.) - ret.wheelbase = {CAR.KONA_EV_2ND_GEN: 2.66, }.get(candidate, 2.6) - ret.steerRatio = {CAR.KONA_EV_2ND_GEN: 13.6, }.get(candidate, 13.42) # Spec - ret.tireStiffnessFactor = 0.385 - elif candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV_2019, CAR.IONIQ_HEV_2022, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV): - ret.mass = 1490. # weight per hyundai site https://www.hyundaiusa.com/ioniq-electric/specifications.aspx - ret.wheelbase = 2.7 - ret.steerRatio = 13.73 # Spec - ret.tireStiffnessFactor = 0.385 - if candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV_2019): - ret.minSteerSpeed = 32 * CV.MPH_TO_MS - elif candidate in (CAR.IONIQ_5, CAR.IONIQ_6): - ret.mass = 1948 - ret.wheelbase = 2.97 - ret.steerRatio = 14.26 - ret.tireStiffnessFactor = 0.65 - elif candidate == CAR.VELOSTER: - ret.mass = 2917. * CV.LB_TO_KG - ret.wheelbase = 2.80 - ret.steerRatio = 13.75 * 1.15 - ret.tireStiffnessFactor = 0.5 - elif candidate == CAR.TUCSON: - ret.mass = 3520. * CV.LB_TO_KG - ret.wheelbase = 2.67 - ret.steerRatio = 14.00 * 1.15 - ret.tireStiffnessFactor = 0.385 - elif candidate == CAR.TUCSON_4TH_GEN: - ret.mass = 1630. # average - ret.wheelbase = 2.756 - ret.steerRatio = 16. - ret.tireStiffnessFactor = 0.385 - elif candidate == CAR.SANTA_CRUZ_1ST_GEN: - ret.mass = 1870. # weight from Limited trim - the only supported trim - ret.wheelbase = 3.000 - # steering ratio according to Hyundai News https://www.hyundainews.com/assets/documents/original/48035-2022SantaCruzProductGuideSpecsv2081521.pdf - ret.steerRatio = 14.2 - elif candidate == CAR.CUSTIN_1ST_GEN: - ret.mass = 1690. # from https://www.hyundai-motor.com.tw/clicktobuy/custin#spec_0 - ret.wheelbase = 3.055 - ret.steerRatio = 17.0 # from learner - elif candidate == CAR.STARIA_4TH_GEN: - ret.mass = 2205. - ret.wheelbase = 3.273 - ret.steerRatio = 11.94 # https://www.hyundai.com/content/dam/hyundai/au/en/models/staria-load/premium-pip-update-2023/spec-sheet/STARIA_Load_Spec-Table_March_2023_v3.1.pdf - - # Kia - elif candidate == CAR.KIA_SORENTO: - ret.mass = 1985. - ret.wheelbase = 2.78 - ret.steerRatio = 14.4 * 1.1 # 10% higher at the center seems reasonable - elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_PHEV_2022): - ret.mass = 3543. * CV.LB_TO_KG # average of all the cars - ret.wheelbase = 2.7 - ret.steerRatio = 13.6 # average of all the cars - ret.tireStiffnessFactor = 0.385 - if candidate == CAR.KIA_NIRO_PHEV: - ret.minSteerSpeed = 32 * CV.MPH_TO_MS - elif candidate == CAR.KIA_SELTOS: - ret.mass = 1337. - ret.wheelbase = 2.63 - ret.steerRatio = 14.56 - elif candidate == CAR.KIA_SPORTAGE_5TH_GEN: - ret.mass = 1725. # weight from SX and above trims, average of FWD and AWD versions - ret.wheelbase = 2.756 - ret.steerRatio = 13.6 # steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sportage/2023/specifications - elif candidate in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL): - ret.mass = 3558. * CV.LB_TO_KG - ret.wheelbase = 2.80 - ret.steerRatio = 13.75 - ret.tireStiffnessFactor = 0.5 - if candidate == CAR.KIA_OPTIMA_G4: - ret.minSteerSpeed = 32 * CV.MPH_TO_MS - elif candidate in (CAR.KIA_STINGER, CAR.KIA_STINGER_2022): - ret.mass = 1825. - ret.wheelbase = 2.78 - ret.steerRatio = 14.4 * 1.15 # 15% higher at the center seems reasonable - elif candidate == CAR.KIA_FORTE: - ret.mass = 2878. * CV.LB_TO_KG - ret.wheelbase = 2.80 - ret.steerRatio = 13.75 - ret.tireStiffnessFactor = 0.5 - elif candidate == CAR.KIA_CEED: - ret.mass = 1450. - ret.wheelbase = 2.65 - ret.steerRatio = 13.75 - ret.tireStiffnessFactor = 0.5 - elif candidate in (CAR.KIA_K5_2021, CAR.KIA_K5_HEV_2020): - ret.mass = 3381. * CV.LB_TO_KG - ret.wheelbase = 2.85 - ret.steerRatio = 13.27 # 2021 Kia K5 Steering Ratio (all trims) - ret.tireStiffnessFactor = 0.5 - elif candidate == CAR.KIA_EV6: - ret.mass = 2055 - ret.wheelbase = 2.9 - ret.steerRatio = 16. - ret.tireStiffnessFactor = 0.65 - elif candidate in (CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN): - ret.wheelbase = 2.81 - ret.steerRatio = 13.5 # average of the platforms - if candidate == CAR.KIA_SORENTO_4TH_GEN: - ret.mass = 3957 * CV.LB_TO_KG - else: - ret.mass = 4396 * CV.LB_TO_KG - elif candidate == CAR.KIA_CARNIVAL_4TH_GEN: - ret.mass = 2087. - ret.wheelbase = 3.09 - ret.steerRatio = 14.23 - elif candidate == CAR.KIA_K8_HEV_1ST_GEN: - ret.mass = 1630. # https://carprices.ae/brands/kia/2023/k8/1.6-turbo-hybrid - ret.wheelbase = 2.895 - ret.steerRatio = 13.27 # guesstimate from K5 platform - - # Genesis - elif candidate == CAR.GENESIS_GV60_EV_1ST_GEN: - ret.mass = 2205 - ret.wheelbase = 2.9 - # https://www.motor1.com/reviews/586376/2023-genesis-gv60-first-drive/#:~:text=Relative%20to%20the%20related%20Ioniq,5%2FEV6%27s%2014.3%3A1. - ret.steerRatio = 12.6 - elif candidate == CAR.GENESIS_G70: - ret.steerActuatorDelay = 0.1 - ret.mass = 1640.0 - ret.wheelbase = 2.84 - ret.steerRatio = 13.56 - elif candidate == CAR.GENESIS_G70_2020: - ret.mass = 3673.0 * CV.LB_TO_KG - ret.wheelbase = 2.83 - ret.steerRatio = 12.9 - elif candidate == CAR.GENESIS_GV70_1ST_GEN: - ret.mass = 1950. - ret.wheelbase = 2.87 - ret.steerRatio = 14.6 - elif candidate == CAR.GENESIS_G80: - ret.mass = 2060. - ret.wheelbase = 3.01 - ret.steerRatio = 16.5 - elif candidate == CAR.GENESIS_G90: - ret.mass = 2200. - ret.wheelbase = 3.15 - ret.steerRatio = 12.069 - elif candidate == CAR.GENESIS_GV80: - ret.mass = 2258. - ret.wheelbase = 2.95 - ret.steerRatio = 14.14 - elif candidate == CAR.GENESIS_G70_1ST_GEN_FL: - ret.mass = 1769. - ret.wheelbase = 2.83 - ret.steerRatio = 12.9 # guesstimate from G70 1st Gen platform + if candidate == CAR.KIA_OPTIMA_G4_FL: + ret.steerActuatorDelay = 0.2 # *** longitudinal control *** if candidate in CANFD_CAR: - ret.longitudinalTuning.kpV = [0.1] - ret.longitudinalTuning.kiV = [0.0] ret.experimentalLongitudinalAvailable = candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR) else: - ret.longitudinalTuning.kpV = [0.5] - ret.longitudinalTuning.kiV = [0.0] ret.experimentalLongitudinalAvailable = candidate not in (UNSUPPORTED_LONGITUDINAL_CAR | CAMERA_SCC_CAR) ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable ret.pcmCruise = not ret.openpilotLongitudinalControl @@ -286,8 +90,7 @@ class CarInterface(CarInterfaceBase): ret.startingState = True ret.vEgoStarting = 0.1 ret.startAccel = 1.0 - ret.longitudinalActuatorDelayLowerBound = 0.5 - ret.longitudinalActuatorDelayUpperBound = 0.5 + ret.longitudinalActuatorDelay = 0.5 # *** feature detection *** if candidate in CANFD_CAR: @@ -327,7 +130,7 @@ class CarInterface(CarInterfaceBase): elif ret.flags & HyundaiFlags.EV: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_EV_GAS - if candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, CAR.GENESIS_G70_1ST_GEN_FL): + if candidate in (CAR.HYUNDAI_KONA, CAR.HYUNDAI_KONA_EV, CAR.HYUNDAI_KONA_HEV, CAR.HYUNDAI_KONA_EV_2022, CAR.GENESIS_G70_1ST_GEN_FL): ret.flags |= HyundaiFlags.ALT_LIMITS.value ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_ALT_LIMITS @@ -370,6 +173,3 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() return ret - - def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py old mode 100755 new mode 100644 index c7100d03f0..4d31d7d15e --- a/selfdrive/car/hyundai/tests/test_hyundai.py +++ b/selfdrive/car/hyundai/tests/test_hyundai.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 from hypothesis import settings, given, strategies as st -import unittest + +import pytest from cereal import car from openpilot.selfdrive.car.fw_versions import build_fw_dict @@ -18,77 +18,75 @@ ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} NO_DATES_PLATFORMS = { # CAN FD CAR.KIA_SPORTAGE_5TH_GEN, - CAR.SANTA_CRUZ_1ST_GEN, - CAR.TUCSON_4TH_GEN, + CAR.HYUNDAI_SANTA_CRUZ_1ST_GEN, + CAR.HYUNDAI_TUCSON_4TH_GEN, # CAN - CAR.ELANTRA, - CAR.ELANTRA_GT_I30, + CAR.HYUNDAI_ELANTRA, + CAR.HYUNDAI_ELANTRA_GT_I30, CAR.KIA_CEED, CAR.KIA_FORTE, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_SORENTO, - CAR.KONA, - CAR.KONA_EV, - CAR.KONA_EV_2022, - CAR.KONA_HEV, - CAR.SONATA_LF, - CAR.VELOSTER, + CAR.HYUNDAI_KONA, + CAR.HYUNDAI_KONA_EV, + CAR.HYUNDAI_KONA_EV_2022, + CAR.HYUNDAI_KONA_HEV, + CAR.HYUNDAI_SONATA_LF, + CAR.HYUNDAI_VELOSTER, } +CANFD_EXPECTED_ECUS = {Ecu.fwdCamera, Ecu.fwdRadar} -class TestHyundaiFingerprint(unittest.TestCase): + +class TestHyundaiFingerprint: def test_can_features(self): # Test no EV/HEV in any gear lists (should all use ELECT_GEAR) - self.assertEqual(set.union(*CAN_GEARS.values()) & (HYBRID_CAR | EV_CAR), set()) + assert set.union(*CAN_GEARS.values()) & (HYBRID_CAR | EV_CAR) == set() # Test CAN FD car not in CAN feature lists can_specific_feature_list = set.union(*CAN_GEARS.values(), *CHECKSUM.values(), LEGACY_SAFETY_MODE_CAR, UNSUPPORTED_LONGITUDINAL_CAR, CAMERA_SCC_CAR) for car_model in CANFD_CAR: - self.assertNotIn(car_model, can_specific_feature_list, "CAN FD car unexpectedly found in a CAN feature list") + assert car_model not in can_specific_feature_list, "CAN FD car unexpectedly found in a CAN feature list" def test_hybrid_ev_sets(self): - self.assertEqual(HYBRID_CAR & EV_CAR, set(), "Shared cars between hybrid and EV") - self.assertEqual(CANFD_CAR & HYBRID_CAR, set(), "Hard coding CAN FD cars as hybrid is no longer supported") - - def test_auxiliary_request_ecu_whitelist(self): - # Asserts only auxiliary Ecus can exist in database for CAN-FD cars - whitelisted_ecus = {ecu for r in FW_QUERY_CONFIG.requests for ecu in r.whitelist_ecus if r.auxiliary} + assert HYBRID_CAR & EV_CAR == set(), "Shared cars between hybrid and EV" + assert CANFD_CAR & HYBRID_CAR == set(), "Hard coding CAN FD cars as hybrid is no longer supported" + def test_canfd_ecu_whitelist(self): + # Asserts only expected Ecus can exist in database for CAN-FD cars for car_model in CANFD_CAR: ecus = {fw[0] for fw in FW_VERSIONS[car_model].keys()} - ecus_not_in_whitelist = ecus - whitelisted_ecus + ecus_not_in_whitelist = ecus - CANFD_EXPECTED_ECUS ecu_strings = ", ".join([f"Ecu.{ECU_NAME[ecu]}" for ecu in ecus_not_in_whitelist]) - self.assertEqual(len(ecus_not_in_whitelist), 0, - f"{car_model}: Car model has ECUs not in auxiliary request whitelists: {ecu_strings}") + assert len(ecus_not_in_whitelist) == 0, \ + f"{car_model}: Car model has unexpected ECUs: {ecu_strings}" - def test_blacklisted_parts(self): + def test_blacklisted_parts(self, subtests): # Asserts no ECUs known to be shared across platforms exist in the database. # Tucson having Santa Cruz camera and EPS for example for car_model, ecus in FW_VERSIONS.items(): - with self.subTest(car_model=car_model.value): - if car_model == CAR.SANTA_CRUZ_1ST_GEN: - raise unittest.SkipTest("Skip checking Santa Cruz for its parts") + with subtests.test(car_model=car_model.value): + if car_model == CAR.HYUNDAI_SANTA_CRUZ_1ST_GEN: + pytest.skip("Skip checking Santa Cruz for its parts") for code, _ in get_platform_codes(ecus[(Ecu.fwdCamera, 0x7c4, None)]): if b"-" not in code: continue part = code.split(b"-")[1] - self.assertFalse(part.startswith(b'CW'), "Car has bad part number") + assert not part.startswith(b'CW'), "Car has bad part number" - def test_correct_ecu_response_database(self): + def test_correct_ecu_response_database(self, subtests): """ Assert standard responses for certain ECUs, since they can respond to multiple queries with different data """ expected_fw_prefix = HYUNDAI_VERSION_REQUEST_LONG[1:] for car_model, ecus in FW_VERSIONS.items(): - with self.subTest(car_model=car_model.value): + with subtests.test(car_model=car_model.value): for ecu, fws in ecus.items(): - # TODO: enable for Ecu.fwdRadar, Ecu.abs, Ecu.eps, Ecu.transmission - if ecu[0] in (Ecu.fwdCamera,): - self.assertTrue(all(fw.startswith(expected_fw_prefix) for fw in fws), - f"FW from unexpected request in database: {(ecu, fws)}") + assert all(fw.startswith(expected_fw_prefix) for fw in fws), \ + f"FW from unexpected request in database: {(ecu, fws)}" @settings(max_examples=100) @given(data=st.data()) @@ -98,31 +96,48 @@ class TestHyundaiFingerprint(unittest.TestCase): fws = data.draw(fw_strategy) get_platform_codes(fws) + def test_expected_platform_codes(self, subtests): + # Ensures we don't accidentally add multiple platform codes for a car unless it is intentional + for car_model, ecus in FW_VERSIONS.items(): + with subtests.test(car_model=car_model.value): + for ecu, fws in ecus.items(): + if ecu[0] not in PLATFORM_CODE_ECUS: + continue + + # Third and fourth character are usually EV/hybrid identifiers + codes = {code.split(b"-")[0][:2] for code, _ in get_platform_codes(fws)} + if car_model == CAR.HYUNDAI_PALISADE: + assert codes == {b"LX", b"ON"}, f"Car has unexpected platform codes: {car_model} {codes}" + elif car_model == CAR.HYUNDAI_KONA_EV and ecu[0] == Ecu.fwdCamera: + assert codes == {b"OE", b"OS"}, f"Car has unexpected platform codes: {car_model} {codes}" + else: + assert len(codes) == 1, f"Car has multiple platform codes: {car_model} {codes}" + # Tests for platform codes, part numbers, and FW dates which Hyundai will use to fuzzy # fingerprint in the absence of full FW matches: - def test_platform_code_ecus_available(self): + def test_platform_code_ecus_available(self, subtests): # TODO: add queries for these non-CAN FD cars to get EPS no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H, - CAR.KIA_OPTIMA_H_G4_FL, CAR.SONATA_LF, CAR.TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80, CAR.ELANTRA} + CAR.KIA_OPTIMA_H_G4_FL, CAR.HYUNDAI_SONATA_LF, CAR.HYUNDAI_TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80, CAR.HYUNDAI_ELANTRA} # Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms for car_model, ecus in FW_VERSIONS.items(): - with self.subTest(car_model=car_model.value): + with subtests.test(car_model=car_model.value): for platform_code_ecu in PLATFORM_CODE_ECUS: if platform_code_ecu in (Ecu.fwdRadar, Ecu.eps) and car_model == CAR.HYUNDAI_GENESIS: continue if platform_code_ecu == Ecu.eps and car_model in no_eps_platforms: continue - self.assertIn(platform_code_ecu, [e[0] for e in ecus]) + assert platform_code_ecu in [e[0] for e in ecus] - def test_fw_format(self): + def test_fw_format(self, subtests): # Asserts: # - every supported ECU FW version returns one platform code # - every supported ECU FW version has a part number # - expected parsing of ECU FW dates for car_model, ecus in FW_VERSIONS.items(): - with self.subTest(car_model=car_model.value): + with subtests.test(car_model=car_model.value): for ecu, fws in ecus.items(): if ecu[0] not in PLATFORM_CODE_ECUS: continue @@ -130,40 +145,40 @@ class TestHyundaiFingerprint(unittest.TestCase): codes = set() for fw in fws: result = get_platform_codes([fw]) - self.assertEqual(1, len(result), f"Unable to parse FW: {fw}") + assert 1 == len(result), f"Unable to parse FW: {fw}" codes |= result if ecu[0] not in DATE_FW_ECUS or car_model in NO_DATES_PLATFORMS: - self.assertTrue(all(date is None for _, date in codes)) + assert all(date is None for _, date in codes) else: - self.assertTrue(all(date is not None for _, date in codes)) + assert all(date is not None for _, date in codes) if car_model == CAR.HYUNDAI_GENESIS: - raise unittest.SkipTest("No part numbers for car model") + pytest.skip("No part numbers for car model") # Hyundai places the ECU part number in their FW versions, assert all parsable # Some examples of valid formats: b"56310-L0010", b"56310L0010", b"56310/M6300" - self.assertTrue(all(b"-" in code for code, _ in codes), - f"FW does not have part number: {fw}") + assert all(b"-" in code for code, _ in codes), \ + f"FW does not have part number: {fw}" def test_platform_codes_spot_check(self): # Asserts basic platform code parsing behavior for a few cases results = get_platform_codes([b"\xf1\x00DH LKAS 1.1 -150210"]) - self.assertEqual(results, {(b"DH", b"150210")}) + assert results == {(b"DH", b"150210")} # Some cameras and all radars do not have dates results = get_platform_codes([b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 "]) - self.assertEqual(results, {(b"AEhe-G2000", None)}) + assert results == {(b"AEhe-G2000", None)} results = get_platform_codes([b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 "]) - self.assertEqual(results, {(b"CV1-CV000", None)}) + assert results == {(b"CV1-CV000", None)} results = get_platform_codes([ b"\xf1\x00DH LKAS 1.1 -150210", b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ", b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ", ]) - self.assertEqual(results, {(b"DH", b"150210"), (b"AEhe-G2000", None), (b"CV1-CV000", None)}) + assert results == {(b"DH", b"150210"), (b"AEhe-G2000", None), (b"CV1-CV000", None)} results = get_platform_codes([ b"\xf1\x00LX2 MFC AT USA LHD 1.00 1.07 99211-S8100 220222", @@ -171,8 +186,8 @@ class TestHyundaiFingerprint(unittest.TestCase): b"\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 190405", b"\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 190720", ]) - self.assertEqual(results, {(b"LX2-S8100", b"220222"), (b"LX2-S8100", b"211103"), - (b"ON-S9100", b"190405"), (b"ON-S9100", b"190720")}) + assert results == {(b"LX2-S8100", b"220222"), (b"LX2-S8100", b"211103"), + (b"ON-S9100", b"190405"), (b"ON-S9100", b"190720")} def test_fuzzy_excluded_platforms(self): # Asserts a list of platforms that will not fuzzy fingerprint with platform codes due to them being shared. @@ -194,14 +209,10 @@ class TestHyundaiFingerprint(unittest.TestCase): "subAddress": 0 if sub_addr is None else sub_addr}) CP = car.CarParams.new_message(carFw=car_fw) - matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), FW_VERSIONS) + matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, FW_VERSIONS) if len(matches) == 1: - self.assertEqual(list(matches)[0], platform) + assert list(matches)[0] == platform else: platforms_with_shared_codes.add(platform) - self.assertEqual(platforms_with_shared_codes, excluded_platforms) - - -if __name__ == "__main__": - unittest.main() + assert platforms_with_shared_codes == excluded_platforms diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index cda3bc0c59..2a5902b97a 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1,12 +1,12 @@ import re -from dataclasses import dataclass -from enum import Enum, IntFlag, StrEnum +from dataclasses import dataclass, field +from enum import Enum, IntFlag from cereal import car from panda.python import uds from openpilot.common.conversions import Conversions as CV -from openpilot.selfdrive.car import dbc_dict -from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column +from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu @@ -35,8 +35,8 @@ class CarControllerParams: # To determine the limit for your car, find the maximum value that the stock LKAS will request. # If the max stock LKAS request is <384, add your car to this list. - elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.IONIQ, - CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV, + elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.HYUNDAI_ELANTRA, CAR.HYUNDAI_ELANTRA_GT_I30, CAR.HYUNDAI_IONIQ, + CAR.HYUNDAI_IONIQ_EV_LTD, CAR.HYUNDAI_SANTA_FE_PHEV_2022, CAR.HYUNDAI_SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV, CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL, CAR.KIA_SORENTO): self.STEER_MAX = 255 @@ -52,93 +52,48 @@ class CarControllerParams: class HyundaiFlags(IntFlag): + # Dynamic Flags CANFD_HDA2 = 1 CANFD_ALT_BUTTONS = 2 - CANFD_ALT_GEARS = 4 - CANFD_CAMERA_SCC = 8 + CANFD_ALT_GEARS = 2 ** 2 + CANFD_CAMERA_SCC = 2 ** 3 - ALT_LIMITS = 16 - ENABLE_BLINKERS = 32 - CANFD_ALT_GEARS_2 = 64 - SEND_LFA = 128 - USE_FCA = 256 - CANFD_HDA2_ALT_STEERING = 512 - HYBRID = 1024 - EV = 2048 + ALT_LIMITS = 2 ** 4 + ENABLE_BLINKERS = 2 ** 5 + CANFD_ALT_GEARS_2 = 2 ** 6 + SEND_LFA = 2 ** 7 + USE_FCA = 2 ** 8 + CANFD_HDA2_ALT_STEERING = 2 ** 9 + # these cars use a different gas signal + HYBRID = 2 ** 10 + EV = 2 ** 11 -class CAR(StrEnum): - # Hyundai - AZERA_6TH_GEN = "HYUNDAI AZERA 6TH GEN" - AZERA_HEV_6TH_GEN = "HYUNDAI AZERA HYBRID 6TH GEN" - ELANTRA = "HYUNDAI ELANTRA 2017" - ELANTRA_GT_I30 = "HYUNDAI I30 N LINE 2019 & GT 2018 DCT" - ELANTRA_2021 = "HYUNDAI ELANTRA 2021" - ELANTRA_HEV_2021 = "HYUNDAI ELANTRA HYBRID 2021" - HYUNDAI_GENESIS = "HYUNDAI GENESIS 2015-2016" - IONIQ = "HYUNDAI IONIQ HYBRID 2017-2019" - IONIQ_HEV_2022 = "HYUNDAI IONIQ HYBRID 2020-2022" - IONIQ_EV_LTD = "HYUNDAI IONIQ ELECTRIC LIMITED 2019" - IONIQ_EV_2020 = "HYUNDAI IONIQ ELECTRIC 2020" - IONIQ_PHEV_2019 = "HYUNDAI IONIQ PLUG-IN HYBRID 2019" - IONIQ_PHEV = "HYUNDAI IONIQ PHEV 2020" - KONA = "HYUNDAI KONA 2020" - KONA_EV = "HYUNDAI KONA ELECTRIC 2019" - KONA_EV_2022 = "HYUNDAI KONA ELECTRIC 2022" - KONA_EV_2ND_GEN = "HYUNDAI KONA ELECTRIC 2ND GEN" - KONA_HEV = "HYUNDAI KONA HYBRID 2020" - SANTA_FE = "HYUNDAI SANTA FE 2019" - SANTA_FE_2022 = "HYUNDAI SANTA FE 2022" - SANTA_FE_HEV_2022 = "HYUNDAI SANTA FE HYBRID 2022" - SANTA_FE_PHEV_2022 = "HYUNDAI SANTA FE PlUG-IN HYBRID 2022" - SONATA = "HYUNDAI SONATA 2020" - SONATA_LF = "HYUNDAI SONATA 2019" - STARIA_4TH_GEN = "HYUNDAI STARIA 4TH GEN" - TUCSON = "HYUNDAI TUCSON 2019" - PALISADE = "HYUNDAI PALISADE 2020" - VELOSTER = "HYUNDAI VELOSTER 2019" - SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021" - IONIQ_5 = "HYUNDAI IONIQ 5 2022" - IONIQ_6 = "HYUNDAI IONIQ 6 2023" - TUCSON_4TH_GEN = "HYUNDAI TUCSON 4TH GEN" - SANTA_CRUZ_1ST_GEN = "HYUNDAI SANTA CRUZ 1ST GEN" - CUSTIN_1ST_GEN = "HYUNDAI CUSTIN 1ST GEN" + # Static flags - # Kia - KIA_FORTE = "KIA FORTE E 2018 & GT 2021" - KIA_K5_2021 = "KIA K5 2021" - KIA_K5_HEV_2020 = "KIA K5 HYBRID 2020" - KIA_K8_HEV_1ST_GEN = "KIA K8 HYBRID 1ST GEN" - KIA_NIRO_EV = "KIA NIRO EV 2020" - KIA_NIRO_EV_2ND_GEN = "KIA NIRO EV 2ND GEN" - KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019" - KIA_NIRO_PHEV_2022 = "KIA NIRO PLUG-IN HYBRID 2022" - KIA_NIRO_HEV_2021 = "KIA NIRO HYBRID 2021" - KIA_NIRO_HEV_2ND_GEN = "KIA NIRO HYBRID 2ND GEN" - KIA_OPTIMA_G4 = "KIA OPTIMA 4TH GEN" - KIA_OPTIMA_G4_FL = "KIA OPTIMA 4TH GEN FACELIFT" - KIA_OPTIMA_H = "KIA OPTIMA HYBRID 2017 & SPORTS 2019" - KIA_OPTIMA_H_G4_FL = "KIA OPTIMA HYBRID 4TH GEN FACELIFT" - KIA_SELTOS = "KIA SELTOS 2021" - KIA_SPORTAGE_5TH_GEN = "KIA SPORTAGE 5TH GEN" - KIA_SORENTO = "KIA SORENTO GT LINE 2018" - KIA_SORENTO_4TH_GEN = "KIA SORENTO 4TH GEN" - KIA_SORENTO_HEV_4TH_GEN = "KIA SORENTO HYBRID 4TH GEN" - KIA_STINGER = "KIA STINGER GT2 2018" - KIA_STINGER_2022 = "KIA STINGER 2022" - KIA_CEED = "KIA CEED INTRO ED 2019" - KIA_EV6 = "KIA EV6 2022" - KIA_CARNIVAL_4TH_GEN = "KIA CARNIVAL 4TH GEN" + # If 0x500 is present on bus 1 it probably has a Mando radar outputting radar points. + # If no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py + MANDO_RADAR = 2 ** 12 + CANFD = 2 ** 13 - # Genesis - GENESIS_GV60_EV_1ST_GEN = "GENESIS GV60 ELECTRIC 1ST GEN" - GENESIS_G70 = "GENESIS G70 2018" - GENESIS_G70_2020 = "GENESIS G70 2020" - GENESIS_G70_1ST_GEN_FL = "GENESIS G70 1ST GEN FACELIFT" - GENESIS_GV70_1ST_GEN = "GENESIS GV70 1ST GEN" - GENESIS_G80 = "GENESIS G80 2017" - GENESIS_G90 = "GENESIS G90 2017" - GENESIS_GV80 = "GENESIS GV80 2023" + # The radar does SCC on these cars when HDA I, rather than the camera + RADAR_SCC = 2 ** 14 + CAMERA_SCC = 2 ** 15 + CHECKSUM_CRC8 = 2 ** 16 + CHECKSUM_6B = 2 ** 17 + + # these cars require a special panda safety mode due to missing counters and checksums in the messages + LEGACY = 2 ** 18 + + # these cars have not been verified to work with longitudinal yet - radar disable, sending correct messages, etc. + UNSUPPORTED_LONGITUDINAL = 2 ** 19 + + CANFD_NO_RADAR_DISABLE = 2 ** 20 + + CLUSTER_GEARS = 2 ** 21 + TCU_GEARS = 2 ** 22 + + MIN_STEER_32_MPH = 2 ** 23 class Footnote(Enum): @@ -149,165 +104,448 @@ class Footnote(Enum): @dataclass -class HyundaiCarInfo(CarInfo): +class HyundaiCarDocs(CarDocs): package: str = "Smart Cruise Control (SCC)" def init_make(self, CP: car.CarParams): - if CP.carFingerprint in CANFD_CAR: + if CP.flags & HyundaiFlags.CANFD: self.footnotes.insert(0, Footnote.CANFD) -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])), - HyundaiCarInfo("Hyundai Azera Hybrid 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), - ], - CAR.ELANTRA: [ - # TODO: 2017-18 could be Hyundai G - HyundaiCarInfo("Hyundai Elantra 2017-18", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b])), - HyundaiCarInfo("Hyundai Elantra 2019", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_g])), - ], - CAR.ELANTRA_GT_I30: [ - HyundaiCarInfo("Hyundai Elantra GT 2017-19", car_parts=CarParts.common([CarHarness.hyundai_e])), - HyundaiCarInfo("Hyundai i30 2017-19", car_parts=CarParts.common([CarHarness.hyundai_e])), - ], - CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-23", video_link="https://youtu.be/_EdYQtV52-c", car_parts=CarParts.common([CarHarness.hyundai_k])), - CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c", - car_parts=CarParts.common([CarHarness.hyundai_k])), - CAR.HYUNDAI_GENESIS: [ - # TODO: check 2015 packages - HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_j])), - HyundaiCarInfo("Genesis G80 2017", "All", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_j])), - ], - CAR.IONIQ: HyundaiCarInfo("Hyundai Ioniq Hybrid 2017-19", car_parts=CarParts.common([CarHarness.hyundai_c])), - CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", car_parts=CarParts.common([CarHarness.hyundai_h])), # TODO: confirm 2020-21 harness - CAR.IONIQ_EV_LTD: HyundaiCarInfo("Hyundai Ioniq Electric 2019", car_parts=CarParts.common([CarHarness.hyundai_c])), - CAR.IONIQ_EV_2020: HyundaiCarInfo("Hyundai Ioniq Electric 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_h])), - CAR.IONIQ_PHEV_2019: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2019", car_parts=CarParts.common([CarHarness.hyundai_c])), - CAR.IONIQ_PHEV: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2020-22", "All", car_parts=CarParts.common([CarHarness.hyundai_h])), - CAR.KONA: HyundaiCarInfo("Hyundai Kona 2020", car_parts=CarParts.common([CarHarness.hyundai_b])), - CAR.KONA_EV: HyundaiCarInfo("Hyundai Kona Electric 2018-21", car_parts=CarParts.common([CarHarness.hyundai_g])), - CAR.KONA_EV_2022: HyundaiCarInfo("Hyundai Kona Electric 2022-23", car_parts=CarParts.common([CarHarness.hyundai_o])), - CAR.KONA_HEV: HyundaiCarInfo("Hyundai Kona Hybrid 2020", car_parts=CarParts.common([CarHarness.hyundai_i])), # TODO: check packages - # TODO: this is the 2024 US MY, not yet released - CAR.KONA_EV_2ND_GEN: HyundaiCarInfo("Hyundai Kona Electric (with HDA II, Korea only) 2023", video_link="https://www.youtube.com/watch?v=U2fOCmcQ8hw", - car_parts=CarParts.common([CarHarness.hyundai_r])), - CAR.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", video_link="https://youtu.be/bjDR0YjM__s", - car_parts=CarParts.common([CarHarness.hyundai_d])), - CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-23", "All", video_link="https://youtu.be/VnHzSTygTS4", - car_parts=CarParts.common([CarHarness.hyundai_l])), - CAR.SANTA_FE_HEV_2022: HyundaiCarInfo("Hyundai Santa Fe Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l])), - CAR.SANTA_FE_PHEV_2022: HyundaiCarInfo("Hyundai Santa Fe Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l])), - CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-23", "All", video_link="https://www.youtube.com/watch?v=ix63r9kE3Fw", - car_parts=CarParts.common([CarHarness.hyundai_a])), - CAR.STARIA_4TH_GEN: HyundaiCarInfo("Hyundai Staria 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), - CAR.SONATA_LF: HyundaiCarInfo("Hyundai Sonata 2018-19", car_parts=CarParts.common([CarHarness.hyundai_e])), - CAR.TUCSON: [ - HyundaiCarInfo("Hyundai Tucson 2021", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_l])), - HyundaiCarInfo("Hyundai Tucson Diesel 2019", car_parts=CarParts.common([CarHarness.hyundai_l])), - ], - CAR.PALISADE: [ - HyundaiCarInfo("Hyundai Palisade 2020-22", "All", video_link="https://youtu.be/TAnDqjF4fDY?t=456", car_parts=CarParts.common([CarHarness.hyundai_h])), - HyundaiCarInfo("Kia Telluride 2020-22", "All", car_parts=CarParts.common([CarHarness.hyundai_h])), - ], - CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_e])), - CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), - CAR.IONIQ_5: [ - HyundaiCarInfo("Hyundai Ioniq 5 (Southeast Asia only) 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_q])), - HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_k])), - HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q])), - ], - CAR.IONIQ_6: [ - HyundaiCarInfo("Hyundai Ioniq 6 (with HDA II) 2023", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p])), - ], - CAR.TUCSON_4TH_GEN: [ - HyundaiCarInfo("Hyundai Tucson 2022", car_parts=CarParts.common([CarHarness.hyundai_n])), - HyundaiCarInfo("Hyundai Tucson 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_n])), - HyundaiCarInfo("Hyundai Tucson Hybrid 2022-24", "All", car_parts=CarParts.common([CarHarness.hyundai_n])), - ], - CAR.SANTA_CRUZ_1ST_GEN: HyundaiCarInfo("Hyundai Santa Cruz 2022-23", car_parts=CarParts.common([CarHarness.hyundai_n])), - CAR.CUSTIN_1ST_GEN: HyundaiCarInfo("Hyundai Custin 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), +@dataclass +class HyundaiPlatformConfig(PlatformConfig): + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict("hyundai_kia_generic", None)) + + def init(self): + if self.flags & HyundaiFlags.MANDO_RADAR: + self.dbc_dict = dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated') + + if self.flags & HyundaiFlags.MIN_STEER_32_MPH: + self.specs = self.specs.override(minSteerSpeed=32 * CV.MPH_TO_MS) + + +@dataclass +class HyundaiCanFDPlatformConfig(PlatformConfig): + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict("hyundai_canfd", None)) + + def init(self): + self.flags |= HyundaiFlags.CANFD + + +class CAR(Platforms): + # Hyundai + HYUNDAI_AZERA_6TH_GEN = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Azera 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_k]))], + CarSpecs(mass=1600, wheelbase=2.885, steerRatio=14.5), + ) + HYUNDAI_AZERA_HEV_6TH_GEN = HyundaiPlatformConfig( + [ + HyundaiCarDocs("Hyundai Azera Hybrid 2019", "All", car_parts=CarParts.common([CarHarness.hyundai_c])), + HyundaiCarDocs("Hyundai Azera Hybrid 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), + ], + CarSpecs(mass=1675, wheelbase=2.885, steerRatio=14.5), + flags=HyundaiFlags.HYBRID, + ) + HYUNDAI_ELANTRA = HyundaiPlatformConfig( + [ + # TODO: 2017-18 could be Hyundai G + HyundaiCarDocs("Hyundai Elantra 2017-18", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b])), + HyundaiCarDocs("Hyundai Elantra 2019", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_g])), + ], + # steerRatio: 14 is Stock | Settled Params Learner values are steerRatio: 15.401566348670535, stiffnessFactor settled on 1.0081302973865127 + CarSpecs(mass=1275, wheelbase=2.7, steerRatio=15.4, tireStiffnessFactor=0.385), + flags=HyundaiFlags.LEGACY | HyundaiFlags.CLUSTER_GEARS | HyundaiFlags.MIN_STEER_32_MPH, + ) + HYUNDAI_ELANTRA_GT_I30 = HyundaiPlatformConfig( + [ + HyundaiCarDocs("Hyundai Elantra GT 2017-19", car_parts=CarParts.common([CarHarness.hyundai_e])), + HyundaiCarDocs("Hyundai i30 2017-19", car_parts=CarParts.common([CarHarness.hyundai_e])), + ], + HYUNDAI_ELANTRA.specs, + flags=HyundaiFlags.LEGACY | HyundaiFlags.CLUSTER_GEARS | HyundaiFlags.MIN_STEER_32_MPH, + ) + HYUNDAI_ELANTRA_2021 = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Elantra 2021-23", video_link="https://youtu.be/_EdYQtV52-c", car_parts=CarParts.common([CarHarness.hyundai_k]))], + CarSpecs(mass=2800 * CV.LB_TO_KG, wheelbase=2.72, steerRatio=12.9, tireStiffnessFactor=0.65), + flags=HyundaiFlags.CHECKSUM_CRC8, + ) + HYUNDAI_ELANTRA_HEV_2021 = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c", + car_parts=CarParts.common([CarHarness.hyundai_k]))], + CarSpecs(mass=3017 * CV.LB_TO_KG, wheelbase=2.72, steerRatio=12.9, tireStiffnessFactor=0.65), + flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID, + ) + HYUNDAI_GENESIS = HyundaiPlatformConfig( + [ + # TODO: check 2015 packages + HyundaiCarDocs("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_j])), + HyundaiCarDocs("Genesis G80 2017", "All", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_j])), + ], + CarSpecs(mass=2060, wheelbase=3.01, steerRatio=16.5, minSteerSpeed=60 * CV.KPH_TO_MS), + flags=HyundaiFlags.CHECKSUM_6B | HyundaiFlags.LEGACY, + ) + HYUNDAI_IONIQ = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Ioniq Hybrid 2017-19", car_parts=CarParts.common([CarHarness.hyundai_c]))], + CarSpecs(mass=1490, wheelbase=2.7, steerRatio=13.73, tireStiffnessFactor=0.385), + flags=HyundaiFlags.HYBRID | HyundaiFlags.MIN_STEER_32_MPH, + ) + HYUNDAI_IONIQ_HEV_2022 = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Ioniq Hybrid 2020-22", car_parts=CarParts.common([CarHarness.hyundai_h]))], # TODO: confirm 2020-21 harness, + CarSpecs(mass=1490, wheelbase=2.7, steerRatio=13.73, tireStiffnessFactor=0.385), + flags=HyundaiFlags.HYBRID | HyundaiFlags.LEGACY, + ) + HYUNDAI_IONIQ_EV_LTD = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Ioniq Electric 2019", car_parts=CarParts.common([CarHarness.hyundai_c]))], + CarSpecs(mass=1490, wheelbase=2.7, steerRatio=13.73, tireStiffnessFactor=0.385), + flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.EV | HyundaiFlags.LEGACY | HyundaiFlags.MIN_STEER_32_MPH, + ) + HYUNDAI_IONIQ_EV_2020 = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Ioniq Electric 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_h]))], + CarSpecs(mass=1490, wheelbase=2.7, steerRatio=13.73, tireStiffnessFactor=0.385), + flags=HyundaiFlags.EV, + ) + HYUNDAI_IONIQ_PHEV_2019 = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Ioniq Plug-in Hybrid 2019", car_parts=CarParts.common([CarHarness.hyundai_c]))], + CarSpecs(mass=1490, wheelbase=2.7, steerRatio=13.73, tireStiffnessFactor=0.385), + flags=HyundaiFlags.HYBRID | HyundaiFlags.MIN_STEER_32_MPH, + ) + HYUNDAI_IONIQ_PHEV = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Ioniq Plug-in Hybrid 2020-22", "All", car_parts=CarParts.common([CarHarness.hyundai_h]))], + CarSpecs(mass=1490, wheelbase=2.7, steerRatio=13.73, tireStiffnessFactor=0.385), + flags=HyundaiFlags.HYBRID, + ) + HYUNDAI_KONA = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Kona 2020", min_enable_speed=6 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b]))], + CarSpecs(mass=1275, wheelbase=2.6, steerRatio=13.42, tireStiffnessFactor=0.385), + flags=HyundaiFlags.CLUSTER_GEARS, + ) + HYUNDAI_KONA_EV = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Kona Electric 2018-21", car_parts=CarParts.common([CarHarness.hyundai_g]))], + CarSpecs(mass=1685, wheelbase=2.6, steerRatio=13.42, tireStiffnessFactor=0.385), + flags=HyundaiFlags.EV, + ) + HYUNDAI_KONA_EV_2022 = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Kona Electric 2022-23", car_parts=CarParts.common([CarHarness.hyundai_o]))], + CarSpecs(mass=1743, wheelbase=2.6, steerRatio=13.42, tireStiffnessFactor=0.385), + flags=HyundaiFlags.CAMERA_SCC | HyundaiFlags.EV, + ) + HYUNDAI_KONA_EV_2ND_GEN = HyundaiCanFDPlatformConfig( + [HyundaiCarDocs("Hyundai Kona Electric (with HDA II, Korea only) 2023", video_link="https://www.youtube.com/watch?v=U2fOCmcQ8hw", + car_parts=CarParts.common([CarHarness.hyundai_r]))], + CarSpecs(mass=1740, wheelbase=2.66, steerRatio=13.6, tireStiffnessFactor=0.385), + flags=HyundaiFlags.EV | HyundaiFlags.CANFD_NO_RADAR_DISABLE, + ) + HYUNDAI_KONA_HEV = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Kona Hybrid 2020", car_parts=CarParts.common([CarHarness.hyundai_i]))], # TODO: check packages, + CarSpecs(mass=1425, wheelbase=2.6, steerRatio=13.42, tireStiffnessFactor=0.385), + flags=HyundaiFlags.HYBRID, + ) + HYUNDAI_SANTA_FE = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Santa Fe 2019-20", "All", video_link="https://youtu.be/bjDR0YjM__s", + car_parts=CarParts.common([CarHarness.hyundai_d]))], + CarSpecs(mass=3982 * CV.LB_TO_KG, wheelbase=2.766, steerRatio=16.55, tireStiffnessFactor=0.82), + flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8, + ) + HYUNDAI_SANTA_FE_2022 = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Santa Fe 2021-23", "All", video_link="https://youtu.be/VnHzSTygTS4", + car_parts=CarParts.common([CarHarness.hyundai_l]))], + HYUNDAI_SANTA_FE.specs, + flags=HyundaiFlags.CHECKSUM_CRC8, + ) + HYUNDAI_SANTA_FE_HEV_2022 = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Santa Fe Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l]))], + HYUNDAI_SANTA_FE.specs, + flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID, + ) + HYUNDAI_SANTA_FE_PHEV_2022 = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Santa Fe Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l]))], + HYUNDAI_SANTA_FE.specs, + flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID, + ) + HYUNDAI_SONATA = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Sonata 2020-23", "All", video_link="https://www.youtube.com/watch?v=ix63r9kE3Fw", + car_parts=CarParts.common([CarHarness.hyundai_a]))], + CarSpecs(mass=1513, wheelbase=2.84, steerRatio=13.27 * 1.15, tireStiffnessFactor=0.65), # 15% higher at the center seems reasonable + flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8, + ) + HYUNDAI_SONATA_LF = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Sonata 2018-19", car_parts=CarParts.common([CarHarness.hyundai_e]))], + CarSpecs(mass=1536, wheelbase=2.804, steerRatio=13.27 * 1.15), # 15% higher at the center seems reasonable + + flags=HyundaiFlags.UNSUPPORTED_LONGITUDINAL | HyundaiFlags.TCU_GEARS, + ) + HYUNDAI_STARIA_4TH_GEN = HyundaiCanFDPlatformConfig( + [HyundaiCarDocs("Hyundai Staria 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_k]))], + CarSpecs(mass=2205, wheelbase=3.273, steerRatio=11.94), # https://www.hyundai.com/content/dam/hyundai/au/en/models/staria-load/premium-pip-update-2023/spec-sheet/STARIA_Load_Spec-Table_March_2023_v3.1.pdf + ) + HYUNDAI_TUCSON = HyundaiPlatformConfig( + [ + HyundaiCarDocs("Hyundai Tucson 2021", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_l])), + HyundaiCarDocs("Hyundai Tucson Diesel 2019", car_parts=CarParts.common([CarHarness.hyundai_l])), + ], + CarSpecs(mass=3520 * CV.LB_TO_KG, wheelbase=2.67, steerRatio=16.1, tireStiffnessFactor=0.385), + flags=HyundaiFlags.TCU_GEARS, + ) + HYUNDAI_PALISADE = HyundaiPlatformConfig( + [ + HyundaiCarDocs("Hyundai Palisade 2020-22", "All", video_link="https://youtu.be/TAnDqjF4fDY?t=456", car_parts=CarParts.common([CarHarness.hyundai_h])), + HyundaiCarDocs("Kia Telluride 2020-22", "All", car_parts=CarParts.common([CarHarness.hyundai_h])), + ], + CarSpecs(mass=1999, wheelbase=2.9, steerRatio=15.6 * 1.15, tireStiffnessFactor=0.63), + flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8, + ) + HYUNDAI_VELOSTER = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_e]))], + CarSpecs(mass=2917 * CV.LB_TO_KG, wheelbase=2.8, steerRatio=13.75 * 1.15, tireStiffnessFactor=0.5), + flags=HyundaiFlags.LEGACY | HyundaiFlags.TCU_GEARS, + ) + HYUNDAI_SONATA_HYBRID = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Sonata Hybrid 2020-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a]))], + HYUNDAI_SONATA.specs, + flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID, + ) + HYUNDAI_IONIQ_5 = HyundaiCanFDPlatformConfig( + [ + HyundaiCarDocs("Hyundai Ioniq 5 (Non-US only) 2022-24", "All", car_parts=CarParts.common([CarHarness.hyundai_q])), + HyundaiCarDocs("Hyundai Ioniq 5 (without HDA II) 2022-24", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_k])), + HyundaiCarDocs("Hyundai Ioniq 5 (with HDA II) 2022-24", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q])), + ], + CarSpecs(mass=1948, wheelbase=2.97, steerRatio=14.26, tireStiffnessFactor=0.65), + flags=HyundaiFlags.EV, + ) + HYUNDAI_IONIQ_6 = HyundaiCanFDPlatformConfig( + [HyundaiCarDocs("Hyundai Ioniq 6 (with HDA II) 2023-24", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p]))], + HYUNDAI_IONIQ_5.specs, + flags=HyundaiFlags.EV | HyundaiFlags.CANFD_NO_RADAR_DISABLE, + ) + HYUNDAI_TUCSON_4TH_GEN = HyundaiCanFDPlatformConfig( + [ + HyundaiCarDocs("Hyundai Tucson 2022", car_parts=CarParts.common([CarHarness.hyundai_n])), + HyundaiCarDocs("Hyundai Tucson 2023-24", "All", car_parts=CarParts.common([CarHarness.hyundai_n])), + HyundaiCarDocs("Hyundai Tucson Hybrid 2022-24", "All", car_parts=CarParts.common([CarHarness.hyundai_n])), + HyundaiCarDocs("Hyundai Tucson Plug-in Hybrid 2024", "All", car_parts=CarParts.common([CarHarness.hyundai_n])), + ], + CarSpecs(mass=1630, wheelbase=2.756, steerRatio=13.7, tireStiffnessFactor=0.385), + ) + HYUNDAI_SANTA_CRUZ_1ST_GEN = HyundaiCanFDPlatformConfig( + [HyundaiCarDocs("Hyundai Santa Cruz 2022-24", car_parts=CarParts.common([CarHarness.hyundai_n]))], + # weight from Limited trim - the only supported trim, steering ratio according to Hyundai News https://www.hyundainews.com/assets/documents/original/48035-2022SantaCruzProductGuideSpecsv2081521.pdf + CarSpecs(mass=1870, wheelbase=3, steerRatio=14.2), + ) + HYUNDAI_CUSTIN_1ST_GEN = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Custin 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_k]))], + CarSpecs(mass=1690, wheelbase=3.055, steerRatio=17), # mass: from https://www.hyundai-motor.com.tw/clicktobuy/custin#spec_0, steerRatio: from learner + flags=HyundaiFlags.CHECKSUM_CRC8, + ) # Kia - CAR.KIA_FORTE: [ - HyundaiCarInfo("Kia Forte 2019-21", car_parts=CarParts.common([CarHarness.hyundai_g])), - HyundaiCarInfo("Kia Forte 2023", car_parts=CarParts.common([CarHarness.hyundai_e])), - ], - CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-24", car_parts=CarParts.common([CarHarness.hyundai_a])), - CAR.KIA_K5_HEV_2020: HyundaiCarInfo("Kia K5 Hybrid 2020-22", car_parts=CarParts.common([CarHarness.hyundai_a])), - CAR.KIA_K8_HEV_1ST_GEN: HyundaiCarInfo("Kia K8 Hybrid (with HDA II) 2023", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q])), - CAR.KIA_NIRO_EV: [ - HyundaiCarInfo("Kia Niro EV 2019", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_h])), - HyundaiCarInfo("Kia Niro EV 2020", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_f])), - HyundaiCarInfo("Kia Niro EV 2021", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_c])), - HyundaiCarInfo("Kia Niro EV 2022", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_h])), - ], - CAR.KIA_NIRO_EV_2ND_GEN: HyundaiCarInfo("Kia Niro EV 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), - CAR.KIA_NIRO_PHEV: [ - 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 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])), - ], - CAR.KIA_NIRO_HEV_2ND_GEN: HyundaiCarInfo("Kia Niro Hybrid 2023", car_parts=CarParts.common([CarHarness.hyundai_a])), - CAR.KIA_OPTIMA_G4: HyundaiCarInfo("Kia Optima 2017", "Advanced Smart Cruise Control", - car_parts=CarParts.common([CarHarness.hyundai_b])), # TODO: may support 2016, 2018 - CAR.KIA_OPTIMA_G4_FL: HyundaiCarInfo("Kia Optima 2019-20", car_parts=CarParts.common([CarHarness.hyundai_g])), + KIA_FORTE = HyundaiPlatformConfig( + [ + HyundaiCarDocs("Kia Forte 2019-21", min_enable_speed=6 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_g])), + HyundaiCarDocs("Kia Forte 2022-23", car_parts=CarParts.common([CarHarness.hyundai_e])), + ], + CarSpecs(mass=2878 * CV.LB_TO_KG, wheelbase=2.8, steerRatio=13.75, tireStiffnessFactor=0.5) + ) + KIA_K5_2021 = HyundaiPlatformConfig( + [HyundaiCarDocs("Kia K5 2021-24", car_parts=CarParts.common([CarHarness.hyundai_a]))], + CarSpecs(mass=3381 * CV.LB_TO_KG, wheelbase=2.85, steerRatio=13.27, tireStiffnessFactor=0.5), # 2021 Kia K5 Steering Ratio (all trims) + flags=HyundaiFlags.CHECKSUM_CRC8, + ) + KIA_K5_HEV_2020 = HyundaiPlatformConfig( + [HyundaiCarDocs("Kia K5 Hybrid 2020-22", car_parts=CarParts.common([CarHarness.hyundai_a]))], + KIA_K5_2021.specs, + flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID, + ) + KIA_K8_HEV_1ST_GEN = HyundaiCanFDPlatformConfig( + [HyundaiCarDocs("Kia K8 Hybrid (with HDA II) 2023", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q]))], + # mass: https://carprices.ae/brands/kia/2023/k8/1.6-turbo-hybrid, steerRatio: guesstimate from K5 platform + CarSpecs(mass=1630, wheelbase=2.895, steerRatio=13.27) + ) + KIA_NIRO_EV = HyundaiPlatformConfig( + [ + HyundaiCarDocs("Kia Niro EV 2019", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_h])), + HyundaiCarDocs("Kia Niro EV 2020", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_f])), + HyundaiCarDocs("Kia Niro EV 2021", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_c])), + HyundaiCarDocs("Kia Niro EV 2022", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_h])), + ], + CarSpecs(mass=3543 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=13.6, tireStiffnessFactor=0.385), # average of all the cars + flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.EV, + ) + KIA_NIRO_EV_2ND_GEN = HyundaiCanFDPlatformConfig( + [HyundaiCarDocs("Kia Niro EV 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_a]))], + KIA_NIRO_EV.specs, + flags=HyundaiFlags.EV, + ) + KIA_NIRO_PHEV = HyundaiPlatformConfig( + [ + HyundaiCarDocs("Kia Niro Hybrid 2018", "All", min_enable_speed=10. * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_c])), + HyundaiCarDocs("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_c])), + HyundaiCarDocs("Kia Niro Plug-in Hybrid 2020", car_parts=CarParts.common([CarHarness.hyundai_d])), + ], + KIA_NIRO_EV.specs, + flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.HYBRID | HyundaiFlags.UNSUPPORTED_LONGITUDINAL | HyundaiFlags.MIN_STEER_32_MPH, + ) + KIA_NIRO_PHEV_2022 = HyundaiPlatformConfig( + [ + HyundaiCarDocs("Kia Niro Plug-in Hybrid 2021", car_parts=CarParts.common([CarHarness.hyundai_d])), + HyundaiCarDocs("Kia Niro Plug-in Hybrid 2022", car_parts=CarParts.common([CarHarness.hyundai_f])), + ], + KIA_NIRO_EV.specs, + flags=HyundaiFlags.HYBRID | HyundaiFlags.MANDO_RADAR, + ) + KIA_NIRO_HEV_2021 = HyundaiPlatformConfig( + [ + HyundaiCarDocs("Kia Niro Hybrid 2021", car_parts=CarParts.common([CarHarness.hyundai_d])), + HyundaiCarDocs("Kia Niro Hybrid 2022", car_parts=CarParts.common([CarHarness.hyundai_f])), + ], + KIA_NIRO_EV.specs, + flags=HyundaiFlags.HYBRID, + ) + KIA_NIRO_HEV_2ND_GEN = HyundaiCanFDPlatformConfig( + [HyundaiCarDocs("Kia Niro Hybrid 2023", car_parts=CarParts.common([CarHarness.hyundai_a]))], + KIA_NIRO_EV.specs, + ) + KIA_OPTIMA_G4 = HyundaiPlatformConfig( + [HyundaiCarDocs("Kia Optima 2017", "Advanced Smart Cruise Control", + car_parts=CarParts.common([CarHarness.hyundai_b]))], # TODO: may support 2016, 2018 + CarSpecs(mass=3558 * CV.LB_TO_KG, wheelbase=2.8, steerRatio=13.75, tireStiffnessFactor=0.5), + flags=HyundaiFlags.LEGACY | HyundaiFlags.TCU_GEARS | HyundaiFlags.MIN_STEER_32_MPH, + ) + KIA_OPTIMA_G4_FL = HyundaiPlatformConfig( + [HyundaiCarDocs("Kia Optima 2019-20", car_parts=CarParts.common([CarHarness.hyundai_g]))], + CarSpecs(mass=3558 * CV.LB_TO_KG, wheelbase=2.8, steerRatio=13.75, tireStiffnessFactor=0.5), + flags=HyundaiFlags.UNSUPPORTED_LONGITUDINAL | HyundaiFlags.TCU_GEARS, + ) # TODO: may support adjacent years. may have a non-zero minimum steering speed - CAR.KIA_OPTIMA_H: HyundaiCarInfo("Kia Optima Hybrid 2017", "Advanced Smart Cruise Control", car_parts=CarParts.common([CarHarness.hyundai_c])), - CAR.KIA_OPTIMA_H_G4_FL: HyundaiCarInfo("Kia Optima Hybrid 2019", car_parts=CarParts.common([CarHarness.hyundai_h])), - CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", car_parts=CarParts.common([CarHarness.hyundai_a])), - CAR.KIA_SPORTAGE_5TH_GEN: [ - HyundaiCarInfo("Kia Sportage 2023", car_parts=CarParts.common([CarHarness.hyundai_n])), - HyundaiCarInfo("Kia Sportage Hybrid 2023", car_parts=CarParts.common([CarHarness.hyundai_n])), - ], - CAR.KIA_SORENTO: [ - HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control & LKAS", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", - car_parts=CarParts.common([CarHarness.hyundai_e])), - HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", car_parts=CarParts.common([CarHarness.hyundai_e])), - ], - CAR.KIA_SORENTO_4TH_GEN: HyundaiCarInfo("Kia Sorento 2021-23", car_parts=CarParts.common([CarHarness.hyundai_k])), - CAR.KIA_SORENTO_HEV_4TH_GEN: [ - HyundaiCarInfo("Kia Sorento Hybrid 2021-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), - HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), - ], - CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", - car_parts=CarParts.common([CarHarness.hyundai_c])), - CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), - CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", car_parts=CarParts.common([CarHarness.hyundai_e])), - CAR.KIA_EV6: [ - HyundaiCarInfo("Kia EV6 (Southeast Asia only) 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_p])), - HyundaiCarInfo("Kia EV6 (without HDA II) 2022-23", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_l])), - HyundaiCarInfo("Kia EV6 (with HDA II) 2022-23", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p])) - ], - CAR.KIA_CARNIVAL_4TH_GEN: [ - HyundaiCarInfo("Kia Carnival 2022-24", car_parts=CarParts.common([CarHarness.hyundai_a])), - HyundaiCarInfo("Kia Carnival (China only) 2023", car_parts=CarParts.common([CarHarness.hyundai_k])) - ], + KIA_OPTIMA_H = HyundaiPlatformConfig( + [HyundaiCarDocs("Kia Optima Hybrid 2017", "Advanced Smart Cruise Control", car_parts=CarParts.common([CarHarness.hyundai_c]))], + CarSpecs(mass=3558 * CV.LB_TO_KG, wheelbase=2.8, steerRatio=13.75, tireStiffnessFactor=0.5), + flags=HyundaiFlags.HYBRID | HyundaiFlags.LEGACY, + ) + KIA_OPTIMA_H_G4_FL = HyundaiPlatformConfig( + [HyundaiCarDocs("Kia Optima Hybrid 2019", car_parts=CarParts.common([CarHarness.hyundai_h]))], + CarSpecs(mass=3558 * CV.LB_TO_KG, wheelbase=2.8, steerRatio=13.75, tireStiffnessFactor=0.5), + flags=HyundaiFlags.HYBRID | HyundaiFlags.UNSUPPORTED_LONGITUDINAL, + ) + KIA_SELTOS = HyundaiPlatformConfig( + [HyundaiCarDocs("Kia Seltos 2021", car_parts=CarParts.common([CarHarness.hyundai_a]))], + CarSpecs(mass=1337, wheelbase=2.63, steerRatio=14.56), + flags=HyundaiFlags.CHECKSUM_CRC8, + ) + KIA_SPORTAGE_5TH_GEN = HyundaiCanFDPlatformConfig( + [ + HyundaiCarDocs("Kia Sportage 2023-24", car_parts=CarParts.common([CarHarness.hyundai_n])), + HyundaiCarDocs("Kia Sportage Hybrid 2023", car_parts=CarParts.common([CarHarness.hyundai_n])), + ], + # weight from SX and above trims, average of FWD and AWD version, steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sportage/2023/specifications + CarSpecs(mass=1725, wheelbase=2.756, steerRatio=13.6), + ) + KIA_SORENTO = HyundaiPlatformConfig( + [ + HyundaiCarDocs("Kia Sorento 2018", "Advanced Smart Cruise Control & LKAS", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", + car_parts=CarParts.common([CarHarness.hyundai_e])), + HyundaiCarDocs("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", car_parts=CarParts.common([CarHarness.hyundai_e])), + ], + CarSpecs(mass=1985, wheelbase=2.78, steerRatio=14.4 * 1.1), # 10% higher at the center seems reasonable + flags=HyundaiFlags.CHECKSUM_6B | HyundaiFlags.UNSUPPORTED_LONGITUDINAL, + ) + KIA_SORENTO_4TH_GEN = HyundaiCanFDPlatformConfig( + [HyundaiCarDocs("Kia Sorento 2021-23", car_parts=CarParts.common([CarHarness.hyundai_k]))], + CarSpecs(mass=3957 * CV.LB_TO_KG, wheelbase=2.81, steerRatio=13.5), # average of the platforms + flags=HyundaiFlags.RADAR_SCC, + ) + KIA_SORENTO_HEV_4TH_GEN = HyundaiCanFDPlatformConfig( + [ + HyundaiCarDocs("Kia Sorento Hybrid 2021-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), + HyundaiCarDocs("Kia Sorento Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), + ], + CarSpecs(mass=4395 * CV.LB_TO_KG, wheelbase=2.81, steerRatio=13.5), # average of the platforms + flags=HyundaiFlags.RADAR_SCC, + ) + KIA_STINGER = HyundaiPlatformConfig( + [HyundaiCarDocs("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", + car_parts=CarParts.common([CarHarness.hyundai_c]))], + CarSpecs(mass=1825, wheelbase=2.78, steerRatio=14.4 * 1.15) # 15% higher at the center seems reasonable + ) + KIA_STINGER_2022 = HyundaiPlatformConfig( + [HyundaiCarDocs("Kia Stinger 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_k]))], + KIA_STINGER.specs, + ) + KIA_CEED = HyundaiPlatformConfig( + [HyundaiCarDocs("Kia Ceed 2019", car_parts=CarParts.common([CarHarness.hyundai_e]))], + CarSpecs(mass=1450, wheelbase=2.65, steerRatio=13.75, tireStiffnessFactor=0.5), + flags=HyundaiFlags.LEGACY, + ) + KIA_EV6 = HyundaiCanFDPlatformConfig( + [ + HyundaiCarDocs("Kia EV6 (Non-US only) 2022-24", "All", car_parts=CarParts.common([CarHarness.hyundai_p])), + HyundaiCarDocs("Kia EV6 (without HDA II) 2022-24", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_l])), + HyundaiCarDocs("Kia EV6 (with HDA II) 2022-24", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p])) + ], + CarSpecs(mass=2055, wheelbase=2.9, steerRatio=16, tireStiffnessFactor=0.65), + flags=HyundaiFlags.EV, + ) + KIA_CARNIVAL_4TH_GEN = HyundaiCanFDPlatformConfig( + [ + HyundaiCarDocs("Kia Carnival 2022-24", car_parts=CarParts.common([CarHarness.hyundai_a])), + HyundaiCarDocs("Kia Carnival (China only) 2023", car_parts=CarParts.common([CarHarness.hyundai_k])) + ], + CarSpecs(mass=2087, wheelbase=3.09, steerRatio=14.23), + flags=HyundaiFlags.RADAR_SCC, + ) # Genesis - CAR.GENESIS_GV60_EV_1ST_GEN: [ - HyundaiCarInfo("Genesis GV60 (Advanced Trim) 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), - HyundaiCarInfo("Genesis GV60 (Performance Trim) 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), - ], - CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", car_parts=CarParts.common([CarHarness.hyundai_f])), - CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_f])), - CAR.GENESIS_G70_1ST_GEN_FL: HyundaiCarInfo("Genesis G70 2024", "All", car_parts=CarParts.common([CarHarness.hyundai_l])), - CAR.GENESIS_GV70_1ST_GEN: [ - HyundaiCarInfo("Genesis GV70 (2.5T Trim) 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l])), - HyundaiCarInfo("Genesis GV70 (3.5T Trim) 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_m])), - ], - CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2018-19", "All", car_parts=CarParts.common([CarHarness.hyundai_h])), - CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", car_parts=CarParts.common([CarHarness.hyundai_c])), - CAR.GENESIS_GV80: HyundaiCarInfo("Genesis GV80 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_m])), -} + GENESIS_GV60_EV_1ST_GEN = HyundaiCanFDPlatformConfig( + [ + HyundaiCarDocs("Genesis GV60 (Advanced Trim) 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), + HyundaiCarDocs("Genesis GV60 (Performance Trim) 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), + ], + CarSpecs(mass=2205, wheelbase=2.9, steerRatio=12.6), # steerRatio: https://www.motor1.com/reviews/586376/2023-genesis-gv60-first-drive/#:~:text=Relative%20to%20the%20related%20Ioniq,5%2FEV6%27s%2014.3%3A1. + flags=HyundaiFlags.EV, + ) + GENESIS_G70 = HyundaiPlatformConfig( + [HyundaiCarDocs("Genesis G70 2018", "All", car_parts=CarParts.common([CarHarness.hyundai_f]))], + CarSpecs(mass=1640, wheelbase=2.84, steerRatio=13.56), + flags=HyundaiFlags.LEGACY, + ) + GENESIS_G70_2020 = HyundaiPlatformConfig( + [ + # TODO: 2021 MY harness is unknown + HyundaiCarDocs("Genesis G70 2019-21", "All", car_parts=CarParts.common([CarHarness.hyundai_f])), + # TODO: From 3.3T Sport Advanced 2022 & Prestige 2023 Trim, 2.0T is unknown + HyundaiCarDocs("Genesis G70 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l])), + ], + GENESIS_G70.specs, + flags=HyundaiFlags.MANDO_RADAR, + ) + GENESIS_G70_1ST_GEN_FL = HyundaiPlatformConfig( + [HyundaiCarDocs("Genesis G70 (3.3T Trim) 2024", "All", car_parts=CarParts.common([CarHarness.hyundai_l]))], + CarSpecs(mass=1769, wheelbase=2.83, steerRatio=12.9), # steerRatio guesstimate from G70 1st Gen platform + flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.CAMERA_SCC, + ) + GENESIS_GV70_1ST_GEN = HyundaiCanFDPlatformConfig( + [ + HyundaiCarDocs("Genesis GV70 (2.5T Trim) 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l])), + HyundaiCarDocs("Genesis GV70 (3.5T Trim) 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_m])), + ], + CarSpecs(mass=1950, wheelbase=2.87, steerRatio=14.6), + flags=HyundaiFlags.RADAR_SCC, + ) + GENESIS_G80 = HyundaiPlatformConfig( + [HyundaiCarDocs("Genesis G80 2018-19", "All", car_parts=CarParts.common([CarHarness.hyundai_h]))], + CarSpecs(mass=2060, wheelbase=3.01, steerRatio=16.5), + flags=HyundaiFlags.LEGACY, + ) + GENESIS_G90 = HyundaiPlatformConfig( + [HyundaiCarDocs("Genesis G90 2017-20", "All", car_parts=CarParts.common([CarHarness.hyundai_c]))], + CarSpecs(mass=2200, wheelbase=3.15, steerRatio=12.069), + ) + GENESIS_GV80 = HyundaiCanFDPlatformConfig( + [HyundaiCarDocs("Genesis GV80 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_m]))], + CarSpecs(mass=2258, wheelbase=2.95, steerRatio=14.14), + flags=HyundaiFlags.RADAR_SCC, + ) + class Buttons: NONE = 0 @@ -336,7 +574,7 @@ def get_platform_codes(fw_versions: list[bytes]) -> set[tuple[bytes, bytes | Non 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, vin, 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. @@ -392,11 +630,6 @@ HYUNDAI_VERSION_REQUEST_LONG = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) HYUNDAI_VERSION_REQUEST_ALT = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(0xf110) # Alt long description -HYUNDAI_VERSION_REQUEST_MULTI = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ - p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER) + \ - p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) + \ - p16(0xf100) - HYUNDAI_ECU_MANUFACTURING_DATE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(uds.DATA_IDENTIFIER_TYPE.ECU_MANUFACTURING_DATE) @@ -409,7 +642,9 @@ DATE_FW_PATTERN = re.compile(b'(?<=[ -])([0-9]{6}$)') PART_NUMBER_FW_PATTERN = re.compile(b'(?<=[0-9][.,][0-9]{2} )([0-9]{5}[-/]?[A-Z][A-Z0-9]{3}[0-9])') # We've seen both ICE and hybrid for these platforms, and they have hybrid descriptors (e.g. MQ4 vs MQ4H) -CANFD_FUZZY_WHITELIST = {CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN} +CANFD_FUZZY_WHITELIST = {CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_K8_HEV_1ST_GEN, + # TODO: the hybrid variant is not out yet + CAR.KIA_CARNIVAL_4TH_GEN} # List of ECUs expected to have platform codes, camera and radar should exist on all cars # TODO: use abs, it has the platform code and part number on many platforms @@ -418,36 +653,27 @@ 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] - +# Note: an ECU on CAN FD cars may sometimes send 0x30080aaaaaaaaaaa (flow control continue) while we +# are attempting to query ECUs. This currently does not seem to affect fingerprinting from the camera FW_QUERY_CONFIG = FwQueryConfig( requests=[ - # TODO: minimize shared whitelists for CAN and cornerRadar for CAN-FD + # TODO: add back whitelists # CAN queries (OBD-II port) Request( [HYUNDAI_VERSION_REQUEST_LONG], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.transmission, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera], - ), - Request( - [HYUNDAI_VERSION_REQUEST_MULTI], - [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.engine, Ecu.transmission, Ecu.eps, Ecu.abs, Ecu.fwdRadar], ), - # CAN-FD queries (from camera) - # TODO: combine shared whitelists with CAN requests + # CAN & CAN-FD queries (from camera) Request( [HYUNDAI_VERSION_REQUEST_LONG], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.cornerRadar, Ecu.hvac], bus=0, auxiliary=True, ), Request( [HYUNDAI_VERSION_REQUEST_LONG], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.fwdCamera, Ecu.adas, Ecu.cornerRadar, Ecu.hvac], bus=1, auxiliary=True, obd_multiplexing=False, @@ -458,44 +684,15 @@ FW_QUERY_CONFIG = FwQueryConfig( 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 + # CAN-FD alt request logging queries for hvac and parkingAdas Request( [HYUNDAI_VERSION_REQUEST_ALT], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.parkingAdas, Ecu.hvac], bus=0, auxiliary=True, logging=True, @@ -503,136 +700,57 @@ FW_QUERY_CONFIG = FwQueryConfig( Request( [HYUNDAI_VERSION_REQUEST_ALT], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.parkingAdas, Ecu.hvac], bus=1, auxiliary=True, 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.abs: [CAR.HYUNDAI_PALISADE, CAR.HYUNDAI_SONATA, CAR.HYUNDAI_SANTA_FE_2022, CAR.KIA_K5_2021, CAR.HYUNDAI_ELANTRA_2021, + CAR.HYUNDAI_SANTA_FE, CAR.HYUNDAI_KONA_EV_2022, CAR.HYUNDAI_KONA_EV, CAR.HYUNDAI_CUSTIN_1ST_GEN, CAR.KIA_SORENTO, + CAR.KIA_CEED, CAR.KIA_SELTOS], + }, extra_ecus=[ - (Ecu.adas, 0x730, None), # ADAS Driving ECU on HDA2 platforms - (Ecu.parkingAdas, 0x7b1, None), # ADAS Parking ECU (may exist on all platforms) - (Ecu.hvac, 0x7b3, None), # HVAC Control Assembly + (Ecu.adas, 0x730, None), # ADAS Driving ECU on HDA2 platforms + (Ecu.parkingAdas, 0x7b1, None), # ADAS Parking ECU (may exist on all platforms) + (Ecu.hvac, 0x7b3, None), # HVAC Control Assembly (Ecu.cornerRadar, 0x7b7, None), + (Ecu.combinationMeter, 0x7c6, None), # CAN FD Instrument cluster ], # Custom fuzzy fingerprinting function using platform codes, part numbers + FW dates: match_fw_to_car_fuzzy=match_fw_to_car_fuzzy, ) - CHECKSUM = { - "crc8": [CAR.SANTA_FE, CAR.SONATA, CAR.PALISADE, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, - CAR.SONATA_HYBRID, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, - CAR.KIA_K5_HEV_2020, CAR.CUSTIN_1ST_GEN, CAR.GENESIS_G70_1ST_GEN_FL], - "6B": [CAR.KIA_SORENTO, CAR.HYUNDAI_GENESIS], + "crc8": CAR.with_flags(HyundaiFlags.CHECKSUM_CRC8), + "6B": CAR.with_flags(HyundaiFlags.CHECKSUM_6B), } CAN_GEARS = { # which message has the gear. hybrid and EV use ELECT_GEAR - "use_cluster_gears": {CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA}, - "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, + "use_cluster_gears": CAR.with_flags(HyundaiFlags.CLUSTER_GEARS), + "use_tcu_gears": CAR.with_flags(HyundaiFlags.TCU_GEARS), } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, - CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_EV_2ND_GEN, - CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN, CAR.KIA_K8_HEV_1ST_GEN, - CAR.STARIA_4TH_GEN} - -# The radar does SCC on these cars when HDA I, rather than the camera -CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN} +CANFD_CAR = CAR.with_flags(HyundaiFlags.CANFD) +CANFD_RADAR_SCC_CAR = CAR.with_flags(HyundaiFlags.RADAR_SCC) # These CAN FD cars do not accept communication control to disable the ADAS ECU, # responds with 0x7F2822 - 'conditions not correct' -CANFD_UNSUPPORTED_LONGITUDINAL_CAR = {CAR.IONIQ_6, CAR.KONA_EV_2ND_GEN} +CANFD_UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.CANFD_NO_RADAR_DISABLE) # The camera does SCC on these cars, rather than the radar -CAMERA_SCC_CAR = {CAR.KONA_EV_2022, CAR.GENESIS_G70_1ST_GEN_FL} +CAMERA_SCC_CAR = CAR.with_flags(HyundaiFlags.CAMERA_SCC) -# these cars use a different gas signal -HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, - CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KIA_K5_HEV_2020, - CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL, CAR.AZERA_HEV_6TH_GEN, CAR.KIA_NIRO_PHEV_2022} +HYBRID_CAR = CAR.with_flags(HyundaiFlags.HYBRID) -EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KONA_EV_2022, - CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KONA_EV_2ND_GEN} +EV_CAR = CAR.with_flags(HyundaiFlags.EV) -# these cars require a special panda safety mode due to missing counters and checksums in the messages -LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_LTD, CAR.KIA_OPTIMA_G4, - CAR.VELOSTER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022, - CAR.KIA_OPTIMA_H, CAR.ELANTRA_GT_I30} +LEGACY_SAFETY_MODE_CAR = CAR.with_flags(HyundaiFlags.LEGACY) -# these cars have not been verified to work with longitudinal yet - radar disable, sending correct messages, etc. -UNSUPPORTED_LONGITUDINAL_CAR = LEGACY_SAFETY_MODE_CAR | {CAR.KIA_NIRO_PHEV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4_FL, - CAR.KIA_OPTIMA_H_G4_FL} +UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.LEGACY) | CAR.with_flags(HyundaiFlags.UNSUPPORTED_LONGITUDINAL) -# If 0x500 is present on bus 1 it probably has a Mando radar outputting radar points. -# If no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py -DBC = { - CAR.AZERA_6TH_GEN: dbc_dict('hyundai_kia_generic', None), - CAR.AZERA_HEV_6TH_GEN: dbc_dict('hyundai_kia_generic', None), - CAR.ELANTRA: dbc_dict('hyundai_kia_generic', None), - CAR.ELANTRA_GT_I30: dbc_dict('hyundai_kia_generic', None), - CAR.ELANTRA_2021: dbc_dict('hyundai_kia_generic', None), - CAR.ELANTRA_HEV_2021: dbc_dict('hyundai_kia_generic', None), - CAR.GENESIS_G70: dbc_dict('hyundai_kia_generic', None), - CAR.GENESIS_G70_2020: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), - CAR.GENESIS_G80: dbc_dict('hyundai_kia_generic', None), - CAR.GENESIS_G90: dbc_dict('hyundai_kia_generic', None), - CAR.HYUNDAI_GENESIS: dbc_dict('hyundai_kia_generic', None), - CAR.IONIQ_PHEV_2019: dbc_dict('hyundai_kia_generic', None), - CAR.IONIQ_PHEV: dbc_dict('hyundai_kia_generic', None), - CAR.IONIQ_EV_2020: dbc_dict('hyundai_kia_generic', None), - CAR.IONIQ_EV_LTD: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), - CAR.IONIQ: dbc_dict('hyundai_kia_generic', None), - CAR.IONIQ_HEV_2022: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_FORTE: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_K5_2021: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_K5_HEV_2020: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), - CAR.KIA_NIRO_EV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), - CAR.KIA_NIRO_PHEV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), - CAR.KIA_NIRO_HEV_2021: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_OPTIMA_G4: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_OPTIMA_G4_FL: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_OPTIMA_H: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_OPTIMA_H_G4_FL: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_SELTOS: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_SORENTO: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format - CAR.KIA_STINGER: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_STINGER_2022: dbc_dict('hyundai_kia_generic', None), - CAR.KONA: dbc_dict('hyundai_kia_generic', None), - CAR.KONA_EV: dbc_dict('hyundai_kia_generic', None), - CAR.KONA_EV_2022: dbc_dict('hyundai_kia_generic', None), - CAR.KONA_HEV: dbc_dict('hyundai_kia_generic', None), - CAR.SANTA_FE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), - CAR.SANTA_FE_2022: dbc_dict('hyundai_kia_generic', None), - CAR.SANTA_FE_HEV_2022: dbc_dict('hyundai_kia_generic', None), - CAR.SANTA_FE_PHEV_2022: dbc_dict('hyundai_kia_generic', None), - CAR.SONATA: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), - CAR.SONATA_LF: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format - CAR.TUCSON: dbc_dict('hyundai_kia_generic', None), - CAR.PALISADE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), - CAR.VELOSTER: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_CEED: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_EV6: dbc_dict('hyundai_canfd', None), - CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), - CAR.TUCSON_4TH_GEN: dbc_dict('hyundai_canfd', None), - CAR.IONIQ_5: dbc_dict('hyundai_canfd', None), - CAR.IONIQ_6: dbc_dict('hyundai_canfd', None), - CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None), - CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None), - CAR.GENESIS_GV70_1ST_GEN: dbc_dict('hyundai_canfd', None), - CAR.GENESIS_GV60_EV_1ST_GEN: dbc_dict('hyundai_canfd', None), - CAR.KIA_SORENTO_4TH_GEN: dbc_dict('hyundai_canfd', None), - CAR.KIA_NIRO_HEV_2ND_GEN: dbc_dict('hyundai_canfd', None), - CAR.KIA_NIRO_EV_2ND_GEN: dbc_dict('hyundai_canfd', None), - CAR.GENESIS_GV80: dbc_dict('hyundai_canfd', None), - CAR.KIA_CARNIVAL_4TH_GEN: dbc_dict('hyundai_canfd', None), - CAR.KIA_SORENTO_HEV_4TH_GEN: dbc_dict('hyundai_canfd', None), - CAR.KONA_EV_2ND_GEN: dbc_dict('hyundai_canfd', None), - CAR.KIA_K8_HEV_1ST_GEN: dbc_dict('hyundai_canfd', None), - CAR.CUSTIN_1ST_GEN: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_NIRO_PHEV_2022: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), - CAR.STARIA_4TH_GEN: dbc_dict('hyundai_canfd', None), - CAR.GENESIS_G70_1ST_GEN_FL: dbc_dict('hyundai_kia_generic', None), -} +DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 0a5ec94aa0..e5762ed52a 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -6,6 +6,7 @@ from abc import abstractmethod, ABC from enum import StrEnum from typing import Any, NamedTuple from collections.abc import Callable +from functools import cache from cereal import car from openpilot.common.basedir import BASEDIR @@ -14,7 +15,7 @@ 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.values import Platform +from openpilot.selfdrive.car.values import PLATFORMS 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 @@ -32,6 +33,18 @@ TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.tom TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.toml') TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/substitute.toml') +GEAR_SHIFTER_MAP: dict[str, car.CarState.GearShifter] = { + 'P': GearShifter.park, 'PARK': GearShifter.park, + 'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse, + 'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral, + 'E': GearShifter.eco, 'ECO': GearShifter.eco, + 'T': GearShifter.manumatic, 'MANUAL': GearShifter.manumatic, + 'D': GearShifter.drive, 'DRIVE': GearShifter.drive, + 'S': GearShifter.sport, 'SPORT': GearShifter.sport, + 'L': GearShifter.low, 'LOW': GearShifter.low, + 'B': GearShifter.brake, 'BRAKE': GearShifter.brake, +} + class LatControlInputs(NamedTuple): lateral_acceleration: float @@ -43,29 +56,34 @@ class LatControlInputs(NamedTuple): TorqueFromLateralAccelCallbackType = Callable[[LatControlInputs, car.CarParams.LateralTorqueTuning, float, float, bool, bool], float] -def get_torque_params(candidate): +@cache +def get_torque_params(): with open(TORQUE_SUBSTITUTE_PATH, 'rb') as f: sub = tomllib.load(f) - if candidate in sub: - candidate = sub[candidate] - with open(TORQUE_PARAMS_PATH, 'rb') as f: params = tomllib.load(f) with open(TORQUE_OVERRIDE_PATH, 'rb') as f: override = tomllib.load(f) - # Ensure no overlap - if sum([candidate in x for x in [sub, params, override]]) > 1: - raise RuntimeError(f'{candidate} is defined twice in torque config') + torque_params = {} + for candidate in (sub.keys() | params.keys() | override.keys()) - {'legend'}: + if sum([candidate in x for x in [sub, params, override]]) > 1: + raise RuntimeError(f'{candidate} is defined twice in torque config') - if candidate in override: - out = override[candidate] - elif candidate in params: - out = params[candidate] - else: - raise NotImplementedError(f"Did not find torque params for {candidate}") - return {key: out[i] for i, key in enumerate(params['legend'])} + sub_candidate = sub.get(candidate, candidate) + if sub_candidate in override: + out = override[sub_candidate] + elif sub_candidate in params: + out = params[sub_candidate] + else: + raise NotImplementedError(f"Did not find torque params for {sub_candidate}") + + torque_params[sub_candidate] = {key: out[i] for i, key in enumerate(params['legend'])} + if candidate in sub: + torque_params[candidate] = torque_params[sub_candidate] + + return torque_params # generic car and radar interfaces @@ -81,45 +99,44 @@ class CarInterfaceBase(ABC): self.silent_steer_warning = True self.v_ego_cluster_seen = False - self.CS = None - self.can_parsers = [] - if CarState is not None: - self.CS = CarState(CP) + self.CS = CarState(CP) + self.cp = self.CS.get_can_parser(CP) + self.cp_cam = self.CS.get_cam_can_parser(CP) + self.cp_adas = self.CS.get_adas_can_parser(CP) + self.cp_body = self.CS.get_body_can_parser(CP) + self.cp_loopback = self.CS.get_loopback_can_parser(CP) + self.can_parsers = [self.cp, self.cp_cam, self.cp_adas, self.cp_body, self.cp_loopback] - self.cp = self.CS.get_can_parser(CP) - self.cp_cam = self.CS.get_cam_can_parser(CP) - self.cp_adas = self.CS.get_adas_can_parser(CP) - self.cp_body = self.CS.get_body_can_parser(CP) - self.cp_loopback = self.CS.get_loopback_can_parser(CP) - self.can_parsers = [self.cp, self.cp_cam, self.cp_adas, self.cp_body, self.cp_loopback] + dbc_name = "" if self.cp is None else self.cp.dbc_name + self.CC: CarControllerBase = CarController(dbc_name, CP, self.VM) - self.CC = None - if CarController is not None: - self.CC = CarController(self.cp.dbc_name, CP, self.VM) + def apply(self, c: car.CarControl, now_nanos: int) -> tuple[car.CarControl.Actuators, list[tuple[int, int, bytes, int]]]: + return self.CC.update(c, self.CS, now_nanos) @staticmethod def get_pid_accel_limits(CP, current_speed, cruise_speed): return ACCEL_MIN, ACCEL_MAX @classmethod - def get_non_essential_params(cls, candidate: Platform): + def get_non_essential_params(cls, candidate: str): """ Parameters essential to controlling the car may be incomplete or wrong without FW versions or fingerprints. """ return cls.get_params(candidate, gen_empty_fingerprint(), list(), False, False) @classmethod - def get_params(cls, candidate: Platform, fingerprint: dict[int, dict[int, int]], car_fw: list[car.CarParams.CarFw], experimental_long: bool, docs: bool): + def get_params(cls, candidate: str, 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"): - if candidate.config.specs is not None: - ret.mass = candidate.config.specs.mass - ret.wheelbase = candidate.config.specs.wheelbase - ret.steerRatio = candidate.config.specs.steerRatio - ret.centerToFront = ret.wheelbase * candidate.config.specs.centerToFrontRatio - ret.minEnableSpeed = candidate.config.specs.minEnableSpeed - ret.minSteerSpeed = candidate.config.specs.minSteerSpeed + platform = PLATFORMS[candidate] + 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.tireStiffnessFactor = platform.config.specs.tireStiffnessFactor + ret.flags |= int(platform.config.flags) ret = cls._get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs) @@ -135,7 +152,7 @@ class CarInterfaceBase(ABC): @staticmethod @abstractmethod - def _get_params(ret: car.CarParams, candidate: Platform, fingerprint: dict[int, dict[int, int]], + def _get_params(ret: car.CarParams, candidate, fingerprint: dict[int, dict[int, int]], car_fw: list[car.CarParams.CarFw], experimental_long: bool, docs: bool): raise NotImplementedError @@ -167,7 +184,7 @@ class CarInterfaceBase(ABC): ret.carFingerprint = candidate # Car docs fields - ret.maxLateralAccel = get_torque_params(candidate)['MAX_LAT_ACCEL_MEASURED'] + ret.maxLateralAccel = get_torque_params()[candidate]['MAX_LAT_ACCEL_MEASURED'] ret.autoResumeSng = True # describes whether car can resume from a stop automatically # standard ALC params @@ -185,22 +202,19 @@ class CarInterfaceBase(ABC): ret.vEgoStopping = 0.5 ret.vEgoStarting = 0.5 ret.stoppingControl = True - ret.longitudinalTuning.deadzoneBP = [0.] - ret.longitudinalTuning.deadzoneV = [0.] ret.longitudinalTuning.kf = 1. ret.longitudinalTuning.kpBP = [0.] - ret.longitudinalTuning.kpV = [1.] + ret.longitudinalTuning.kpV = [0.] ret.longitudinalTuning.kiBP = [0.] - ret.longitudinalTuning.kiV = [1.] + ret.longitudinalTuning.kiV = [0.] # TODO estimate car specific lag, use .15s for now - ret.longitudinalActuatorDelayLowerBound = 0.15 - ret.longitudinalActuatorDelayUpperBound = 0.15 + ret.longitudinalActuatorDelay = 0.15 ret.steerLimitTimer = 1.0 return ret @staticmethod def configure_torque_tune(candidate, tune, steering_angle_deadzone_deg=0.0, use_steering_angle=True): - params = get_torque_params(candidate) + params = get_torque_params()[candidate] tune.init('torque') tune.torque.useSteeringAngle = use_steering_angle @@ -243,15 +257,11 @@ class CarInterfaceBase(ABC): ret.cruiseState.speedCluster = ret.cruiseState.speed # copy back for next iteration - reader = ret.as_reader() if self.CS is not None: - self.CS.out = reader + self.CS.out = ret.as_reader() - return reader + return ret - @abstractmethod - 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, enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise)): @@ -286,6 +296,10 @@ class CarInterfaceBase(ABC): events.add(EventName.accFaulted) if cs_out.steeringPressed: events.add(EventName.steerOverride) + if cs_out.brakePressed and cs_out.standstill: + events.add(EventName.preEnableStandstill) + if cs_out.gasPressed: + events.add(EventName.gasPressedOverride) # Handle button presses for b in cs_out.buttonEvents: @@ -334,7 +348,6 @@ class RadarInterfaceBase(ABC): 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): self.frame += 1 @@ -424,19 +437,11 @@ class CarStateBase(ABC): def parse_gear_shifter(gear: str | None) -> car.CarState.GearShifter: if gear is None: return GearShifter.unknown + return GEAR_SHIFTER_MAP.get(gear.upper(), GearShifter.unknown) - d: dict[str, car.CarState.GearShifter] = { - 'P': GearShifter.park, 'PARK': GearShifter.park, - 'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse, - 'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral, - 'E': GearShifter.eco, 'ECO': GearShifter.eco, - 'T': GearShifter.manumatic, 'MANUAL': GearShifter.manumatic, - 'D': GearShifter.drive, 'DRIVE': GearShifter.drive, - 'S': GearShifter.sport, 'SPORT': GearShifter.sport, - 'L': GearShifter.low, 'LOW': GearShifter.low, - 'B': GearShifter.brake, 'BRAKE': GearShifter.brake, - } - return d.get(gear.upper(), GearShifter.unknown) + @staticmethod + def get_can_parser(CP): + return None @staticmethod def get_cam_can_parser(CP): @@ -455,6 +460,18 @@ class CarStateBase(ABC): return None +SendCan = tuple[int, int, bytes, int] + + +class CarControllerBase(ABC): + def __init__(self, dbc_name: str, CP, VM): + pass + + @abstractmethod + def update(self, CC: car.CarControl.Actuators, CS: car.CarState, now_nanos: int) -> tuple[car.CarControl.Actuators, list[SendCan]]: + pass + + INTERFACE_ATTR_FILE = { "FINGERPRINTS": "fingerprints", "FW_VERSIONS": "fingerprints", diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 8fdc747e9e..447c7093c5 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -4,7 +4,7 @@ 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.pandad 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 diff --git a/selfdrive/car/mazda/carcontroller.py b/selfdrive/car/mazda/carcontroller.py index 320ad19bb4..3d41634879 100644 --- a/selfdrive/car/mazda/carcontroller.py +++ b/selfdrive/car/mazda/carcontroller.py @@ -1,13 +1,14 @@ from cereal import car from opendbc.can.packer import CANPacker from openpilot.selfdrive.car import apply_driver_steer_torque_limits +from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.car.mazda import mazdacan from openpilot.selfdrive.car.mazda.values import CarControllerParams, Buttons VisualAlert = car.CarControl.HUDControl.VisualAlert -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP self.apply_steer_last = 0 @@ -35,13 +36,13 @@ class CarController: if self.frame % 10 == 0 and not (CS.out.brakePressed and self.brake_counter < 7): # Cancel Stock ACC if it's enabled while OP is disengaged # Send at a rate of 10hz until we sync with stock ACC state - can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP.carFingerprint, CS.crz_btns_counter, Buttons.CANCEL)) + can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP, CS.crz_btns_counter, Buttons.CANCEL)) else: self.brake_counter = 0 if CC.cruiseControl.resume and self.frame % 5 == 0: # Mazda Stop and Go requires a RES button (or gas) press if the car stops more than 3 seconds # Send Resume button when planner wants car to move - can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP.carFingerprint, CS.crz_btns_counter, Buttons.RESUME)) + can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP, CS.crz_btns_counter, Buttons.RESUME)) self.apply_steer_last = apply_steer @@ -54,10 +55,10 @@ class CarController: can_sends.append(mazdacan.create_alert_command(self.packer, CS.cam_laneinfo, ldw, steer_required)) # send steering command - can_sends.append(mazdacan.create_steering_control(self.packer, self.CP.carFingerprint, + can_sends.append(mazdacan.create_steering_control(self.packer, self.CP, self.frame, apply_steer, CS.cam_lkas)) - new_actuators = CC.actuators.copy() + new_actuators = CC.actuators.as_builder() new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX new_actuators.steerOutputCan = apply_steer diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py index c0819592d4..83b238fb68 100644 --- a/selfdrive/car/mazda/carstate.py +++ b/selfdrive/car/mazda/carstate.py @@ -3,7 +3,7 @@ from openpilot.common.conversions import Conversions as CV from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from openpilot.selfdrive.car.interfaces import CarStateBase -from openpilot.selfdrive.car.mazda.values import DBC, LKAS_LIMITS, GEN1 +from openpilot.selfdrive.car.mazda.values import DBC, LKAS_LIMITS, MazdaFlags class CarState(CarStateBase): def __init__(self, CP): @@ -18,9 +18,16 @@ class CarState(CarStateBase): self.lkas_allowed_speed = False self.lkas_disabled = False + self.prev_distance_button = 0 + self.distance_button = 0 + def update(self, cp, cp_cam): ret = car.CarState.new_message() + + self.prev_distance_button = self.distance_button + self.distance_button = cp.vl["CRZ_BTNS"]["DISTANCE_LESS"] + ret.wheelSpeeds = self.get_wheel_speeds( cp.vl["WHEEL_SPEEDS"]["FL"], cp.vl["WHEEL_SPEEDS"]["FR"], @@ -116,7 +123,7 @@ class CarState(CarStateBase): ("WHEEL_SPEEDS", 100), ] - if CP.carFingerprint in GEN1: + if CP.flags & MazdaFlags.GEN1: messages += [ ("ENGINE_DATA", 100), ("CRZ_CTRL", 50), @@ -136,7 +143,7 @@ class CarState(CarStateBase): def get_cam_can_parser(CP): messages = [] - if CP.carFingerprint in GEN1: + if CP.flags & MazdaFlags.GEN1: messages += [ # sig_address, frequency ("CAM_LANEINFO", 2), diff --git a/selfdrive/car/mazda/fingerprints.py b/selfdrive/car/mazda/fingerprints.py index 292f407935..f460fe9950 100644 --- a/selfdrive/car/mazda/fingerprints.py +++ b/selfdrive/car/mazda/fingerprints.py @@ -4,12 +4,14 @@ from openpilot.selfdrive.car.mazda.values import CAR Ecu = car.CarParams.Ecu FW_VERSIONS = { - CAR.CX5_2022: { + CAR.MAZDA_CX5_2022: { (Ecu.eps, 0x730, None): [ b'KSD5-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'PEW5-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PW67-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2D-188K2-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2G-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -31,15 +33,17 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'PG69-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PW66-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXDL-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXFG-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXFG-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB2-21PS1-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYJ3-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'SH51-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.CX5: { + CAR.MAZDA_CX5: { (Ecu.eps, 0x730, None): [ b'K319-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KCB8-3210X-B-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -108,7 +112,7 @@ FW_VERSIONS = { b'SH9T-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.CX9: { + CAR.MAZDA_CX9: { (Ecu.eps, 0x730, None): [ b'K070-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -158,7 +162,7 @@ FW_VERSIONS = { b'PYFM-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.MAZDA3: { + CAR.MAZDA_3: { (Ecu.eps, 0x730, None): [ b'BHN1-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'K070-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -193,7 +197,7 @@ FW_VERSIONS = { b'PYKE-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.MAZDA6: { + CAR.MAZDA_6: { (Ecu.eps, 0x730, None): [ b'GBEF-3210X-B-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GBEF-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -225,7 +229,7 @@ FW_VERSIONS = { b'PYH7-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.CX9_2021: { + CAR.MAZDA_CX9_2021: { (Ecu.eps, 0x730, None): [ b'TC3M-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -250,6 +254,7 @@ FW_VERSIONS = { b'GSH7-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-U\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 7ac93d9dee..6992d49ffe 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -2,7 +2,7 @@ from cereal import car from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car.mazda.values import CAR, LKAS_LIMITS -from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type @@ -16,32 +16,14 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.mazda)] ret.radarUnavailable = True - ret.dashcamOnly = candidate not in (CAR.CX5_2022, CAR.CX9_2021) + ret.dashcamOnly = candidate not in (CAR.MAZDA_CX5_2022, CAR.MAZDA_CX9_2021) ret.steerActuatorDelay = 0.1 ret.steerLimitTimer = 0.8 - ret.tireStiffnessFactor = 0.70 # not optimized yet CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - if candidate in (CAR.CX5, CAR.CX5_2022): - ret.mass = 3655 * CV.LB_TO_KG - ret.wheelbase = 2.7 - ret.steerRatio = 15.5 - elif candidate in (CAR.CX9, CAR.CX9_2021): - ret.mass = 4217 * CV.LB_TO_KG - ret.wheelbase = 3.1 - ret.steerRatio = 17.6 - elif candidate == CAR.MAZDA3: - ret.mass = 2875 * CV.LB_TO_KG - ret.wheelbase = 2.7 - ret.steerRatio = 14.0 - elif candidate == CAR.MAZDA6: - ret.mass = 3443 * CV.LB_TO_KG - ret.wheelbase = 2.83 - ret.steerRatio = 15.5 - - if candidate not in (CAR.CX5_2022, ): + if candidate not in (CAR.MAZDA_CX5_2022, ): ret.minSteerSpeed = LKAS_LIMITS.DISABLE_SPEED * CV.KPH_TO_MS ret.centerToFront = ret.wheelbase * 0.41 @@ -52,6 +34,9 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) + # TODO: add button types for inc and dec + ret.buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) + # events events = self.create_common_events(ret) @@ -63,6 +48,3 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() return ret - - def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/mazda/mazdacan.py b/selfdrive/car/mazda/mazdacan.py index e350c5587f..74f6af04c5 100644 --- a/selfdrive/car/mazda/mazdacan.py +++ b/selfdrive/car/mazda/mazdacan.py @@ -1,7 +1,7 @@ -from openpilot.selfdrive.car.mazda.values import GEN1, Buttons +from openpilot.selfdrive.car.mazda.values import Buttons, MazdaFlags -def create_steering_control(packer, car_fingerprint, frame, apply_steer, lkas): +def create_steering_control(packer, CP, frame, apply_steer, lkas): tmp = apply_steer + 2048 @@ -45,7 +45,7 @@ def create_steering_control(packer, car_fingerprint, frame, apply_steer, lkas): csum = csum % 256 values = {} - if car_fingerprint in GEN1: + if CP.flags & MazdaFlags.GEN1: values = { "LKAS_REQUEST": apply_steer, "CTR": ctr, @@ -88,12 +88,12 @@ def create_alert_command(packer, cam_msg: dict, ldw: bool, steer_required: bool) return packer.make_can_msg("CAM_LANEINFO", 0, values) -def create_button_cmd(packer, car_fingerprint, counter, button): +def create_button_cmd(packer, CP, counter, button): can = int(button == Buttons.CANCEL) res = int(button == Buttons.RESUME) - if car_fingerprint in GEN1: + if CP.flags & MazdaFlags.GEN1: values = { "CAN_OFF": can, "CAN_OFF_INV": (can + 1) % 2, diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index eaf76d6a72..a8c808d582 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -1,9 +1,10 @@ from dataclasses import dataclass, field -from enum import StrEnum +from enum import IntFlag from cereal import car -from openpilot.selfdrive.car import dbc_dict -from openpilot.selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarHarness, CarDocs, CarParts from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu @@ -25,29 +26,54 @@ class CarControllerParams: pass -class CAR(StrEnum): - CX5 = "MAZDA CX-5" - CX9 = "MAZDA CX-9" - MAZDA3 = "MAZDA 3" - MAZDA6 = "MAZDA 6" - CX9_2021 = "MAZDA CX-9 2021" - CX5_2022 = "MAZDA CX-5 2022" - - @dataclass -class MazdaCarInfo(CarInfo): +class MazdaCarDocs(CarDocs): package: str = "All" car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.mazda])) -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"), - CAR.MAZDA6: MazdaCarInfo("Mazda 6 2017-20"), - CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021-23", video_link="https://youtu.be/dA3duO4a0O4"), - CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022-24"), -} +@dataclass(frozen=True, kw_only=True) +class MazdaCarSpecs(CarSpecs): + tireStiffnessFactor: float = 0.7 # not optimized yet + + +class MazdaFlags(IntFlag): + # Static flags + # Gen 1 hardware: same CAN messages and same camera + GEN1 = 1 + + +@dataclass +class MazdaPlatformConfig(PlatformConfig): + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('mazda_2017', None)) + flags: int = MazdaFlags.GEN1 + + +class CAR(Platforms): + MAZDA_CX5 = MazdaPlatformConfig( + [MazdaCarDocs("Mazda CX-5 2017-21")], + MazdaCarSpecs(mass=3655 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.5) + ) + MAZDA_CX9 = MazdaPlatformConfig( + [MazdaCarDocs("Mazda CX-9 2016-20")], + MazdaCarSpecs(mass=4217 * CV.LB_TO_KG, wheelbase=3.1, steerRatio=17.6) + ) + MAZDA_3 = MazdaPlatformConfig( + [MazdaCarDocs("Mazda 3 2017-18")], + MazdaCarSpecs(mass=2875 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=14.0) + ) + MAZDA_6 = MazdaPlatformConfig( + [MazdaCarDocs("Mazda 6 2017-20")], + MazdaCarSpecs(mass=3443 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=15.5) + ) + MAZDA_CX9_2021 = MazdaPlatformConfig( + [MazdaCarDocs("Mazda CX-9 2021-23", video_link="https://youtu.be/dA3duO4a0O4")], + MAZDA_CX9.specs + ) + MAZDA_CX5_2022 = MazdaPlatformConfig( + [MazdaCarDocs("Mazda CX-5 2022-24")], + MAZDA_CX5.specs, + ) class LKAS_LIMITS: @@ -75,15 +101,4 @@ FW_QUERY_CONFIG = FwQueryConfig( ], ) - -DBC = { - CAR.CX5: dbc_dict('mazda_2017', None), - CAR.CX9: dbc_dict('mazda_2017', None), - CAR.MAZDA3: dbc_dict('mazda_2017', None), - CAR.MAZDA6: dbc_dict('mazda_2017', None), - CAR.CX9_2021: dbc_dict('mazda_2017', None), - CAR.CX5_2022: dbc_dict('mazda_2017', None), -} - -# Gen 1 hardware: same CAN messages and same camera -GEN1 = {CAR.CX5, CAR.CX9, CAR.CX9_2021, CAR.MAZDA3, CAR.MAZDA6, CAR.CX5_2022} +DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/mock/carcontroller.py b/selfdrive/car/mock/carcontroller.py new file mode 100644 index 0000000000..0cd37c0369 --- /dev/null +++ b/selfdrive/car/mock/carcontroller.py @@ -0,0 +1,5 @@ +from openpilot.selfdrive.car.interfaces import CarControllerBase + +class CarController(CarControllerBase): + def update(self, CC, CS, now_nanos): + return CC.actuators.as_builder(), [] diff --git a/selfdrive/car/mock/carstate.py b/selfdrive/car/mock/carstate.py new file mode 100644 index 0000000000..ece908b51c --- /dev/null +++ b/selfdrive/car/mock/carstate.py @@ -0,0 +1,4 @@ +from openpilot.selfdrive.car.interfaces import CarStateBase + +class CarState(CarStateBase): + pass diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py index 2e4ac43033..7506bab053 100755 --- a/selfdrive/car/mock/interface.py +++ b/selfdrive/car/mock/interface.py @@ -18,6 +18,7 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.70 ret.centerToFront = ret.wheelbase * 0.5 ret.steerRatio = 13. + ret.dashcamOnly = True return ret def _update(self, c): @@ -29,7 +30,3 @@ class CarInterface(CarInterfaceBase): ret.vEgoRaw = self.sm[gps_sock].speed return ret - - def apply(self, c, now_nanos): - actuators = car.CarControl.Actuators.new_message() - return actuators, [] diff --git a/selfdrive/car/mock/values.py b/selfdrive/car/mock/values.py index e75665c98f..f98aac2ee3 100644 --- a/selfdrive/car/mock/values.py +++ b/selfdrive/car/mock/values.py @@ -1,12 +1,9 @@ -from enum import StrEnum - -from openpilot.selfdrive.car.docs_definitions import CarInfo +from openpilot.selfdrive.car import CarSpecs, PlatformConfig, Platforms -class CAR(StrEnum): - MOCK = 'mock' - - -CAR_INFO: dict[str, CarInfo | list[CarInfo] | None] = { - CAR.MOCK: None, -} +class CAR(Platforms): + MOCK = PlatformConfig( + [], + CarSpecs(mass=1700, wheelbase=2.7, steerRatio=13), + {} + ) diff --git a/selfdrive/car/nissan/carcontroller.py b/selfdrive/car/nissan/carcontroller.py index 2da518bbf1..c7bd231398 100644 --- a/selfdrive/car/nissan/carcontroller.py +++ b/selfdrive/car/nissan/carcontroller.py @@ -1,13 +1,14 @@ from cereal import car from opendbc.can.packer import CANPacker from openpilot.selfdrive.car import apply_std_steer_angle_limits +from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.car.nissan import nissancan from openpilot.selfdrive.car.nissan.values import CAR, CarControllerParams VisualAlert = car.CarControl.HUDControl.VisualAlert -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP self.car_fingerprint = CP.carFingerprint @@ -50,21 +51,21 @@ class CarController: self.apply_angle_last = apply_angle - if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA) and pcm_cancel_cmd: + if self.CP.carFingerprint in (CAR.NISSAN_ROGUE, CAR.NISSAN_XTRAIL, CAR.NISSAN_ALTIMA) and pcm_cancel_cmd: can_sends.append(nissancan.create_acc_cancel_cmd(self.packer, self.car_fingerprint, CS.cruise_throttle_msg)) # TODO: Find better way to cancel! # For some reason spamming the cancel button is unreliable on the Leaf # We now cancel by making propilot think the seatbelt is unlatched, # this generates a beep and a warning message every time you disengage - if self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC) and self.frame % 2 == 0: + if self.CP.carFingerprint in (CAR.NISSAN_LEAF, CAR.NISSAN_LEAF_IC) and self.frame % 2 == 0: can_sends.append(nissancan.create_cancel_msg(self.packer, CS.cancel_msg, pcm_cancel_cmd)) can_sends.append(nissancan.create_steering_control( self.packer, apply_angle, self.frame, CC.latActive, self.lkas_max_torque)) # Below are the HUD messages. We copy the stock message and modify - if self.CP.carFingerprint != CAR.ALTIMA: + if self.CP.carFingerprint != CAR.NISSAN_ALTIMA: if self.frame % 2 == 0: can_sends.append(nissancan.create_lkas_hud_msg(self.packer, CS.lkas_hud_msg, CC.enabled, hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart)) @@ -74,7 +75,7 @@ class CarController: self.packer, CS.lkas_hud_info_msg, steer_hud_alert )) - new_actuators = actuators.copy() + new_actuators = actuators.as_builder() new_actuators.steeringAngleDeg = apply_angle self.frame += 1 diff --git a/selfdrive/car/nissan/carstate.py b/selfdrive/car/nissan/carstate.py index b2ba9ce290..57146b49c4 100644 --- a/selfdrive/car/nissan/carstate.py +++ b/selfdrive/car/nissan/carstate.py @@ -20,19 +20,25 @@ class CarState(CarStateBase): self.steeringTorqueSamples = deque(TORQUE_SAMPLES*[0], TORQUE_SAMPLES) self.shifter_values = can_define.dv["GEARBOX"]["GEAR_SHIFTER"] + self.prev_distance_button = 0 + self.distance_button = 0 + def update(self, cp, cp_adas, cp_cam): ret = car.CarState.new_message() - if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA): + self.prev_distance_button = self.distance_button + self.distance_button = cp.vl["CRUISE_THROTTLE"]["FOLLOW_DISTANCE_BUTTON"] + + if self.CP.carFingerprint in (CAR.NISSAN_ROGUE, CAR.NISSAN_XTRAIL, CAR.NISSAN_ALTIMA): ret.gas = cp.vl["GAS_PEDAL"]["GAS_PEDAL"] - elif self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): + elif self.CP.carFingerprint in (CAR.NISSAN_LEAF, CAR.NISSAN_LEAF_IC): ret.gas = cp.vl["CRUISE_THROTTLE"]["GAS_PEDAL"] ret.gasPressed = bool(ret.gas > 3) - if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA): + if self.CP.carFingerprint in (CAR.NISSAN_ROGUE, CAR.NISSAN_XTRAIL, CAR.NISSAN_ALTIMA): ret.brakePressed = bool(cp.vl["DOORS_LIGHTS"]["USER_BRAKE_PRESSED"]) - elif self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): + elif self.CP.carFingerprint in (CAR.NISSAN_LEAF, CAR.NISSAN_LEAF_IC): ret.brakePressed = bool(cp.vl["CRUISE_THROTTLE"]["USER_BRAKE_PRESSED"]) ret.wheelSpeeds = self.get_wheel_speeds( @@ -46,38 +52,38 @@ class CarState(CarStateBase): ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.standstill = cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RL"] == 0.0 and cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RR"] == 0.0 - if self.CP.carFingerprint == CAR.ALTIMA: + if self.CP.carFingerprint == CAR.NISSAN_ALTIMA: ret.cruiseState.enabled = bool(cp.vl["CRUISE_STATE"]["CRUISE_ENABLED"]) else: ret.cruiseState.enabled = bool(cp_adas.vl["CRUISE_STATE"]["CRUISE_ENABLED"]) - if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL): + if self.CP.carFingerprint in (CAR.NISSAN_ROGUE, CAR.NISSAN_XTRAIL): ret.seatbeltUnlatched = cp.vl["HUD"]["SEATBELT_DRIVER_LATCHED"] == 0 ret.cruiseState.available = bool(cp_cam.vl["PRO_PILOT"]["CRUISE_ON"]) - elif self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): - if self.CP.carFingerprint == CAR.LEAF: + elif self.CP.carFingerprint in (CAR.NISSAN_LEAF, CAR.NISSAN_LEAF_IC): + if self.CP.carFingerprint == CAR.NISSAN_LEAF: ret.seatbeltUnlatched = cp.vl["SEATBELT"]["SEATBELT_DRIVER_LATCHED"] == 0 - elif self.CP.carFingerprint == CAR.LEAF_IC: + elif self.CP.carFingerprint == CAR.NISSAN_LEAF_IC: ret.seatbeltUnlatched = cp.vl["CANCEL_MSG"]["CANCEL_SEATBELT"] == 1 ret.cruiseState.available = bool(cp.vl["CRUISE_THROTTLE"]["CRUISE_AVAILABLE"]) - elif self.CP.carFingerprint == CAR.ALTIMA: + elif self.CP.carFingerprint == CAR.NISSAN_ALTIMA: ret.seatbeltUnlatched = cp.vl["HUD"]["SEATBELT_DRIVER_LATCHED"] == 0 ret.cruiseState.available = bool(cp_adas.vl["PRO_PILOT"]["CRUISE_ON"]) - if self.CP.carFingerprint == CAR.ALTIMA: + if self.CP.carFingerprint == CAR.NISSAN_ALTIMA: speed = cp.vl["PROPILOT_HUD"]["SET_SPEED"] else: speed = cp_adas.vl["PROPILOT_HUD"]["SET_SPEED"] if speed != 255: - if self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): + if self.CP.carFingerprint in (CAR.NISSAN_LEAF, CAR.NISSAN_LEAF_IC): conversion = CV.MPH_TO_MS if cp.vl["HUD_SETTINGS"]["SPEED_MPH"] else CV.KPH_TO_MS else: conversion = CV.MPH_TO_MS if cp.vl["HUD"]["SPEED_MPH"] else CV.KPH_TO_MS ret.cruiseState.speed = speed * conversion ret.cruiseState.speedCluster = (speed - 1) * conversion # Speed on HUD is always 1 lower than actually sent on can bus - if self.CP.carFingerprint == CAR.ALTIMA: + if self.CP.carFingerprint == CAR.NISSAN_ALTIMA: ret.steeringTorque = cp_cam.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_DRIVER"] else: ret.steeringTorque = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_DRIVER"] @@ -101,17 +107,17 @@ class CarState(CarStateBase): can_gear = int(cp.vl["GEARBOX"]["GEAR_SHIFTER"]) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None)) - if self.CP.carFingerprint == CAR.ALTIMA: + if self.CP.carFingerprint == CAR.NISSAN_ALTIMA: self.lkas_enabled = bool(cp.vl["LKAS_SETTINGS"]["LKAS_ENABLED"]) else: self.lkas_enabled = bool(cp_adas.vl["LKAS_SETTINGS"]["LKAS_ENABLED"]) self.cruise_throttle_msg = copy.copy(cp.vl["CRUISE_THROTTLE"]) - if self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): + if self.CP.carFingerprint in (CAR.NISSAN_LEAF, CAR.NISSAN_LEAF_IC): self.cancel_msg = copy.copy(cp.vl["CANCEL_MSG"]) - if self.CP.carFingerprint != CAR.ALTIMA: + if self.CP.carFingerprint != CAR.NISSAN_ALTIMA: self.lkas_hud_msg = copy.copy(cp_adas.vl["PROPILOT_HUD"]) self.lkas_hud_info_msg = copy.copy(cp_adas.vl["PROPILOT_HUD_INFO_MSG"]) @@ -130,14 +136,14 @@ class CarState(CarStateBase): ("LIGHTS", 10), ] - if CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA): + if CP.carFingerprint in (CAR.NISSAN_ROGUE, CAR.NISSAN_XTRAIL, CAR.NISSAN_ALTIMA): messages += [ ("GAS_PEDAL", 100), ("CRUISE_THROTTLE", 50), ("HUD", 25), ] - elif CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): + elif CP.carFingerprint in (CAR.NISSAN_LEAF, CAR.NISSAN_LEAF_IC): messages += [ ("BRAKE_PEDAL", 100), ("CRUISE_THROTTLE", 50), @@ -146,7 +152,7 @@ class CarState(CarStateBase): ("SEATBELT", 10), ] - if CP.carFingerprint == CAR.ALTIMA: + if CP.carFingerprint == CAR.NISSAN_ALTIMA: messages += [ ("CRUISE_STATE", 10), ("LKAS_SETTINGS", 10), @@ -162,7 +168,7 @@ class CarState(CarStateBase): def get_adas_can_parser(CP): # this function generates lists for signal, messages and initial values - if CP.carFingerprint == CAR.ALTIMA: + if CP.carFingerprint == CAR.NISSAN_ALTIMA: messages = [ ("LKAS", 100), ("PRO_PILOT", 100), @@ -182,9 +188,9 @@ class CarState(CarStateBase): def get_cam_can_parser(CP): messages = [] - if CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL): + if CP.carFingerprint in (CAR.NISSAN_ROGUE, CAR.NISSAN_XTRAIL): messages.append(("PRO_PILOT", 100)) - elif CP.carFingerprint == CAR.ALTIMA: + elif CP.carFingerprint == CAR.NISSAN_ALTIMA: messages.append(("STEER_TORQUE_SENSOR", 100)) return CANParser(DBC[CP.carFingerprint]["pt"], messages, 0) diff --git a/selfdrive/car/nissan/fingerprints.py b/selfdrive/car/nissan/fingerprints.py index 19267ded46..743feeace9 100644 --- a/selfdrive/car/nissan/fingerprints.py +++ b/selfdrive/car/nissan/fingerprints.py @@ -5,31 +5,31 @@ from openpilot.selfdrive.car.nissan.values import CAR Ecu = car.CarParams.Ecu FINGERPRINTS = { - CAR.XTRAIL: [{ + CAR.NISSAN_XTRAIL: [{ 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 548: 8, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 768: 2, 783: 3, 851: 8, 855: 8, 1041: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1111: 4, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 2, 1497: 3, 1821: 8, 1823: 8, 1837: 8, 2015: 8, 2016: 8, 2024: 8 }, { 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 527: 1, 548: 8, 637: 4, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 768: 6, 783: 3, 851: 8, 855: 8, 1041: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1111: 4, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 8, 1497: 3, 1534: 6, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 2015: 8, 2016: 8, 2024: 8 }], - CAR.LEAF: [{ + CAR.NISSAN_LEAF: [{ 2: 5, 42: 6, 264: 3, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 724: 6, 758: 3, 761: 2, 783: 3, 852: 8, 853: 8, 856: 8, 861: 8, 944: 1, 976: 6, 1008: 7, 1011: 7, 1057: 3, 1227: 8, 1228: 8, 1261: 5, 1342: 1, 1354: 8, 1361: 8, 1459: 8, 1477: 8, 1497: 3, 1549: 8, 1573: 6, 1821: 8, 1837: 8, 1856: 8, 1859: 8, 1861: 8, 1864: 8, 1874: 8, 1888: 8, 1891: 8, 1893: 8, 1906: 8, 1947: 8, 1949: 8, 1979: 8, 1981: 8, 2016: 8, 2017: 8, 2021: 8, 643: 5, 1792: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8 }, { 2: 5, 42: 8, 264: 3, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 643: 5, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 724: 6, 758: 3, 761: 2, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 783: 3, 852: 8, 853: 8, 856: 8, 861: 8, 943: 8, 944: 1, 976: 6, 1008: 7, 1009: 8, 1010: 8, 1011: 7, 1012: 8, 1013: 8, 1019: 8, 1020: 8, 1021: 8, 1022: 8, 1057: 3, 1227: 8, 1228: 8, 1261: 5, 1342: 1, 1354: 8, 1361: 8, 1402: 8, 1459: 8, 1477: 8, 1497: 3, 1549: 8, 1573: 6, 1821: 8, 1837: 8 }], - CAR.LEAF_IC: [{ + CAR.NISSAN_LEAF_IC: [{ 2: 5, 42: 6, 264: 3, 282: 8, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 643: 5, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 756: 5, 758: 3, 761: 2, 783: 3, 830: 2, 852: 8, 853: 8, 856: 8, 861: 8, 943: 8, 944: 1, 1001: 6, 1057: 3, 1227: 8, 1228: 8, 1229: 8, 1342: 1, 1354: 8, 1361: 8, 1459: 8, 1477: 8, 1497: 3, 1514: 6, 1549: 8, 1573: 6, 1792: 8, 1821: 8, 1822: 8, 1837: 8, 1838: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8 }], - CAR.ROGUE: [{ + CAR.NISSAN_ROGUE: [{ 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 548: 8, 634: 7, 643: 5, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 783: 3, 851: 8, 855: 8, 1041: 8, 1042: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1110: 7, 1111: 7, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 2, 1497: 3, 1534: 7, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1839: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }], - CAR.ALTIMA: [{ + CAR.NISSAN_ALTIMA: [{ 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 438: 8, 451: 8, 517: 8, 520: 2, 522: 8, 523: 6, 539: 8, 541: 7, 542: 8, 543: 8, 544: 8, 545: 8, 546: 8, 547: 8, 548: 8, 570: 8, 576: 8, 577: 8, 582: 8, 583: 8, 584: 8, 586: 8, 587: 8, 588: 8, 589: 8, 590: 8, 591: 8, 592: 8, 600: 8, 601: 8, 610: 8, 611: 8, 612: 8, 614: 8, 615: 8, 616: 8, 617: 8, 622: 8, 623: 8, 634: 7, 638: 8, 645: 8, 648: 5, 654: 6, 658: 8, 659: 8, 660: 8, 661: 8, 665: 8, 666: 8, 674: 2, 675: 8, 676: 8, 682: 8, 683: 8, 684: 8, 685: 8, 686: 8, 687: 8, 689: 8, 690: 8, 703: 8, 708: 7, 709: 7, 711: 7, 712: 7, 713: 7, 714: 8, 715: 8, 716: 8, 717: 7, 718: 7, 719: 7, 720: 7, 723: 8, 726: 7, 727: 7, 728: 7, 735: 8, 746: 8, 748: 6, 749: 6, 750: 8, 758: 3, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 779: 7, 781: 7, 782: 7, 783: 3, 851: 8, 855: 5, 1001: 6, 1041: 8, 1042: 8, 1055: 3, 1100: 7, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1110: 7, 1111: 7, 1144: 7, 1145: 7, 1227: 8, 1228: 8, 1229: 8, 1232: 8, 1247: 4, 1258: 8, 1259: 8, 1266: 8, 1273: 7, 1306: 1, 1314: 8, 1323: 8, 1324: 8, 1342: 1, 1376: 8, 1401: 8, 1454: 8, 1497: 3, 1514: 6, 1526: 8, 1527: 5, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }], } FW_VERSIONS = { - CAR.ALTIMA: { + CAR.NISSAN_ALTIMA: { (Ecu.fwdCamera, 0x707, None): [ b'284N86CA1D', ], @@ -43,7 +43,7 @@ FW_VERSIONS = { b'284U29HE0A', ], }, - CAR.LEAF: { + CAR.NISSAN_LEAF: { (Ecu.abs, 0x740, None): [ b'476605SA1C', b'476605SA7D', @@ -72,7 +72,7 @@ FW_VERSIONS = { b'284U26WK0C', ], }, - CAR.LEAF_IC: { + CAR.NISSAN_LEAF_IC: { (Ecu.fwdCamera, 0x707, None): [ b'5SH1BDB\x04\x18\x00\x00\x00\x00\x00_-?\x04\x91\xf2\x00\x00\x00\x80', b'5SH4BDB\x04\x18\x00\x00\x00\x00\x00_-?\x04\x91\xf2\x00\x00\x00\x80', @@ -94,7 +94,7 @@ FW_VERSIONS = { b'284U25SK2D', ], }, - CAR.XTRAIL: { + CAR.NISSAN_XTRAIL: { (Ecu.fwdCamera, 0x707, None): [ b'284N86FR2A', ], diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index a94b97de20..2e9a990610 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -1,9 +1,11 @@ from cereal import car from panda import Panda -from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.nissan.values import CAR +ButtonType = car.CarState.ButtonEvent.Type + class CarInterface(CarInterfaceBase): @@ -20,7 +22,7 @@ class CarInterface(CarInterfaceBase): ret.steerControlType = car.CarParams.SteerControlType.angle ret.radarUnavailable = True - if candidate == CAR.ALTIMA: + if candidate == CAR.NISSAN_ALTIMA: # Altima has EPS on C-CAN unlike the others that have it on V-CAN ret.safetyConfigs[0].safetyParam |= Panda.FLAG_NISSAN_ALT_EPS_BUS @@ -30,10 +32,7 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_adas, self.cp_cam) - buttonEvents = [] - be = car.CarState.ButtonEvent.new_message() - be.type = car.CarState.ButtonEvent.Type.accelCruise - buttonEvents.append(be) + ret.buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) events = self.create_common_events(ret, extra_gears=[car.CarState.GearShifter.brake]) @@ -43,6 +42,3 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() return ret - - def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/nissan/nissancan.py b/selfdrive/car/nissan/nissancan.py index 49dcd6fe93..b9a1b4f843 100644 --- a/selfdrive/car/nissan/nissancan.py +++ b/selfdrive/car/nissan/nissancan.py @@ -39,7 +39,7 @@ def create_acc_cancel_cmd(packer, car_fingerprint, cruise_throttle_msg): "unsure2", "unsure3", ]} - can_bus = 1 if car_fingerprint == CAR.ALTIMA else 2 + can_bus = 1 if car_fingerprint == CAR.NISSAN_ALTIMA else 2 values["CANCEL_BUTTON"] = 1 values["NO_BUTTON_PRESSED"] = 0 diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index 58e4eb051a..eecffb21bc 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -3,7 +3,7 @@ from dataclasses import dataclass, field from cereal import car from panda.python import uds from openpilot.selfdrive.car import AngleRateLimit, CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict -from openpilot.selfdrive.car.docs_definitions import CarInfo, CarHarness, CarParts +from openpilot.selfdrive.car.docs_definitions import CarDocs, CarHarness, CarParts from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu @@ -20,7 +20,7 @@ class CarControllerParams: @dataclass -class NissanCarInfo(CarInfo): +class NissanCarDocs(CarDocs): package: str = "ProPILOT Assist" car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.nissan_a])) @@ -31,39 +31,34 @@ class NissanCarSpecs(CarSpecs): steerRatio: float = 17. -@dataclass(frozen=True) -class NissanPlaformConfig(PlatformConfig): +@dataclass +class NissanPlatformConfig(PlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('nissan_x_trail_2017_generated', None)) class CAR(Platforms): - XTRAIL = NissanPlaformConfig( - "NISSAN X-TRAIL 2017", - NissanCarInfo("Nissan X-Trail 2017"), - specs=NissanCarSpecs(mass=1610, wheelbase=2.705) + NISSAN_XTRAIL = NissanPlatformConfig( + [NissanCarDocs("Nissan X-Trail 2017")], + NissanCarSpecs(mass=1610, wheelbase=2.705) ) - LEAF = NissanPlaformConfig( - "NISSAN LEAF 2018", - NissanCarInfo("Nissan Leaf 2018-23", video_link="https://youtu.be/vaMbtAh_0cY"), - dbc_dict=dbc_dict('nissan_leaf_2018_generated', None), - specs=NissanCarSpecs(mass=1610, wheelbase=2.705) + NISSAN_LEAF = NissanPlatformConfig( + [NissanCarDocs("Nissan Leaf 2018-23", video_link="https://youtu.be/vaMbtAh_0cY")], + NissanCarSpecs(mass=1610, wheelbase=2.705), + dbc_dict('nissan_leaf_2018_generated', None), ) # Leaf with ADAS ECU found behind instrument cluster instead of glovebox # Currently the only known difference between them is the inverted seatbelt signal. - LEAF_IC = LEAF.override(platform_str="NISSAN LEAF 2018 Instrument Cluster", car_info=None) - ROGUE = NissanPlaformConfig( - "NISSAN ROGUE 2019", - NissanCarInfo("Nissan Rogue 2018-20"), - specs=NissanCarSpecs(mass=1610, wheelbase=2.705) + NISSAN_LEAF_IC = NISSAN_LEAF.override(car_docs=[]) + NISSAN_ROGUE = NissanPlatformConfig( + [NissanCarDocs("Nissan Rogue 2018-20")], + NissanCarSpecs(mass=1610, wheelbase=2.705) ) - ALTIMA = NissanPlaformConfig( - "NISSAN ALTIMA 2020", - NissanCarInfo("Nissan Altima 2019-20", car_parts=CarParts.common([CarHarness.nissan_b])), - specs=NissanCarSpecs(mass=1492, wheelbase=2.824) + NISSAN_ALTIMA = NissanPlatformConfig( + [NissanCarDocs("Nissan Altima 2019-20", car_parts=CarParts.common([CarHarness.nissan_b]))], + NissanCarSpecs(mass=1492, wheelbase=2.824) ) -CAR_INFO = CAR.create_carinfo_map() DBC = CAR.create_dbc_map() # Default diagnostic session diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py index cc8ce4f722..d89ae8c639 100644 --- a/selfdrive/car/subaru/carcontroller.py +++ b/selfdrive/car/subaru/carcontroller.py @@ -1,9 +1,9 @@ from openpilot.common.numpy_fast import clip, interp from opendbc.can.packer import CANPacker from openpilot.selfdrive.car import apply_driver_steer_torque_limits, common_fault_avoidance +from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.car.subaru import subarucan -from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_ES_ADDR, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, STEER_RATE_LIMITED, \ - CanBus, CarControllerParams, SubaruFlags +from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_ES_ADDR, CanBus, CarControllerParams, SubaruFlags # FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and # involves the total steering angle change rather than rate, but these limits work well for now @@ -11,7 +11,7 @@ MAX_STEER_RATE = 25 # deg/s MAX_STEER_RATE_FRAMES = 7 # tx control frames needed before torque can be cut -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP self.apply_steer_last = 0 @@ -42,12 +42,12 @@ class CarController: if not CC.latActive: apply_steer = 0 - if self.CP.carFingerprint in PREGLOBAL_CARS: + if self.CP.flags & SubaruFlags.PREGLOBAL: can_sends.append(subarucan.create_preglobal_steering_control(self.packer, self.frame // self.p.STEER_STEP, apply_steer, CC.latActive)) else: apply_steer_req = CC.latActive - if self.CP.carFingerprint in STEER_RATE_LIMITED: + if self.CP.flags & SubaruFlags.STEER_RATE_LIMITED: # Steering rate fault prevention self.steer_rate_counter, apply_steer_req = \ common_fault_avoidance(abs(CS.out.steeringRateDeg) > MAX_STEER_RATE, apply_steer_req, @@ -74,7 +74,7 @@ class CarController: cruise_brake = CarControllerParams.BRAKE_MIN # *** alerts and pcm cancel *** - if self.CP.carFingerprint in PREGLOBAL_CARS: + if self.CP.flags & SubaruFlags.PREGLOBAL: if self.frame % 5 == 0: # 1 = main, 2 = set shallow, 3 = set deep, 4 = resume shallow, 5 = resume deep # disengage ACC when OP is disengaged @@ -117,8 +117,8 @@ class CarController: self.CP.openpilotLongitudinalControl, cruise_brake > 0, cruise_throttle)) else: if pcm_cancel_cmd: - if self.CP.carFingerprint not in HYBRID_CARS: - bus = CanBus.alt if self.CP.carFingerprint in GLOBAL_GEN2 else CanBus.main + if not (self.CP.flags & SubaruFlags.HYBRID): + bus = CanBus.alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else CanBus.main can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg["COUNTER"] + 1, CS.es_distance_msg, bus, pcm_cancel_cmd)) if self.CP.flags & SubaruFlags.DISABLE_EYESIGHT: @@ -136,7 +136,7 @@ class CarController: if self.frame % 2 == 0: can_sends.append(subarucan.create_es_static_2(self.packer)) - new_actuators = actuators.copy() + new_actuators = actuators.as_builder() new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX new_actuators.steerOutputCan = self.apply_steer_last diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index c8a6dfe1e6..821ff2c151 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -4,7 +4,7 @@ from opendbc.can.can_define import CANDefine from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser -from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, CanBus, SubaruFlags +from openpilot.selfdrive.car.subaru.values import DBC, CanBus, SubaruFlags from openpilot.selfdrive.car import CanSignalRateCalculator @@ -19,17 +19,27 @@ class CarState(CarStateBase): def update(self, cp, cp_cam, cp_body): ret = car.CarState.new_message() - throttle_msg = cp.vl["Throttle"] if self.car_fingerprint not in HYBRID_CARS else cp_body.vl["Throttle_Hybrid"] + throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_body.vl["Throttle_Hybrid"] ret.gas = throttle_msg["Throttle_Pedal"] / 255. ret.gasPressed = ret.gas > 1e-5 - if self.car_fingerprint in PREGLOBAL_CARS: + if self.CP.flags & SubaruFlags.PREGLOBAL: ret.brakePressed = cp.vl["Brake_Pedal"]["Brake_Pedal"] > 0 else: - cp_brakes = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp + cp_brakes = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp ret.brakePressed = cp_brakes.vl["Brake_Status"]["Brake"] == 1 - cp_wheels = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp + cp_es_distance = cp_body if self.CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID) else cp_cam + if not (self.CP.flags & SubaruFlags.HYBRID): + eyesight_fault = bool(cp_es_distance.vl["ES_Distance"]["Cruise_Fault"]) + + # if openpilot is controlling long, an eyesight fault is a non-critical fault. otherwise it's an ACC fault + if self.CP.openpilotLongitudinalControl: + ret.carFaultedNonCritical = eyesight_fault + else: + ret.accFaulted = eyesight_fault + + cp_wheels = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp ret.wheelSpeeds = self.get_wheel_speeds( cp_wheels.vl["Wheel_Speeds"]["FL"], cp_wheels.vl["Wheel_Speeds"]["FR"], @@ -48,24 +58,24 @@ class CarState(CarStateBase): ret.leftBlindspot = (cp.vl["BSD_RCTA"]["L_ADJACENT"] == 1) or (cp.vl["BSD_RCTA"]["L_APPROACHING"] == 1) ret.rightBlindspot = (cp.vl["BSD_RCTA"]["R_ADJACENT"] == 1) or (cp.vl["BSD_RCTA"]["R_APPROACHING"] == 1) - cp_transmission = cp_body if self.car_fingerprint in HYBRID_CARS else cp + cp_transmission = cp_body if self.CP.flags & SubaruFlags.HYBRID else cp can_gear = int(cp_transmission.vl["Transmission"]["Gear"]) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None)) ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"] - if self.car_fingerprint not in PREGLOBAL_CARS: + if not (self.CP.flags & SubaruFlags.PREGLOBAL): # ideally we get this from the car, but unclear if it exists. diagnostic software doesn't even have it ret.steeringRateDeg = self.angle_rate_calulator.update(ret.steeringAngleDeg, cp.vl["Steering_Torque"]["COUNTER"]) ret.steeringTorque = cp.vl["Steering_Torque"]["Steer_Torque_Sensor"] ret.steeringTorqueEps = cp.vl["Steering_Torque"]["Steer_Torque_Output"] - steer_threshold = 75 if self.CP.carFingerprint in PREGLOBAL_CARS else 80 + steer_threshold = 75 if self.CP.flags & SubaruFlags.PREGLOBAL else 80 ret.steeringPressed = abs(ret.steeringTorque) > steer_threshold - cp_cruise = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp - if self.car_fingerprint in HYBRID_CARS: + cp_cruise = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp + if self.CP.flags & SubaruFlags.HYBRID: ret.cruiseState.enabled = cp_cam.vl["ES_DashStatus"]['Cruise_Activated'] != 0 ret.cruiseState.available = cp_cam.vl["ES_DashStatus"]['Cruise_On'] != 0 else: @@ -73,8 +83,8 @@ class CarState(CarStateBase): ret.cruiseState.available = cp_cruise.vl["CruiseControl"]["Cruise_On"] != 0 ret.cruiseState.speed = cp_cam.vl["ES_DashStatus"]["Cruise_Set_Speed"] * CV.KPH_TO_MS - if (self.car_fingerprint in PREGLOBAL_CARS and cp.vl["Dash_State2"]["UNITS"] == 1) or \ - (self.car_fingerprint not in PREGLOBAL_CARS and cp.vl["Dashlights"]["UNITS"] == 1): + if (self.CP.flags & SubaruFlags.PREGLOBAL and cp.vl["Dash_State2"]["UNITS"] == 1) or \ + (not (self.CP.flags & SubaruFlags.PREGLOBAL) and cp.vl["Dashlights"]["UNITS"] == 1): ret.cruiseState.speed *= CV.MPH_TO_KPH ret.seatbeltUnlatched = cp.vl["Dashlights"]["SEATBELT_FL"] == 1 @@ -84,8 +94,7 @@ class CarState(CarStateBase): cp.vl["BodyInfo"]["DOOR_OPEN_FL"]]) ret.steerFaultPermanent = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1 - cp_es_distance = cp_body if self.car_fingerprint in (GLOBAL_GEN2 | HYBRID_CARS) else cp_cam - if self.car_fingerprint in PREGLOBAL_CARS: + if self.CP.flags & SubaruFlags.PREGLOBAL: self.cruise_button = cp_cam.vl["ES_Distance"]["Cruise_Button"] self.ready = not cp_cam.vl["ES_DashStatus"]["Not_Ready_Startup"] else: @@ -96,12 +105,12 @@ class CarState(CarStateBase): (cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 2) self.es_lkas_state_msg = copy.copy(cp_cam.vl["ES_LKAS_State"]) - cp_es_brake = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam + cp_es_brake = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp_cam self.es_brake_msg = copy.copy(cp_es_brake.vl["ES_Brake"]) - cp_es_status = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam + cp_es_status = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp_cam # TODO: Hybrid cars don't have ES_Distance, need a replacement - if self.car_fingerprint not in HYBRID_CARS: + if not (self.CP.flags & SubaruFlags.HYBRID): # 8 is known AEB, there are a few other values related to AEB we ignore ret.stockAeb = (cp_es_distance.vl["ES_Brake"]["AEB_Status"] == 8) and \ (cp_es_distance.vl["ES_Brake"]["Brake_Pressure"] != 0) @@ -109,7 +118,7 @@ class CarState(CarStateBase): self.es_status_msg = copy.copy(cp_es_status.vl["ES_Status"]) self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"]) - if self.car_fingerprint not in HYBRID_CARS: + if not (self.CP.flags & SubaruFlags.HYBRID): self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"]) self.es_dashstatus_msg = copy.copy(cp_cam.vl["ES_DashStatus"]) @@ -125,7 +134,7 @@ class CarState(CarStateBase): ("Brake_Status", 50), ] - if CP.carFingerprint not in HYBRID_CARS: + if not (CP.flags & SubaruFlags.HYBRID): messages.append(("CruiseControl", 20)) return messages @@ -136,7 +145,7 @@ class CarState(CarStateBase): ("ES_Brake", 20), ] - if CP.carFingerprint not in HYBRID_CARS: + if not (CP.flags & SubaruFlags.HYBRID): messages += [ ("ES_Distance", 20), ("ES_Status", 20) @@ -164,7 +173,7 @@ class CarState(CarStateBase): ("Brake_Pedal", 50), ] - if CP.carFingerprint not in HYBRID_CARS: + if not (CP.flags & SubaruFlags.HYBRID): messages += [ ("Throttle", 100), ("Transmission", 100) @@ -173,8 +182,8 @@ class CarState(CarStateBase): if CP.enableBsm: messages.append(("BSD_RCTA", 17)) - if CP.carFingerprint not in PREGLOBAL_CARS: - if CP.carFingerprint not in GLOBAL_GEN2: + if not (CP.flags & SubaruFlags.PREGLOBAL): + if not (CP.flags & SubaruFlags.GLOBAL_GEN2): messages += CarState.get_common_global_body_messages(CP) else: messages += CarState.get_common_preglobal_body_messages() @@ -183,7 +192,7 @@ class CarState(CarStateBase): @staticmethod def get_cam_can_parser(CP): - if CP.carFingerprint in PREGLOBAL_CARS: + if CP.flags & SubaruFlags.PREGLOBAL: messages = [ ("ES_DashStatus", 20), ("ES_Distance", 20), @@ -194,7 +203,7 @@ class CarState(CarStateBase): ("ES_LKAS_State", 10), ] - if CP.carFingerprint not in GLOBAL_GEN2: + if not (CP.flags & SubaruFlags.GLOBAL_GEN2): messages += CarState.get_common_global_es_messages(CP) if CP.flags & SubaruFlags.SEND_INFOTAINMENT: @@ -206,11 +215,11 @@ class CarState(CarStateBase): def get_body_can_parser(CP): messages = [] - if CP.carFingerprint in GLOBAL_GEN2: + if CP.flags & SubaruFlags.GLOBAL_GEN2: messages += CarState.get_common_global_body_messages(CP) messages += CarState.get_common_global_es_messages(CP) - if CP.carFingerprint in HYBRID_CARS: + if CP.flags & SubaruFlags.HYBRID: messages += [ ("Throttle_Hybrid", 40), ("Transmission", 100) diff --git a/selfdrive/car/subaru/fingerprints.py b/selfdrive/car/subaru/fingerprints.py index 90fa6093d9..7f3ae73163 100644 --- a/selfdrive/car/subaru/fingerprints.py +++ b/selfdrive/car/subaru/fingerprints.py @@ -4,7 +4,7 @@ from openpilot.selfdrive.car.subaru.values import CAR Ecu = car.CarParams.Ecu FW_VERSIONS = { - CAR.ASCENT: { + CAR.SUBARU_ASCENT: { (Ecu.abs, 0x7b0, None): [ b'\xa5 \x19\x02\x00', b'\xa5 !\x02\x00', @@ -26,13 +26,14 @@ FW_VERSIONS = { b'\xd1,\xa0q\x07', ], (Ecu.transmission, 0x7e1, None): [ + b'\x00>\xf0\x00\x00', b'\x00\xfe\xf7\x00\x00', b'\x01\xfe\xf7\x00\x00', b'\x01\xfe\xf9\x00\x00', b'\x01\xfe\xfa\x00\x00', ], }, - CAR.ASCENT_2023: { + CAR.SUBARU_ASCENT_2023: { (Ecu.abs, 0x7b0, None): [ b'\xa5 #\x03\x00', ], @@ -49,7 +50,7 @@ FW_VERSIONS = { b'\x04\xfe\xf3\x00\x00', ], }, - CAR.LEGACY: { + CAR.SUBARU_LEGACY: { (Ecu.abs, 0x7b0, None): [ b'\xa1 \x02\x01', b'\xa1 \x02\x02', @@ -77,7 +78,7 @@ FW_VERSIONS = { b'\xa7\xfe\xc4@\x00', ], }, - CAR.IMPREZA: { + CAR.SUBARU_IMPREZA: { (Ecu.abs, 0x7b0, None): [ b'z\x84\x19\x90\x00', b'z\x94\x08\x90\x00', @@ -148,6 +149,7 @@ FW_VERSIONS = { b'\xe3\xf5C\x00\x00', b'\xe3\xf5F\x00\x00', b'\xe3\xf5G\x00\x00', + b'\xe4\xe5\x021\x00', b'\xe4\xe5\x061\x00', b'\xe4\xf5\x02\x00\x00', b'\xe4\xf5\x07\x00\x00', @@ -156,12 +158,13 @@ FW_VERSIONS = { b'\xe5\xf5B\x00\x00', ], }, - CAR.IMPREZA_2020: { + CAR.SUBARU_IMPREZA_2020: { (Ecu.abs, 0x7b0, None): [ b'\xa2 \x193\x00', b'\xa2 \x194\x00', b'\xa2 `\x00', b'\xa2 !3\x00', + b'\xa2 !6\x00', b'\xa2 !`\x00', b'\xa2 !i\x00', ], @@ -170,6 +173,7 @@ FW_VERSIONS = { b'\n\xc0\x04\x01', b'\x9a\xc0\x00\x00', b'\x9a\xc0\x04\x00', + b'\x9a\xc0\n\x01', ], (Ecu.fwdCamera, 0x787, None): [ b'\x00\x00eb\x1f@ "', @@ -179,6 +183,7 @@ FW_VERSIONS = { b'\x00\x00e\x8f\x1f@ )', b'\x00\x00e\x92\x00\x00\x00\x00', b'\x00\x00e\xa4\x00\x00\x00\x00', + b'\x00\x00e\xa4\x1f@ (', ], (Ecu.engine, 0x7e0, None): [ b'\xca!`0\x07', @@ -186,6 +191,7 @@ FW_VERSIONS = { b'\xca!ap\x07', b'\xca!f@\x07', b'\xca!fp\x07', + b'\xcaacp\x07', b'\xcc!`p\x07', b'\xcc!fp\x07', b'\xcc"f0\x07', @@ -195,8 +201,10 @@ FW_VERSIONS = { b'\xe6"fp\x07', b'\xf3"f@\x07', b'\xf3"fp\x07', + b'\xf3"fr\x07', ], (Ecu.transmission, 0x7e1, None): [ + b'\xe6\x15\x042\x00', b'\xe6\xf5\x04\x00\x00', b'\xe6\xf5$\x00\x00', b'\xe6\xf5D0\x00', @@ -209,7 +217,7 @@ FW_VERSIONS = { b'\xe9\xf6F0\x00', ], }, - CAR.CROSSTREK_HYBRID: { + CAR.SUBARU_CROSSTREK_HYBRID: { (Ecu.abs, 0x7b0, None): [ b'\xa2 \x19e\x01', b'\xa2 !e\x01', @@ -227,12 +235,13 @@ FW_VERSIONS = { b'\xf4!`0\x07', ], }, - CAR.FORESTER: { + CAR.SUBARU_FORESTER: { (Ecu.abs, 0x7b0, None): [ b'\xa3 \x18\x14\x00', b'\xa3 \x18&\x00', b'\xa3 \x19\x14\x00', b'\xa3 \x19&\x00', + b'\xa3 \x19h\x00', b'\xa3 \x14\x00', b'\xa3 \x14\x01', ], @@ -245,6 +254,7 @@ FW_VERSIONS = { b'\x00\x00e!\x1f@ \x11', b'\x00\x00e^\x00\x00\x00\x00', b'\x00\x00e^\x1f@ !', + b'\x00\x00e`\x00\x00\x00\x00', b'\x00\x00e`\x1f@ ', b'\x00\x00e\x97\x00\x00\x00\x00', b'\x00\x00e\x97\x1f@ 0', @@ -265,9 +275,10 @@ FW_VERSIONS = { b'\x1a\xf6F`\x00', b'\x1a\xf6b0\x00', b'\x1a\xf6b`\x00', + b'\x1a\xf6f`\x00', ], }, - CAR.FORESTER_HYBRID: { + CAR.SUBARU_FORESTER_HYBRID: { (Ecu.abs, 0x7b0, None): [ b'\xa3 \x19T\x00', ], @@ -284,7 +295,7 @@ FW_VERSIONS = { b'\x1b\xa7@a\x00', ], }, - CAR.FORESTER_PREGLOBAL: { + CAR.SUBARU_FORESTER_PREGLOBAL: { (Ecu.abs, 0x7b0, None): [ b'm\x97\x14@', b'}\x97\x14@', @@ -300,6 +311,7 @@ FW_VERSIONS = { b'\x00\x00d\xd3\x1f@ \t', ], (Ecu.engine, 0x7e0, None): [ + b'\xa7"@0\x07', b'\xa7"@p\x07', b'\xa7)\xa0q\x07', b'\xba"@@\x07', @@ -307,6 +319,7 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'\x1a\xf6F`\x00', + b'\xda\xf2`p\x00', b'\xda\xf2`\x80\x00', b'\xda\xfd\xe0\x80\x00', b'\xdc\xf2@`\x00', @@ -315,7 +328,7 @@ FW_VERSIONS = { b'\xdc\xf2`\x81\x00', ], }, - CAR.LEGACY_PREGLOBAL: { + CAR.SUBARU_LEGACY_PREGLOBAL: { (Ecu.abs, 0x7b0, None): [ b'[\x97D\x00', b'[\xba\xc4\x03', @@ -348,7 +361,7 @@ FW_VERSIONS = { b'\xbf\xfb\xc0\x80\x00', ], }, - CAR.OUTBACK_PREGLOBAL: { + CAR.SUBARU_OUTBACK_PREGLOBAL: { (Ecu.abs, 0x7b0, None): [ b'[\xba\xac\x03', b'[\xf7\xac\x00', @@ -401,7 +414,7 @@ FW_VERSIONS = { b'\xbf\xfb\xe0b\x00', ], }, - CAR.OUTBACK_PREGLOBAL_2018: { + CAR.SUBARU_OUTBACK_PREGLOBAL_2018: { (Ecu.abs, 0x7b0, None): [ b'\x8b\x97\xac\x00', b'\x8b\x97\xbc\x00', @@ -444,7 +457,7 @@ FW_VERSIONS = { b'\xbc\xfb\xe0\x80\x00', ], }, - CAR.OUTBACK: { + CAR.SUBARU_OUTBACK: { (Ecu.abs, 0x7b0, None): [ b'\xa1 \x06\x00', b'\xa1 \x06\x01', @@ -493,7 +506,7 @@ FW_VERSIONS = { b'\xa7\xfe\xf4@\x00', ], }, - CAR.FORESTER_2022: { + CAR.SUBARU_FORESTER_2022: { (Ecu.abs, 0x7b0, None): [ b'\xa3 !v\x00', b'\xa3 !x\x00', @@ -526,7 +539,7 @@ FW_VERSIONS = { b'\x1e\xf6D0\x00', ], }, - CAR.OUTBACK_2023: { + CAR.SUBARU_OUTBACK_2023: { (Ecu.abs, 0x7b0, None): [ b'\xa1 #\x14\x00', b'\xa1 #\x17\x00', diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 2dfb4f9565..cb00934376 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -1,101 +1,96 @@ 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 +from openpilot.selfdrive.car.subaru.values import CAR, GLOBAL_ES_ADDR, SubaruFlags class CarInterface(CarInterfaceBase): @staticmethod - def _get_params(ret, candidate: Platform, fingerprint, car_fw, experimental_long, docs): + def _get_params(ret, candidate: CAR, fingerprint, car_fw, experimental_long, docs): ret.carName = "subaru" ret.radarUnavailable = True # for HYBRID CARS to be upstreamed, we need: # - replacement for ES_Distance so we can cancel the cruise control # - to find the Cruise_Activated bit from the car # - proper panda safety setup (use the correct cruise_activated bit, throttle from Throttle_Hybrid, etc) - ret.dashcamOnly = candidate in (PREGLOBAL_CARS | LKAS_ANGLE | HYBRID_CARS) + ret.dashcamOnly = bool(ret.flags & (SubaruFlags.PREGLOBAL | SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID)) ret.autoResumeSng = False # Detect infotainment message sent from the camera - if candidate not in PREGLOBAL_CARS and 0x323 in fingerprint[2]: + if not (ret.flags & SubaruFlags.PREGLOBAL) and 0x323 in fingerprint[2]: ret.flags |= SubaruFlags.SEND_INFOTAINMENT.value - if candidate in PREGLOBAL_CARS: + if ret.flags & SubaruFlags.PREGLOBAL: ret.enableBsm = 0x25c in fingerprint[0] ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaruPreglobal)] else: ret.enableBsm = 0x228 in fingerprint[0] ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaru)] - if candidate in GLOBAL_GEN2: + if ret.flags & SubaruFlags.GLOBAL_GEN2: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_SUBARU_GEN2 ret.steerLimitTimer = 0.4 ret.steerActuatorDelay = 0.1 - if candidate in LKAS_ANGLE: + if ret.flags & SubaruFlags.LKAS_ANGLE: ret.steerControlType = car.CarParams.SteerControlType.angle else: CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - if candidate in (CAR.ASCENT, CAR.ASCENT_2023): - ret.steerActuatorDelay = 0.3 # end-to-end angle controller + if candidate in (CAR.SUBARU_ASCENT, CAR.SUBARU_ASCENT_2023): + ret.steerActuatorDelay = 0.3 # end-to-end angle controller ret.lateralTuning.init('pid') ret.lateralTuning.pid.kf = 0.00003 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.0025, 0.1], [0.00025, 0.01]] - elif candidate == CAR.IMPREZA: - ret.steerActuatorDelay = 0.4 # end-to-end angle controller + elif candidate == CAR.SUBARU_IMPREZA: + ret.steerActuatorDelay = 0.4 # end-to-end angle controller ret.lateralTuning.init('pid') ret.lateralTuning.pid.kf = 0.00005 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.3], [0.02, 0.03]] - elif candidate == CAR.IMPREZA_2020: + elif candidate == CAR.SUBARU_IMPREZA_2020: 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: + elif candidate == CAR.SUBARU_CROSSTREK_HYBRID: ret.steerActuatorDelay = 0.1 - elif candidate in (CAR.FORESTER, CAR.FORESTER_2022, CAR.FORESTER_HYBRID): + elif candidate in (CAR.SUBARU_FORESTER, CAR.SUBARU_FORESTER_2022, CAR.SUBARU_FORESTER_HYBRID): 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): + elif candidate in (CAR.SUBARU_OUTBACK, CAR.SUBARU_LEGACY, CAR.SUBARU_OUTBACK_2023): ret.steerActuatorDelay = 0.1 - elif candidate in (CAR.FORESTER_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018): + elif candidate in (CAR.SUBARU_FORESTER_PREGLOBAL, CAR.SUBARU_OUTBACK_PREGLOBAL_2018): ret.safetyConfigs[0].safetyParam = Panda.FLAG_SUBARU_PREGLOBAL_REVERSED_DRIVER_TORQUE # Outback 2018-2019 and Forester have reversed driver torque signal - elif candidate == CAR.LEGACY_PREGLOBAL: + elif candidate == CAR.SUBARU_LEGACY_PREGLOBAL: ret.steerActuatorDelay = 0.15 - elif candidate == CAR.OUTBACK_PREGLOBAL: + elif candidate == CAR.SUBARU_OUTBACK_PREGLOBAL: pass else: raise ValueError(f"unknown car: {candidate}") - ret.experimentalLongitudinalAvailable = candidate not in (GLOBAL_GEN2 | PREGLOBAL_CARS | LKAS_ANGLE | HYBRID_CARS) + ret.experimentalLongitudinalAvailable = not (ret.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.PREGLOBAL | + SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID)) ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable - if candidate in GLOBAL_GEN2 and ret.openpilotLongitudinalControl: + if ret.flags & SubaruFlags.GLOBAL_GEN2 and ret.openpilotLongitudinalControl: ret.flags |= SubaruFlags.DISABLE_EYESIGHT.value if ret.openpilotLongitudinalControl: - ret.longitudinalTuning.kpBP = [0., 5., 35.] - ret.longitudinalTuning.kpV = [0.8, 1.0, 1.5] - ret.longitudinalTuning.kiBP = [0., 35.] - ret.longitudinalTuning.kiV = [0.54, 0.36] - ret.stoppingControl = True ret.safetyConfigs[0].safetyParam |= Panda.FLAG_SUBARU_LONG @@ -114,6 +109,3 @@ class CarInterface(CarInterfaceBase): def init(CP, logcan, sendcan): if CP.flags & SubaruFlags.DISABLE_EYESIGHT: disable_ecu(logcan, sendcan, bus=2, addr=GLOBAL_ES_ADDR, com_cont_req=b'\x28\x03\x01') - - def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/subaru/tests/test_subaru.py b/selfdrive/car/subaru/tests/test_subaru.py index c8cdf66065..33040442b6 100644 --- a/selfdrive/car/subaru/tests/test_subaru.py +++ b/selfdrive/car/subaru/tests/test_subaru.py @@ -1,5 +1,4 @@ from cereal import car -import unittest from openpilot.selfdrive.car.subaru.fingerprints import FW_VERSIONS Ecu = car.CarParams.Ecu @@ -7,14 +6,11 @@ Ecu = car.CarParams.Ecu ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} -class TestSubaruFingerprint(unittest.TestCase): +class TestSubaruFingerprint: def test_fw_version_format(self): for platform, fws_per_ecu in FW_VERSIONS.items(): for (ecu, _, _), fws in fws_per_ecu.items(): fw_size = len(fws[0]) for fw in fws: - self.assertEqual(len(fw), fw_size, f"{platform} {ecu}: {len(fw)} {fw_size}") + assert len(fw) == fw_size, f"{platform} {ecu}: {len(fw)} {fw_size}" - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index a296550b12..dcbea1979f 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -4,7 +4,7 @@ from enum import Enum, IntFlag from cereal import car from panda.python import uds 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.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Tool, Column from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 Ecu = car.CarParams.Ecu @@ -19,11 +19,11 @@ class CarControllerParams: self.STEER_DRIVER_MULTIPLIER = 50 # weight driver torque heavily self.STEER_DRIVER_FACTOR = 1 # from dbc - if CP.carFingerprint in GLOBAL_GEN2: + if CP.flags & SubaruFlags.GLOBAL_GEN2: self.STEER_MAX = 1000 self.STEER_DELTA_UP = 40 self.STEER_DELTA_DOWN = 40 - elif CP.carFingerprint == CAR.IMPREZA_2020: + elif CP.carFingerprint == CAR.SUBARU_IMPREZA_2020: self.STEER_MAX = 1439 else: self.STEER_MAX = 2047 @@ -38,7 +38,7 @@ class CarControllerParams: BRAKE_MAX = 600 # about -3.5m/s2 from testing RPM_MIN = 0 - RPM_MAX = 2400 + RPM_MAX = 3600 RPM_INACTIVE = 600 # a good base rpm for zero acceleration @@ -53,9 +53,20 @@ class CarControllerParams: class SubaruFlags(IntFlag): + # Detected flags SEND_INFOTAINMENT = 1 DISABLE_EYESIGHT = 2 + # Static flags + GLOBAL_GEN2 = 4 + + # Cars that temporarily fault when steering angle rate is greater than some threshold. + # Appears to be all torque-based cars produced around 2019 - present + STEER_RATE_LIMITED = 8 + PREGLOBAL = 16 + HYBRID = 32 + LKAS_ANGLE = 64 + GLOBAL_ES_ADDR = 0x787 GEN2_ES_BUTTONS_DID = b'\x11\x30' @@ -77,7 +88,7 @@ class Footnote(Enum): @dataclass -class SubaruCarInfo(CarInfo): +class SubaruCarDocs(CarDocs): 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]) @@ -89,142 +100,164 @@ class SubaruCarInfo(CarInfo): self.footnotes.append(Footnote.EXP_LONG) -@dataclass(frozen=True) +@dataclass class SubaruPlatformConfig(PlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('subaru_global_2017_generated', None)) + def init(self): + if self.flags & SubaruFlags.HYBRID: + self.dbc_dict = dbc_dict('subaru_global_2020_hybrid_generated', None) + + +@dataclass +class SubaruGen2PlatformConfig(SubaruPlatformConfig): + def init(self): + super().init() + self.flags |= SubaruFlags.GLOBAL_GEN2 + if not (self.flags & SubaruFlags.LKAS_ANGLE): + self.flags |= SubaruFlags.STEER_RATE_LIMITED + 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), + SUBARU_ASCENT = SubaruPlatformConfig( + [SubaruCarDocs("Subaru Ascent 2019-21", "All")], + 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), + SUBARU_OUTBACK = SubaruGen2PlatformConfig( + [SubaruCarDocs("Subaru Outback 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b]))], + 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, + SUBARU_LEGACY = SubaruGen2PlatformConfig( + [SubaruCarDocs("Subaru Legacy 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b]))], + SUBARU_OUTBACK.specs, ) - IMPREZA = SubaruPlatformConfig( - "SUBARU IMPREZA LIMITED 2019", + SUBARU_IMPREZA = SubaruPlatformConfig( [ - 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"), + SubaruCarDocs("Subaru Impreza 2017-19"), + SubaruCarDocs("Subaru Crosstrek 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"), + SubaruCarDocs("Subaru XV 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"), ], - specs=CarSpecs(mass=1568, wheelbase=2.67, steerRatio=15), + CarSpecs(mass=1568, wheelbase=2.67, steerRatio=15), ) - IMPREZA_2020 = SubaruPlatformConfig( - "SUBARU IMPREZA SPORT 2020", + SUBARU_IMPREZA_2020 = SubaruPlatformConfig( [ - SubaruCarInfo("Subaru Impreza 2020-22"), - SubaruCarInfo("Subaru Crosstrek 2020-23"), - SubaruCarInfo("Subaru XV 2020-21"), + SubaruCarDocs("Subaru Impreza 2020-22"), + SubaruCarDocs("Subaru Crosstrek 2020-23"), + SubaruCarDocs("Subaru XV 2020-21"), ], - specs=CarSpecs(mass=1480, wheelbase=2.67, steerRatio=17), + CarSpecs(mass=1480, wheelbase=2.67, steerRatio=17), + flags=SubaruFlags.STEER_RATE_LIMITED, ) # TODO: is there an XV and Impreza too? - 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), + SUBARU_CROSSTREK_HYBRID = SubaruPlatformConfig( + [SubaruCarDocs("Subaru Crosstrek Hybrid 2020", car_parts=CarParts.common([CarHarness.subaru_b]))], + CarSpecs(mass=1668, wheelbase=2.67, steerRatio=17), + flags=SubaruFlags.HYBRID, ) - FORESTER = SubaruPlatformConfig( - "SUBARU FORESTER 2019", - SubaruCarInfo("Subaru Forester 2019-21", "All"), - specs=CarSpecs(mass=1568, wheelbase=2.67, steerRatio=17), + SUBARU_FORESTER = SubaruPlatformConfig( + [SubaruCarDocs("Subaru Forester 2019-21", "All")], + CarSpecs(mass=1568, wheelbase=2.67, steerRatio=17), + flags=SubaruFlags.STEER_RATE_LIMITED, ) - FORESTER_HYBRID = SubaruPlatformConfig( - "SUBARU FORESTER HYBRID 2020", - SubaruCarInfo("Subaru Forester Hybrid 2020"), - dbc_dict('subaru_global_2020_hybrid_generated', None), - specs=FORESTER.specs, + SUBARU_FORESTER_HYBRID = SubaruPlatformConfig( + [SubaruCarDocs("Subaru Forester Hybrid 2020")], + SUBARU_FORESTER.specs, + flags=SubaruFlags.HYBRID, ) # Pre-global - FORESTER_PREGLOBAL = SubaruPlatformConfig( - "SUBARU FORESTER 2017 - 2018", - SubaruCarInfo("Subaru Forester 2017-18"), + SUBARU_FORESTER_PREGLOBAL = SubaruPlatformConfig( + [SubaruCarDocs("Subaru Forester 2017-18")], + CarSpecs(mass=1568, wheelbase=2.67, steerRatio=20), dbc_dict('subaru_forester_2017_generated', None), - specs=CarSpecs(mass=1568, wheelbase=2.67, steerRatio=20), + flags=SubaruFlags.PREGLOBAL, ) - LEGACY_PREGLOBAL = SubaruPlatformConfig( - "SUBARU LEGACY 2015 - 2018", - SubaruCarInfo("Subaru Legacy 2015-18"), + SUBARU_LEGACY_PREGLOBAL = SubaruPlatformConfig( + [SubaruCarDocs("Subaru Legacy 2015-18")], + CarSpecs(mass=1568, wheelbase=2.67, steerRatio=12.5), dbc_dict('subaru_outback_2015_generated', None), - specs=CarSpecs(mass=1568, wheelbase=2.67, steerRatio=12.5), + flags=SubaruFlags.PREGLOBAL, ) - OUTBACK_PREGLOBAL = SubaruPlatformConfig( - "SUBARU OUTBACK 2015 - 2017", - SubaruCarInfo("Subaru Outback 2015-17"), + SUBARU_OUTBACK_PREGLOBAL = SubaruPlatformConfig( + [SubaruCarDocs("Subaru Outback 2015-17")], + SUBARU_FORESTER_PREGLOBAL.specs, dbc_dict('subaru_outback_2015_generated', None), - specs=FORESTER_PREGLOBAL.specs, + flags=SubaruFlags.PREGLOBAL, ) - OUTBACK_PREGLOBAL_2018 = SubaruPlatformConfig( - "SUBARU OUTBACK 2018 - 2019", - SubaruCarInfo("Subaru Outback 2018-19"), + SUBARU_OUTBACK_PREGLOBAL_2018 = SubaruPlatformConfig( + [SubaruCarDocs("Subaru Outback 2018-19")], + SUBARU_FORESTER_PREGLOBAL.specs, dbc_dict('subaru_outback_2019_generated', None), - specs=FORESTER_PREGLOBAL.specs, + flags=SubaruFlags.PREGLOBAL, ) # Angle LKAS - FORESTER_2022 = SubaruPlatformConfig( - "SUBARU FORESTER 2022", - SubaruCarInfo("Subaru Forester 2022-24", "All", car_parts=CarParts.common([CarHarness.subaru_c])), - specs=FORESTER.specs, + SUBARU_FORESTER_2022 = SubaruPlatformConfig( + [SubaruCarDocs("Subaru Forester 2022-24", "All", car_parts=CarParts.common([CarHarness.subaru_c]))], + SUBARU_FORESTER.specs, + flags=SubaruFlags.LKAS_ANGLE, ) - OUTBACK_2023 = SubaruPlatformConfig( - "SUBARU OUTBACK 7TH GEN", - SubaruCarInfo("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), - specs=OUTBACK.specs, + SUBARU_OUTBACK_2023 = SubaruGen2PlatformConfig( + [SubaruCarDocs("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d]))], + SUBARU_OUTBACK.specs, + flags=SubaruFlags.LKAS_ANGLE, ) - ASCENT_2023 = SubaruPlatformConfig( - "SUBARU ASCENT 2023", - SubaruCarInfo("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), - specs=ASCENT.specs, + SUBARU_ASCENT_2023 = SubaruGen2PlatformConfig( + [SubaruCarDocs("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d]))], + SUBARU_ASCENT.specs, + flags=SubaruFlags.LKAS_ANGLE, ) -LKAS_ANGLE = {CAR.FORESTER_2022, CAR.OUTBACK_2023, CAR.ASCENT_2023} -GLOBAL_GEN2 = {CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023, CAR.ASCENT_2023} -PREGLOBAL_CARS = {CAR.FORESTER_PREGLOBAL, CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018} -HYBRID_CARS = {CAR.CROSSTREK_HYBRID, CAR.FORESTER_HYBRID} - -# Cars that temporarily fault when steering angle rate is greater than some threshold. -# Appears to be all torque-based cars produced around 2019 - present -STEER_RATE_LIMITED = GLOBAL_GEN2 | {CAR.IMPREZA_2020, CAR.FORESTER} - SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) +# The EyeSight ECU takes 10s to respond to SUBARU_VERSION_REQUEST properly, +# log this alternate manufacturer-specific query +SUBARU_ALT_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(0xf100) +SUBARU_ALT_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ + p16(0xf100) + FW_QUERY_CONFIG = FwQueryConfig( requests=[ Request( [StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission], + logging=True, ), + # Non-OBD requests # Some Eyesight modules fail on TESTER_PRESENT_REQUEST # TODO: check if this resolves the fingerprinting issue for the 2023 Ascent and other new Subaru cars Request( [SUBARU_VERSION_REQUEST], [SUBARU_VERSION_RESPONSE], whitelist_ecus=[Ecu.fwdCamera], + bus=0, + ), + Request( + [SUBARU_ALT_VERSION_REQUEST], + [SUBARU_ALT_VERSION_RESPONSE], + whitelist_ecus=[Ecu.fwdCamera], + bus=0, + logging=True, + ), + Request( + [StdQueries.DEFAULT_DIAGNOSTIC_REQUEST, StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST], + [StdQueries.DEFAULT_DIAGNOSTIC_RESPONSE, StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], + whitelist_ecus=[Ecu.fwdCamera], + bus=0, + logging=True, ), - # Non-OBD requests Request( [StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission], bus=0, ), + # GEN2 powertrain bus query Request( [StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], @@ -235,9 +268,8 @@ FW_QUERY_CONFIG = FwQueryConfig( ], # We don't get the EPS from non-OBD queries on GEN2 cars. Note that we still attempt to match when it exists non_essential_ecus={ - Ecu.eps: list(GLOBAL_GEN2), + Ecu.eps: list(CAR.with_flags(SubaruFlags.GLOBAL_GEN2)), } ) -CAR_INFO = CAR.create_carinfo_map() DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/tesla/carcontroller.py b/selfdrive/car/tesla/carcontroller.py index 95a248a614..e460111e32 100644 --- a/selfdrive/car/tesla/carcontroller.py +++ b/selfdrive/car/tesla/carcontroller.py @@ -1,11 +1,12 @@ from openpilot.common.numpy_fast import clip from opendbc.can.packer import CANPacker from openpilot.selfdrive.car import apply_std_steer_angle_limits +from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.car.tesla.teslacan import TeslaCAN from openpilot.selfdrive.car.tesla.values import DBC, CANBUS, CarControllerParams -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP self.frame = 0 @@ -59,7 +60,7 @@ class CarController: # TODO: HUD control - new_actuators = actuators.copy() + new_actuators = actuators.as_builder() new_actuators.steeringAngleDeg = self.apply_angle_last self.frame += 1 diff --git a/selfdrive/car/tesla/carstate.py b/selfdrive/car/tesla/carstate.py index 2cb4f09d79..645ea46014 100644 --- a/selfdrive/car/tesla/carstate.py +++ b/selfdrive/car/tesla/carstate.py @@ -2,7 +2,7 @@ import copy from collections import deque from cereal import car from openpilot.common.conversions import Conversions as CV -from openpilot.selfdrive.car.tesla.values import DBC, CANBUS, GEAR_MAP, DOORS, BUTTONS +from openpilot.selfdrive.car.tesla.values import CAR, DBC, CANBUS, GEAR_MAP, DOORS, BUTTONS from openpilot.selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine @@ -37,13 +37,15 @@ class CarState(CarStateBase): ret.brakePressed = bool(cp.vl["BrakeMessage"]["driverBrakeStatus"] != 1) # Steering wheel - self.hands_on_level = cp.vl["EPAS_sysStatus"]["EPAS_handsOnLevel"] - self.steer_warning = self.can_define.dv["EPAS_sysStatus"]["EPAS_eacErrorCode"].get(int(cp.vl["EPAS_sysStatus"]["EPAS_eacErrorCode"]), None) - steer_status = self.can_define.dv["EPAS_sysStatus"]["EPAS_eacStatus"].get(int(cp.vl["EPAS_sysStatus"]["EPAS_eacStatus"]), None) + epas_status = cp_cam.vl["EPAS3P_sysStatus"] if self.CP.carFingerprint == CAR.TESLA_MODELS_RAVEN else cp.vl["EPAS_sysStatus"] - ret.steeringAngleDeg = -cp.vl["EPAS_sysStatus"]["EPAS_internalSAS"] + self.hands_on_level = epas_status["EPAS_handsOnLevel"] + self.steer_warning = self.can_define.dv["EPAS_sysStatus"]["EPAS_eacErrorCode"].get(int(epas_status["EPAS_eacErrorCode"]), None) + steer_status = self.can_define.dv["EPAS_sysStatus"]["EPAS_eacStatus"].get(int(epas_status["EPAS_eacStatus"]), None) + + ret.steeringAngleDeg = -epas_status["EPAS_internalSAS"] ret.steeringRateDeg = -cp.vl["STW_ANGLHP_STAT"]["StW_AnglHP_Spd"] # This is from a different angle sensor, and at different rate - ret.steeringTorque = -cp.vl["EPAS_sysStatus"]["EPAS_torsionBarTorque"] + ret.steeringTorque = -epas_status["EPAS_torsionBarTorque"] ret.steeringPressed = (self.hands_on_level > 0) ret.steerFaultPermanent = steer_status == "EAC_FAULT" ret.steerFaultTemporary = (self.steer_warning not in ("EAC_ERROR_IDLE", "EAC_ERROR_HANDS_ON")) @@ -85,7 +87,10 @@ class CarState(CarStateBase): ret.rightBlinker = (cp.vl["GTW_carState"]["BC_indicatorRStatus"] == 1) # Seatbelt - ret.seatbeltUnlatched = (cp.vl["SDM1"]["SDM_bcklDrivStatus"] != 1) + if self.CP.carFingerprint == CAR.TESLA_MODELS_RAVEN: + ret.seatbeltUnlatched = (cp.vl["DriverSeat"]["buckleStatus"] != 1) + else: + ret.seatbeltUnlatched = (cp.vl["SDM1"]["SDM_bcklDrivStatus"] != 1) # TODO: blindspot @@ -111,9 +116,14 @@ class CarState(CarStateBase): ("DI_state", 10), ("STW_ACTN_RQ", 10), ("GTW_carState", 10), - ("SDM1", 10), ("BrakeMessage", 50), ] + + if CP.carFingerprint == CAR.TESLA_MODELS_RAVEN: + messages.append(("DriverSeat", 20)) + else: + messages.append(("SDM1", 10)) + return CANParser(DBC[CP.carFingerprint]['chassis'], messages, CANBUS.chassis) @staticmethod @@ -122,4 +132,8 @@ class CarState(CarStateBase): # sig_address, frequency ("DAS_control", 40), ] + + if CP.carFingerprint == CAR.TESLA_MODELS_RAVEN: + messages.append(("EPAS3P_sysStatus", 100)) + return CANParser(DBC[CP.carFingerprint]['chassis'], messages, CANBUS.autopilot_chassis) diff --git a/selfdrive/car/tesla/fingerprints.py b/selfdrive/car/tesla/fingerprints.py index 772ca59efa..68c50a62ed 100644 --- a/selfdrive/car/tesla/fingerprints.py +++ b/selfdrive/car/tesla/fingerprints.py @@ -1,17 +1,10 @@ -# ruff: noqa: E501 from cereal import car from openpilot.selfdrive.car.tesla.values import CAR Ecu = car.CarParams.Ecu -FINGERPRINTS = { - CAR.AP1_MODELS: [{ - 1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 267: 5, 277: 6, 280: 6, 283: 5, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 577: 8, 582: 5, 584: 4, 585: 8, 590: 8, 606: 8, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 660: 5, 693: 8, 696: 8, 697: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 809: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 841: 8, 845: 8, 846: 5, 852: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 873: 8, 877: 8, 878: 8, 879: 8, 880: 8, 884: 8, 888: 8, 889: 8, 893: 8, 896: 8, 901: 6, 904: 3, 905: 8, 908: 2, 909: 8, 920: 8, 921: 8, 925: 4, 936: 8, 937: 8, 941: 8, 949: 8, 952: 8, 953: 6, 957: 8, 968: 8, 973: 8, 984: 8, 987: 8, 989: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1016: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1064: 8, 1070: 8, 1080: 8, 1160: 4, 1281: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1368: 8, 1412: 8, 1436: 8, 1465: 8, 1476: 8, 1497: 8, 1524: 8, 1527: 8, 1601: 8, 1605: 8, 1611: 8, 1614: 8, 1617: 8, 1621: 8, 1627: 8, 1630: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2043: 5, 2045: 4 - }], -} - FW_VERSIONS = { - CAR.AP2_MODELS: { + CAR.TESLA_AP2_MODELS: { (Ecu.adas, 0x649, None): [ b'\x01\x00\x8b\x07\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11', ], @@ -25,4 +18,15 @@ FW_VERSIONS = { b'\x10#\x01', ], }, + CAR.TESLA_MODELS_RAVEN: { + (Ecu.electricBrakeBooster, 0x64d, None): [ + b'1037123-00-A', + ], + (Ecu.fwdRadar, 0x671, None): [ + b'\x01\x00\x99\x02\x01\x00\x10\x00\x00AP8.3.03\x00\x10', + ], + (Ecu.eps, 0x730, None): [ + b'SX_0.0.0 (99),SR013.7', + ], + }, } diff --git a/selfdrive/car/tesla/interface.py b/selfdrive/car/tesla/interface.py index e06139729c..deb0e00230 100755 --- a/selfdrive/car/tesla/interface.py +++ b/selfdrive/car/tesla/interface.py @@ -18,37 +18,25 @@ class CarInterface(CarInterfaceBase): ret.steerControlType = car.CarParams.SteerControlType.angle - # Set kP and kI to 0 over the whole speed range to have the planner accel as actuator command - ret.longitudinalTuning.kpBP = [0] - ret.longitudinalTuning.kpV = [0] - ret.longitudinalTuning.kiBP = [0] - ret.longitudinalTuning.kiV = [0] - ret.longitudinalActuatorDelayUpperBound = 0.5 # s + ret.longitudinalActuatorDelay = 0.5 # s ret.radarTimeStep = (1.0 / 8) # 8Hz # Check if we have messages on an auxiliary panda, and that 0x2bf (DAS_control) is present on the AP powertrain bus # If so, we assume that it is connected to the longitudinal harness. + flags = (Panda.FLAG_TESLA_RAVEN if candidate == CAR.TESLA_MODELS_RAVEN else 0) if (CANBUS.autopilot_powertrain in fingerprint.keys()) and (0x2bf in fingerprint[CANBUS.autopilot_powertrain].keys()): ret.openpilotLongitudinalControl = True + flags |= Panda.FLAG_TESLA_LONG_CONTROL ret.safetyConfigs = [ - get_safety_config(car.CarParams.SafetyModel.tesla, Panda.FLAG_TESLA_LONG_CONTROL), - get_safety_config(car.CarParams.SafetyModel.tesla, Panda.FLAG_TESLA_LONG_CONTROL | Panda.FLAG_TESLA_POWERTRAIN), + get_safety_config(car.CarParams.SafetyModel.tesla, flags), + get_safety_config(car.CarParams.SafetyModel.tesla, flags | Panda.FLAG_TESLA_POWERTRAIN), ] else: ret.openpilotLongitudinalControl = False - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.tesla, 0)] + ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.tesla, flags)] ret.steerLimitTimer = 1.0 ret.steerActuatorDelay = 0.25 - - if candidate in (CAR.AP2_MODELS, CAR.AP1_MODELS): - ret.mass = 2100. - ret.wheelbase = 2.959 - ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 15.0 - else: - raise ValueError(f"Unsupported car: {candidate}") - return ret def _update(self, c): @@ -57,6 +45,3 @@ class CarInterface(CarInterfaceBase): ret.events = self.create_common_events(ret).to_msg() return ret - - def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/tesla/radar_interface.py b/selfdrive/car/tesla/radar_interface.py index b3e7c7fcb1..ae5077824b 100755 --- a/selfdrive/car/tesla/radar_interface.py +++ b/selfdrive/car/tesla/radar_interface.py @@ -1,38 +1,33 @@ #!/usr/bin/env python3 from cereal import car from opendbc.can.parser import CANParser -from openpilot.selfdrive.car.tesla.values import DBC, CANBUS +from openpilot.selfdrive.car.tesla.values import CAR, DBC, CANBUS from openpilot.selfdrive.car.interfaces import RadarInterfaceBase -RADAR_MSGS_A = list(range(0x310, 0x36E, 3)) -RADAR_MSGS_B = list(range(0x311, 0x36F, 3)) -NUM_POINTS = len(RADAR_MSGS_A) - -def get_radar_can_parser(CP): - # Status messages - messages = [ - ('TeslaRadarSguInfo', 10), - ] - - # Radar tracks. There are also raw point clouds available, - # we don't use those. - for i in range(NUM_POINTS): - msg_id_a = RADAR_MSGS_A[i] - msg_id_b = RADAR_MSGS_B[i] - messages.extend([ - (msg_id_a, 8), - (msg_id_b, 8), - ]) - - return CANParser(DBC[CP.carFingerprint]['radar'], messages, CANBUS.radar) class RadarInterface(RadarInterfaceBase): def __init__(self, CP): super().__init__(CP) - self.rcp = get_radar_can_parser(CP) + self.CP = CP + + if CP.carFingerprint == CAR.TESLA_MODELS_RAVEN: + messages = [('RadarStatus', 16)] + self.num_points = 40 + self.trigger_msg = 1119 + else: + messages = [('TeslaRadarSguInfo', 10)] + self.num_points = 32 + self.trigger_msg = 878 + + for i in range(self.num_points): + messages.extend([ + (f'RadarPoint{i}_A', 16), + (f'RadarPoint{i}_B', 16), + ]) + + self.rcp = CANParser(DBC[CP.carFingerprint]['radar'], messages, CANBUS.radar) self.updated_messages = set() self.track_id = 0 - self.trigger_msg = RADAR_MSGS_B[-1] def update(self, can_strings): if self.rcp is None: @@ -48,17 +43,24 @@ class RadarInterface(RadarInterfaceBase): # Errors errors = [] - sgu_info = self.rcp.vl['TeslaRadarSguInfo'] if not self.rcp.can_valid: errors.append('canError') - if sgu_info['RADC_HWFail'] or sgu_info['RADC_SGUFail'] or sgu_info['RADC_SensorDirty']: - errors.append('fault') + + if self.CP.carFingerprint == CAR.TESLA_MODELS_RAVEN: + radar_status = self.rcp.vl['RadarStatus'] + if radar_status['sensorBlocked'] or radar_status['shortTermUnavailable'] or radar_status['vehDynamicsError']: + errors.append('fault') + else: + radar_status = self.rcp.vl['TeslaRadarSguInfo'] + if radar_status['RADC_HWFail'] or radar_status['RADC_SGUFail'] or radar_status['RADC_SensorDirty']: + errors.append('fault') + ret.errors = errors # Radar tracks - for i in range(NUM_POINTS): - msg_a = self.rcp.vl[RADAR_MSGS_A[i]] - msg_b = self.rcp.vl[RADAR_MSGS_B[i]] + for i in range(self.num_points): + msg_a = self.rcp.vl[f'RadarPoint{i}_A'] + msg_b = self.rcp.vl[f'RadarPoint{i}_B'] # Make sure msg A and B are together if msg_a['Index'] != msg_b['Index2']: diff --git a/selfdrive/car/tesla/values.py b/selfdrive/car/tesla/values.py index 2a51d15da8..0f9cd00f63 100644 --- a/selfdrive/car/tesla/values.py +++ b/selfdrive/car/tesla/values.py @@ -1,31 +1,30 @@ from collections import namedtuple -from enum import StrEnum from cereal import car -from openpilot.selfdrive.car import AngleRateLimit, dbc_dict -from openpilot.selfdrive.car.docs_definitions import CarInfo +from openpilot.selfdrive.car import AngleRateLimit, CarSpecs, PlatformConfig, Platforms, dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarDocs from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) - -class CAR(StrEnum): - AP1_MODELS = 'TESLA AP1 MODEL S' - AP2_MODELS = 'TESLA AP2 MODEL S' - - -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"), -} - - -DBC = { - CAR.AP2_MODELS: dbc_dict('tesla_powertrain', 'tesla_radar', chassis_dbc='tesla_can'), - CAR.AP1_MODELS: dbc_dict('tesla_powertrain', 'tesla_radar', chassis_dbc='tesla_can'), -} +class CAR(Platforms): + TESLA_AP1_MODELS = PlatformConfig( + [CarDocs("Tesla AP1 Model S", "All")], + CarSpecs(mass=2100., wheelbase=2.959, steerRatio=15.0), + dbc_dict('tesla_powertrain', 'tesla_radar_bosch_generated', chassis_dbc='tesla_can') + ) + TESLA_AP2_MODELS = PlatformConfig( + [CarDocs("Tesla AP2 Model S", "All")], + TESLA_AP1_MODELS.specs, + TESLA_AP1_MODELS.dbc_dict + ) + TESLA_MODELS_RAVEN = PlatformConfig( + [CarDocs("Tesla Model S Raven", "All")], + TESLA_AP1_MODELS.specs, + dbc_dict('tesla_powertrain', 'tesla_radar_continental_generated', chassis_dbc='tesla_can') + ) FW_QUERY_CONFIG = FwQueryConfig( requests=[ @@ -36,6 +35,13 @@ FW_QUERY_CONFIG = FwQueryConfig( rx_offset=0x08, bus=0, ), + Request( + [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.SUPPLIER_SOFTWARE_VERSION_REQUEST], + [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.SUPPLIER_SOFTWARE_VERSION_RESPONSE], + whitelist_ecus=[Ecu.eps], + rx_offset=0x08, + bus=0, + ), Request( [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], @@ -46,7 +52,6 @@ FW_QUERY_CONFIG = FwQueryConfig( ] ) - class CANBUS: # Lateral harness chassis = 0 @@ -88,3 +93,6 @@ class CarControllerParams: def __init__(self, CP): pass + + +DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index dd0e57dd31..ea3674da38 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -10,105 +10,108 @@ from openpilot.selfdrive.car.nissan.values import CAR as NISSAN from openpilot.selfdrive.car.mazda.values import CAR as MAZDA from openpilot.selfdrive.car.subaru.values import CAR as SUBARU from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA +from openpilot.selfdrive.car.values import Platform from openpilot.selfdrive.car.volkswagen.values import CAR as VOLKSWAGEN from openpilot.selfdrive.car.tesla.values import CAR as TESLA from openpilot.selfdrive.car.body.values import CAR as COMMA # TODO: add routes for these cars non_tested_cars = [ - FORD.F_150_MK14, + FORD.FORD_F_150_MK14, GM.CADILLAC_ATS, GM.HOLDEN_ASTRA, - GM.MALIBU, + GM.CHEVROLET_MALIBU, HYUNDAI.GENESIS_G90, - HONDA.ODYSSEY_CHN, - VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter - SUBARU.FORESTER_HYBRID, + HONDA.HONDA_ODYSSEY_CHN, + VOLKSWAGEN.VOLKSWAGEN_CRAFTER_MK2, # need a route from an ACC-equipped Crafter + SUBARU.SUBARU_FORESTER_HYBRID, ] class CarTestRoute(NamedTuple): route: str - car_model: str | None + car_model: Platform | None segment: int | None = None routes = [ - CarTestRoute("efdf9af95e71cd84|2022-05-13--19-03-31", COMMA.BODY), + CarTestRoute("efdf9af95e71cd84|2022-05-13--19-03-31", COMMA.COMMA_BODY), CarTestRoute("0c94aa1e1296d7c6|2021-05-05--19-48-37", CHRYSLER.JEEP_GRAND_CHEROKEE), CarTestRoute("91dfedae61d7bd75|2021-05-22--20-07-52", CHRYSLER.JEEP_GRAND_CHEROKEE_2019), - CarTestRoute("420a8e183f1aed48|2020-03-05--07-15-29", CHRYSLER.PACIFICA_2017_HYBRID), - CarTestRoute("43a685a66291579b|2021-05-27--19-47-29", CHRYSLER.PACIFICA_2018), - CarTestRoute("378472f830ee7395|2021-05-28--07-38-43", CHRYSLER.PACIFICA_2018_HYBRID), - CarTestRoute("8190c7275a24557b|2020-01-29--08-33-58", CHRYSLER.PACIFICA_2019_HYBRID), - 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("420a8e183f1aed48|2020-03-05--07-15-29", CHRYSLER.CHRYSLER_PACIFICA_2017_HYBRID), + CarTestRoute("43a685a66291579b|2021-05-27--19-47-29", CHRYSLER.CHRYSLER_PACIFICA_2018), + CarTestRoute("378472f830ee7395|2021-05-28--07-38-43", CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID), + CarTestRoute("8190c7275a24557b|2020-01-29--08-33-58", CHRYSLER.CHRYSLER_PACIFICA_2019_HYBRID), + CarTestRoute("3d84727705fecd04|2021-05-25--08-38-56", CHRYSLER.CHRYSLER_PACIFICA_2020), + CarTestRoute("221c253375af4ee9|2022-06-15--18-38-24", CHRYSLER.RAM_1500_5TH_GEN), + CarTestRoute("8fb5eabf914632ae|2022-08-04--17-28-53", CHRYSLER.RAM_HD_5TH_GEN, 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), - CarTestRoute("62241b0c7fea4589|2022-09-01--15-32-49", FORD.EXPLORER_MK6), - CarTestRoute("e886087f430e7fe7|2023-06-16--23-06-36", FORD.FOCUS_MK4), - CarTestRoute("bd37e43731e5964b|2023-04-30--10-42-26", FORD.MAVERICK_MK1), - CarTestRoute("112e4d6e0cad05e1|2023-11-14--08-21-43", FORD.F_150_LIGHTNING_MK1), - CarTestRoute("83a4e056c7072678|2023-11-13--16-51-33", FORD.MUSTANG_MACH_E_MK1), + CarTestRoute("54827bf84c38b14f|2023-01-25--14-14-11", FORD.FORD_BRONCO_SPORT_MK1), + CarTestRoute("f8eaaccd2a90aef8|2023-05-04--15-10-09", FORD.FORD_ESCAPE_MK4), + CarTestRoute("62241b0c7fea4589|2022-09-01--15-32-49", FORD.FORD_EXPLORER_MK6), + CarTestRoute("e886087f430e7fe7|2023-06-16--23-06-36", FORD.FORD_FOCUS_MK4), + CarTestRoute("bd37e43731e5964b|2023-04-30--10-42-26", FORD.FORD_MAVERICK_MK1), + CarTestRoute("112e4d6e0cad05e1|2023-11-14--08-21-43", FORD.FORD_F_150_LIGHTNING_MK1), + CarTestRoute("83a4e056c7072678|2023-11-13--16-51-33", FORD.FORD_MUSTANG_MACH_E_MK1), + CarTestRoute("37998aa0fade36ab/00000000--48f927c4f5", FORD.FORD_RANGER_MK2), #TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION), - CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.ACADIA), + CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.GMC_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), - CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT), - CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV, segment=1), - CarTestRoute("555d4087cf86aa91|2022-12-02--12-15-07", GM.BOLT_EUV, segment=14), # Bolt EV - CarTestRoute("38aa7da107d5d252|2022-08-15--16-01-12", GM.SILVERADO), - CarTestRoute("5085c761395d1fe6|2023-04-07--18-20-06", GM.TRAILBLAZER), + CarTestRoute("e746f59bc96fd789|2024-01-31--22-25-58", GM.CHEVROLET_EQUINOX), + CarTestRoute("ef8f2185104d862e|2023-02-09--18-37-13", GM.CADILLAC_ESCALADE), + CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.CADILLAC_ESCALADE_ESV), + CarTestRoute("168f8b3be57f66ae|2023-09-12--21-44-42", GM.CADILLAC_ESCALADE_ESV_2019), + CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.CHEVROLET_VOLT), + CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.CHEVROLET_BOLT_EUV, segment=1), + CarTestRoute("555d4087cf86aa91|2022-12-02--12-15-07", GM.CHEVROLET_BOLT_EUV, segment=14), # Bolt EV + CarTestRoute("38aa7da107d5d252|2022-08-15--16-01-12", GM.CHEVROLET_SILVERADO), + CarTestRoute("5085c761395d1fe6|2023-04-07--18-20-06", GM.CHEVROLET_TRAILBLAZER), CarTestRoute("0e7a2ba168465df5|2020-10-18--14-14-22", HONDA.ACURA_RDX_3G), - CarTestRoute("a74b011b32b51b56|2020-07-26--17-09-36", HONDA.CIVIC), - CarTestRoute("a859a044a447c2b0|2020-03-03--18-42-45", HONDA.CRV_EU), - CarTestRoute("68aac44ad69f838e|2021-05-18--20-40-52", HONDA.CRV), - CarTestRoute("14fed2e5fa0aa1a5|2021-05-25--14-59-42", HONDA.CRV_HYBRID), - CarTestRoute("52f3e9ae60c0d886|2021-05-23--15-59-43", HONDA.FIT), - CarTestRoute("2c4292a5cd10536c|2021-08-19--21-32-15", HONDA.FREED), - CarTestRoute("03be5f2fd5c508d1|2020-04-19--18-44-15", HONDA.HRV), - CarTestRoute("320098ff6c5e4730|2023-04-13--17-47-46", HONDA.HRV_3G), + CarTestRoute("a74b011b32b51b56|2020-07-26--17-09-36", HONDA.HONDA_CIVIC), + CarTestRoute("a859a044a447c2b0|2020-03-03--18-42-45", HONDA.HONDA_CRV_EU), + CarTestRoute("68aac44ad69f838e|2021-05-18--20-40-52", HONDA.HONDA_CRV), + CarTestRoute("14fed2e5fa0aa1a5|2021-05-25--14-59-42", HONDA.HONDA_CRV_HYBRID), + CarTestRoute("52f3e9ae60c0d886|2021-05-23--15-59-43", HONDA.HONDA_FIT), + CarTestRoute("2c4292a5cd10536c|2021-08-19--21-32-15", HONDA.HONDA_FREED), + CarTestRoute("03be5f2fd5c508d1|2020-04-19--18-44-15", HONDA.HONDA_HRV), + CarTestRoute("320098ff6c5e4730|2023-04-13--17-47-46", HONDA.HONDA_HRV_3G), + CarTestRoute("147613502316e718/00000001--dd141a3140", HONDA.HONDA_HRV_3G), # Brazilian model CarTestRoute("917b074700869333|2021-05-24--20-40-20", HONDA.ACURA_ILX), - CarTestRoute("08a3deb07573f157|2020-03-06--16-11-19", HONDA.ACCORD), # 1.5T - CarTestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.ACCORD), # 2.0T - CarTestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.ACCORD), # 2021 with new style HUD msgs - CarTestRoute("07585b0da3c88459|2021-05-26--18-52-04", HONDA.ACCORDH), - CarTestRoute("f29e2b57a55e7ad5|2021-03-24--20-52-38", HONDA.ACCORDH), # 2021 with new style HUD msgs - CarTestRoute("1ad763dd22ef1a0e|2020-02-29--18-37-03", HONDA.CRV_5G), - CarTestRoute("0a96f86fcfe35964|2020-02-05--07-25-51", HONDA.ODYSSEY), - CarTestRoute("d83f36766f8012a5|2020-02-05--18-42-21", HONDA.CIVIC_BOSCH_DIESEL), - CarTestRoute("f0890d16a07a236b|2021-05-25--17-27-22", HONDA.INSIGHT), - CarTestRoute("07d37d27996096b6|2020-03-04--21-57-27", HONDA.PILOT), - CarTestRoute("684e8f96bd491a0e|2021-11-03--11-08-42", HONDA.PILOT), # Passport - CarTestRoute("0a78dfbacc8504ef|2020-03-04--13-29-55", HONDA.CIVIC_BOSCH), + CarTestRoute("08a3deb07573f157|2020-03-06--16-11-19", HONDA.HONDA_ACCORD), # 1.5T + CarTestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.HONDA_ACCORD), # 2.0T + CarTestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.HONDA_ACCORD), # 2021 with new style HUD msgs + CarTestRoute("07585b0da3c88459|2021-05-26--18-52-04", HONDA.HONDA_ACCORD), # hybrid + CarTestRoute("f29e2b57a55e7ad5|2021-03-24--20-52-38", HONDA.HONDA_ACCORD), # hybrid, 2021 with new style HUD msgs + CarTestRoute("1ad763dd22ef1a0e|2020-02-29--18-37-03", HONDA.HONDA_CRV_5G), + CarTestRoute("0a96f86fcfe35964|2020-02-05--07-25-51", HONDA.HONDA_ODYSSEY), + CarTestRoute("d83f36766f8012a5|2020-02-05--18-42-21", HONDA.HONDA_CIVIC_BOSCH_DIESEL), + CarTestRoute("f0890d16a07a236b|2021-05-25--17-27-22", HONDA.HONDA_INSIGHT), + CarTestRoute("07d37d27996096b6|2020-03-04--21-57-27", HONDA.HONDA_PILOT), + CarTestRoute("684e8f96bd491a0e|2021-11-03--11-08-42", HONDA.HONDA_PILOT), # Passport + CarTestRoute("0a78dfbacc8504ef|2020-03-04--13-29-55", HONDA.HONDA_CIVIC_BOSCH), CarTestRoute("f34a60d68d83b1e5|2020-10-06--14-35-55", HONDA.ACURA_RDX), - CarTestRoute("54fd8451b3974762|2021-04-01--14-50-10", HONDA.RIDGELINE), + CarTestRoute("54fd8451b3974762|2021-04-01--14-50-10", HONDA.HONDA_RIDGELINE), CarTestRoute("2d5808fae0b38ac6|2021-09-01--17-14-11", HONDA.HONDA_E), - CarTestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.CIVIC_2022), + CarTestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.HONDA_CIVIC_2022), - CarTestRoute("87d7f06ade479c2e|2023-09-11--23-30-11", HYUNDAI.AZERA_6TH_GEN), - CarTestRoute("66189dd8ec7b50e6|2023-09-20--07-02-12", HYUNDAI.AZERA_HEV_6TH_GEN), + CarTestRoute("87d7f06ade479c2e|2023-09-11--23-30-11", HYUNDAI.HYUNDAI_AZERA_6TH_GEN), + CarTestRoute("66189dd8ec7b50e6|2023-09-20--07-02-12", HYUNDAI.HYUNDAI_AZERA_HEV_6TH_GEN), CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS), CarTestRoute("b5d6dc830ad63071|2022-12-12--21-28-25", HYUNDAI.GENESIS_GV60_EV_1ST_GEN, segment=12), CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70), CarTestRoute("ca4de5b12321bd98|2022-10-18--21-15-59", HYUNDAI.GENESIS_GV70_1ST_GEN), CarTestRoute("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80), - CarTestRoute("0bbe367c98fa1538|2023-09-16--00-16-49", HYUNDAI.CUSTIN_1ST_GEN), - CarTestRoute("f0709d2bc6ca451f|2022-10-15--08-13-54", HYUNDAI.SANTA_CRUZ_1ST_GEN), - CarTestRoute("4dbd55df87507948|2022-03-01--09-45-38", HYUNDAI.SANTA_FE), - CarTestRoute("bf43d9df2b660eb0|2021-09-23--14-16-37", HYUNDAI.SANTA_FE_2022), - CarTestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022), - CarTestRoute("656ac0d830792fcc|2021-12-28--14-45-56", HYUNDAI.SANTA_FE_PHEV_2022, segment=1), + CarTestRoute("0bbe367c98fa1538|2023-09-16--00-16-49", HYUNDAI.HYUNDAI_CUSTIN_1ST_GEN), + CarTestRoute("f0709d2bc6ca451f|2022-10-15--08-13-54", HYUNDAI.HYUNDAI_SANTA_CRUZ_1ST_GEN), + CarTestRoute("4dbd55df87507948|2022-03-01--09-45-38", HYUNDAI.HYUNDAI_SANTA_FE), + CarTestRoute("bf43d9df2b660eb0|2021-09-23--14-16-37", HYUNDAI.HYUNDAI_SANTA_FE_2022), + CarTestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.HYUNDAI_SANTA_FE_HEV_2022), + CarTestRoute("656ac0d830792fcc|2021-12-28--14-45-56", HYUNDAI.HYUNDAI_SANTA_FE_PHEV_2022, segment=1), CarTestRoute("de59124955b921d8|2023-06-24--00-12-50", HYUNDAI.KIA_CARNIVAL_4TH_GEN), CarTestRoute("409c9409979a8abc|2023-07-11--09-06-44", HYUNDAI.KIA_CARNIVAL_4TH_GEN), # Chinese model CarTestRoute("e0e98335f3ebc58f|2021-03-07--16-38-29", HYUNDAI.KIA_CEED), @@ -117,36 +120,36 @@ routes = [ CarTestRoute("f9716670b2481438|2023-08-23--14-49-50", HYUNDAI.KIA_OPTIMA_H), CarTestRoute("6a42c1197b2a8179|2023-09-21--10-23-44", HYUNDAI.KIA_OPTIMA_H_G4_FL), CarTestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS), - CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA), - CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.SONATA_LF), - CarTestRoute("c344fd2492c7a9d2|2023-12-11--09-03-23", HYUNDAI.STARIA_4TH_GEN), - CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON), - CarTestRoute("db68bbe12250812c|2022-12-05--00-54-12", HYUNDAI.TUCSON_4TH_GEN), # 2023 - CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_4TH_GEN), # hybrid + CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.HYUNDAI_SONATA), + CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.HYUNDAI_SONATA_LF), + CarTestRoute("c344fd2492c7a9d2|2023-12-11--09-03-23", HYUNDAI.HYUNDAI_STARIA_4TH_GEN), + CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.HYUNDAI_TUCSON), + CarTestRoute("db68bbe12250812c|2022-12-05--00-54-12", HYUNDAI.HYUNDAI_TUCSON_4TH_GEN), # 2023 + CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.HYUNDAI_TUCSON_4TH_GEN), # hybrid CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO), CarTestRoute("1d0d000db3370fd0|2023-01-04--22-28-42", HYUNDAI.KIA_SORENTO_4TH_GEN, segment=5), CarTestRoute("fc19648042eb6896|2023-08-16--11-43-27", HYUNDAI.KIA_SORENTO_HEV_4TH_GEN, segment=14), CarTestRoute("628935d7d3e5f4f7|2022-11-30--01-12-46", HYUNDAI.KIA_SORENTO_HEV_4TH_GEN), # plug-in hybrid - CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.PALISADE), - CarTestRoute("05a8f0197fdac372|2022-10-19--14-14-09", HYUNDAI.IONIQ_5), # HDA2 - CarTestRoute("eb4eae1476647463|2023-08-26--18-07-04", HYUNDAI.IONIQ_6, segment=6), # HDA2 - CarTestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.IONIQ_PHEV_2019), - CarTestRoute("fa8db5869167f821|2021-06-10--22-50-10", HYUNDAI.IONIQ_PHEV), - CarTestRoute("e1107f9d04dfb1e2|2023-09-05--22-32-12", HYUNDAI.IONIQ_PHEV), # openpilot longitudinal enabled - CarTestRoute("2c5cf2dd6102e5da|2020-12-17--16-06-44", HYUNDAI.IONIQ_EV_2020), - CarTestRoute("610ebb9faaad6b43|2020-06-13--15-28-36", HYUNDAI.IONIQ_EV_LTD), - CarTestRoute("2c5cf2dd6102e5da|2020-06-26--16-00-08", HYUNDAI.IONIQ), - CarTestRoute("012c95f06918eca4|2023-01-15--11-19-36", HYUNDAI.IONIQ), # openpilot longitudinal enabled - CarTestRoute("ab59fe909f626921|2021-10-18--18-34-28", HYUNDAI.IONIQ_HEV_2022), - CarTestRoute("22d955b2cd499c22|2020-08-10--19-58-21", HYUNDAI.KONA), - CarTestRoute("efc48acf44b1e64d|2021-05-28--21-05-04", HYUNDAI.KONA_EV), - CarTestRoute("f90d3cd06caeb6fa|2023-09-06--17-15-47", HYUNDAI.KONA_EV), # openpilot longitudinal enabled - CarTestRoute("ff973b941a69366f|2022-07-28--22-01-19", HYUNDAI.KONA_EV_2022, segment=11), - CarTestRoute("1618132d68afc876|2023-08-27--09-32-14", HYUNDAI.KONA_EV_2ND_GEN, segment=13), - CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV), + CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.HYUNDAI_PALISADE), + CarTestRoute("05a8f0197fdac372|2022-10-19--14-14-09", HYUNDAI.HYUNDAI_IONIQ_5), # HDA2 + CarTestRoute("eb4eae1476647463|2023-08-26--18-07-04", HYUNDAI.HYUNDAI_IONIQ_6, segment=6), # HDA2 + CarTestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.HYUNDAI_IONIQ_PHEV_2019), + CarTestRoute("fa8db5869167f821|2021-06-10--22-50-10", HYUNDAI.HYUNDAI_IONIQ_PHEV), + CarTestRoute("e1107f9d04dfb1e2|2023-09-05--22-32-12", HYUNDAI.HYUNDAI_IONIQ_PHEV), # openpilot longitudinal enabled + CarTestRoute("2c5cf2dd6102e5da|2020-12-17--16-06-44", HYUNDAI.HYUNDAI_IONIQ_EV_2020), + CarTestRoute("610ebb9faaad6b43|2020-06-13--15-28-36", HYUNDAI.HYUNDAI_IONIQ_EV_LTD), + CarTestRoute("2c5cf2dd6102e5da|2020-06-26--16-00-08", HYUNDAI.HYUNDAI_IONIQ), + CarTestRoute("012c95f06918eca4|2023-01-15--11-19-36", HYUNDAI.HYUNDAI_IONIQ), # openpilot longitudinal enabled + CarTestRoute("ab59fe909f626921|2021-10-18--18-34-28", HYUNDAI.HYUNDAI_IONIQ_HEV_2022), + CarTestRoute("22d955b2cd499c22|2020-08-10--19-58-21", HYUNDAI.HYUNDAI_KONA), + CarTestRoute("efc48acf44b1e64d|2021-05-28--21-05-04", HYUNDAI.HYUNDAI_KONA_EV), + CarTestRoute("f90d3cd06caeb6fa|2023-09-06--17-15-47", HYUNDAI.HYUNDAI_KONA_EV), # openpilot longitudinal enabled + CarTestRoute("ff973b941a69366f|2022-07-28--22-01-19", HYUNDAI.HYUNDAI_KONA_EV_2022, segment=11), + CarTestRoute("1618132d68afc876|2023-08-27--09-32-14", HYUNDAI.HYUNDAI_KONA_EV_2ND_GEN, segment=13), + CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.HYUNDAI_KONA_HEV), CarTestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER), CarTestRoute("5b50b883a4259afb|2022-11-09--15-00-42", HYUNDAI.KIA_STINGER_2022), - CarTestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.VELOSTER), + CarTestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.HYUNDAI_VELOSTER), CarTestRoute("d545129f3ca90f28|2022-10-19--09-22-54", HYUNDAI.KIA_EV6), # HDA2 CarTestRoute("68d6a96e703c00c9|2022-09-10--16-09-39", HYUNDAI.KIA_EV6), # HDA1 CarTestRoute("9b25e8c1484a1b67|2023-04-13--10-41-45", HYUNDAI.KIA_EV6), @@ -163,38 +166,38 @@ routes = [ CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN), CarTestRoute("09559f1fcaed4704|2023-11-16--02-24-57", HYUNDAI.KIA_SPORTAGE_5TH_GEN), # openpilot longitudinal CarTestRoute("b3537035ffe6a7d6|2022-10-17--15-23-49", HYUNDAI.KIA_SPORTAGE_5TH_GEN), # hybrid - CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA), - CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA_GT_I30), - CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021), - CarTestRoute("715ac05b594e9c59|2021-06-20--16-21-07", HYUNDAI.ELANTRA_HEV_2021), - CarTestRoute("7120aa90bbc3add7|2021-08-02--07-12-31", HYUNDAI.SONATA_HYBRID), - CarTestRoute("a6310918f9699ef5/2024-02-28--14-55-10", HYUNDAI.GENESIS_G70_1ST_GEN_FL), + CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.HYUNDAI_ELANTRA), + CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.HYUNDAI_ELANTRA_GT_I30), + CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.HYUNDAI_ELANTRA_2021), + CarTestRoute("715ac05b594e9c59|2021-06-20--16-21-07", HYUNDAI.HYUNDAI_ELANTRA_HEV_2021), + CarTestRoute("7120aa90bbc3add7|2021-08-02--07-12-31", HYUNDAI.HYUNDAI_SONATA_HYBRID), + CarTestRoute("a6310918f9699ef5/2024-02-28--14-55-10", HYUNDAI.GENESIS_G70_1ST_GEN_FL), # 3.3T CarTestRoute("715ac05b594e9c59|2021-10-27--23-24-56", HYUNDAI.GENESIS_G70_2020), CarTestRoute("6b0d44d22df18134|2023-05-06--10-36-55", HYUNDAI.GENESIS_GV80), - CarTestRoute("00c829b1b7613dea|2021-06-24--09-10-10", TOYOTA.ALPHARD_TSS2), - CarTestRoute("912119ebd02c7a42|2022-03-19--07-24-50", TOYOTA.ALPHARD_TSS2), # hybrid - CarTestRoute("000cf3730200c71c|2021-05-24--10-42-05", TOYOTA.AVALON), - CarTestRoute("0bb588106852abb7|2021-05-26--12-22-01", TOYOTA.AVALON_2019), - CarTestRoute("87bef2930af86592|2021-05-30--09-40-54", TOYOTA.AVALON_2019), # hybrid - CarTestRoute("e9966711cfb04ce3|2022-01-11--07-59-43", TOYOTA.AVALON_TSS2), - CarTestRoute("eca1080a91720a54|2022-03-17--13-32-29", TOYOTA.AVALON_TSS2), # hybrid - CarTestRoute("6cdecc4728d4af37|2020-02-23--15-44-18", TOYOTA.CAMRY), - CarTestRoute("2f37c007683e85ba|2023-09-02--14-39-44", TOYOTA.CAMRY), # openpilot longitudinal, with radar CAN filter - CarTestRoute("54034823d30962f5|2021-05-24--06-37-34", TOYOTA.CAMRY), # hybrid - CarTestRoute("3456ad0cd7281b24|2020-12-13--17-45-56", TOYOTA.CAMRY_TSS2), - CarTestRoute("ffccc77938ddbc44|2021-01-04--16-55-41", TOYOTA.CAMRY_TSS2), # hybrid - CarTestRoute("4e45c89c38e8ec4d|2021-05-02--02-49-28", TOYOTA.COROLLA), - 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), - CarTestRoute("ad5a3fa719bc2f83|2023-10-17--19-48-42", TOYOTA.RAV4_TSS2_2023), - CarTestRoute("7e34a988419b5307|2019-12-18--19-13-30", TOYOTA.RAV4_TSS2), # hybrid - CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.RAV4_TSS2_2022), # hybrid + CarTestRoute("00c829b1b7613dea|2021-06-24--09-10-10", TOYOTA.TOYOTA_ALPHARD_TSS2), + CarTestRoute("912119ebd02c7a42|2022-03-19--07-24-50", TOYOTA.TOYOTA_ALPHARD_TSS2), # hybrid + CarTestRoute("000cf3730200c71c|2021-05-24--10-42-05", TOYOTA.TOYOTA_AVALON), + CarTestRoute("0bb588106852abb7|2021-05-26--12-22-01", TOYOTA.TOYOTA_AVALON_2019), + CarTestRoute("87bef2930af86592|2021-05-30--09-40-54", TOYOTA.TOYOTA_AVALON_2019), # hybrid + CarTestRoute("e9966711cfb04ce3|2022-01-11--07-59-43", TOYOTA.TOYOTA_AVALON_TSS2), + CarTestRoute("eca1080a91720a54|2022-03-17--13-32-29", TOYOTA.TOYOTA_AVALON_TSS2), # hybrid + CarTestRoute("6cdecc4728d4af37|2020-02-23--15-44-18", TOYOTA.TOYOTA_CAMRY), + CarTestRoute("2f37c007683e85ba|2023-09-02--14-39-44", TOYOTA.TOYOTA_CAMRY), # openpilot longitudinal, with radar CAN filter + CarTestRoute("54034823d30962f5|2021-05-24--06-37-34", TOYOTA.TOYOTA_CAMRY), # hybrid + CarTestRoute("3456ad0cd7281b24|2020-12-13--17-45-56", TOYOTA.TOYOTA_CAMRY_TSS2), + CarTestRoute("ffccc77938ddbc44|2021-01-04--16-55-41", TOYOTA.TOYOTA_CAMRY_TSS2), # hybrid + CarTestRoute("4e45c89c38e8ec4d|2021-05-02--02-49-28", TOYOTA.TOYOTA_COROLLA), + CarTestRoute("5f5afb36036506e4|2019-05-14--02-09-54", TOYOTA.TOYOTA_COROLLA_TSS2), + CarTestRoute("5ceff72287a5c86c|2019-10-19--10-59-02", TOYOTA.TOYOTA_COROLLA_TSS2), # hybrid + CarTestRoute("d2525c22173da58b|2021-04-25--16-47-04", TOYOTA.TOYOTA_PRIUS), + CarTestRoute("b14c5b4742e6fc85|2020-07-28--19-50-11", TOYOTA.TOYOTA_RAV4), + CarTestRoute("32a7df20486b0f70|2020-02-06--16-06-50", TOYOTA.TOYOTA_RAV4H), + CarTestRoute("cdf2f7de565d40ae|2019-04-25--03-53-41", TOYOTA.TOYOTA_RAV4_TSS2), + CarTestRoute("a5c341bb250ca2f0|2022-05-18--16-05-17", TOYOTA.TOYOTA_RAV4_TSS2_2022), + CarTestRoute("ad5a3fa719bc2f83|2023-10-17--19-48-42", TOYOTA.TOYOTA_RAV4_TSS2_2023), + CarTestRoute("7e34a988419b5307|2019-12-18--19-13-30", TOYOTA.TOYOTA_RAV4_TSS2), # hybrid + CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.TOYOTA_RAV4_TSS2_2022), # hybrid CarTestRoute("7a31f030957b9c85|2023-04-01--14-12-51", TOYOTA.LEXUS_ES), CarTestRoute("37041c500fd30100|2020-12-30--12-17-24", TOYOTA.LEXUS_ES), # hybrid CarTestRoute("e6a24be49a6cd46e|2019-10-29--10-52-42", TOYOTA.LEXUS_ES_TSS2), @@ -211,88 +214,88 @@ routes = [ 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), - CarTestRoute("80d16a262e33d57f|2021-05-23--20-01-43", TOYOTA.HIGHLANDER), # hybrid - CarTestRoute("eb6acd681135480d|2019-06-20--20-00-00", TOYOTA.SIENNA), + CarTestRoute("0a302ffddbb3e3d3|2020-02-08--16-19-08", TOYOTA.TOYOTA_HIGHLANDER_TSS2), + CarTestRoute("437e4d2402abf524|2021-05-25--07-58-50", TOYOTA.TOYOTA_HIGHLANDER_TSS2), # hybrid + CarTestRoute("3183cd9b021e89ce|2021-05-25--10-34-44", TOYOTA.TOYOTA_HIGHLANDER), + CarTestRoute("80d16a262e33d57f|2021-05-23--20-01-43", TOYOTA.TOYOTA_HIGHLANDER), # hybrid + CarTestRoute("eb6acd681135480d|2019-06-20--20-00-00", TOYOTA.TOYOTA_SIENNA), CarTestRoute("2e07163a1ba9a780|2019-08-25--13-15-13", TOYOTA.LEXUS_IS), CarTestRoute("649bf2997ada6e3a|2023-08-08--18-04-22", TOYOTA.LEXUS_IS_TSS2), - CarTestRoute("0a0de17a1e6a2d15|2020-09-21--21-24-41", TOYOTA.PRIUS_TSS2), - CarTestRoute("9b36accae406390e|2021-03-30--10-41-38", TOYOTA.MIRAI), - CarTestRoute("cd9cff4b0b26c435|2021-05-13--15-12-39", TOYOTA.CHR), - CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.CHR), # hybrid - 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("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V), + CarTestRoute("0a0de17a1e6a2d15|2020-09-21--21-24-41", TOYOTA.TOYOTA_PRIUS_TSS2), + CarTestRoute("9b36accae406390e|2021-03-30--10-41-38", TOYOTA.TOYOTA_MIRAI), + CarTestRoute("cd9cff4b0b26c435|2021-05-13--15-12-39", TOYOTA.TOYOTA_CHR), + CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.TOYOTA_CHR), # hybrid + CarTestRoute("ea8fbe72b96a185c|2023-02-08--15-11-46", TOYOTA.TOYOTA_CHR_TSS2), + CarTestRoute("ea8fbe72b96a185c|2023-02-22--09-20-34", TOYOTA.TOYOTA_CHR_TSS2), # openpilot longitudinal, with smartDSU + CarTestRoute("6719965b0e1d1737|2023-02-09--22-44-05", TOYOTA.TOYOTA_CHR_TSS2), # hybrid + CarTestRoute("6719965b0e1d1737|2023-08-29--06-40-05", TOYOTA.TOYOTA_CHR_TSS2), # hybrid, openpilot longitudinal, radar disabled + CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.TOYOTA_PRIUS_V), - CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1), - CarTestRoute("2c68dda277d887ac|2021-05-11--15-22-20", VOLKSWAGEN.ATLAS_MK1), - CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.GOLF_MK7), # Stock ACC - CarTestRoute("3cfdec54aa035f3f|2022-10-13--14-58-58", VOLKSWAGEN.GOLF_MK7), # openpilot longitudinal - CarTestRoute("58a7d3b707987d65|2021-03-25--17-26-37", VOLKSWAGEN.JETTA_MK7), - CarTestRoute("4d134e099430fba2|2021-03-26--00-26-06", VOLKSWAGEN.PASSAT_MK8), - CarTestRoute("3cfdec54aa035f3f|2022-07-19--23-45-10", VOLKSWAGEN.PASSAT_NMS), - CarTestRoute("0cd0b7f7e31a3853|2021-11-03--19-30-22", VOLKSWAGEN.POLO_MK6), - CarTestRoute("064d1816e448f8eb|2022-09-29--15-32-34", VOLKSWAGEN.SHARAN_MK2), - CarTestRoute("7d82b2f3a9115f1f|2021-10-21--15-39-42", VOLKSWAGEN.TAOS_MK1), - CarTestRoute("2744c89a8dda9a51|2021-07-24--21-28-06", VOLKSWAGEN.TCROSS_MK1), - CarTestRoute("2cef8a0b898f331a|2021-03-25--20-13-57", VOLKSWAGEN.TIGUAN_MK2), - CarTestRoute("a589dcc642fdb10a|2021-06-14--20-54-26", VOLKSWAGEN.TOURAN_MK2), - CarTestRoute("a459f4556782eba1|2021-09-19--09-48-00", VOLKSWAGEN.TRANSPORTER_T61), - CarTestRoute("0cd0b7f7e31a3853|2021-11-18--00-38-32", VOLKSWAGEN.TROC_MK1), + CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.VOLKSWAGEN_ARTEON_MK1), + CarTestRoute("2c68dda277d887ac|2021-05-11--15-22-20", VOLKSWAGEN.VOLKSWAGEN_ATLAS_MK1), + CarTestRoute("ffcd23abbbd02219|2024-02-28--14-59-38", VOLKSWAGEN.VOLKSWAGEN_CADDY_MK3), + CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.VOLKSWAGEN_GOLF_MK7), # Stock ACC + CarTestRoute("3cfdec54aa035f3f|2022-10-13--14-58-58", VOLKSWAGEN.VOLKSWAGEN_GOLF_MK7), # openpilot longitudinal + CarTestRoute("58a7d3b707987d65|2021-03-25--17-26-37", VOLKSWAGEN.VOLKSWAGEN_JETTA_MK7), + CarTestRoute("4d134e099430fba2|2021-03-26--00-26-06", VOLKSWAGEN.VOLKSWAGEN_PASSAT_MK8), + CarTestRoute("3cfdec54aa035f3f|2022-07-19--23-45-10", VOLKSWAGEN.VOLKSWAGEN_PASSAT_NMS), + CarTestRoute("0cd0b7f7e31a3853|2021-11-03--19-30-22", VOLKSWAGEN.VOLKSWAGEN_POLO_MK6), + CarTestRoute("064d1816e448f8eb|2022-09-29--15-32-34", VOLKSWAGEN.VOLKSWAGEN_SHARAN_MK2), + CarTestRoute("7d82b2f3a9115f1f|2021-10-21--15-39-42", VOLKSWAGEN.VOLKSWAGEN_TAOS_MK1), + CarTestRoute("2744c89a8dda9a51|2021-07-24--21-28-06", VOLKSWAGEN.VOLKSWAGEN_TCROSS_MK1), + CarTestRoute("2cef8a0b898f331a|2021-03-25--20-13-57", VOLKSWAGEN.VOLKSWAGEN_TIGUAN_MK2), + CarTestRoute("a589dcc642fdb10a|2021-06-14--20-54-26", VOLKSWAGEN.VOLKSWAGEN_TOURAN_MK2), + CarTestRoute("a459f4556782eba1|2021-09-19--09-48-00", VOLKSWAGEN.VOLKSWAGEN_TRANSPORTER_T61), + CarTestRoute("0cd0b7f7e31a3853|2021-11-18--00-38-32", VOLKSWAGEN.VOLKSWAGEN_TROC_MK1), CarTestRoute("07667b885add75fd|2021-01-23--19-48-42", VOLKSWAGEN.AUDI_A3_MK3), CarTestRoute("6c6b466346192818|2021-06-06--14-17-47", VOLKSWAGEN.AUDI_Q2_MK1), CarTestRoute("0cd0b7f7e31a3853|2021-12-03--03-12-05", VOLKSWAGEN.AUDI_Q3_MK2), CarTestRoute("8f205bdd11bcbb65|2021-03-26--01-00-17", VOLKSWAGEN.SEAT_ATECA_MK1), - CarTestRoute("fc6b6c9a3471c846|2021-05-27--13-39-56", VOLKSWAGEN.SEAT_LEON_MK3), + CarTestRoute("fc6b6c9a3471c846|2021-05-27--13-39-56", VOLKSWAGEN.SEAT_ATECA_MK1), # Leon CarTestRoute("0bbe367c98fa1538|2023-03-04--17-46-11", VOLKSWAGEN.SKODA_FABIA_MK4), CarTestRoute("12d6ae3057c04b0d|2021-09-15--00-04-07", VOLKSWAGEN.SKODA_KAMIQ_MK1), CarTestRoute("12d6ae3057c04b0d|2021-09-04--21-21-21", VOLKSWAGEN.SKODA_KAROQ_MK1), CarTestRoute("90434ff5d7c8d603|2021-03-15--12-07-31", VOLKSWAGEN.SKODA_KODIAQ_MK1), CarTestRoute("66e5edc3a16459c5|2021-05-25--19-00-29", VOLKSWAGEN.SKODA_OCTAVIA_MK3), - CarTestRoute("026b6d18fba6417f|2021-03-26--09-17-04", VOLKSWAGEN.SKODA_SCALA_MK1), + CarTestRoute("026b6d18fba6417f|2021-03-26--09-17-04", VOLKSWAGEN.SKODA_KAMIQ_MK1), # Scala CarTestRoute("b2e9858e29db492b|2021-03-26--16-58-42", VOLKSWAGEN.SKODA_SUPERB_MK3), - CarTestRoute("3c8f0c502e119c1c|2020-06-30--12-58-02", SUBARU.ASCENT), - CarTestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER), - CarTestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA), - CarTestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020), - CarTestRoute("8de015561e1ea4a0|2023-08-29--17-08-31", SUBARU.IMPREZA), # openpilot longitudinal + CarTestRoute("3c8f0c502e119c1c|2020-06-30--12-58-02", SUBARU.SUBARU_ASCENT), + CarTestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.SUBARU_FORESTER), + CarTestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.SUBARU_IMPREZA), + CarTestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.SUBARU_IMPREZA_2020), + CarTestRoute("8de015561e1ea4a0|2023-08-29--17-08-31", SUBARU.SUBARU_IMPREZA), # openpilot longitudinal # CarTestRoute("c3d1ccb52f5f9d65|2023-07-22--01-23-20", SUBARU.OUTBACK, segment=9), # gen2 longitudinal, eyesight disabled - CarTestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.OUTBACK, segment=10), - CarTestRoute("c56e69bbc74b8fad|2022-08-18--09-43-51", SUBARU.LEGACY, segment=3), - CarTestRoute("f4e3a0c511a076f4|2022-08-04--16-16-48", SUBARU.CROSSTREK_HYBRID, segment=2), - CarTestRoute("7fd1e4f3a33c1673|2022-12-04--15-09-53", SUBARU.FORESTER_2022, segment=4), - CarTestRoute("f3b34c0d2632aa83|2023-07-23--20-43-25", SUBARU.OUTBACK_2023, segment=7), - CarTestRoute("99437cef6d5ff2ee|2023-03-13--21-21-38", SUBARU.ASCENT_2023, segment=7), + CarTestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.SUBARU_OUTBACK, segment=10), + CarTestRoute("c56e69bbc74b8fad|2022-08-18--09-43-51", SUBARU.SUBARU_LEGACY, segment=3), + CarTestRoute("f4e3a0c511a076f4|2022-08-04--16-16-48", SUBARU.SUBARU_CROSSTREK_HYBRID, segment=2), + CarTestRoute("7fd1e4f3a33c1673|2022-12-04--15-09-53", SUBARU.SUBARU_FORESTER_2022, segment=4), + CarTestRoute("f3b34c0d2632aa83|2023-07-23--20-43-25", SUBARU.SUBARU_OUTBACK_2023, segment=7), + CarTestRoute("99437cef6d5ff2ee|2023-03-13--21-21-38", SUBARU.SUBARU_ASCENT_2023, segment=7), # Pre-global, dashcam - CarTestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL), - CarTestRoute("df5ca7660000fba8|2020-06-16--17-37-19", SUBARU.LEGACY_PREGLOBAL), - CarTestRoute("5ab784f361e19b78|2020-06-08--16-30-41", SUBARU.OUTBACK_PREGLOBAL), - CarTestRoute("e19eb5d5353b1ac1|2020-08-09--14-37-56", SUBARU.OUTBACK_PREGLOBAL_2018), + CarTestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.SUBARU_FORESTER_PREGLOBAL), + CarTestRoute("df5ca7660000fba8|2020-06-16--17-37-19", SUBARU.SUBARU_LEGACY_PREGLOBAL), + CarTestRoute("5ab784f361e19b78|2020-06-08--16-30-41", SUBARU.SUBARU_OUTBACK_PREGLOBAL), + CarTestRoute("e19eb5d5353b1ac1|2020-08-09--14-37-56", SUBARU.SUBARU_OUTBACK_PREGLOBAL_2018), - CarTestRoute("fbbfa6af821552b9|2020-03-03--08-09-43", NISSAN.XTRAIL), - CarTestRoute("5b7c365c50084530|2020-03-25--22-10-13", NISSAN.LEAF), - CarTestRoute("22c3dcce2dd627eb|2020-12-30--16-38-48", NISSAN.LEAF_IC), - CarTestRoute("059ab9162e23198e|2020-05-30--09-41-01", NISSAN.ROGUE), - CarTestRoute("b72d3ec617c0a90f|2020-12-11--15-38-17", NISSAN.ALTIMA), + CarTestRoute("fbbfa6af821552b9|2020-03-03--08-09-43", NISSAN.NISSAN_XTRAIL), + CarTestRoute("5b7c365c50084530|2020-03-25--22-10-13", NISSAN.NISSAN_LEAF), + CarTestRoute("22c3dcce2dd627eb|2020-12-30--16-38-48", NISSAN.NISSAN_LEAF_IC), + CarTestRoute("059ab9162e23198e|2020-05-30--09-41-01", NISSAN.NISSAN_ROGUE), + CarTestRoute("b72d3ec617c0a90f|2020-12-11--15-38-17", NISSAN.NISSAN_ALTIMA), - CarTestRoute("32a319f057902bb3|2020-04-27--15-18-58", MAZDA.CX5), - CarTestRoute("10b5a4b380434151|2020-08-26--17-11-45", MAZDA.CX9), - CarTestRoute("74f1038827005090|2020-08-26--20-05-50", MAZDA.MAZDA3), - CarTestRoute("fb53c640f499b73d|2021-06-01--04-17-56", MAZDA.MAZDA6), - CarTestRoute("f6d5b1a9d7a1c92e|2021-07-08--06-56-59", MAZDA.CX9_2021), - CarTestRoute("a4af1602d8e668ac|2022-02-03--12-17-07", MAZDA.CX5_2022), + CarTestRoute("32a319f057902bb3|2020-04-27--15-18-58", MAZDA.MAZDA_CX5), + CarTestRoute("10b5a4b380434151|2020-08-26--17-11-45", MAZDA.MAZDA_CX9), + CarTestRoute("74f1038827005090|2020-08-26--20-05-50", MAZDA.MAZDA_3), + CarTestRoute("fb53c640f499b73d|2021-06-01--04-17-56", MAZDA.MAZDA_6), + CarTestRoute("f6d5b1a9d7a1c92e|2021-07-08--06-56-59", MAZDA.MAZDA_CX9_2021), + CarTestRoute("a4af1602d8e668ac|2022-02-03--12-17-07", MAZDA.MAZDA_CX5_2022), - CarTestRoute("6c14ee12b74823ce|2021-06-30--11-49-02", TESLA.AP1_MODELS), - CarTestRoute("bb50caf5f0945ab1|2021-06-19--17-20-18", TESLA.AP2_MODELS), + CarTestRoute("6c14ee12b74823ce|2021-06-30--11-49-02", TESLA.TESLA_AP1_MODELS), + CarTestRoute("bb50caf5f0945ab1|2021-06-19--17-20-18", TESLA.TESLA_AP2_MODELS), + CarTestRoute("66c1699b7697267d/2024-03-03--13-09-53", TESLA.TESLA_MODELS_RAVEN), # Segments that test specific issues - # Controls mismatch due to interceptor threshold - CarTestRoute("cfb32f0fb91b173b|2022-04-06--14-54-45", HONDA.CIVIC, segment=21), # Controls mismatch due to standstill threshold - CarTestRoute("bec2dcfde6a64235|2022-04-08--14-21-32", HONDA.CRV_HYBRID, segment=22), + CarTestRoute("bec2dcfde6a64235|2022-04-08--14-21-32", HONDA.HONDA_CRV_HYBRID, segment=22), ] diff --git a/selfdrive/car/tests/test_can_fingerprint.py b/selfdrive/car/tests/test_can_fingerprint.py old mode 100755 new mode 100644 index 63621b459d..f236986d8e --- a/selfdrive/car/tests/test_can_fingerprint.py +++ b/selfdrive/car/tests/test_can_fingerprint.py @@ -1,13 +1,11 @@ -#!/usr/bin/env python3 from parameterized import parameterized -import unittest from cereal import log, messaging from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, can_fingerprint from openpilot.selfdrive.car.fingerprints import _FINGERPRINTS as FINGERPRINTS -class TestCanFingerprint(unittest.TestCase): +class TestCanFingerprint: @parameterized.expand(list(FINGERPRINTS.items())) def test_can_fingerprint(self, car_model, fingerprints): """Tests online fingerprinting function on offline fingerprints""" @@ -21,14 +19,14 @@ class TestCanFingerprint(unittest.TestCase): empty_can = messaging.new_message('can', 0) car_fingerprint, finger = can_fingerprint(lambda: next(fingerprint_iter, empty_can)) # noqa: B023 - self.assertEqual(car_fingerprint, car_model) - self.assertEqual(finger[0], fingerprint) - self.assertEqual(finger[1], fingerprint) - self.assertEqual(finger[2], {}) + assert car_fingerprint == car_model + assert finger[0] == fingerprint + assert finger[1] == fingerprint + assert finger[2] == {} - def test_timing(self): + def test_timing(self, subtests): # just pick any CAN fingerprinting car - car_model = 'CHEVROLET BOLT EUV 2022' + car_model = "CHEVROLET_BOLT_EUV" fingerprint = FINGERPRINTS[car_model][0] cases = [] @@ -50,7 +48,7 @@ class TestCanFingerprint(unittest.TestCase): cases.append((FRAME_FINGERPRINT * 2, None, can)) for expected_frames, car_model, can in cases: - with self.subTest(expected_frames=expected_frames, car_model=car_model): + with subtests.test(expected_frames=expected_frames, car_model=car_model): frames = 0 def test(): @@ -59,9 +57,5 @@ class TestCanFingerprint(unittest.TestCase): return can # noqa: B023 car_fingerprint, _ = can_fingerprint(test) - self.assertEqual(car_fingerprint, car_model) - self.assertEqual(frames, expected_frames + 2) # TODO: fix extra frames - - -if __name__ == "__main__": - unittest.main() + assert car_fingerprint == car_model + assert frames == expected_frames + 2# TODO: fix extra frames diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py old mode 100755 new mode 100644 index a454f616cb..a6f74d685f --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -1,7 +1,5 @@ -#!/usr/bin/env python3 import os import math -import unittest import hypothesis.strategies as st from hypothesis import Phase, given, settings import importlib @@ -12,7 +10,7 @@ from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car import gen_empty_fingerprint 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.fw_versions import FW_VERSIONS, FW_QUERY_CONFIGS 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 @@ -20,9 +18,12 @@ 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()}) +ALL_ECUS = {ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()} +ALL_ECUS |= {ecu for config in FW_QUERY_CONFIGS.values() for ecu in config.extra_ecus} -MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '20')) +ALL_REQUESTS = {tuple(r.request) for config in FW_QUERY_CONFIGS.values() for r in config.requests} + +MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '40')) def get_fuzzy_car_interface_args(draw: DrawType) -> dict: @@ -32,7 +33,7 @@ def get_fuzzy_car_interface_args(draw: DrawType) -> dict: gen_empty_fingerprint()}) # only pick from possible ecus to reduce search space - car_fw_strategy = st.lists(st.sampled_from(ALL_ECUS)) + car_fw_strategy = st.lists(st.sampled_from(sorted(ALL_ECUS))) params_strategy = st.fixed_dictionaries({ 'fingerprints': fingerprint_strategy, @@ -41,11 +42,13 @@ def get_fuzzy_car_interface_args(draw: DrawType) -> dict: }) params: dict = draw(params_strategy) - params['car_fw'] = [car.CarParams.CarFw(ecu=fw[0], address=fw[1], subAddress=fw[2] or 0) for fw in params['car_fw']] + params['car_fw'] = [car.CarParams.CarFw(ecu=fw[0], address=fw[1], subAddress=fw[2] or 0, + request=draw(st.sampled_from(sorted(ALL_REQUESTS)))) + for fw in params['car_fw']] return params -class TestCarInterfaces(unittest.TestCase): +class TestCarInterfaces: # 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())]) @@ -63,32 +66,27 @@ class TestCarInterfaces(unittest.TestCase): assert car_params assert car_interface - self.assertGreater(car_params.mass, 1) - self.assertGreater(car_params.wheelbase, 0) + assert car_params.mass > 1 + assert car_params.wheelbase > 0 # centerToFront is center of gravity to front wheels, assert a reasonable range - self.assertTrue(car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7) - self.assertGreater(car_params.maxLateralAccel, 0) + assert car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7 + assert car_params.maxLateralAccel > 0 # Longitudinal sanity checks - self.assertEqual(len(car_params.longitudinalTuning.kpV), len(car_params.longitudinalTuning.kpBP)) - self.assertEqual(len(car_params.longitudinalTuning.kiV), len(car_params.longitudinalTuning.kiBP)) - self.assertEqual(len(car_params.longitudinalTuning.deadzoneV), len(car_params.longitudinalTuning.deadzoneBP)) - - # If we're using the interceptor for gasPressed, we should be commanding gas with it - if car_params.enableGasInterceptor: - self.assertTrue(car_params.openpilotLongitudinalControl) + assert len(car_params.longitudinalTuning.kpV) == len(car_params.longitudinalTuning.kpBP) + assert len(car_params.longitudinalTuning.kiV) == len(car_params.longitudinalTuning.kiBP) # Lateral sanity checks if car_params.steerControlType != car.CarParams.SteerControlType.angle: tune = car_params.lateralTuning if tune.which() == 'pid': - self.assertTrue(not math.isnan(tune.pid.kf) and tune.pid.kf > 0) - self.assertTrue(len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP)) - self.assertTrue(len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP)) + assert not math.isnan(tune.pid.kf) and tune.pid.kf > 0 + assert len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP) + assert len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP) elif tune.which() == 'torque': - self.assertTrue(not math.isnan(tune.torque.kf) and tune.torque.kf > 0) - self.assertTrue(not math.isnan(tune.torque.friction) and tune.torque.friction > 0) + assert not math.isnan(tune.torque.kf) and tune.torque.kf > 0 + assert not math.isnan(tune.torque.friction) and tune.torque.friction > 0 cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True) # Run car interface @@ -96,16 +94,14 @@ class TestCarInterfaces(unittest.TestCase): CC = car.CarControl.new_message(**cc_msg) for _ in range(10): car_interface.update(CC, []) - car_interface.apply(CC, now_nanos) - car_interface.apply(CC, now_nanos) + car_interface.apply(CC.as_reader(), now_nanos) now_nanos += DT_CTRL * 1e9 # 10 ms CC = car.CarControl.new_message(**cc_msg) CC.enabled = True for _ in range(10): car_interface.update(CC, []) - car_interface.apply(CC, now_nanos) - car_interface.apply(CC, now_nanos) + car_interface.apply(CC.as_reader(), now_nanos) now_nanos += DT_CTRL * 1e9 # 10ms # Test controller initialization @@ -134,33 +130,29 @@ class TestCarInterfaces(unittest.TestCase): if not car_params.radarUnavailable and radar_interface.rcp is not None: cans = [messaging.new_message('can', 1).to_bytes() for _ in range(5)] rr = radar_interface.update(cans) - self.assertTrue(rr is None or len(rr.errors) > 0) + assert rr is None or len(rr.errors) > 0 def test_interface_attrs(self): """Asserts basic behavior of interface attribute getter""" num_brands = len(get_interface_attr('CAR')) - self.assertGreaterEqual(num_brands, 13) + assert num_brands >= 13 # Should return value for all brands when not combining, even if attribute doesn't exist ret = get_interface_attr('FAKE_ATTR') - self.assertEqual(len(ret), num_brands) + assert len(ret) == num_brands # Make sure we can combine dicts ret = get_interface_attr('DBC', combine_brands=True) - self.assertGreaterEqual(len(ret), 160) + assert len(ret) >= 160 # We don't support combining non-dicts ret = get_interface_attr('CAR', combine_brands=True) - self.assertEqual(len(ret), 0) + assert len(ret) == 0 # If brand has None value, it shouldn't return when ignore_none=True is specified none_brands = {b for b, v in get_interface_attr('FINGERPRINTS').items() if v is None} - self.assertGreaterEqual(len(none_brands), 1) + assert len(none_brands) >= 1 ret = get_interface_attr('FINGERPRINTS', ignore_none=True) none_brands_in_ret = none_brands.intersection(ret) - self.assertEqual(len(none_brands_in_ret), 0, f'Brands with None values in ignore_none=True result: {none_brands_in_ret}') - - -if __name__ == "__main__": - unittest.main() + assert len(none_brands_in_ret) == 0, f'Brands with None values in ignore_none=True result: {none_brands_in_ret}' diff --git a/selfdrive/car/tests/test_docs.py b/selfdrive/car/tests/test_docs.py old mode 100755 new mode 100644 index 0cafb508f7..40ad07b283 --- a/selfdrive/car/tests/test_docs.py +++ b/selfdrive/car/tests/test_docs.py @@ -1,96 +1,91 @@ -#!/usr/bin/env python3 from collections import defaultdict import os +import pytest import re -import unittest from openpilot.common.basedir import BASEDIR -from openpilot.selfdrive.car.car_helpers import interfaces, get_interface_attr -from openpilot.selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_all_car_info +from openpilot.selfdrive.car.car_helpers import interfaces +from openpilot.selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_all_car_docs from openpilot.selfdrive.car.docs_definitions import Cable, Column, PartType, Star from openpilot.selfdrive.car.honda.values import CAR as HONDA -from openpilot.selfdrive.debug.dump_car_info import dump_car_info -from openpilot.selfdrive.debug.print_docs_diff import print_car_info_diff +from openpilot.selfdrive.car.values import PLATFORMS +from openpilot.selfdrive.debug.dump_car_docs import dump_car_docs +from openpilot.selfdrive.debug.print_docs_diff import print_car_docs_diff -class TestCarDocs(unittest.TestCase): +class TestCarDocs: @classmethod - def setUpClass(cls): - cls.all_cars = get_all_car_info() + def setup_class(cls): + cls.all_cars = get_all_car_docs() def test_generator(self): generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE) with open(CARS_MD_OUT) as f: current_cars_md = f.read() - self.assertEqual(generated_cars_md, current_cars_md, - "Run selfdrive/car/docs.py to update the compatibility documentation") + assert generated_cars_md == current_cars_md, "Run selfdrive/car/docs.py to update the compatibility documentation" def test_docs_diff(self): dump_path = os.path.join(BASEDIR, "selfdrive", "car", "tests", "cars_dump") - dump_car_info(dump_path) - print_car_info_diff(dump_path) + dump_car_docs(dump_path) + print_car_docs_diff(dump_path) os.remove(dump_path) - def test_duplicate_years(self): + def test_duplicate_years(self, subtests): make_model_years = defaultdict(list) for car in self.all_cars: - with self.subTest(car_info_name=car.name): + with subtests.test(car_docs_name=car.name): make_model = (car.make, car.model) for year in car.year_list: - self.assertNotIn(year, make_model_years[make_model], f"{car.name}: Duplicate model year") + assert year not in make_model_years[make_model], f"{car.name}: Duplicate model year" make_model_years[make_model].append(year) - def test_missing_car_info(self): - all_car_info_platforms = get_interface_attr("CAR_INFO", combine_brands=True).keys() + def test_missing_car_docs(self, subtests): + all_car_docs_platforms = [name for name, config in PLATFORMS.items()] for platform in sorted(interfaces.keys()): - with self.subTest(platform=platform): - self.assertTrue(platform in all_car_info_platforms, f"Platform: {platform} doesn't exist in CarInfo") + with subtests.test(platform=platform): + assert platform in all_car_docs_platforms, f"Platform: {platform} doesn't have a CarDocs entry" - def test_naming_conventions(self): + def test_naming_conventions(self, subtests): # Asserts market-standard car naming conventions by brand for car in self.all_cars: - with self.subTest(car=car): + with subtests.test(car=car.name): tokens = car.model.lower().split(" ") if car.car_name == "hyundai": - self.assertNotIn("phev", tokens, "Use `Plug-in Hybrid`") - self.assertNotIn("hev", tokens, "Use `Hybrid`") + assert "phev" not in tokens, "Use `Plug-in Hybrid`" + assert "hev" not in tokens, "Use `Hybrid`" if "plug-in hybrid" in car.model.lower(): - self.assertIn("Plug-in Hybrid", car.model, "Use correct capitalization") + assert "Plug-in Hybrid" in car.model, "Use correct capitalization" if car.make != "Kia": - self.assertNotIn("ev", tokens, "Use `Electric`") + assert "ev" not in tokens, "Use `Electric`" elif car.car_name == "toyota": if "rav4" in tokens: - self.assertIn("RAV4", car.model, "Use correct capitalization") + assert "RAV4" in car.model, "Use correct capitalization" - def test_torque_star(self): + def test_torque_star(self, subtests): # Asserts brand-specific assumptions around steering torque star for car in self.all_cars: - with self.subTest(car=car): + with subtests.test(car=car.name): # honda sanity check, it's the definition of a no torque star - if car.car_fingerprint in (HONDA.ACCORD, HONDA.CIVIC, HONDA.CRV, HONDA.ODYSSEY, HONDA.PILOT): - self.assertEqual(car.row[Column.STEERING_TORQUE], Star.EMPTY, f"{car.name} has full torque star") + if car.car_fingerprint in (HONDA.HONDA_ACCORD, HONDA.HONDA_CIVIC, HONDA.HONDA_CRV, HONDA.HONDA_ODYSSEY, HONDA.HONDA_PILOT): + assert car.row[Column.STEERING_TORQUE] == Star.EMPTY, f"{car.name} has full torque star" elif car.car_name in ("toyota", "hyundai"): - self.assertNotEqual(car.row[Column.STEERING_TORQUE], Star.EMPTY, f"{car.name} has no torque star") + assert car.row[Column.STEERING_TORQUE] != Star.EMPTY, f"{car.name} has no torque star" - def test_year_format(self): + def test_year_format(self, subtests): for car in self.all_cars: - with self.subTest(car=car): - self.assertIsNone(re.search(r"\d{4}-\d{4}", car.name), f"Format years correctly: {car.name}") + with subtests.test(car=car.name): + assert re.search(r"\d{4}-\d{4}", car.name) is None, f"Format years correctly: {car.name}" - def test_harnesses(self): + def test_harnesses(self, subtests): for car in self.all_cars: - with self.subTest(car=car): + with subtests.test(car=car.name): if car.name == "comma body": - raise unittest.SkipTest + pytest.skip() car_part_type = [p.part_type for p in car.car_parts.all_parts()] car_parts = list(car.car_parts.all_parts()) - self.assertTrue(len(car_parts) > 0, f"Need to specify car parts: {car.name}") - self.assertTrue(car_part_type.count(PartType.connector) == 1, f"Need to specify one harness connector: {car.name}") - self.assertTrue(car_part_type.count(PartType.mount) == 1, f"Need to specify one mount: {car.name}") - self.assertTrue(Cable.right_angle_obd_c_cable_1_5ft in car_parts, f"Need to specify a right angle OBD-C cable (1.5ft): {car.name}") - - -if __name__ == "__main__": - unittest.main() + assert len(car_parts) > 0, f"Need to specify car parts: {car.name}" + assert car_part_type.count(PartType.connector) == 1, f"Need to specify one harness connector: {car.name}" + assert car_part_type.count(PartType.mount) == 1, f"Need to specify one mount: {car.name}" + assert Cable.right_angle_obd_c_cable_1_5ft in car_parts, f"Need to specify a right angle OBD-C cable (1.5ft): {car.name}" diff --git a/selfdrive/car/tests/test_fingerprints.py b/selfdrive/car/tests/test_fingerprints.py deleted file mode 100755 index 34f30bc703..0000000000 --- a/selfdrive/car/tests/test_fingerprints.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys - -from openpilot.common.basedir import BASEDIR - -# messages reserved for CAN based ignition (see can_ignition_hook function in panda/board/drivers/can) -# (addr, len) -CAN_IGNITION_MSGS = { - 'gm': [(0x1F1, 8), (0x160, 5)], - #'tesla' : [(0x348, 8)], -} - -def _get_fingerprints(): - # read all the folders in selfdrive/car and return a dict where: - # - keys are all the car names that which we have a fingerprint dict for - # - values are dicts of fingeprints for each trim - fingerprints = {} - for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]: - car_name = car_folder.split('/')[-1] - try: - fingerprints[car_name] = __import__(f'selfdrive.car.{car_name}.values', fromlist=['FINGERPRINTS']).FINGERPRINTS - except (ImportError, OSError, AttributeError): - pass - - return fingerprints - - -def check_fingerprint_consistency(f1, f2): - # return false if it finds a fingerprint fully included in another - # max message worth checking is 1800, as above that they usually come too infrequently and not - # usable for fingerprinting - - max_msg = 1800 - - is_f1_in_f2 = True - for k in f1: - if (k not in f2 or f1[k] != f2[k]) and k < max_msg: - is_f1_in_f2 = False - - is_f2_in_f1 = True - for k in f2: - if (k not in f1 or f2[k] != f1[k]) and k < max_msg: - is_f2_in_f1 = False - - return not is_f1_in_f2 and not is_f2_in_f1 - - -def check_can_ignition_conflicts(fingerprints, brands): - # loops through all the fingerprints and exits if CAN ignition dedicated messages - # are found in unexpected fingerprints - - for brand_can, msgs_can in CAN_IGNITION_MSGS.items(): - for i, f in enumerate(fingerprints): - for msg_can in msgs_can: - if brand_can != brands[i] and msg_can[0] in f and msg_can[1] == f[msg_can[0]]: - print("CAN ignition dedicated msg %d with len %d found in %s fingerprints!" % (msg_can[0], msg_can[1], brands[i])) - print("TEST FAILED") - sys.exit(1) - - - -if __name__ == "__main__": - fingerprints = _get_fingerprints() - - fingerprints_flat: list[dict] = [] - car_names = [] - brand_names = [] - for brand in fingerprints: - for car in fingerprints[brand]: - fingerprints_flat += fingerprints[brand][car] - for _ in range(len(fingerprints[brand][car])): - car_names.append(car) - brand_names.append(brand) - - # first check if CAN ignition specific messages are unexpectedly included in other fingerprints - check_can_ignition_conflicts(fingerprints_flat, brand_names) - - valid = True - for idx1, f1 in enumerate(fingerprints_flat): - for idx2, f2 in enumerate(fingerprints_flat): - if idx1 < idx2 and not check_fingerprint_consistency(f1, f2): - valid = False - print(f"Those two fingerprints are inconsistent {car_names[idx1]} {car_names[idx2]}") - print("") - print(', '.join("%d: %d" % v for v in sorted(f1.items()))) - print("") - print(', '.join("%d: %d" % v for v in sorted(f2.items()))) - print("") - - print(f"Found {len(fingerprints_flat)} individual fingerprints") - if not valid or len(fingerprints_flat) == 0: - print("TEST FAILED") - sys.exit(1) - else: - print("TEST SUCCESSFUL") diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py old mode 100755 new mode 100644 index 88c7225f22..f872972542 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -1,15 +1,13 @@ -#!/usr/bin/env python3 +import pytest import random import time -import unittest from collections import defaultdict from parameterized import parameterized -from unittest import mock 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, \ +from openpilot.selfdrive.car.fw_versions import ESSENTIAL_ECUS, FW_QUERY_CONFIGS, FUZZY_EXCLUDE_ECUS, VERSIONS, build_fw_dict, \ match_fw_to_car, get_brand_ecu_matches, get_fw_versions, get_present_ecus from openpilot.selfdrive.car.vin import get_vin @@ -27,11 +25,11 @@ class FakeSocket: pass -class TestFwFingerprint(unittest.TestCase): +class TestFwFingerprint: def assertFingerprints(self, candidates, expected): candidates = list(candidates) - self.assertEqual(len(candidates), 1, f"got more than one candidate: {candidates}") - self.assertEqual(candidates[0], expected) + assert len(candidates) == 1, f"got more than one candidate: {candidates}" + assert candidates[0] == expected @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): @@ -49,7 +47,7 @@ class TestFwFingerprint(unittest.TestCase): 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) + _, matches = match_fw_to_car(CP.carFw, CP.carVin, allow_fuzzy=False) if not test_non_essential: self.assertFingerprints(matches, car_model) else: @@ -62,7 +60,7 @@ class TestFwFingerprint(unittest.TestCase): # Assert brand-specific fuzzy fingerprinting function doesn't disagree with standard fuzzy function config = FW_QUERY_CONFIGS[brand] if config.match_fw_to_car_fuzzy is None: - raise unittest.SkipTest("Brand does not implement custom fuzzy fingerprinting function") + pytest.skip("Brand does not implement custom fuzzy fingerprinting function") CP = car.CarParams.new_message() for _ in range(5): @@ -72,19 +70,19 @@ class TestFwFingerprint(unittest.TestCase): 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_exact=False, log=False) - brand_matches = config.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), VERSIONS[brand]) + _, matches = match_fw_to_car(CP.carFw, CP.carVin, allow_exact=False, log=False) + brand_matches = config.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, VERSIONS[brand]) # If both have matches, they must agree if len(matches) == 1 and len(brand_matches) == 1: - self.assertEqual(matches, brand_matches) + assert matches == brand_matches @parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e]) def test_fuzzy_match_ecu_count(self, brand, car_model, ecus): # Asserts that fuzzy matching does not count matching FW, but ECU address keys valid_ecus = [e for e in ecus if e[0] not in FUZZY_EXCLUDE_ECUS] if not len(valid_ecus): - raise unittest.SkipTest("Car model has no compatible ECUs for fuzzy matching") + pytest.skip("Car model has no compatible ECUs for fuzzy matching") fw = [] for ecu in valid_ecus: @@ -94,24 +92,24 @@ class TestFwFingerprint(unittest.TestCase): fw.append({"ecu": ecu_name, "fwVersion": random.choice(ecus[ecu]), 'brand': brand, "address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) CP = car.CarParams.new_message(carFw=fw) - _, matches = match_fw_to_car(CP.carFw, allow_exact=False, log=False) + _, matches = match_fw_to_car(CP.carFw, CP.carVin, allow_exact=False, log=False) # Assert no match if there are not enough unique ECUs unique_ecus = {(f['address'], f['subAddress']) for f in fw} if len(unique_ecus) < 2: - self.assertEqual(len(matches), 0, car_model) + assert len(matches) == 0, car_model # There won't always be a match due to shared FW, but if there is it should be correct elif len(matches): self.assertFingerprints(matches, car_model) - def test_fw_version_lists(self): + def test_fw_version_lists(self, subtests): for car_model, ecus in FW_VERSIONS.items(): - with self.subTest(car_model=car_model.value): + with subtests.test(car_model=car_model.value): for ecu, ecu_fw in ecus.items(): - with self.subTest(ecu): + with subtests.test(ecu): duplicates = {fw for fw in ecu_fw if ecu_fw.count(fw) > 1} - self.assertFalse(len(duplicates), f'{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}') - self.assertGreater(len(ecu_fw), 0, f'{car_model}: No FW versions: Ecu.{ECU_NAME[ecu[0]]}') + assert not len(duplicates), f'{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}' + assert len(ecu_fw) > 0, f'{car_model}: No FW versions: Ecu.{ECU_NAME[ecu[0]]}' def test_all_addrs_map_to_one_ecu(self): for brand, cars in VERSIONS.items(): @@ -121,51 +119,59 @@ class TestFwFingerprint(unittest.TestCase): addr_to_ecu[(addr, sub_addr)].add(ecu_type) ecus_for_addr = addr_to_ecu[(addr, sub_addr)] ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_for_addr]) - self.assertLessEqual(len(ecus_for_addr), 1, f"{brand} has multiple ECUs that map to one address: {ecu_strings} -> ({hex(addr)}, {sub_addr})") + assert len(ecus_for_addr) <= 1, f"{brand} has multiple ECUs that map to one address: {ecu_strings} -> ({hex(addr)}, {sub_addr})" - def test_data_collection_ecus(self): + def test_data_collection_ecus(self, subtests): # Asserts no extra ECUs are in the fingerprinting database for brand, config in FW_QUERY_CONFIGS.items(): for car_model, ecus in VERSIONS[brand].items(): bad_ecus = set(ecus).intersection(config.extra_ecus) - with self.subTest(car_model=car_model.value): - self.assertFalse(len(bad_ecus), f'{car_model}: Fingerprints contain ECUs added for data collection: {bad_ecus}') + with subtests.test(car_model=car_model.value): + assert not len(bad_ecus), f'{car_model}: Fingerprints contain ECUs added for data collection: {bad_ecus}' - def test_blacklisted_ecus(self): + def test_blacklisted_ecus(self, subtests): blacklisted_addrs = (0x7c4, 0x7d0) # includes A/C ecu and an unknown ecu for car_model, ecus in FW_VERSIONS.items(): - with self.subTest(car_model=car_model.value): + with subtests.test(car_model=car_model.value): CP = interfaces[car_model][0].get_non_essential_params(car_model) if CP.carName == 'subaru': for ecu in ecus.keys(): - self.assertNotIn(ecu[1], blacklisted_addrs, f'{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})') + assert ecu[1] not in blacklisted_addrs, f'{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})' elif CP.carName == "chrysler": # Some HD trucks have a combined TCM and ECM if CP.carFingerprint.startswith("RAM HD"): for ecu in ecus.keys(): - self.assertNotEqual(ecu[0], Ecu.transmission, f"{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})") + assert ecu[0] != Ecu.transmission, f"{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})" - def test_missing_versions_and_configs(self): + def test_non_essential_ecus(self, subtests): + for brand, config in FW_QUERY_CONFIGS.items(): + with subtests.test(brand): + # These ECUs are already not in ESSENTIAL_ECUS which the fingerprint functions give a pass if missing + unnecessary_non_essential_ecus = set(config.non_essential_ecus) - set(ESSENTIAL_ECUS) + assert unnecessary_non_essential_ecus == set(), "Declaring non-essential ECUs non-essential is not required: " + \ + f"{', '.join([f'Ecu.{ECU_NAME[ecu]}' for ecu in unnecessary_non_essential_ecus])}" + + def test_missing_versions_and_configs(self, subtests): brand_versions = set(VERSIONS.keys()) brand_configs = set(FW_QUERY_CONFIGS.keys()) if len(brand_configs - brand_versions): - with self.subTest(): - self.fail(f"Brands do not implement FW_VERSIONS: {brand_configs - brand_versions}") + with subtests.test(): + pytest.fail(f"Brands do not implement FW_VERSIONS: {brand_configs - brand_versions}") if len(brand_versions - brand_configs): - with self.subTest(): - self.fail(f"Brands do not implement FW_QUERY_CONFIG: {brand_versions - brand_configs}") + with subtests.test(): + pytest.fail(f"Brands do not implement FW_QUERY_CONFIG: {brand_versions - brand_configs}") # 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({}), set(config.extra_ecus)) - self.assertGreater(len(config.get_all_ecus(VERSIONS[brand])), 0) + assert len(config.get_all_ecus({}, include_extra_ecus=False)) == 0 + assert config.get_all_ecus({}) == set(config.extra_ecus) + assert len(config.get_all_ecus(VERSIONS[brand])) > 0 - def test_fw_request_ecu_whitelist(self): + def test_fw_request_ecu_whitelist(self, subtests): for brand, config in FW_QUERY_CONFIGS.items(): - with self.subTest(brand=brand): + with subtests.test(brand=brand): whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus} brand_ecus = {fw[0] for car_fw in VERSIONS[brand].values() for fw in car_fw} brand_ecus |= {ecu[0] for ecu in config.extra_ecus} @@ -174,30 +180,30 @@ class TestFwFingerprint(unittest.TestCase): ecus_not_whitelisted = brand_ecus - whitelisted_ecus ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_whitelisted]) - self.assertFalse(len(whitelisted_ecus) and len(ecus_not_whitelisted), - f'{brand.title()}: ECUs not in any FW query whitelists: {ecu_strings}') + assert not (len(whitelisted_ecus) and len(ecus_not_whitelisted)), \ + f'{brand.title()}: ECUs not in any FW query whitelists: {ecu_strings}' - def test_fw_requests(self): + def test_fw_requests(self, subtests): # Asserts equal length request and response lists for brand, config in FW_QUERY_CONFIGS.items(): - with self.subTest(brand=brand): + with subtests.test(brand=brand): for request_obj in config.requests: - self.assertEqual(len(request_obj.request), len(request_obj.response)) + assert len(request_obj.request) == len(request_obj.response) # No request on the OBD port (bus 1, multiplexed) should be run on an aux panda - 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}") + assert not (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) + assert 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) + assert get_brand_ecu_matches({(0x758, 0xf, 99)}) == expected_response -class TestFwFingerprintTiming(unittest.TestCase): +class TestFwFingerprintTiming: N: int = 5 TOL: float = 0.05 @@ -215,29 +221,29 @@ class TestFwFingerprintTiming(unittest.TestCase): self.total_time += timeout return {} - def _benchmark_brand(self, brand, num_pandas): + def _benchmark_brand(self, brand, num_pandas, mocker): fake_socket = FakeSocket() self.total_time = 0 - with (mock.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing), - mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)): - for _ in range(self.N): - # Treat each brand as the most likely (aka, the first) brand with OBD multiplexing initially on - self.current_obd_multiplexing = True + mocker.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing) + mocker.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data) + for _ in range(self.N): + # Treat each brand as the most likely (aka, the first) brand with OBD multiplexing initially on + self.current_obd_multiplexing = True - t = time.perf_counter() - get_fw_versions(fake_socket, fake_socket, brand, num_pandas=num_pandas) - self.total_time += time.perf_counter() - t + t = time.perf_counter() + get_fw_versions(fake_socket, fake_socket, brand, num_pandas=num_pandas) + self.total_time += time.perf_counter() - t return self.total_time / self.N def _assert_timing(self, avg_time, ref_time): - self.assertLess(avg_time, ref_time + self.TOL) - self.assertGreater(avg_time, ref_time - self.TOL, "Performance seems to have improved, update test refs.") + assert avg_time < ref_time + self.TOL + assert avg_time > ref_time - self.TOL, "Performance seems to have improved, update test refs." - def test_startup_timing(self): + def test_startup_timing(self, subtests, mocker): # Tests worse-case VIN query time and typical present ECU query time - 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 + vin_ref_times = {'worst': 1.4, 'best': 0.7} # best assumes we go through all queries to get a match + present_ecu_ref_time = 0.45 def fake_get_ecu_addrs(*_, timeout): self.total_time += timeout @@ -245,51 +251,52 @@ class TestFwFingerprintTiming(unittest.TestCase): fake_socket = FakeSocket() self.total_time = 0.0 - with (mock.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing), - mock.patch("openpilot.selfdrive.car.fw_versions.get_ecu_addrs", fake_get_ecu_addrs)): - for _ in range(self.N): - self.current_obd_multiplexing = True - get_present_ecus(fake_socket, fake_socket, num_pandas=2) + mocker.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing) + mocker.patch("openpilot.selfdrive.car.fw_versions.get_ecu_addrs", fake_get_ecu_addrs) + for _ in range(self.N): + self.current_obd_multiplexing = True + get_present_ecus(fake_socket, fake_socket, num_pandas=2) self._assert_timing(self.total_time / self.N, present_ecu_ref_time) print(f'get_present_ecus, query time={self.total_time / self.N} seconds') for name, args in (('worst', {}), ('best', {'retry': 1})): - with self.subTest(name=name): + with subtests.test(name=name): self.total_time = 0.0 - with (mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)): - for _ in range(self.N): - get_vin(fake_socket, fake_socket, (0, 1), **args) + mocker.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data) + for _ in range(self.N): + get_vin(fake_socket, fake_socket, (0, 1), **args) 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') - def test_fw_query_timing(self): - total_ref_time = {1: 7.8, 2: 8.7} + def test_fw_query_timing(self, subtests, mocker): + total_ref_time = {1: 7.2, 2: 7.8} brand_ref_times = { 1: { - 'gm': 0.5, + 'gm': 1.0, 'body': 0.1, 'chrysler': 0.3, 'ford': 1.5, - 'honda': 0.55, - 'hyundai': 1.05, + 'honda': 0.45, + 'hyundai': 0.65, 'mazda': 0.1, 'nissan': 0.8, - 'subaru': 0.45, - 'tesla': 0.2, - 'toyota': 1.6, + 'subaru': 0.65, + 'tesla': 0.3, + 'toyota': 0.7, 'volkswagen': 0.65, }, 2: { 'ford': 1.6, - 'hyundai': 1.85, + 'hyundai': 1.15, + 'tesla': 0.3, } } 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): - avg_time = self._benchmark_brand(brand, num_pandas) + with subtests.test(brand=brand, num_pandas=num_pandas): + avg_time = self._benchmark_brand(brand, num_pandas, mocker) total_times[num_pandas] += avg_time avg_time = round(avg_time, 2) @@ -302,11 +309,7 @@ class TestFwFingerprintTiming(unittest.TestCase): print(f'{brand=}, {num_pandas=}, {len(config.requests)=}, avg FW query time={avg_time} seconds') for num_pandas in (1, 2): - with self.subTest(brand='all_brands', num_pandas=num_pandas): + with subtests.test(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__": - unittest.main() diff --git a/selfdrive/car/tests/test_lateral_limits.py b/selfdrive/car/tests/test_lateral_limits.py index 083cdd5a5e..e61d197f4b 100755 --- a/selfdrive/car/tests/test_lateral_limits.py +++ b/selfdrive/car/tests/test_lateral_limits.py @@ -2,14 +2,13 @@ from collections import defaultdict import importlib from parameterized import parameterized_class +import pytest import sys -import unittest from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car.car_helpers import interfaces from openpilot.selfdrive.car.fingerprints import all_known_cars from openpilot.selfdrive.car.interfaces import get_torque_params -from openpilot.selfdrive.car.subaru.values import CAR as SUBARU CAR_MODELS = all_known_cars() @@ -22,40 +21,29 @@ MAX_LAT_JERK_UP_TOLERANCE = 0.5 # m/s^3 # jerk is measured over half a second JERK_MEAS_T = 0.5 -# TODO: put these cars within limits -ABOVE_LIMITS_CARS = [ - SUBARU.LEGACY, - SUBARU.OUTBACK, -] - -car_model_jerks: defaultdict[str, dict[str, float]] = defaultdict(dict) - @parameterized_class('car_model', [(c,) for c in sorted(CAR_MODELS)]) -class TestLateralLimits(unittest.TestCase): +class TestLateralLimits: car_model: str @classmethod - def setUpClass(cls): + def setup_class(cls): CarInterface, _, _ = interfaces[cls.car_model] CP = CarInterface.get_non_essential_params(cls.car_model) if CP.dashcamOnly: - raise unittest.SkipTest("Platform is behind dashcamOnly") + pytest.skip("Platform is behind dashcamOnly") # TODO: test all platforms if CP.lateralTuning.which() != 'torque': - raise unittest.SkipTest + pytest.skip() if CP.notCar: - raise unittest.SkipTest - - if CP.carFingerprint in ABOVE_LIMITS_CARS: - raise unittest.SkipTest + pytest.skip() CarControllerParams = importlib.import_module(f'selfdrive.car.{CP.carName}.values').CarControllerParams cls.control_params = CarControllerParams(CP) - cls.torque_params = get_torque_params(cls.car_model) + cls.torque_params = get_torque_params()[cls.car_model] @staticmethod def calculate_0_5s_jerk(control_params, torque_params): @@ -75,27 +63,36 @@ class TestLateralLimits(unittest.TestCase): def test_jerk_limits(self): up_jerk, down_jerk = self.calculate_0_5s_jerk(self.control_params, self.torque_params) - car_model_jerks[self.car_model] = {"up_jerk": up_jerk, "down_jerk": down_jerk} - self.assertLessEqual(up_jerk, MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE) - self.assertLessEqual(down_jerk, MAX_LAT_JERK_DOWN) + assert up_jerk <= MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE + assert down_jerk <= MAX_LAT_JERK_DOWN def test_max_lateral_accel(self): - self.assertLessEqual(self.torque_params["MAX_LAT_ACCEL_MEASURED"], MAX_LAT_ACCEL) + assert self.torque_params["MAX_LAT_ACCEL_MEASURED"] <= MAX_LAT_ACCEL -if __name__ == "__main__": - result = unittest.main(exit=False) +class LatAccelReport: + car_model_jerks: defaultdict[str, dict[str, float]] = defaultdict(dict) - print(f"\n\n---- Lateral limit report ({len(CAR_MODELS)} cars) ----\n") + def pytest_sessionfinish(self): + print(f"\n\n---- Lateral limit report ({len(CAR_MODELS)} cars) ----\n") - max_car_model_len = max([len(car_model) for car_model in car_model_jerks]) - for car_model, _jerks in sorted(car_model_jerks.items(), key=lambda i: i[1]['up_jerk'], reverse=True): - violation = _jerks["up_jerk"] > MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE or \ - _jerks["down_jerk"] > MAX_LAT_JERK_DOWN - violation_str = " - VIOLATION" if violation else "" + max_car_model_len = max([len(car_model) for car_model in self.car_model_jerks]) + for car_model, _jerks in sorted(self.car_model_jerks.items(), key=lambda i: i[1]['up_jerk'], reverse=True): + violation = _jerks["up_jerk"] > MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE or \ + _jerks["down_jerk"] > MAX_LAT_JERK_DOWN + violation_str = " - VIOLATION" if violation else "" - print(f"{car_model:{max_car_model_len}} - up jerk: {round(_jerks['up_jerk'], 2):5} " + - f"m/s^3, down jerk: {round(_jerks['down_jerk'], 2):5} m/s^3{violation_str}") + print(f"{car_model:{max_car_model_len}} - up jerk: {round(_jerks['up_jerk'], 2):5} " + + f"m/s^3, down jerk: {round(_jerks['down_jerk'], 2):5} m/s^3{violation_str}") - # exit with test result - sys.exit(not result.result.wasSuccessful()) + @pytest.fixture(scope="class", autouse=True) + def class_setup(self, request): + yield + cls = request.cls + if hasattr(cls, "control_params"): + up_jerk, down_jerk = TestLateralLimits.calculate_0_5s_jerk(cls.control_params, cls.torque_params) + self.car_model_jerks[cls.car_model] = {"up_jerk": up_jerk, "down_jerk": down_jerk} + + +if __name__ == '__main__': + sys.exit(pytest.main([__file__, '-n0', '--no-summary'], plugins=[LatAccelReport()])) # noqa: TID251 diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py old mode 100755 new mode 100644 index 2b29c14f72..b5d75e665b --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -1,10 +1,9 @@ -#!/usr/bin/env python3 import capnp import os import importlib import pytest import random -import unittest +import unittest # noqa: TID251 from collections import defaultdict, Counter import hypothesis.strategies as st from hypothesis import Phase, given, settings @@ -15,11 +14,12 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car import gen_empty_fingerprint -from openpilot.selfdrive.car.fingerprints import all_known_cars +from openpilot.selfdrive.car.card import Car +from openpilot.selfdrive.car.fingerprints import all_known_cars, MIGRATION from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces -from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH +from openpilot.selfdrive.car.honda.values import CAR as HONDA, HondaFlags from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute -from openpilot.selfdrive.controls.controlsd import Controls +from openpilot.selfdrive.car.values import Platform from openpilot.selfdrive.test.helpers import read_segment_list from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT from openpilot.tools.lib.logreader import LogReader, internal_source, openpilotci_source @@ -64,7 +64,7 @@ def get_test_cases() -> list[tuple[str, CarTestRoute | None]]: @pytest.mark.slow @pytest.mark.shared_download_cache class TestCarModelBase(unittest.TestCase): - car_model: str | None = None + platform: Platform | None = None test_route: CarTestRoute | None = None test_route_on_bucket: bool = True # whether the route is on the preserved CI bucket @@ -93,8 +93,9 @@ class TestCarModelBase(unittest.TestCase): car_fw = msg.carParams.carFw if msg.carParams.openpilotLongitudinalControl: experimental_long = True - if cls.car_model is None and not cls.ci: - cls.car_model = msg.carParams.carFingerprint + if cls.platform is None and not cls.test_route_on_bucket: + live_fingerprint = msg.carParams.carFingerprint + cls.platform = MIGRATION.get(live_fingerprint, live_fingerprint) # Log which can frame the panda safety mode left ELM327, for CAN validity checks elif msg.which() == 'pandaStates': @@ -155,15 +156,11 @@ class TestCarModelBase(unittest.TestCase): if cls.__name__ == 'TestCarModel' or cls.__name__.endswith('Base'): raise unittest.SkipTest - if 'FILTER' in os.environ: - if not cls.car_model.startswith(tuple(os.environ.get('FILTER').split(','))): - raise unittest.SkipTest - if cls.test_route is None: - if cls.car_model in non_tested_cars: - print(f"Skipping tests for {cls.car_model}: missing route") + if cls.platform in non_tested_cars: + print(f"Skipping tests for {cls.platform}: missing route") raise unittest.SkipTest - raise Exception(f"missing test route for {cls.car_model}") + raise Exception(f"missing test route for {cls.platform}") car_fw, can_msgs, experimental_long = cls.get_testing_data() @@ -172,10 +169,10 @@ class TestCarModelBase(unittest.TestCase): cls.can_msgs = sorted(can_msgs, key=lambda msg: msg.logMonoTime) - cls.CarInterface, cls.CarController, cls.CarState = interfaces[cls.car_model] - cls.CP = cls.CarInterface.get_params(cls.car_model, cls.fingerprint, car_fw, experimental_long, docs=False) + cls.CarInterface, cls.CarController, cls.CarState = interfaces[cls.platform] + cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, experimental_long, docs=False) assert cls.CP - assert cls.CP.carFingerprint == cls.car_model + assert cls.CP.carFingerprint == cls.platform os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT @@ -217,7 +214,7 @@ class TestCarModelBase(unittest.TestCase): # TODO: also check for checksum violations from can parser can_invalid_cnt = 0 can_valid = False - CC = car.CarControl.new_message() + CC = car.CarControl.new_message().as_reader() for i, msg in enumerate(self.can_msgs): CS = self.CI.update(CC, (msg.as_builder().to_bytes(),)) @@ -310,17 +307,17 @@ class TestCarModelBase(unittest.TestCase): # Make sure we can send all messages while inactive CC = car.CarControl.new_message() - test_car_controller(CC) + test_car_controller(CC.as_reader()) # Test cancel + general messages (controls_allowed=False & cruise_engaged=True) self.safety.set_cruise_engaged_prev(True) CC = car.CarControl.new_message(cruiseControl={'cancel': True}) - test_car_controller(CC) + test_car_controller(CC.as_reader()) # Test resume + general messages (controls_allowed=True & cruise_engaged=True) self.safety.set_controls_allowed(True) CC = car.CarControl.new_message(cruiseControl={'resume': True}) - test_car_controller(CC) + test_car_controller(CC.as_reader()) # Skip stdout/stderr capture with pytest, causes elevated memory usage @pytest.mark.nocapture @@ -370,7 +367,7 @@ class TestCarModelBase(unittest.TestCase): # TODO: remove this exception once this mismatch is resolved brake_pressed = CS.brakePressed if CS.brakePressed and not self.safety.get_brake_pressed_prev(): - if self.CP.carFingerprint in (HONDA.PILOT, HONDA.RIDGELINE) and CS.brake > 0.05: + if self.CP.carFingerprint in (HONDA.HONDA_PILOT, HONDA.HONDA_RIDGELINE) and CS.brake > 0.05: brake_pressed = False self.assertEqual(brake_pressed, self.safety.get_brake_pressed_prev()) @@ -381,7 +378,7 @@ class TestCarModelBase(unittest.TestCase): if self.safety.get_vehicle_moving() != prev_panda_vehicle_moving: self.assertEqual(not CS.standstill, self.safety.get_vehicle_moving()) - if not (self.CP.carName == "honda" and self.CP.carFingerprint not in HONDA_BOSCH): + if not (self.CP.carName == "honda" and not (self.CP.flags & HondaFlags.BOSCH)): if self.safety.get_cruise_engaged_prev() != prev_panda_cruise_engaged: self.assertEqual(CS.cruiseState.enabled, self.safety.get_cruise_engaged_prev()) @@ -408,8 +405,7 @@ class TestCarModelBase(unittest.TestCase): controls_allowed_prev = False CS_prev = car.CarState.new_message() checks = defaultdict(int) - controlsd = Controls(CI=self.CI) - controlsd.initialized = True + card = Car(CI=self.CI) for idx, can in enumerate(self.can_msgs): CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) for msg in filter(lambda m: m.src in range(64), can.can): @@ -433,7 +429,7 @@ class TestCarModelBase(unittest.TestCase): # TODO: remove this exception once this mismatch is resolved brake_pressed = CS.brakePressed if CS.brakePressed and not self.safety.get_brake_pressed_prev(): - if self.CP.carFingerprint in (HONDA.PILOT, HONDA.RIDGELINE) and CS.brake > 0.05: + if self.CP.carFingerprint in (HONDA.HONDA_PILOT, HONDA.HONDA_RIDGELINE) and CS.brake > 0.05: brake_pressed = False checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev() checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev() @@ -442,7 +438,7 @@ class TestCarModelBase(unittest.TestCase): # On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state. # On Honda Nidec, we always engage on the rising edge of the PCM cruise state, but # openpilot brakes to zero even if the min ACC speed is non-zero (i.e. the PCM disengages). - if self.CP.carName == "honda" and self.CP.carFingerprint not in HONDA_BOSCH: + if self.CP.carName == "honda" and not (self.CP.flags & HondaFlags.BOSCH): # only the rising edges are expected to match if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled: checks['controlsAllowed'] += not self.safety.get_controls_allowed() @@ -454,10 +450,10 @@ class TestCarModelBase(unittest.TestCase): checks['cruiseState'] += CS.cruiseState.enabled != self.safety.get_cruise_engaged_prev() else: # Check for enable events on rising edge of controls allowed - controlsd.update_events(CS) - controlsd.CS_prev = CS + card.update_events(CS) + card.CS_prev = CS button_enable = (any(evt.enable for evt in CS.events) and - not any(evt == EventName.pedalPressed for evt in controlsd.events.names)) + not any(evt == EventName.pedalPressed for evt in card.events.names)) mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev) checks['controlsAllowed'] += mismatch controls_allowed_prev = self.safety.get_controls_allowed() @@ -478,7 +474,7 @@ class TestCarModelBase(unittest.TestCase): "This is fine to fail for WIP car ports, just let us know and we can upload your routes to the CI bucket.") -@parameterized_class(('car_model', 'test_route'), get_test_cases()) +@parameterized_class(('platform', 'test_route'), get_test_cases()) @pytest.mark.xdist_group_class_property('test_route') class TestCarModel(TestCarModelBase): pass diff --git a/selfdrive/car/tests/test_models_segs.txt b/selfdrive/car/tests/test_models_segs.txt index ca089bbdde..c983fb08e7 100644 --- a/selfdrive/car/tests/test_models_segs.txt +++ b/selfdrive/car/tests/test_models_segs.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cadad21e1230729c70cffb4c46dd0a5dda4eec1a262b1bcd9f1b6b98265c20b5 -size 125104 +oid sha256:0810a361ec5b5f5f9a2ee73b89ffb2df62ef40e8feff7e97ecb62f80fa53f6f5 +size 124950 diff --git a/selfdrive/car/tests/test_platform_configs.py b/selfdrive/car/tests/test_platform_configs.py new file mode 100644 index 0000000000..31e4be4fe6 --- /dev/null +++ b/selfdrive/car/tests/test_platform_configs.py @@ -0,0 +1,17 @@ +from openpilot.selfdrive.car.values import PLATFORMS + + +class TestPlatformConfigs: + def test_configs(self, subtests): + + for name, platform in PLATFORMS.items(): + with subtests.test(platform=str(platform)): + assert platform.config._frozen + + if platform != "MOCK": + assert "pt" in platform.config.dbc_dict + assert len(platform.config.platform_str) > 0 + + assert name == platform.config.platform_str + + assert platform.config.specs is not None diff --git a/selfdrive/car/torque_data/neural_ff_weights.json b/selfdrive/car/torque_data/neural_ff_weights.json index c526f07fa2..251b66efb0 100644 --- a/selfdrive/car/torque_data/neural_ff_weights.json +++ b/selfdrive/car/torque_data/neural_ff_weights.json @@ -1 +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 +{"CHEVROLET_BOLT_EUV": {"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 32f9dda18c..d48aeb521e 100644 --- a/selfdrive/car/torque_data/override.toml +++ b/selfdrive/car/torque_data/override.toml @@ -1,75 +1,78 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] ### angle control # Nissan appears to have torque -"NISSAN X-TRAIL 2017" = [nan, 1.5, nan] -"NISSAN ALTIMA 2020" = [nan, 1.5, nan] -"NISSAN LEAF 2018 Instrument Cluster" = [nan, 1.5, nan] -"NISSAN LEAF 2018" = [nan, 1.5, nan] -"NISSAN ROGUE 2019" = [nan, 1.5, nan] +"NISSAN_XTRAIL" = [nan, 1.5, nan] +"NISSAN_ALTIMA" = [nan, 1.5, nan] +"NISSAN_LEAF_IC" = [nan, 1.5, nan] +"NISSAN_LEAF" = [nan, 1.5, nan] +"NISSAN_ROGUE" = [nan, 1.5, nan] # New subarus angle based controllers -"SUBARU FORESTER 2022" = [nan, 3.0, nan] -"SUBARU OUTBACK 7TH GEN" = [nan, 3.0, nan] -"SUBARU ASCENT 2023" = [nan, 3.0, nan] +"SUBARU_FORESTER_2022" = [nan, 3.0, nan] +"SUBARU_OUTBACK_2023" = [nan, 3.0, nan] +"SUBARU_ASCENT_2023" = [nan, 3.0, nan] # Toyota LTA also has torque -"TOYOTA RAV4 2023" = [nan, 3.0, nan] +"TOYOTA_RAV4_TSS2_2023" = [nan, 3.0, nan] # Tesla has high torque -"TESLA AP1 MODEL S" = [nan, 2.5, nan] -"TESLA AP2 MODEL S" = [nan, 2.5, nan] +"TESLA_AP1_MODELS" = [nan, 2.5, nan] +"TESLA_AP2_MODELS" = [nan, 2.5, nan] +"TESLA_MODELS_RAVEN" = [nan, 2.5, nan] # Guess -"FORD BRONCO SPORT 1ST GEN" = [nan, 1.5, nan] -"FORD ESCAPE 4TH GEN" = [nan, 1.5, nan] -"FORD EXPLORER 6TH GEN" = [nan, 1.5, nan] -"FORD F-150 14TH GEN" = [nan, 1.5, nan] -"FORD FOCUS 4TH GEN" = [nan, 1.5, nan] -"FORD MAVERICK 1ST GEN" = [nan, 1.5, nan] -"FORD F-150 LIGHTNING 1ST GEN" = [nan, 1.5, nan] -"FORD MUSTANG MACH-E 1ST GEN" = [nan, 1.5, nan] +"FORD_BRONCO_SPORT_MK1" = [nan, 1.5, nan] +"FORD_ESCAPE_MK4" = [nan, 1.5, nan] +"FORD_EXPLORER_MK6" = [nan, 1.5, nan] +"FORD_F_150_MK14" = [nan, 1.5, nan] +"FORD_FOCUS_MK4" = [nan, 1.5, nan] +"FORD_MAVERICK_MK1" = [nan, 1.5, nan] +"FORD_F_150_LIGHTNING_MK1" = [nan, 1.5, nan] +"FORD_MUSTANG_MACH_E_MK1" = [nan, 1.5, nan] +"FORD_RANGER_MK2" = [nan, 1.5, nan] ### # No steering wheel -"COMMA BODY" = [nan, 1000, nan] +"COMMA_BODY" = [nan, 1000, nan] # Totally new cars -"RAM 1500 5TH GEN" = [2.0, 2.0, 0.05] -"RAM HD 5TH GEN" = [1.4, 1.4, 0.05] -"SUBARU OUTBACK 6TH GEN" = [2.0, 2.0, 0.2] -"CADILLAC ESCALADE 2017" = [1.899999976158142, 1.842270016670227, 0.1120000034570694] -"CADILLAC ESCALADE ESV 2019" = [1.15, 1.3, 0.2] -"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.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] -"KIA SPORTAGE 5TH GEN" = [2.6, 2.6, 0.1] -"GENESIS GV70 1ST GEN" = [2.42, 2.42, 0.1] -"GENESIS GV60 ELECTRIC 1ST GEN" = [2.5, 2.5, 0.1] -"KIA SORENTO 4TH GEN" = [2.5, 2.5, 0.1] -"KIA SORENTO HYBRID 4TH GEN" = [2.5, 2.5, 0.1] -"KIA NIRO HYBRID 2ND GEN" = [2.42, 2.5, 0.12] -"KIA NIRO EV 2ND GEN" = [2.05, 2.5, 0.14] -"GENESIS GV80 2023" = [2.5, 2.5, 0.1] -"KIA CARNIVAL 4TH GEN" = [1.75, 1.75, 0.15] -"GMC ACADIA DENALI 2018" = [1.6, 1.6, 0.2] -"LEXUS IS 2023" = [2.0, 2.0, 0.1] -"HYUNDAI KONA ELECTRIC 2ND GEN" = [2.5, 2.5, 0.1] -"HYUNDAI IONIQ 6 2023" = [2.5, 2.5, 0.1] -"HYUNDAI AZERA 6TH GEN" = [1.8, 1.8, 0.1] -"HYUNDAI AZERA HYBRID 6TH GEN" = [1.8, 1.8, 0.1] -"KIA K8 HYBRID 1ST GEN" = [2.5, 2.5, 0.1] -"HYUNDAI CUSTIN 1ST GEN" = [2.5, 2.5, 0.1] -"LEXUS GS F 2016" = [2.5, 2.5, 0.08] -"HYUNDAI STARIA 4TH GEN" = [1.8, 2.0, 0.15] -"GENESIS G70 1ST GEN FACELIFT" = [3.0, 3.0, 0.05] +"RAM_1500_5TH_GEN" = [2.0, 2.0, 0.05] +"RAM_HD_5TH_GEN" = [1.4, 1.4, 0.05] +"SUBARU_OUTBACK" = [2.0, 1.5, 0.2] +"CADILLAC_ESCALADE" = [1.899999976158142, 1.842270016670227, 0.1120000034570694] +"CADILLAC_ESCALADE_ESV_2019" = [1.15, 1.3, 0.2] +"CHEVROLET_BOLT_EUV" = [2.0, 2.0, 0.05] +"CHEVROLET_SILVERADO" = [1.9, 1.9, 0.112] +"CHEVROLET_TRAILBLAZER" = [1.33, 1.9, 0.16] +"CHEVROLET_EQUINOX" = [2.5, 2.5, 0.05] +"VOLKSWAGEN_CADDY_MK3" = [1.2, 1.2, 0.1] +"VOLKSWAGEN_PASSAT_NMS" = [2.5, 2.5, 0.1] +"VOLKSWAGEN_SHARAN_MK2" = [2.5, 2.5, 0.1] +"HYUNDAI_SANTA_CRUZ_1ST_GEN" = [2.7, 2.7, 0.1] +"KIA_SPORTAGE_5TH_GEN" = [2.6, 2.6, 0.1] +"GENESIS_GV70_1ST_GEN" = [2.42, 2.42, 0.1] +"GENESIS_GV60_EV_1ST_GEN" = [2.5, 2.5, 0.1] +"KIA_SORENTO_4TH_GEN" = [2.5, 2.5, 0.1] +"KIA_SORENTO_HEV_4TH_GEN" = [2.5, 2.5, 0.1] +"KIA_NIRO_HEV_2ND_GEN" = [2.42, 2.5, 0.12] +"KIA_NIRO_EV_2ND_GEN" = [2.05, 2.5, 0.14] +"GENESIS_GV80" = [2.5, 2.5, 0.1] +"KIA_CARNIVAL_4TH_GEN" = [1.75, 1.75, 0.15] +"GMC_ACADIA" = [1.6, 1.6, 0.2] +"LEXUS_IS_TSS2" = [2.0, 2.0, 0.1] +"HYUNDAI_KONA_EV_2ND_GEN" = [2.5, 2.5, 0.1] +"HYUNDAI_IONIQ_6" = [2.5, 2.5, 0.005] +"HYUNDAI_AZERA_6TH_GEN" = [1.8, 1.8, 0.1] +"HYUNDAI_AZERA_HEV_6TH_GEN" = [1.8, 1.8, 0.1] +"KIA_K8_HEV_1ST_GEN" = [2.5, 2.5, 0.1] +"HYUNDAI_CUSTIN_1ST_GEN" = [2.5, 2.5, 0.1] +"LEXUS_GS_F" = [2.5, 2.5, 0.08] +"HYUNDAI_STARIA_4TH_GEN" = [1.8, 2.0, 0.15] +"GENESIS_G70_1ST_GEN_FL" = [3.0, 3.0, 0.05] # Dashcam or fallback configured as ideal car -"mock" = [10.0, 10, 0.0] +"MOCK" = [10.0, 10, 0.0] # Manually checked -"HONDA CIVIC 2022" = [2.5, 1.2, 0.15] -"HONDA HR-V 2023" = [2.5, 1.2, 0.2] +"HONDA_CIVIC_2022" = [2.5, 1.2, 0.15] +"HONDA_HRV_3G" = [2.5, 1.2, 0.2] diff --git a/selfdrive/car/torque_data/params.toml b/selfdrive/car/torque_data/params.toml index 568646c84c..34cfd0e066 100644 --- a/selfdrive/car/torque_data/params.toml +++ b/selfdrive/car/torque_data/params.toml @@ -1,86 +1,82 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] -"ACURA ILX 2016" = [1.524988973896102, 0.519011053086259, 0.34236219253028] -"ACURA RDX 2018" = [0.9987728568686902, 0.5323765166196301, 0.303218805715844] -"ACURA RDX 2020" = [1.4314459806646749, 0.33874701282109954, 0.18048847083897598] -"AUDI A3 3RD GEN" = [1.5122414863077502, 1.7443517531719404, 0.15194151892450905] -"AUDI Q3 2ND GEN" = [1.4439223359448605, 1.2254955789112076, 0.1413798895978097] -"CHEVROLET VOLT PREMIER 2017" = [1.5961527626411784, 1.8422651988094612, 0.1572393918005158] -"CHRYSLER PACIFICA 2018" = [2.07140, 1.3366521181047952, 0.13776367250652022] -"CHRYSLER PACIFICA 2020" = [1.86206, 1.509076559398423, 0.14328246159386085] -"CHRYSLER PACIFICA HYBRID 2017" = [1.79422, 1.06831764583744, 0.116237] -"CHRYSLER PACIFICA HYBRID 2018" = [2.08887, 1.2943025830995154, 0.114818] -"CHRYSLER PACIFICA HYBRID 2019" = [1.90120, 1.1958788168371808, 0.131520] -"GENESIS G70 2018" = [3.8520195946707947, 2.354697063349854, 0.06830285485626221] -"HONDA ACCORD 2018" = [1.7135052593468778, 0.3461280068322071, 0.21579936052863807] -"HONDA ACCORD HYBRID 2018" = [1.6651615004829625, 0.30322180951193245, 0.2083000440586149] -"HONDA CIVIC (BOSCH) 2019" = [1.691708637466905, 0.40132900729454185, 0.25460295304024094] -"HONDA CIVIC 2016" = [1.6528895627785531, 0.4018518740819229, 0.25458812851328544] -"HONDA CR-V 2016" = [0.7667141440182675, 0.5927571534745969, 0.40909087636157127] -"HONDA CR-V 2017" = [2.01323205142022, 0.2700612209345081, 0.2238412881331528] -"HONDA CR-V HYBRID 2019" = [2.072034634644233, 0.7152085160516978, 0.20237105008376083] -"HONDA FIT 2018" = [1.5719981427109775, 0.5712761407108976, 0.110773383324281] -"HONDA HRV 2019" = [2.0661212805710205, 0.7521343418694775, 0.17760375789242094] -"HONDA INSIGHT 2019" = [1.5201671214069354, 0.5660229120683284, 0.25808042580281876] -"HONDA ODYSSEY 2018" = [1.8774809275211801, 0.8394431662987996, 0.2096978613792822] -"HONDA PILOT 2017" = [1.7262026201812795, 0.9470005614967523, 0.21351430733218763] -"HONDA RIDGELINE 2017" = [1.4146525028237624, 0.7356572861629564, 0.23307177552211328] -"HYUNDAI ELANTRA 2021" = [3.169, 2.1259108157250735, 0.0819] -"HYUNDAI GENESIS 2015-2016" = [2.7807965280270794, 2.325, 0.0984484465421171] -"HYUNDAI IONIQ 5 2022" = [3.172929, 2.713050, 0.096019] -"HYUNDAI IONIQ ELECTRIC LIMITED 2019" = [1.7662975472852054, 1.613755614526594, 0.17087579756306276] -"HYUNDAI IONIQ PHEV 2020" = [3.2928700076638537, 2.1193482926455656, 0.12463700961468778] -"HYUNDAI IONIQ PLUG-IN HYBRID 2019" = [2.970807902012267, 1.6312321830002083, 0.1088964990357482] -"HYUNDAI KONA ELECTRIC 2019" = [3.078814714619148, 2.307336938253934, 0.12359762054065548] -"HYUNDAI PALISADE 2020" = [2.544642494803999, 1.8721703683337008, 0.1301424599248651] -"HYUNDAI SANTA FE 2019" = [3.0787027729757632, 2.6173437483495565, 0.1207019341823945] -"HYUNDAI SANTA FE HYBRID 2022" = [3.501877602644835, 2.729064118456137, 0.10384068104538963] -"HYUNDAI SANTA FE PlUG-IN HYBRID 2022" = [1.6953050513611045, 1.5837614296206861, 0.12672855941458458] -"HYUNDAI SONATA 2019" = [2.2200457811703953, 1.2967330275895228, 0.14039920986586393] -"HYUNDAI SONATA 2020" = [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] -"HYUNDAI SONATA HYBRID 2021" = [2.8990264092395734, 2.061410192222139, 0.0899805488717382] -"HYUNDAI TUCSON 4TH GEN" = [2.960174, 2.860284, 0.108745] -"JEEP GRAND CHEROKEE 2019" = [2.30972, 1.289689569171081, 0.117048] -"JEEP GRAND CHEROKEE V6 2018" = [2.27116, 1.4057367824262523, 0.11725947414922003] -"KIA EV6 2022" = [3.2, 2.093457, 0.05] -"KIA K5 2021" = [2.405339728085138, 1.460032270828705, 0.11650989850813716] -"KIA NIRO EV 2020" = [2.9215954981365337, 2.1500583840260044, 0.09236802474810267] -"KIA SORENTO GT LINE 2018" = [2.464854685101844, 1.5335274218367956, 0.12056170567599558] -"KIA STINGER GT2 2018" = [2.7499043387418967, 1.849652021986449, 0.12048334239559202] -"LEXUS ES 2019" = [2.0357564999999997, 1.999082295195227, 0.101533] -"LEXUS NX 2018" = [2.3525924753753613, 1.9731412277641067, 0.15168101064205927] -"LEXUS NX 2020" = [2.4331999786982936, 2.1045680431705414, 0.14099899317761067] -"LEXUS RX 2016" = [1.6430539050086406, 1.181960058934143, 0.19768806040843034] -"LEXUS RX 2020" = [1.5375561442049257, 1.343166476215164, 0.1931062001527557] -"MAZDA CX-9 2021" = [1.7601682915983443, 1.0889677335154337, 0.17713792194297195] -"SKODA SUPERB 3RD GEN" = [1.166437404652981, 1.1686163012668165, 0.12194533036948708] -"SUBARU FORESTER 2019" = [3.6617001649776793, 2.342197172531713, 0.11075960785398745] -"SUBARU IMPREZA LIMITED 2019" = [1.0670704910352047, 0.8234374840709592, 0.20986563268614938] -"SUBARU IMPREZA SPORT 2020" = [2.6068223389108303, 2.134872342760203, 0.15261513193561627] -"TOYOTA AVALON 2016" = [2.5185770183845646, 1.7153346784214922, 0.10603968787111022] -"TOYOTA AVALON 2019" = [1.7036141952825095, 1.239619084240008, 0.08459830394899492] -"TOYOTA AVALON 2022" = [2.3154403649717357, 2.7777922854327124, 0.11453999639164605] -"TOYOTA C-HR 2018" = [1.5591084333664578, 1.271271459066948, 0.20259087058453193] -"TOYOTA C-HR 2021" = [1.7678810166088303, 1.3742176337919942, 0.2319674583741509] -"TOYOTA CAMRY 2018" = [2.0568162685952505, 1.7576185169559122, 0.108878753] -"TOYOTA CAMRY 2021" = [2.3548324999999997, 2.368900128946771, 0.118436] -"TOYOTA COROLLA 2017" = [3.117154369115421, 1.8438132575043773, 0.12289685869250652] -"TOYOTA COROLLA TSS2 2019" = [1.991132339206426, 1.868866242720403, 0.19570063298031432] -"TOYOTA HIGHLANDER 2017" = [1.8108348718624456, 1.6348421600679828, 0.15972686105120398] -"TOYOTA HIGHLANDER 2020" = [1.9617570834136164, 1.8611643317268927, 0.14519673256119725] -"TOYOTA MIRAI 2021" = [2.506899832157829, 1.7417213930750164, 0.20182618449440565] -"TOYOTA PRIUS 2017" = [1.60, 1.5023147650693636, 0.151515] -"TOYOTA PRIUS TSS2 2021" = [1.972600, 1.9104337425537743, 0.170968] -"TOYOTA RAV4 2017" = [2.085695074355425, 2.2142832316984733, 0.13339165270103975] -"TOYOTA RAV4 2019" = [2.279239424615458, 2.087101966779332, 0.13682208413446817] -"TOYOTA RAV4 2019 8965" = [2.3080951748210854, 2.1189367835820603, 0.12942102328134028] -"TOYOTA RAV4 2019 x02" = [2.762293266024922, 2.243615865975329, 0.11113568178327986] -"TOYOTA RAV4 HYBRID 2017" = [1.9796257271652042, 1.7503987331707576, 0.14628860048885406] -"TOYOTA RAV4 2022" = [2.241883248393209, 1.9304407208090029, 0.112174] -"TOYOTA RAV4 2022 x02" = [3.044930631831037, 2.3979189796380918, 0.14023209146703736] -"TOYOTA SIENNA 2018" = [1.689726, 1.3208264576110418, 0.140456] -"VOLKSWAGEN ARTEON 1ST GEN" = [1.45136518053819, 1.3639364049316804, 0.23806361745695032] -"VOLKSWAGEN ATLAS 1ST GEN" = [1.4677006726964945, 1.6733266634075656, 0.12959584092073367] -"VOLKSWAGEN GOLF 7TH GEN" = [1.3750394140491293, 1.5814743077200641, 0.2018321939386586] -"VOLKSWAGEN JETTA 7TH GEN" = [1.2271623034089392, 1.216955117387, 0.19437384688370712] -"VOLKSWAGEN PASSAT 8TH GEN" = [1.3432120736752917, 1.7087275587362314, 0.19444383787326647] -"VOLKSWAGEN TIGUAN 2ND GEN" = [0.9711965500094828, 1.0001565939459098, 0.1465626137072916] +"ACURA_ILX" = [1.524988973896102, 0.519011053086259, 0.34236219253028] +"ACURA_RDX" = [0.9987728568686902, 0.5323765166196301, 0.303218805715844] +"ACURA_RDX_3G" = [1.4314459806646749, 0.33874701282109954, 0.18048847083897598] +"AUDI_A3_MK3" = [1.5122414863077502, 1.7443517531719404, 0.15194151892450905] +"AUDI_Q3_MK2" = [1.4439223359448605, 1.2254955789112076, 0.1413798895978097] +"CHEVROLET_VOLT" = [1.5961527626411784, 1.8422651988094612, 0.1572393918005158] +"CHRYSLER_PACIFICA_2018" = [2.07140, 1.3366521181047952, 0.13776367250652022] +"CHRYSLER_PACIFICA_2020" = [1.86206, 1.509076559398423, 0.14328246159386085] +"CHRYSLER_PACIFICA_2017_HYBRID" = [1.79422, 1.06831764583744, 0.116237] +"CHRYSLER_PACIFICA_2018_HYBRID" = [2.08887, 1.2943025830995154, 0.114818] +"CHRYSLER_PACIFICA_2019_HYBRID" = [1.90120, 1.1958788168371808, 0.131520] +"GENESIS_G70" = [3.8520195946707947, 2.354697063349854, 0.06830285485626221] +"HONDA_ACCORD" = [1.6893333799149202, 0.3246749081720698, 0.2120497022936265] +"HONDA_CIVIC_BOSCH" = [1.691708637466905, 0.40132900729454185, 0.25460295304024094] +"HONDA_CIVIC" = [1.6528895627785531, 0.4018518740819229, 0.25458812851328544] +"HONDA_CRV" = [0.7667141440182675, 0.5927571534745969, 0.40909087636157127] +"HONDA_CRV_5G" = [2.01323205142022, 0.2700612209345081, 0.2238412881331528] +"HONDA_CRV_HYBRID" = [2.072034634644233, 0.7152085160516978, 0.20237105008376083] +"HONDA_FIT" = [1.5719981427109775, 0.5712761407108976, 0.110773383324281] +"HONDA_HRV" = [2.0661212805710205, 0.7521343418694775, 0.17760375789242094] +"HONDA_INSIGHT" = [1.5201671214069354, 0.5660229120683284, 0.25808042580281876] +"HONDA_ODYSSEY" = [1.8774809275211801, 0.8394431662987996, 0.2096978613792822] +"HONDA_PILOT" = [1.7262026201812795, 0.9470005614967523, 0.21351430733218763] +"HONDA_RIDGELINE" = [1.4146525028237624, 0.7356572861629564, 0.23307177552211328] +"HYUNDAI_ELANTRA_2021" = [3.169, 2.1259108157250735, 0.0819] +"HYUNDAI_GENESIS" = [2.7807965280270794, 2.325, 0.0984484465421171] +"HYUNDAI_IONIQ_5" = [3.172929, 2.713050, 0.096019] +"HYUNDAI_IONIQ_EV_LTD" = [1.7662975472852054, 1.613755614526594, 0.17087579756306276] +"HYUNDAI_IONIQ_PHEV" = [3.2928700076638537, 2.1193482926455656, 0.12463700961468778] +"HYUNDAI_IONIQ_PHEV_2019" = [2.970807902012267, 1.6312321830002083, 0.1088964990357482] +"HYUNDAI_KONA_EV" = [3.078814714619148, 2.307336938253934, 0.12359762054065548] +"HYUNDAI_PALISADE" = [2.544642494803999, 1.8721703683337008, 0.1301424599248651] +"HYUNDAI_SANTA_FE" = [3.0787027729757632, 2.6173437483495565, 0.1207019341823945] +"HYUNDAI_SANTA_FE_HEV_2022" = [3.501877602644835, 2.729064118456137, 0.10384068104538963] +"HYUNDAI_SANTA_FE_PHEV_2022" = [1.6953050513611045, 1.5837614296206861, 0.12672855941458458] +"HYUNDAI_SONATA_LF" = [2.2200457811703953, 1.2967330275895228, 0.14039920986586393] +"HYUNDAI_SONATA" = [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] +"HYUNDAI_SONATA_HYBRID" = [2.8990264092395734, 2.061410192222139, 0.0899805488717382] +"HYUNDAI_TUCSON_4TH_GEN" = [2.960174, 2.860284, 0.108745] +"JEEP_GRAND_CHEROKEE_2019" = [2.30972, 1.289689569171081, 0.117048] +"JEEP_GRAND_CHEROKEE" = [2.27116, 1.4057367824262523, 0.11725947414922003] +"KIA_EV6" = [3.2, 2.093457, 0.005] +"KIA_K5_2021" = [2.405339728085138, 1.460032270828705, 0.11650989850813716] +"KIA_NIRO_EV" = [2.9215954981365337, 2.1500583840260044, 0.09236802474810267] +"KIA_SORENTO" = [2.464854685101844, 1.5335274218367956, 0.12056170567599558] +"KIA_STINGER" = [2.7499043387418967, 1.849652021986449, 0.12048334239559202] +"LEXUS_ES_TSS2" = [2.0357564999999997, 1.999082295195227, 0.101533] +"LEXUS_NX" = [2.3525924753753613, 1.9731412277641067, 0.15168101064205927] +"LEXUS_NX_TSS2" = [2.4331999786982936, 2.1045680431705414, 0.14099899317761067] +"LEXUS_RX" = [1.6430539050086406, 1.181960058934143, 0.19768806040843034] +"LEXUS_RX_TSS2" = [1.5375561442049257, 1.343166476215164, 0.1931062001527557] +"MAZDA_CX9_2021" = [1.7601682915983443, 1.0889677335154337, 0.17713792194297195] +"SKODA_SUPERB_MK3" = [1.166437404652981, 1.1686163012668165, 0.12194533036948708] +"SUBARU_FORESTER" = [3.6617001649776793, 2.342197172531713, 0.11075960785398745] +"SUBARU_IMPREZA" = [1.0670704910352047, 0.8234374840709592, 0.20986563268614938] +"SUBARU_IMPREZA_2020" = [2.6068223389108303, 2.134872342760203, 0.15261513193561627] +"TOYOTA_AVALON" = [2.5185770183845646, 1.7153346784214922, 0.10603968787111022] +"TOYOTA_AVALON_2019" = [1.7036141952825095, 1.239619084240008, 0.08459830394899492] +"TOYOTA_AVALON_TSS2" = [2.3154403649717357, 2.7777922854327124, 0.11453999639164605] +"TOYOTA_CHR" = [1.5591084333664578, 1.271271459066948, 0.20259087058453193] +"TOYOTA_CHR_TSS2" = [1.7678810166088303, 1.3742176337919942, 0.2319674583741509] +"TOYOTA_CAMRY" = [2.0568162685952505, 1.7576185169559122, 0.108878753] +"TOYOTA_CAMRY_TSS2" = [2.3548324999999997, 2.368900128946771, 0.118436] +"TOYOTA_COROLLA" = [3.117154369115421, 1.8438132575043773, 0.12289685869250652] +"TOYOTA_COROLLA_TSS2" = [1.991132339206426, 1.868866242720403, 0.19570063298031432] +"TOYOTA_HIGHLANDER" = [1.8108348718624456, 1.6348421600679828, 0.15972686105120398] +"TOYOTA_HIGHLANDER_TSS2" = [1.9617570834136164, 1.8611643317268927, 0.14519673256119725] +"TOYOTA_MIRAI" = [2.506899832157829, 1.7417213930750164, 0.20182618449440565] +"TOYOTA_PRIUS" = [1.60, 1.5023147650693636, 0.151515] +"TOYOTA_PRIUS_TSS2" = [1.972600, 1.9104337425537743, 0.170968] +"TOYOTA_RAV4" = [2.085695074355425, 2.2142832316984733, 0.13339165270103975] +"TOYOTA_RAV4_TSS2" = [2.279239424615458, 2.087101966779332, 0.13682208413446817] +"TOYOTA_RAV4H" = [1.9796257271652042, 1.7503987331707576, 0.14628860048885406] +"TOYOTA_RAV4_TSS2_2022" = [2.241883248393209, 1.9304407208090029, 0.112174] +"TOYOTA_SIENNA" = [1.689726, 1.3208264576110418, 0.140456] +"VOLKSWAGEN_ARTEON_MK1" = [1.45136518053819, 1.3639364049316804, 0.23806361745695032] +"VOLKSWAGEN_ATLAS_MK1" = [1.4677006726964945, 1.6733266634075656, 0.12959584092073367] +"VOLKSWAGEN_GOLF_MK7" = [1.3750394140491293, 1.5814743077200641, 0.2018321939386586] +"VOLKSWAGEN_JETTA_MK7" = [1.2271623034089392, 1.216955117387, 0.19437384688370712] +"VOLKSWAGEN_PASSAT_MK8" = [1.3432120736752917, 1.7087275587362314, 0.19444383787326647] +"VOLKSWAGEN_TIGUAN_MK2" = [0.9711965500094828, 1.0001565939459098, 0.1465626137072916] diff --git a/selfdrive/car/torque_data/substitute.toml b/selfdrive/car/torque_data/substitute.toml index 6822ef437b..8724a08010 100644 --- a/selfdrive/car/torque_data/substitute.toml +++ b/selfdrive/car/torque_data/substitute.toml @@ -1,85 +1,83 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] -"MAZDA 3" = "MAZDA CX-9 2021" -"MAZDA 6" = "MAZDA CX-9 2021" -"MAZDA CX-5" = "MAZDA CX-9 2021" -"MAZDA CX-5 2022" = "MAZDA CX-9 2021" -"MAZDA CX-9" = "MAZDA CX-9 2021" +"MAZDA_3" = "MAZDA_CX9_2021" +"MAZDA_6" = "MAZDA_CX9_2021" +"MAZDA_CX5" = "MAZDA_CX9_2021" +"MAZDA_CX5_2022" = "MAZDA_CX9_2021" +"MAZDA_CX9" = "MAZDA_CX9_2021" -"DODGE DURANGO 2021" = "CHRYSLER PACIFICA 2020" +"DODGE_DURANGO" = "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" +"TOYOTA_ALPHARD_TSS2" = "TOYOTA_SIENNA" +"TOYOTA_PRIUS_V" = "TOYOTA_PRIUS" +"LEXUS_IS" = "LEXUS_NX" +"LEXUS_CTH" = "LEXUS_NX" +"LEXUS_ES" = "TOYOTA_CAMRY" +"LEXUS_RC" = "LEXUS_NX_TSS2" +"LEXUS_LC_TSS2" = "LEXUS_NX_TSS2" -"KIA OPTIMA 4TH GEN" = "HYUNDAI SONATA 2020" -"KIA OPTIMA 4TH GEN FACELIFT" = "HYUNDAI SONATA 2020" -"KIA OPTIMA HYBRID 2017 & SPORTS 2019" = "HYUNDAI SONATA 2020" -"KIA OPTIMA HYBRID 4TH GEN FACELIFT" = "HYUNDAI SONATA 2020" -"KIA FORTE E 2018 & GT 2021" = "HYUNDAI SONATA 2020" -"KIA CEED INTRO ED 2019" = "HYUNDAI SONATA 2020" -"KIA SELTOS 2021" = "HYUNDAI SONATA 2020" -"KIA NIRO HYBRID 2019" = "KIA NIRO EV 2020" -"KIA NIRO PLUG-IN HYBRID 2022" = "KIA NIRO EV 2020" -"KIA NIRO HYBRID 2021" = "KIA NIRO EV 2020" -"HYUNDAI VELOSTER 2019" = "HYUNDAI SONATA 2019" -"HYUNDAI KONA 2020" = "HYUNDAI KONA ELECTRIC 2019" -"HYUNDAI KONA HYBRID 2020" = "HYUNDAI KONA ELECTRIC 2019" -"HYUNDAI KONA ELECTRIC 2022" = "HYUNDAI KONA ELECTRIC 2019" -"HYUNDAI IONIQ HYBRID 2017-2019" = "HYUNDAI IONIQ PLUG-IN HYBRID 2019" -"HYUNDAI IONIQ HYBRID 2020-2022" = "HYUNDAI IONIQ PLUG-IN HYBRID 2019" -"HYUNDAI IONIQ ELECTRIC 2020" = "HYUNDAI IONIQ PLUG-IN HYBRID 2019" -"HYUNDAI ELANTRA 2017" = "HYUNDAI SONATA 2019" -"HYUNDAI I30 N LINE 2019 & GT 2018 DCT" = "HYUNDAI SONATA 2019" -"HYUNDAI ELANTRA HYBRID 2021" = "HYUNDAI SONATA 2020" -"HYUNDAI TUCSON 2019" = "HYUNDAI SANTA FE 2019" -"HYUNDAI SANTA FE 2022" = "HYUNDAI SANTA FE HYBRID 2022" -"KIA K5 HYBRID 2020" = "KIA K5 2021" -"KIA STINGER 2022" = "KIA STINGER GT2 2018" -"GENESIS G90 2017" = "GENESIS G70 2018" -"GENESIS G80 2017" = "GENESIS G70 2018" -"GENESIS G70 2020" = "HYUNDAI SONATA 2020" +"KIA_OPTIMA_G4" = "HYUNDAI_SONATA" +"KIA_OPTIMA_G4_FL" = "HYUNDAI_SONATA" +"KIA_OPTIMA_H" = "HYUNDAI_SONATA" +"KIA_OPTIMA_H_G4_FL" = "HYUNDAI_SONATA" +"KIA_FORTE" = "HYUNDAI_SONATA" +"KIA_CEED" = "HYUNDAI_SONATA" +"KIA_SELTOS" = "HYUNDAI_SONATA" +"KIA_NIRO_PHEV" = "KIA_NIRO_EV" +"KIA_NIRO_PHEV_2022" = "KIA_NIRO_EV" +"KIA_NIRO_HEV_2021" = "KIA_NIRO_EV" +"HYUNDAI_VELOSTER" = "HYUNDAI_SONATA_LF" +"HYUNDAI_KONA" = "HYUNDAI_KONA_EV" +"HYUNDAI_KONA_HEV" = "HYUNDAI_KONA_EV" +"HYUNDAI_KONA_EV_2022" = "HYUNDAI_KONA_EV" +"HYUNDAI_IONIQ" = "HYUNDAI_IONIQ_PHEV_2019" +"HYUNDAI_IONIQ_HEV_2022" = "HYUNDAI_IONIQ_PHEV_2019" +"HYUNDAI_IONIQ_EV_2020" = "HYUNDAI_IONIQ_PHEV_2019" +"HYUNDAI_ELANTRA" = "HYUNDAI_SONATA_LF" +"HYUNDAI_ELANTRA_GT_I30" = "HYUNDAI_SONATA_LF" +"HYUNDAI_ELANTRA_HEV_2021" = "HYUNDAI_SONATA" +"HYUNDAI_TUCSON" = "HYUNDAI_SANTA_FE" +"HYUNDAI_SANTA_FE_2022" = "HYUNDAI_SANTA_FE_HEV_2022" +"KIA_K5_HEV_2020" = "KIA_K5_2021" +"KIA_STINGER_2022" = "KIA_STINGER" +"GENESIS_G90" = "GENESIS_G70" +"GENESIS_G80" = "GENESIS_G70" +"GENESIS_G70_2020" = "HYUNDAI_SONATA" -"HONDA FREED 2020" = "HONDA ODYSSEY 2018" -"HONDA CR-V EU 2016" = "HONDA CR-V 2016" -"HONDA CIVIC SEDAN 1.6 DIESEL 2019" = "HONDA CIVIC (BOSCH) 2019" -"HONDA E 2020" = "HONDA CIVIC (BOSCH) 2019" -"HONDA ODYSSEY CHN 2019" = "HONDA ODYSSEY 2018" +"HONDA_FREED" = "HONDA_ODYSSEY" +"HONDA_CRV_EU" = "HONDA_CRV" +"HONDA_CIVIC_BOSCH_DIESEL" = "HONDA_CIVIC_BOSCH" +"HONDA_E" = "HONDA_CIVIC_BOSCH" +"HONDA_ODYSSEY_CHN" = "HONDA_ODYSSEY" -"BUICK LACROSSE 2017" = "CHEVROLET VOLT PREMIER 2017" -"BUICK REGAL ESSENCE 2018" = "CHEVROLET VOLT PREMIER 2017" -"CADILLAC ESCALADE ESV 2016" = "CHEVROLET VOLT PREMIER 2017" -"CADILLAC ATS Premium Performance 2018" = "CHEVROLET VOLT PREMIER 2017" -"CHEVROLET MALIBU PREMIER 2017" = "CHEVROLET VOLT PREMIER 2017" -"HOLDEN ASTRA RS-V BK 2017" = "CHEVROLET VOLT PREMIER 2017" +"BUICK_LACROSSE" = "CHEVROLET_VOLT" +"BUICK_REGAL" = "CHEVROLET_VOLT" +"CADILLAC_ESCALADE_ESV" = "CHEVROLET_VOLT" +"CADILLAC_ATS" = "CHEVROLET_VOLT" +"CHEVROLET_MALIBU" = "CHEVROLET_VOLT" +"HOLDEN_ASTRA" = "CHEVROLET_VOLT" -"SKODA FABIA 4TH GEN" = "VOLKSWAGEN GOLF 7TH GEN" -"SKODA OCTAVIA 3RD GEN" = "SKODA SUPERB 3RD GEN" -"SKODA SCALA 1ST GEN" = "SKODA SUPERB 3RD GEN" -"SKODA KODIAQ 1ST GEN" = "SKODA SUPERB 3RD GEN" -"SKODA KAROQ 1ST GEN" = "SKODA SUPERB 3RD GEN" -"SKODA KAMIQ 1ST GEN" = "SKODA SUPERB 3RD GEN" -"VOLKSWAGEN CRAFTER 2ND GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" -"VOLKSWAGEN T-ROC 1ST GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" -"VOLKSWAGEN T-CROSS 1ST GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" -"VOLKSWAGEN TOURAN 2ND GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" -"VOLKSWAGEN TRANSPORTER T6.1" = "VOLKSWAGEN TIGUAN 2ND GEN" -"AUDI Q2 1ST GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" -"VOLKSWAGEN TAOS 1ST GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" -"VOLKSWAGEN POLO 6TH GEN" = "VOLKSWAGEN GOLF 7TH GEN" -"SEAT LEON 3RD GEN" = "VOLKSWAGEN GOLF 7TH GEN" -"SEAT ATECA 1ST GEN" = "VOLKSWAGEN GOLF 7TH GEN" +"SKODA_FABIA_MK4" = "VOLKSWAGEN_GOLF_MK7" +"SKODA_OCTAVIA_MK3" = "SKODA_SUPERB_MK3" +"SKODA_KODIAQ_MK1" = "SKODA_SUPERB_MK3" +"SKODA_KAROQ_MK1" = "SKODA_SUPERB_MK3" +"SKODA_KAMIQ_MK1" = "SKODA_SUPERB_MK3" +"VOLKSWAGEN_CRAFTER_MK2" = "VOLKSWAGEN_TIGUAN_MK2" +"VOLKSWAGEN_TROC_MK1" = "VOLKSWAGEN_TIGUAN_MK2" +"VOLKSWAGEN_TCROSS_MK1" = "VOLKSWAGEN_TIGUAN_MK2" +"VOLKSWAGEN_TOURAN_MK2" = "VOLKSWAGEN_TIGUAN_MK2" +"VOLKSWAGEN_TRANSPORTER_T61" = "VOLKSWAGEN_TIGUAN_MK2" +"AUDI_Q2_MK1" = "VOLKSWAGEN_TIGUAN_MK2" +"VOLKSWAGEN_TAOS_MK1" = "VOLKSWAGEN_TIGUAN_MK2" +"VOLKSWAGEN_POLO_MK6" = "VOLKSWAGEN_GOLF_MK7" +"SEAT_ATECA_MK1" = "VOLKSWAGEN_GOLF_MK7" -"SUBARU CROSSTREK HYBRID 2020" = "SUBARU IMPREZA SPORT 2020" -"SUBARU FORESTER HYBRID 2020" = "SUBARU IMPREZA SPORT 2020" -"SUBARU LEGACY 7TH GEN" = "SUBARU OUTBACK 6TH GEN" +"SUBARU_CROSSTREK_HYBRID" = "SUBARU_IMPREZA_2020" +"SUBARU_FORESTER_HYBRID" = "SUBARU_IMPREZA_2020" +"SUBARU_LEGACY" = "SUBARU_OUTBACK" # Old subarus don't have much data guessing it's like low torque impreza" -"SUBARU OUTBACK 2018 - 2019" = "SUBARU IMPREZA LIMITED 2019" -"SUBARU OUTBACK 2015 - 2017" = "SUBARU IMPREZA LIMITED 2019" -"SUBARU FORESTER 2017 - 2018" = "SUBARU IMPREZA LIMITED 2019" -"SUBARU LEGACY 2015 - 2018" = "SUBARU IMPREZA LIMITED 2019" -"SUBARU ASCENT LIMITED 2019" = "SUBARU FORESTER 2019" +"SUBARU_OUTBACK_PREGLOBAL_2018" = "SUBARU_IMPREZA" +"SUBARU_OUTBACK_PREGLOBAL" = "SUBARU_IMPREZA" +"SUBARU_FORESTER_PREGLOBAL" = "SUBARU_IMPREZA" +"SUBARU_LEGACY_PREGLOBAL" = "SUBARU_IMPREZA" +"SUBARU_ASCENT" = "SUBARU_FORESTER" diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 343b1d3031..f9b7a478e0 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -1,10 +1,10 @@ from cereal import car -from openpilot.common.numpy_fast import clip, interp -from openpilot.selfdrive.car import apply_meas_steer_torque_limits, apply_std_steer_angle_limits, common_fault_avoidance, \ - create_gas_interceptor_command, make_can_msg +from openpilot.common.numpy_fast import clip +from openpilot.selfdrive.car import apply_meas_steer_torque_limits, apply_std_steer_angle_limits, common_fault_avoidance, make_can_msg +from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.car.toyota import toyotacan from openpilot.selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, TSS2_CAR, \ - MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams, ToyotaFlags, \ + CarControllerParams, ToyotaFlags, \ UNSUPPORTED_DSU_CAR from opendbc.can.packer import CANPacker @@ -25,7 +25,7 @@ MAX_LTA_ANGLE = 94.9461 # deg MAX_LTA_DRIVER_TORQUE_ALLOWANCE = 150 # slightly above steering pressed allows some resistance when changing lanes -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP self.params = CarControllerParams(self.CP) @@ -36,6 +36,7 @@ class CarController: self.last_standstill = False self.standstill_req = False self.steer_rate_counter = 0 + self.distance_button = 0 self.packer = CANPacker(dbc_name) self.gas = 0 @@ -99,30 +100,10 @@ class CarController: lta_active, self.frame // 2, torque_wind_down)) # *** gas and brake *** - if self.CP.enableGasInterceptor and CC.longActive: - MAX_INTERCEPTOR_GAS = 0.5 - # RAV4 has very sensitive gas pedal - if self.CP.carFingerprint in (CAR.RAV4, CAR.RAV4H, CAR.HIGHLANDER): - PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0]) - elif self.CP.carFingerprint in (CAR.COROLLA,): - PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0]) - else: - PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.4, 0.5, 0.0]) - # offset for creep and windbrake - pedal_offset = interp(CS.out.vEgo, [0.0, 2.3, MIN_ACC_SPEED + PEDAL_TRANSITION], [-.4, 0.0, 0.2]) - pedal_command = PEDAL_SCALE * (actuators.accel + pedal_offset) - interceptor_gas_cmd = clip(pedal_command, 0., MAX_INTERCEPTOR_GAS) - else: - interceptor_gas_cmd = 0. pcm_accel_cmd = clip(actuators.accel, self.params.ACCEL_MIN, self.params.ACCEL_MAX) - # TODO: probably can delete this. CS.pcm_acc_status uses a different signal - # than CS.cruiseState.enabled. confirm they're not meaningfully different - if not CC.enabled and CS.pcm_acc_status: - pcm_cancel_cmd = 1 - # on entering standstill, send standstill request - if CS.out.standstill and not self.last_standstill and (self.CP.carFingerprint not in NO_STOP_TIMER_CAR or self.CP.enableGasInterceptor): + if CS.out.standstill and not self.last_standstill and (self.CP.carFingerprint not in NO_STOP_TIMER_CAR): self.standstill_req = True if CS.pcm_acc_status != 8: # pcm entered standstill or it's disabled @@ -138,23 +119,26 @@ class CarController: if (self.frame % 3 == 0 and self.CP.openpilotLongitudinalControl) or pcm_cancel_cmd: lead = hud_control.leadVisible or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged + # Press distance button until we are at the correct bar length. Only change while enabled to avoid skipping startup popup + if self.frame % 6 == 0 and self.CP.openpilotLongitudinalControl: + desired_distance = 4 - hud_control.leadDistanceBars + if CS.out.cruiseState.enabled and CS.pcm_follow_distance != desired_distance: + self.distance_button = not self.distance_button + else: + self.distance_button = 0 + # Lexus IS uses a different cancellation message if pcm_cancel_cmd and self.CP.carFingerprint in UNSUPPORTED_DSU_CAR: can_sends.append(toyotacan.create_acc_cancel_command(self.packer)) elif self.CP.openpilotLongitudinalControl: - can_sends.append(toyotacan.create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type, fcw_alert)) + can_sends.append(toyotacan.create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type, fcw_alert, + self.distance_button)) self.accel = pcm_accel_cmd else: - can_sends.append(toyotacan.create_accel_command(self.packer, 0, pcm_cancel_cmd, False, lead, CS.acc_type, False)) - - if self.frame % 2 == 0 and self.CP.enableGasInterceptor and self.CP.openpilotLongitudinalControl: - # send exactly zero if gas cmd is zero. Interceptor will send the max between read value and gas cmd. - # This prevents unexpected pedal range rescaling - can_sends.append(create_gas_interceptor_command(self.packer, interceptor_gas_cmd, self.frame // 2)) - self.gas = interceptor_gas_cmd + can_sends.append(toyotacan.create_accel_command(self.packer, 0, pcm_cancel_cmd, False, lead, CS.acc_type, False, self.distance_button)) # *** hud ui *** - if self.CP.carFingerprint != CAR.PRIUS_V: + if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V: # ui mesg is at 1Hz but we send asap if: # - there is something to display # - there is something to stop displaying @@ -184,7 +168,7 @@ class CarController: if self.frame % 20 == 0 and self.CP.flags & ToyotaFlags.DISABLE_RADAR.value: can_sends.append([0x750, 0, b"\x0F\x02\x3E\x00\x00\x00\x00\x00", 0]) - new_actuators = actuators.copy() + new_actuators = actuators.as_builder() new_actuators.steer = apply_steer / self.params.STEER_MAX new_actuators.steerOutputCan = apply_steer new_actuators.steeringAngleDeg = self.last_angle diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index e4ea0d30f9..8315f24ae4 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -40,6 +40,11 @@ class CarState(CarStateBase): self.accurate_steer_angle_seen = False self.angle_offset = FirstOrderFilter(None, 60.0, DT_CTRL, initialized=False) + self.prev_distance_button = 0 + self.distance_button = 0 + + self.pcm_follow_distance = 0 + self.low_speed_lockout = False self.acc_type = 1 self.lkas_hud = {} @@ -54,12 +59,8 @@ class CarState(CarStateBase): ret.brakePressed = cp.vl["BRAKE_MODULE"]["BRAKE_PRESSED"] != 0 ret.brakeHoldActive = cp.vl["ESP_CONTROL"]["BRAKE_HOLD_ACTIVE"] == 1 - if self.CP.enableGasInterceptor: - ret.gas = (cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) // 2 - ret.gasPressed = ret.gas > 805 - else: - # TODO: find a common gas pedal percentage signal - ret.gasPressed = cp.vl["PCM_CRUISE"]["GAS_RELEASED"] == 0 + + ret.gasPressed = cp.vl["PCM_CRUISE"]["GAS_RELEASED"] == 0 ret.wheelSpeeds = self.get_wheel_speeds( cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"], @@ -95,7 +96,7 @@ 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: + if self.CP.carFingerprint != CAR.TOYOTA_MIRAI: ret.engineRpm = cp.vl["ENGINE_RPM"]["RPM"] ret.steeringTorque = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_DRIVER"] @@ -148,7 +149,7 @@ class CarState(CarStateBase): # ignore standstill state in certain vehicles, since pcm allows to restart with just an acceleration request ret.cruiseState.standstill = self.pcm_acc_status == 7 ret.cruiseState.enabled = bool(cp.vl["PCM_CRUISE"]["CRUISE_ACTIVE"]) - ret.cruiseState.nonAdaptive = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] in (1, 2, 3, 4, 5, 6) + ret.cruiseState.nonAdaptive = self.pcm_acc_status in (1, 2, 3, 4, 5, 6) ret.genericToggle = bool(cp.vl["LIGHT_STALK"]["AUTO_HIGH_BEAM"]) ret.espDisabled = cp.vl["ESP_CONTROL"]["TC_DISABLED"] != 0 @@ -160,9 +161,20 @@ class CarState(CarStateBase): ret.leftBlindspot = (cp.vl["BSM"]["L_ADJACENT"] == 1) or (cp.vl["BSM"]["L_APPROACHING"] == 1) ret.rightBlindspot = (cp.vl["BSM"]["R_ADJACENT"] == 1) or (cp.vl["BSM"]["R_APPROACHING"] == 1) - if self.CP.carFingerprint != CAR.PRIUS_V: + if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V: self.lkas_hud = copy.copy(cp_cam.vl["LKAS_HUD"]) + if self.CP.carFingerprint not in UNSUPPORTED_DSU_CAR: + self.pcm_follow_distance = cp.vl["PCM_CRUISE_2"]["PCM_FOLLOW_DISTANCE"] + + if self.CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR) or (self.CP.flags & ToyotaFlags.SMART_DSU and not self.CP.flags & ToyotaFlags.RADAR_CAN_FILTER): + # distance button is wired to the ACC module (camera or radar) + self.prev_distance_button = self.distance_button + if self.CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR): + self.distance_button = cp_acc.vl["ACC_CONTROL"]["DISTANCE"] + else: + self.distance_button = cp.vl["SDSU"]["FD_BUTTON"] + return ret @staticmethod @@ -183,7 +195,7 @@ class CarState(CarStateBase): ("STEER_TORQUE_SENSOR", 50), ] - if CP.carFingerprint != CAR.MIRAI: + if CP.carFingerprint != CAR.TOYOTA_MIRAI: messages.append(("ENGINE_RPM", 42)) if CP.carFingerprint in UNSUPPORTED_DSU_CAR: @@ -192,10 +204,6 @@ class CarState(CarStateBase): else: messages.append(("PCM_CRUISE_2", 33)) - # add gas interceptor reading if we are using it - if CP.enableGasInterceptor: - messages.append(("GAS_SENSOR", 50)) - if CP.enableBsm: messages.append(("BSM", 1)) @@ -213,13 +221,18 @@ class CarState(CarStateBase): ("PRE_COLLISION", 33), ] + if CP.flags & ToyotaFlags.SMART_DSU and not CP.flags & ToyotaFlags.RADAR_CAN_FILTER: + messages += [ + ("SDSU", 100), + ] + return CANParser(DBC[CP.carFingerprint]["pt"], messages, 0) @staticmethod def get_cam_can_parser(CP): messages = [] - if CP.carFingerprint != CAR.PRIUS_V: + if CP.carFingerprint != CAR.TOYOTA_PRIUS_V: messages += [ ("LKAS_HUD", 1), ] diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index 6b48408a10..0866a4d43c 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -4,7 +4,7 @@ from openpilot.selfdrive.car.toyota.values import CAR Ecu = car.CarParams.Ecu FW_VERSIONS = { - CAR.AVALON: { + CAR.TOYOTA_AVALON: { (Ecu.abs, 0x7b0, None): [ b'F152607060\x00\x00\x00\x00\x00\x00', ], @@ -30,7 +30,7 @@ FW_VERSIONS = { b'8646F0703000\x00\x00\x00\x00', ], }, - CAR.AVALON_2019: { + CAR.TOYOTA_AVALON_2019: { (Ecu.abs, 0x7b0, None): [ b'F152607110\x00\x00\x00\x00\x00\x00', b'F152607140\x00\x00\x00\x00\x00\x00', @@ -71,7 +71,7 @@ FW_VERSIONS = { b'8646F0702100\x00\x00\x00\x00', ], }, - CAR.AVALON_TSS2: { + CAR.TOYOTA_AVALON_TSS2: { (Ecu.abs, 0x7b0, None): [ b'\x01F152607240\x00\x00\x00\x00\x00\x00', b'\x01F152607250\x00\x00\x00\x00\x00\x00', @@ -95,7 +95,7 @@ FW_VERSIONS = { b'\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', ], }, - CAR.CAMRY: { + CAR.TOYOTA_CAMRY: { (Ecu.engine, 0x700, None): [ b'\x018966306L3100\x00\x00\x00\x00', b'\x018966306L4200\x00\x00\x00\x00', @@ -214,6 +214,7 @@ FW_VERSIONS = { b'8646F0601400 ', b'8646F0603400 ', b'8646F0603500 ', + b'8646F0604000 ', b'8646F0604100 ', b'8646F0605000 ', b'8646F0606000 ', @@ -222,7 +223,7 @@ FW_VERSIONS = { b'8646F0607100 ', ], }, - CAR.CAMRY_TSS2: { + CAR.TOYOTA_CAMRY_TSS2: { (Ecu.eps, 0x7a1, None): [ b'8965B33630\x00\x00\x00\x00\x00\x00', b'8965B33640\x00\x00\x00\x00\x00\x00', @@ -268,7 +269,7 @@ FW_VERSIONS = { b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, - CAR.CHR: { + CAR.TOYOTA_CHR: { (Ecu.engine, 0x700, None): [ b'\x01896631017100\x00\x00\x00\x00', b'\x01896631017200\x00\x00\x00\x00', @@ -353,7 +354,7 @@ FW_VERSIONS = { b'8646FF407100 ', ], }, - CAR.CHR_TSS2: { + CAR.TOYOTA_CHR_TSS2: { (Ecu.abs, 0x7b0, None): [ b'F152610041\x00\x00\x00\x00\x00\x00', b'F152610260\x00\x00\x00\x00\x00\x00', @@ -377,6 +378,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821FF410200\x00\x00\x00\x00', b'\x018821FF410300\x00\x00\x00\x00', + b'\x018821FF410400\x00\x00\x00\x00', b'\x018821FF410500\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ @@ -385,7 +387,7 @@ FW_VERSIONS = { b'\x028646FF413100\x00\x00\x00\x008646GF411100\x00\x00\x00\x00', ], }, - CAR.COROLLA: { + CAR.TOYOTA_COROLLA: { (Ecu.engine, 0x7e0, None): [ b'\x0230ZC2000\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0230ZC2100\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -419,7 +421,7 @@ FW_VERSIONS = { b'8646F0201200\x00\x00\x00\x00', ], }, - CAR.COROLLA_TSS2: { + CAR.TOYOTA_COROLLA_TSS2: { (Ecu.engine, 0x700, None): [ b'\x01896630A22000\x00\x00\x00\x00', b'\x01896630ZG2000\x00\x00\x00\x00', @@ -520,6 +522,7 @@ FW_VERSIONS = { ], (Ecu.abs, 0x7b0, None): [ b'\x01F152602280\x00\x00\x00\x00\x00\x00', + b'\x01F152602281\x00\x00\x00\x00\x00\x00', b'\x01F152602470\x00\x00\x00\x00\x00\x00', b'\x01F152602560\x00\x00\x00\x00\x00\x00', b'\x01F152602590\x00\x00\x00\x00\x00\x00', @@ -594,7 +597,7 @@ FW_VERSIONS = { b'\x028646F7605100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, - CAR.HIGHLANDER: { + CAR.TOYOTA_HIGHLANDER: { (Ecu.engine, 0x700, None): [ b'\x01896630E09000\x00\x00\x00\x00', b'\x01896630E43000\x00\x00\x00\x00', @@ -639,6 +642,7 @@ FW_VERSIONS = { (Ecu.dsu, 0x791, None): [ b'881510E01100\x00\x00\x00\x00', b'881510E01200\x00\x00\x00\x00', + b'881510E02200\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F4702100\x00\x00\x00\x00', @@ -649,7 +653,7 @@ FW_VERSIONS = { b'8646F0E01300\x00\x00\x00\x00', ], }, - CAR.HIGHLANDER_TSS2: { + CAR.TOYOTA_HIGHLANDER_TSS2: { (Ecu.eps, 0x7a1, None): [ b'8965B48241\x00\x00\x00\x00\x00\x00', b'8965B48310\x00\x00\x00\x00\x00\x00', @@ -686,6 +690,7 @@ FW_VERSIONS = { b'\x01896630EB1000\x00\x00\x00\x00', b'\x01896630EB1100\x00\x00\x00\x00', b'\x01896630EB1200\x00\x00\x00\x00', + b'\x01896630EB1300\x00\x00\x00\x00', b'\x01896630EB2000\x00\x00\x00\x00', b'\x01896630EB2100\x00\x00\x00\x00', b'\x01896630EB2200\x00\x00\x00\x00', @@ -725,6 +730,7 @@ FW_VERSIONS = { b'\x018966353M7000\x00\x00\x00\x00', b'\x018966353M7100\x00\x00\x00\x00', b'\x018966353Q2000\x00\x00\x00\x00', + b'\x018966353Q2100\x00\x00\x00\x00', b'\x018966353Q2300\x00\x00\x00\x00', b'\x018966353Q4000\x00\x00\x00\x00', b'\x018966353R1100\x00\x00\x00\x00', @@ -772,23 +778,29 @@ FW_VERSIONS = { b'\x018966353S1000\x00\x00\x00\x00', b'\x018966353S2000\x00\x00\x00\x00', ], + (Ecu.engine, 0x7e0, None): [ + b'\x02353U0000\x00\x00\x00\x00\x00\x00\x00\x0052422000\x00\x00\x00\x00\x00\x00\x00\x00', + ], (Ecu.abs, 0x7b0, None): [ b'\x01F15265337200\x00\x00\x00\x00', b'\x01F15265342000\x00\x00\x00\x00', + b'\x01F15265343000\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B53450\x00\x00\x00\x00\x00\x00', + b'8965B53800\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F6201200\x00\x00\x00\x00', b'\x018821F6201300\x00\x00\x00\x00', + b'\x018821F6201400\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F5303300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F5303400\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, - CAR.PRIUS: { + CAR.TOYOTA_PRIUS: { (Ecu.engine, 0x700, None): [ b'\x02896634761000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896634761100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -886,7 +898,7 @@ FW_VERSIONS = { b'8646F4705200\x00\x00\x00\x00', ], }, - CAR.PRIUS_V: { + CAR.TOYOTA_PRIUS_V: { (Ecu.abs, 0x7b0, None): [ b'F152647280\x00\x00\x00\x00\x00\x00', ], @@ -903,7 +915,7 @@ FW_VERSIONS = { b'8646F4703300\x00\x00\x00\x00', ], }, - CAR.RAV4: { + CAR.TOYOTA_RAV4: { (Ecu.engine, 0x7e0, None): [ b'\x02342Q1000\x00\x00\x00\x00\x00\x00\x00\x0054212000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02342Q1100\x00\x00\x00\x00\x00\x00\x00\x0054212000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -944,7 +956,7 @@ FW_VERSIONS = { b'8646F4204000\x00\x00\x00\x00', ], }, - CAR.RAV4H: { + CAR.TOYOTA_RAV4H: { (Ecu.engine, 0x7e0, None): [ b'\x02342N9000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02342N9100\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -981,7 +993,7 @@ FW_VERSIONS = { b'8646F4204000\x00\x00\x00\x00', ], }, - CAR.RAV4_TSS2: { + CAR.TOYOTA_RAV4_TSS2: { (Ecu.engine, 0x700, None): [ b'\x01896630R58000\x00\x00\x00\x00', b'\x01896630R58100\x00\x00\x00\x00', @@ -1027,6 +1039,7 @@ FW_VERSIONS = { b'\x02896634A13001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896634A13101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896634A13201\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896634A14001\x00\x00\x00\x00897CF0R01000\x00\x00\x00\x00', b'\x02896634A14001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', b'\x02896634A14001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896634A14101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', @@ -1091,7 +1104,7 @@ FW_VERSIONS = { b'\x028646F4203800\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', ], }, - CAR.RAV4_TSS2_2022: { + CAR.TOYOTA_RAV4_TSS2_2022: { (Ecu.abs, 0x7b0, None): [ b'\x01F15260R350\x00\x00\x00\x00\x00\x00', b'\x01F15260R361\x00\x00\x00\x00\x00\x00', @@ -1127,26 +1140,32 @@ FW_VERSIONS = { b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00', ], }, - CAR.RAV4_TSS2_2023: { + CAR.TOYOTA_RAV4_TSS2_2023: { (Ecu.abs, 0x7b0, None): [ b'\x01F15260R450\x00\x00\x00\x00\x00\x00', + b'\x01F15260R50000\x00\x00\x00\x00', b'\x01F15260R51000\x00\x00\x00\x00', b'\x01F15264283200\x00\x00\x00\x00', b'\x01F15264283300\x00\x00\x00\x00', b'\x01F152642F1000\x00\x00\x00\x00', b'\x01F152642F8000\x00\x00\x00\x00', + b'\x01F152642F8100\x00\x00\x00\x00', + b'\x01F152642F9000\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'\x028965B0R11000\x00\x00\x00\x008965B0R12000\x00\x00\x00\x00', b'8965B42371\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ + b'\x01896634A61000\x00\x00\x00\x00', b'\x01896634A88100\x00\x00\x00\x00', b'\x01896634A89100\x00\x00\x00\x00', b'\x01896634AE1001\x00\x00\x00\x00', b'\x01896634AF0000\x00\x00\x00\x00', b'\x01896634AJ2000\x00\x00\x00\x00', b'\x01896634AL5000\x00\x00\x00\x00', + b'\x01896634AL6000\x00\x00\x00\x00', + b'\x01896634AL8000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F0R03100\x00\x00\x00\x00', @@ -1157,7 +1176,7 @@ FW_VERSIONS = { b'\x028646F0R11000\x00\x00\x00\x008646G0R04000\x00\x00\x00\x00', ], }, - CAR.SIENNA: { + CAR.TOYOTA_SIENNA: { (Ecu.engine, 0x700, None): [ b'\x01896630832100\x00\x00\x00\x00', b'\x01896630832200\x00\x00\x00\x00', @@ -1225,6 +1244,7 @@ FW_VERSIONS = { b'\x028966333V4000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x028966333W1000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x02896633T09000\x00\x00\x00\x00897CF3307001\x00\x00\x00\x00', + b'\x02896633T10000\x00\x00\x00\x00897CF3307001\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F152606281\x00\x00\x00\x00\x00\x00', @@ -1266,6 +1286,7 @@ FW_VERSIONS = { }, CAR.LEXUS_ES: { (Ecu.engine, 0x7e0, None): [ + b'\x02333M4100\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02333M4200\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02333R0000\x00\x00\x00\x00\x00\x00\x00\x00A0C01000\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -1277,6 +1298,7 @@ FW_VERSIONS = { b'881513309400\x00\x00\x00\x00', b'881513309500\x00\x00\x00\x00', b'881513310400\x00\x00\x00\x00', + b'881513310500\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B33502\x00\x00\x00\x00\x00\x00', @@ -1321,6 +1343,7 @@ FW_VERSIONS = { b'\x01896637854000\x00\x00\x00\x00', b'\x01896637873000\x00\x00\x00\x00', b'\x01896637878000\x00\x00\x00\x00', + b'\x01896637878100\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\x0237841000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -1406,6 +1429,7 @@ FW_VERSIONS = { b'\x018821F6201400\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F1104200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', b'\x028646F1105200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, @@ -1454,6 +1478,7 @@ FW_VERSIONS = { b'\x01896630E41200\x00\x00\x00\x00', b'\x01896630E41500\x00\x00\x00\x00', b'\x01896630EA3100\x00\x00\x00\x00', + b'\x01896630EA3300\x00\x00\x00\x00', b'\x01896630EA3400\x00\x00\x00\x00', b'\x01896630EA4100\x00\x00\x00\x00', b'\x01896630EA4300\x00\x00\x00\x00', @@ -1536,6 +1561,7 @@ FW_VERSIONS = { b'\x018966348W5100\x00\x00\x00\x00', b'\x018966348W9000\x00\x00\x00\x00', b'\x018966348X0000\x00\x00\x00\x00', + b'\x01896634D11000\x00\x00\x00\x00', b'\x01896634D12000\x00\x00\x00\x00', b'\x01896634D12100\x00\x00\x00\x00', b'\x01896634D43000\x00\x00\x00\x00', @@ -1579,7 +1605,7 @@ FW_VERSIONS = { b'\x028646F4810400\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', ], }, - CAR.PRIUS_TSS2: { + CAR.TOYOTA_PRIUS_TSS2: { (Ecu.engine, 0x700, None): [ b'\x028966347B1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966347C4000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -1610,7 +1636,7 @@ FW_VERSIONS = { b'\x028646F4712000\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', ], }, - CAR.MIRAI: { + CAR.TOYOTA_MIRAI: { (Ecu.abs, 0x7d1, None): [ b'\x01898A36203000\x00\x00\x00\x00', ], @@ -1628,7 +1654,7 @@ FW_VERSIONS = { b'\x028646F6201400\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', ], }, - CAR.ALPHARD_TSS2: { + CAR.TOYOTA_ALPHARD_TSS2: { (Ecu.engine, 0x7e0, None): [ b'\x0235870000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 988b1b4547..98f63597ec 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -1,13 +1,13 @@ from cereal import car -from openpilot.common.conversions import Conversions as CV from panda import Panda from panda.python import uds from openpilot.selfdrive.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, \ MIN_ACC_SPEED, EPS_SCALE, UNSUPPORTED_DSU_CAR, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR -from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.disable_ecu import disable_ecu from openpilot.selfdrive.car.interfaces import CarInterfaceBase +ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName SteerControlType = car.CarParams.SteerControlType @@ -44,82 +44,37 @@ class CarInterface(CarInterfaceBase): stop_and_go = candidate in TSS2_CAR - if candidate == CAR.PRIUS: + # Detect smartDSU, which intercepts ACC_CMD from the DSU (or radar) allowing openpilot to send it + # 0x2AA is sent by a similar device which intercepts the radar instead of DSU on NO_DSU_CARs + if 0x2FF in fingerprint[0] or (0x2AA in fingerprint[0] and candidate in NO_DSU_CAR): + ret.flags |= ToyotaFlags.SMART_DSU.value + + if 0x2AA in fingerprint[0] and candidate in NO_DSU_CAR: + ret.flags |= ToyotaFlags.RADAR_CAN_FILTER.value + + # In TSS2 cars, the camera does long control + found_ecus = [fw.ecu for fw in car_fw] + ret.enableDsu = len(found_ecus) > 0 and Ecu.dsu not in found_ecus and candidate not in (NO_DSU_CAR | UNSUPPORTED_DSU_CAR) \ + and not (ret.flags & ToyotaFlags.SMART_DSU) + + if candidate == CAR.TOYOTA_PRIUS: stop_and_go = True - ret.wheelbase = 2.70 - ret.steerRatio = 15.74 # unknown end-to-end spec - ret.tireStiffnessFactor = 0.6371 # hand-tune - ret.mass = 3045. * CV.LB_TO_KG # Only give steer angle deadzone to for bad angle sensor prius for fw in car_fw: if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00': ret.steerActuatorDelay = 0.25 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg=0.2) - elif candidate == CAR.PRIUS_V: - stop_and_go = True - ret.wheelbase = 2.78 - ret.steerRatio = 17.4 - ret.tireStiffnessFactor = 0.5533 - ret.mass = 3340. * CV.LB_TO_KG - - elif candidate in (CAR.RAV4, CAR.RAV4H): - stop_and_go = True if (candidate in CAR.RAV4H) else False - ret.wheelbase = 2.65 - ret.steerRatio = 16.88 # 14.5 is spec end-to-end - ret.tireStiffnessFactor = 0.5533 - ret.mass = 3650. * CV.LB_TO_KG # mean between normal and hybrid - - elif candidate == CAR.COROLLA: - ret.wheelbase = 2.70 - ret.steerRatio = 18.27 - ret.tireStiffnessFactor = 0.444 # not optimized yet - ret.mass = 2860. * CV.LB_TO_KG # mean between normal and hybrid - elif candidate in (CAR.LEXUS_RX, CAR.LEXUS_RX_TSS2): stop_and_go = True - ret.wheelbase = 2.79 - ret.steerRatio = 16. # 14.8 is spec end-to-end ret.wheelSpeedFactor = 1.035 - ret.tireStiffnessFactor = 0.5533 - ret.mass = 4481. * CV.LB_TO_KG # mean between min and max - elif candidate in (CAR.CHR, CAR.CHR_TSS2): - stop_and_go = True - ret.wheelbase = 2.63906 - ret.steerRatio = 13.6 - ret.tireStiffnessFactor = 0.7933 - ret.mass = 3300. * CV.LB_TO_KG - - elif candidate in (CAR.CAMRY, CAR.CAMRY_TSS2): - stop_and_go = True - ret.wheelbase = 2.82448 - ret.steerRatio = 13.7 - ret.tireStiffnessFactor = 0.7933 - ret.mass = 3400. * CV.LB_TO_KG # mean between normal and hybrid - - elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDER_TSS2): - # TODO: TSS-P models can do stop and go, but unclear if it requires sDSU or unplugging DSU - stop_and_go = True - ret.wheelbase = 2.8194 # average of 109.8 and 112.2 in - ret.steerRatio = 16.0 - ret.tireStiffnessFactor = 0.8 - ret.mass = 4516. * CV.LB_TO_KG # mean between normal and hybrid - - elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALON_TSS2): + elif candidate in (CAR.TOYOTA_AVALON, CAR.TOYOTA_AVALON_2019, CAR.TOYOTA_AVALON_TSS2): # starting from 2019, all Avalon variants have stop and go # https://engage.toyota.com/static/images/toyota_safety_sense/TSS_Applicability_Chart.pdf - stop_and_go = candidate != CAR.AVALON - ret.wheelbase = 2.82 - ret.steerRatio = 14.8 # Found at https://pressroom.toyota.com/releases/2016+avalon+product+specs.download - ret.tireStiffnessFactor = 0.7983 - ret.mass = 3505. * CV.LB_TO_KG # mean between normal and hybrid + stop_and_go = candidate != CAR.TOYOTA_AVALON - elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023): - ret.wheelbase = 2.68986 - ret.steerRatio = 14.3 - ret.tireStiffnessFactor = 0.7933 - ret.mass = 3585. * CV.LB_TO_KG # Average between ICE and Hybrid + elif candidate in (CAR.TOYOTA_RAV4_TSS2, CAR.TOYOTA_RAV4_TSS2_2022, CAR.TOYOTA_RAV4_TSS2_2023): ret.lateralTuning.init('pid') ret.lateralTuning.pid.kiBP = [0.0] ret.lateralTuning.pid.kpBP = [0.0] @@ -136,75 +91,14 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 0.00004 break - elif candidate == CAR.COROLLA_TSS2: - ret.wheelbase = 2.67 # Average between 2.70 for sedan and 2.64 for hatchback - ret.steerRatio = 13.9 - ret.tireStiffnessFactor = 0.444 # not optimized yet - ret.mass = 3060. * CV.LB_TO_KG - - elif candidate in (CAR.LEXUS_ES, CAR.LEXUS_ES_TSS2): - ret.wheelbase = 2.8702 - ret.steerRatio = 16.0 # not optimized - ret.tireStiffnessFactor = 0.444 # not optimized yet - ret.mass = 3677. * CV.LB_TO_KG # mean between min and max - - elif candidate == CAR.SIENNA: + elif candidate in (CAR.TOYOTA_CHR, CAR.TOYOTA_CAMRY, CAR.TOYOTA_SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_NX): + # TODO: Some of these platforms are not advertised to have full range ACC, are they similar to SNG_WITHOUT_DSU cars? stop_and_go = True - ret.wheelbase = 3.03 - ret.steerRatio = 15.5 - ret.tireStiffnessFactor = 0.444 - ret.mass = 4590. * CV.LB_TO_KG - elif candidate in (CAR.LEXUS_IS, CAR.LEXUS_IS_TSS2, CAR.LEXUS_RC): - ret.wheelbase = 2.79908 - ret.steerRatio = 13.3 - ret.tireStiffnessFactor = 0.444 - ret.mass = 3736.8 * CV.LB_TO_KG - - elif candidate == CAR.LEXUS_GS_F: - ret.wheelbase = 2.84988 - ret.steerRatio = 13.3 - ret.tireStiffnessFactor = 0.444 - ret.mass = 4034. * CV.LB_TO_KG - - elif candidate == CAR.LEXUS_CTH: - stop_and_go = True - ret.wheelbase = 2.60 - ret.steerRatio = 18.6 - ret.tireStiffnessFactor = 0.517 - ret.mass = 3108 * CV.LB_TO_KG # mean between min and max - - elif candidate in (CAR.LEXUS_NX, CAR.LEXUS_NX_TSS2): - stop_and_go = True - ret.wheelbase = 2.66 - ret.steerRatio = 14.7 - 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 - ret.tireStiffnessFactor = 0.6371 # hand-tune - ret.mass = 3115. * CV.LB_TO_KG - - elif candidate == CAR.MIRAI: - stop_and_go = True - ret.wheelbase = 2.91 - ret.steerRatio = 14.8 - ret.tireStiffnessFactor = 0.8 - ret.mass = 4300. * CV.LB_TO_KG - - elif candidate == CAR.ALPHARD_TSS2: - ret.wheelbase = 3.00 - ret.steerRatio = 14.2 - ret.tireStiffnessFactor = 0.444 - ret.mass = 4305. * CV.LB_TO_KG + # TODO: these models can do stop and go, but unclear if it requires sDSU or unplugging DSU. + # For now, don't list stop and go functionality in the docs + if ret.flags & ToyotaFlags.SNG_WITHOUT_DSU: + stop_and_go = stop_and_go or bool(ret.flags & ToyotaFlags.SMART_DSU.value) or (ret.enableDsu and not docs) ret.centerToFront = ret.wheelbase * 0.44 @@ -212,20 +106,10 @@ class CarInterface(CarInterfaceBase): # Detect flipped signals and enable for C-HR and others ret.enableBsm = 0x3F6 in fingerprint[0] and candidate in TSS2_CAR - # Detect smartDSU, which intercepts ACC_CMD from the DSU (or radar) allowing openpilot to send it - # 0x2AA is sent by a similar device which intercepts the radar instead of DSU on NO_DSU_CARs - if 0x2FF in fingerprint[0] or (0x2AA in fingerprint[0] and candidate in NO_DSU_CAR): - ret.flags |= ToyotaFlags.SMART_DSU.value - # No radar dbc for cars without DSU which are not TSS 2.0 # TODO: make an adas dbc file for dsu-less models ret.radarUnavailable = DBC[candidate]['radar'] is None or candidate in (NO_DSU_CAR - TSS2_CAR) - # In TSS2 cars, the camera does long control - found_ecus = [fw.ecu for fw in car_fw] - ret.enableDsu = len(found_ecus) > 0 and Ecu.dsu not in found_ecus and candidate not in (NO_DSU_CAR | UNSUPPORTED_DSU_CAR) \ - and not (ret.flags & ToyotaFlags.SMART_DSU) - # 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) @@ -249,35 +133,24 @@ 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 + ret.minEnableSpeed = -1. if stop_and_go else MIN_ACC_SPEED tune = ret.longitudinalTuning - tune.deadzoneBP = [0., 9.] - tune.deadzoneV = [.0, .15] - if candidate in TSS2_CAR or ret.enableGasInterceptor: - tune.kpBP = [0., 5., 20.] - tune.kpV = [1.3, 1.0, 0.7] - tune.kiBP = [0., 5., 12., 20., 27.] - tune.kiV = [.35, .23, .20, .17, .1] - if candidate in TSS2_CAR: - ret.vEgoStopping = 0.25 - ret.vEgoStarting = 0.25 - ret.stoppingDecelRate = 0.3 # reach stopping target smoothly + if candidate in TSS2_CAR: + tune.kpV = [0.0] + tune.kiV = [0.5] + ret.vEgoStopping = 0.25 + ret.vEgoStarting = 0.25 + ret.stoppingDecelRate = 0.3 # reach stopping target smoothly else: - tune.kpBP = [0., 5., 35.] - tune.kiBP = [0., 35.] - tune.kpV = [3.6, 2.4, 1.5] - tune.kiV = [0.54, 0.36] + tune.kiBP = [0., 5., 35.] + tune.kiV = [3.6, 2.4, 1.5] return ret @@ -292,6 +165,9 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) + if self.CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR) or (self.CP.flags & ToyotaFlags.SMART_DSU and not self.CP.flags & ToyotaFlags.RADAR_CAN_FILTER): + ret.buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) + # events events = self.create_common_events(ret) @@ -301,7 +177,7 @@ class CarInterface(CarInterfaceBase): events.add(EventName.vehicleSensorsInvalid) if self.CP.openpilotLongitudinalControl: - if ret.cruiseState.standstill and not ret.brakePressed and not self.CP.enableGasInterceptor: + if ret.cruiseState.standstill and not ret.brakePressed: events.add(EventName.resumeRequired) if self.CS.low_speed_lockout: events.add(EventName.lowSpeedLockout) @@ -317,8 +193,3 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() return ret - - # pass in a car.CarControl - # to be called @ 100hz - def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/toyota/tests/test_toyota.py b/selfdrive/car/toyota/tests/test_toyota.py old mode 100755 new mode 100644 index d9d4fe087f..0217a0fbc1 --- a/selfdrive/car/toyota/tests/test_toyota.py +++ b/selfdrive/car/toyota/tests/test_toyota.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 from hypothesis import given, settings, strategies as st -import unittest from cereal import car from openpilot.selfdrive.car.fw_versions import build_fw_dict @@ -13,51 +11,62 @@ Ecu = car.CarParams.Ecu ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} -class TestToyotaInterfaces(unittest.TestCase): +def check_fw_version(fw_version: bytes) -> bool: + return b'?' not in fw_version + + +class TestToyotaInterfaces: def test_car_sets(self): - self.assertTrue(len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0) - self.assertTrue(len(RADAR_ACC_CAR - TSS2_CAR) == 0) + assert len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0 + assert len(RADAR_ACC_CAR - TSS2_CAR) == 0 def test_lta_platforms(self): # At this time, only RAV4 2023 is expected to use LTA/angle control - self.assertEqual(ANGLE_CONTROL_CAR, {CAR.RAV4_TSS2_2023}) + assert ANGLE_CONTROL_CAR == {CAR.TOYOTA_RAV4_TSS2_2023} def test_tss2_dbc(self): # We make some assumptions about TSS2 platforms, # like looking up certain signals only in this DBC for car_model, dbc in DBC.items(): if car_model in TSS2_CAR: - self.assertEqual(dbc["pt"], "toyota_nodsu_pt_generated") + assert dbc["pt"] == "toyota_nodsu_pt_generated" - def test_essential_ecus(self): + def test_essential_ecus(self, subtests): # Asserts standard ECUs exist for each platform common_ecus = {Ecu.fwdRadar, Ecu.fwdCamera} for car_model, ecus in FW_VERSIONS.items(): - with self.subTest(car_model=car_model.value): + with subtests.test(car_model=car_model.value): present_ecus = {ecu[0] for ecu in ecus} missing_ecus = common_ecus - present_ecus - self.assertEqual(len(missing_ecus), 0) + assert len(missing_ecus) == 0 # Some exceptions for other common ECUs - if car_model not in (CAR.ALPHARD_TSS2,): - self.assertIn(Ecu.abs, present_ecus) + if car_model not in (CAR.TOYOTA_ALPHARD_TSS2,): + assert Ecu.abs in present_ecus - if car_model not in (CAR.MIRAI,): - self.assertIn(Ecu.engine, present_ecus) + if car_model not in (CAR.TOYOTA_MIRAI,): + assert Ecu.engine in present_ecus - if car_model not in (CAR.PRIUS_V, CAR.LEXUS_CTH): - self.assertIn(Ecu.eps, present_ecus) + if car_model not in (CAR.TOYOTA_PRIUS_V, CAR.LEXUS_CTH): + assert Ecu.eps in present_ecus -class TestToyotaFingerprint(unittest.TestCase): - def test_non_essential_ecus(self): +class TestToyotaFingerprint: + def test_non_essential_ecus(self, subtests): # Ensures only the cars that have multiple engine ECUs are in the engine non-essential ECU list for car_model, ecus in FW_VERSIONS.items(): - with self.subTest(car_model=car_model.value): + with subtests.test(car_model=car_model.value): engine_ecus = {ecu for ecu in ecus if ecu[0] == Ecu.engine} - self.assertEqual(len(engine_ecus) > 1, - car_model in FW_QUERY_CONFIG.non_essential_ecus[Ecu.engine], - f"Car model unexpectedly {'not ' if len(engine_ecus) > 1 else ''}in non-essential list") + assert (len(engine_ecus) > 1) == (car_model in FW_QUERY_CONFIG.non_essential_ecus[Ecu.engine]), \ + f"Car model unexpectedly {'not ' if len(engine_ecus) > 1 else ''}in non-essential list" + + def test_valid_fw_versions(self, subtests): + # Asserts all FW versions are valid + for car_model, ecus in FW_VERSIONS.items(): + with subtests.test(car_model=car_model.value): + for fws in ecus.values(): + for fw in fws: + assert check_fw_version(fw), fw # Tests for part numbers, platform codes, and sub-versions which Toyota will use to fuzzy # fingerprint in the absence of full FW matches: @@ -68,25 +77,25 @@ class TestToyotaFingerprint(unittest.TestCase): fws = data.draw(fw_strategy) get_platform_codes(fws) - def test_platform_code_ecus_available(self): + def test_platform_code_ecus_available(self, subtests): # Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms for car_model, ecus in FW_VERSIONS.items(): - with self.subTest(car_model=car_model.value): + with subtests.test(car_model=car_model.value): for platform_code_ecu in PLATFORM_CODE_ECUS: - if platform_code_ecu == Ecu.eps and car_model in (CAR.PRIUS_V, CAR.LEXUS_CTH,): + if platform_code_ecu == Ecu.eps and car_model in (CAR.TOYOTA_PRIUS_V, CAR.LEXUS_CTH,): continue - if platform_code_ecu == Ecu.abs and car_model in (CAR.ALPHARD_TSS2,): + if platform_code_ecu == Ecu.abs and car_model in (CAR.TOYOTA_ALPHARD_TSS2,): continue - self.assertIn(platform_code_ecu, [e[0] for e in ecus]) + assert platform_code_ecu in [e[0] for e in ecus] - def test_fw_format(self): + def test_fw_format(self, subtests): # Asserts: # - every supported ECU FW version returns one platform code # - every supported ECU FW version has a part number # - expected parsing of ECU sub-versions for car_model, ecus in FW_VERSIONS.items(): - with self.subTest(car_model=car_model.value): + with subtests.test(car_model=car_model.value): for ecu, fws in ecus.items(): if ecu[0] not in PLATFORM_CODE_ECUS: continue @@ -95,15 +104,14 @@ class TestToyotaFingerprint(unittest.TestCase): for fw in fws: result = get_platform_codes([fw]) # Check only one platform code and sub-version - self.assertEqual(1, len(result), f"Unable to parse FW: {fw}") - self.assertEqual(1, len(list(result.values())[0]), f"Unable to parse FW: {fw}") + assert 1 == len(result), f"Unable to parse FW: {fw}" + assert 1 == len(list(result.values())[0]), f"Unable to parse FW: {fw}" codes |= result # Toyota places the ECU part number in their FW versions, assert all parsable # Note that there is only one unique part number per ECU across the fleet, so this # is not important for identification, just a sanity check. - self.assertTrue(all(code.count(b"-") > 1 for code in codes), - f"FW does not have part number: {fw} {codes}") + assert all(code.count(b"-") > 1 for code in codes), f"FW does not have part number: {fw} {codes}" def test_platform_codes_spot_check(self): # Asserts basic platform code parsing behavior for a few cases @@ -113,20 +121,20 @@ class TestToyotaFingerprint(unittest.TestCase): b"F152607110\x00\x00\x00\x00\x00\x00", b"F152607180\x00\x00\x00\x00\x00\x00", ]) - self.assertEqual(results, {b"F1526-07-1": {b"10", b"40", b"71", b"80"}}) + assert results == {b"F1526-07-1": {b"10", b"40", b"71", b"80"}} results = get_platform_codes([ b"\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00", b"\x028646F4104100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00", ]) - self.assertEqual(results, {b"8646F-41-04": {b"100"}}) + assert results == {b"8646F-41-04": {b"100"}} # Short version has no part number results = get_platform_codes([ b"\x0235870000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00", b"\x0235883000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00", ]) - self.assertEqual(results, {b"58-70": {b"000"}, b"58-83": {b"000"}}) + assert results == {b"58-70": {b"000"}, b"58-83": {b"000"}} results = get_platform_codes([ b"F152607110\x00\x00\x00\x00\x00\x00", @@ -134,7 +142,7 @@ class TestToyotaFingerprint(unittest.TestCase): b"\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00", b"\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00", ]) - self.assertEqual(results, {b"F1526-07-1": {b"10", b"40"}, b"8646F-41-04": {b"100"}, b"58-79": {b"000"}}) + assert results == {b"F1526-07-1": {b"10", b"40"}, b"8646F-41-04": {b"100"}, b"58-79": {b"000"}} def test_fuzzy_excluded_platforms(self): # Asserts a list of platforms that will not fuzzy fingerprint with platform codes due to them being shared. @@ -148,15 +156,11 @@ class TestToyotaFingerprint(unittest.TestCase): "subAddress": 0 if sub_addr is None else sub_addr}) CP = car.CarParams.new_message(carFw=car_fw) - matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), FW_VERSIONS) + matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, FW_VERSIONS) if len(matches) == 1: - self.assertEqual(list(matches)[0], platform) + assert list(matches)[0] == platform else: # If a platform has multiple matches, add it and its matches platforms_with_shared_codes |= {str(platform), *matches} - self.assertEqual(platforms_with_shared_codes, FUZZY_EXCLUDED_PLATFORMS, (len(platforms_with_shared_codes), len(FW_VERSIONS))) - - -if __name__ == "__main__": - unittest.main() + assert platforms_with_shared_codes == FUZZY_EXCLUDED_PLATFORMS, (len(platforms_with_shared_codes), len(FW_VERSIONS)) diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index e14e3e53a0..1cc99b41b5 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -33,12 +33,12 @@ def create_lta_steer_command(packer, steer_control_type, steer_angle, steer_req, return packer.make_can_msg("STEERING_LTA", 0, values) -def create_accel_command(packer, accel, pcm_cancel, standstill_req, lead, acc_type, fcw_alert): +def create_accel_command(packer, accel, pcm_cancel, standstill_req, lead, acc_type, fcw_alert, distance): # TODO: find the exact canceling bit that does not create a chime values = { "ACCEL_CMD": accel, "ACC_TYPE": acc_type, - "DISTANCE": 0, + "DISTANCE": distance, "MINI_CAR": lead, "PERMIT_BRAKING": 1, "RELEASE_STANDSTILL": not standstill_req, diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 011d153f70..2e781ad0aa 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -1,12 +1,13 @@ import re from collections import defaultdict from dataclasses import dataclass, field -from enum import Enum, IntFlag, StrEnum +from enum import Enum, IntFlag from cereal import car from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car import CarSpecs, PlatformConfig, Platforms from openpilot.selfdrive.car import AngleRateLimit, dbc_dict -from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, CarParts, CarHarness +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarDocs, Column, CarParts, CarHarness from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu @@ -41,50 +42,22 @@ class CarControllerParams: class ToyotaFlags(IntFlag): + # Detected flags HYBRID = 1 SMART_DSU = 2 DISABLE_RADAR = 4 + RADAR_CAN_FILTER = 1024 - -class CAR(StrEnum): - # Toyota - ALPHARD_TSS2 = "TOYOTA ALPHARD 2020" - AVALON = "TOYOTA AVALON 2016" - AVALON_2019 = "TOYOTA AVALON 2019" - AVALON_TSS2 = "TOYOTA AVALON 2022" # TSS 2.5 - CAMRY = "TOYOTA CAMRY 2018" - CAMRY_TSS2 = "TOYOTA CAMRY 2021" # TSS 2.5 - CHR = "TOYOTA C-HR 2018" - CHR_TSS2 = "TOYOTA C-HR 2021" - COROLLA = "TOYOTA COROLLA 2017" - # LSS2 Lexus UX Hybrid is same as a TSS2 Corolla Hybrid - COROLLA_TSS2 = "TOYOTA COROLLA TSS2 2019" - HIGHLANDER = "TOYOTA HIGHLANDER 2017" - HIGHLANDER_TSS2 = "TOYOTA HIGHLANDER 2020" - PRIUS = "TOYOTA PRIUS 2017" - PRIUS_V = "TOYOTA PRIUS v 2017" - PRIUS_TSS2 = "TOYOTA PRIUS TSS2 2021" - RAV4 = "TOYOTA RAV4 2017" - RAV4H = "TOYOTA RAV4 HYBRID 2017" - RAV4_TSS2 = "TOYOTA RAV4 2019" - RAV4_TSS2_2022 = "TOYOTA RAV4 2022" - RAV4_TSS2_2023 = "TOYOTA RAV4 2023" - MIRAI = "TOYOTA MIRAI 2021" # TSS 2.5 - SIENNA = "TOYOTA SIENNA 2018" - - # Lexus - LEXUS_CTH = "LEXUS CT HYBRID 2018" - LEXUS_ES = "LEXUS ES 2018" - LEXUS_ES_TSS2 = "LEXUS ES 2019" - LEXUS_IS = "LEXUS IS 2018" - 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" - LEXUS_GS_F = "LEXUS GS F 2016" + # Static flags + TSS2 = 8 + NO_DSU = 16 + UNSUPPORTED_DSU = 32 + RADAR_ACC = 64 + # these cars use the Lane Tracing Assist (LTA) message for lateral control + ANGLE_CONTROL = 128 + NO_STOP_TIMER = 256 + # these cars are speculated to allow stop and go when the DSU is unplugged or disabled with sDSU + SNG_WITHOUT_DSU = 512 class Footnote(Enum): @@ -94,161 +67,307 @@ class Footnote(Enum): @dataclass -class ToyotaCarInfo(CarInfo): +class ToyotaCarDocs(CarDocs): package: str = "All" car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.toyota_a])) -CAR_INFO: dict[str, ToyotaCarInfo | list[ToyotaCarInfo]] = { +@dataclass +class ToyotaTSS2PlatformConfig(PlatformConfig): + dbc_dict: dict = field(default_factory=lambda: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas')) + + def init(self): + self.flags |= ToyotaFlags.TSS2 | ToyotaFlags.NO_STOP_TIMER | ToyotaFlags.NO_DSU + + if self.flags & ToyotaFlags.RADAR_ACC: + self.dbc_dict = dbc_dict('toyota_nodsu_pt_generated', None) + + +class CAR(Platforms): # Toyota - CAR.ALPHARD_TSS2: [ - ToyotaCarInfo("Toyota Alphard 2019-20"), - ToyotaCarInfo("Toyota Alphard Hybrid 2021"), - ], - CAR.AVALON: [ - ToyotaCarInfo("Toyota Avalon 2016", "Toyota Safety Sense P"), - ToyotaCarInfo("Toyota Avalon 2017-18"), - ], - CAR.AVALON_2019: [ - ToyotaCarInfo("Toyota Avalon 2019-21"), - ToyotaCarInfo("Toyota Avalon Hybrid 2019-21"), - ], - CAR.AVALON_TSS2: [ - ToyotaCarInfo("Toyota Avalon 2022"), - ToyotaCarInfo("Toyota Avalon Hybrid 2022"), - ], - CAR.CAMRY: [ - ToyotaCarInfo("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]), - ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"), - ], - CAR.CAMRY_TSS2: [ - ToyotaCarInfo("Toyota Camry 2021-24", footnotes=[Footnote.CAMRY]), - ToyotaCarInfo("Toyota Camry Hybrid 2021-24"), - ], - CAR.CHR: [ - ToyotaCarInfo("Toyota C-HR 2017-20"), - ToyotaCarInfo("Toyota C-HR Hybrid 2017-20"), - ], - CAR.CHR_TSS2: [ - ToyotaCarInfo("Toyota C-HR 2021"), - ToyotaCarInfo("Toyota C-HR Hybrid 2021-22"), - ], - CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"), - CAR.COROLLA_TSS2: [ - ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), - ToyotaCarInfo("Toyota Corolla Cross (Non-US only) 2020-23", min_enable_speed=7.5), - ToyotaCarInfo("Toyota Corolla Hatchback 2019-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), - # Hybrid platforms - ToyotaCarInfo("Toyota Corolla Hybrid 2020-22"), - ToyotaCarInfo("Toyota Corolla Hybrid (Non-US only) 2020-23", min_enable_speed=7.5), - ToyotaCarInfo("Toyota Corolla Cross Hybrid (Non-US only) 2020-22", min_enable_speed=7.5), - ToyotaCarInfo("Lexus UX Hybrid 2019-23"), - ], - CAR.HIGHLANDER: [ - ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"), - ToyotaCarInfo("Toyota Highlander Hybrid 2017-19"), - ], - CAR.HIGHLANDER_TSS2: [ - ToyotaCarInfo("Toyota Highlander 2020-23"), - ToyotaCarInfo("Toyota Highlander Hybrid 2020-23"), - ], - CAR.PRIUS: [ - ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), - ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), - ToyotaCarInfo("Toyota Prius Prime 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), - ], - CAR.PRIUS_V: ToyotaCarInfo("Toyota Prius v 2017", "Toyota Safety Sense P", min_enable_speed=MIN_ACC_SPEED), - CAR.PRIUS_TSS2: [ - ToyotaCarInfo("Toyota Prius 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"), - ToyotaCarInfo("Toyota Prius Prime 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"), - ], - CAR.RAV4: [ - ToyotaCarInfo("Toyota RAV4 2016", "Toyota Safety Sense P"), - ToyotaCarInfo("Toyota RAV4 2017-18") - ], - CAR.RAV4H: [ - ToyotaCarInfo("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", video_link="https://youtu.be/LhT5VzJVfNI?t=26"), - ToyotaCarInfo("Toyota RAV4 Hybrid 2017-18", video_link="https://youtu.be/LhT5VzJVfNI?t=26") - ], - CAR.RAV4_TSS2: [ - ToyotaCarInfo("Toyota RAV4 2019-21", video_link="https://www.youtube.com/watch?v=wJxjDd42gGA"), - ToyotaCarInfo("Toyota RAV4 Hybrid 2019-21"), - ], - CAR.RAV4_TSS2_2022: [ - ToyotaCarInfo("Toyota RAV4 2022"), - ToyotaCarInfo("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"), - ], - CAR.RAV4_TSS2_2023: [ - ToyotaCarInfo("Toyota RAV4 2023-24"), - ToyotaCarInfo("Toyota RAV4 Hybrid 2023-24"), - ], - CAR.MIRAI: ToyotaCarInfo("Toyota Mirai 2021"), - CAR.SIENNA: ToyotaCarInfo("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", min_enable_speed=MIN_ACC_SPEED), + TOYOTA_ALPHARD_TSS2 = ToyotaTSS2PlatformConfig( + [ + ToyotaCarDocs("Toyota Alphard 2019-20"), + ToyotaCarDocs("Toyota Alphard Hybrid 2021"), + ], + CarSpecs(mass=4305. * CV.LB_TO_KG, wheelbase=3.0, steerRatio=14.2, tireStiffnessFactor=0.444), + ) + TOYOTA_AVALON = PlatformConfig( + [ + ToyotaCarDocs("Toyota Avalon 2016", "Toyota Safety Sense P"), + ToyotaCarDocs("Toyota Avalon 2017-18"), + ], + CarSpecs(mass=3505. * CV.LB_TO_KG, wheelbase=2.82, steerRatio=14.8, tireStiffnessFactor=0.7983), + dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), + ) + TOYOTA_AVALON_2019 = PlatformConfig( + [ + ToyotaCarDocs("Toyota Avalon 2019-21"), + ToyotaCarDocs("Toyota Avalon Hybrid 2019-21"), + ], + TOYOTA_AVALON.specs, + dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), + ) + TOYOTA_AVALON_TSS2 = ToyotaTSS2PlatformConfig( # TSS 2.5 + [ + ToyotaCarDocs("Toyota Avalon 2022"), + ToyotaCarDocs("Toyota Avalon Hybrid 2022"), + ], + TOYOTA_AVALON.specs, + ) + TOYOTA_CAMRY = PlatformConfig( + [ + ToyotaCarDocs("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]), + ToyotaCarDocs("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"), + ], + CarSpecs(mass=3400. * CV.LB_TO_KG, wheelbase=2.82448, steerRatio=13.7, tireStiffnessFactor=0.7933), + dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), + flags=ToyotaFlags.NO_DSU, + ) + TOYOTA_CAMRY_TSS2 = ToyotaTSS2PlatformConfig( # TSS 2.5 + [ + ToyotaCarDocs("Toyota Camry 2021-24", footnotes=[Footnote.CAMRY]), + ToyotaCarDocs("Toyota Camry Hybrid 2021-24"), + ], + TOYOTA_CAMRY.specs, + ) + TOYOTA_CHR = PlatformConfig( + [ + ToyotaCarDocs("Toyota C-HR 2017-20"), + ToyotaCarDocs("Toyota C-HR Hybrid 2017-20"), + ], + CarSpecs(mass=3300. * CV.LB_TO_KG, wheelbase=2.63906, steerRatio=13.6, tireStiffnessFactor=0.7933), + dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), + flags=ToyotaFlags.NO_DSU, + ) + TOYOTA_CHR_TSS2 = ToyotaTSS2PlatformConfig( + [ + ToyotaCarDocs("Toyota C-HR 2021"), + ToyotaCarDocs("Toyota C-HR Hybrid 2021-22"), + ], + TOYOTA_CHR.specs, + flags=ToyotaFlags.RADAR_ACC, + ) + TOYOTA_COROLLA = PlatformConfig( + [ToyotaCarDocs("Toyota Corolla 2017-19")], + CarSpecs(mass=2860. * CV.LB_TO_KG, wheelbase=2.7, steerRatio=18.27, tireStiffnessFactor=0.444), + dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), + ) + # LSS2 Lexus UX Hybrid is same as a TSS2 Corolla Hybrid + TOYOTA_COROLLA_TSS2 = ToyotaTSS2PlatformConfig( + [ + ToyotaCarDocs("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), + ToyotaCarDocs("Toyota Corolla Cross (Non-US only) 2020-23", min_enable_speed=7.5), + ToyotaCarDocs("Toyota Corolla Hatchback 2019-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), + # Hybrid platforms + ToyotaCarDocs("Toyota Corolla Hybrid 2020-22"), + ToyotaCarDocs("Toyota Corolla Hybrid (South America only) 2020-23", min_enable_speed=7.5), + ToyotaCarDocs("Toyota Corolla Cross Hybrid (Non-US only) 2020-22", min_enable_speed=7.5), + ToyotaCarDocs("Lexus UX Hybrid 2019-23"), + ], + CarSpecs(mass=3060. * CV.LB_TO_KG, wheelbase=2.67, steerRatio=13.9, tireStiffnessFactor=0.444), + ) + TOYOTA_HIGHLANDER = PlatformConfig( + [ + ToyotaCarDocs("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"), + ToyotaCarDocs("Toyota Highlander Hybrid 2017-19"), + ], + CarSpecs(mass=4516. * CV.LB_TO_KG, wheelbase=2.8194, steerRatio=16.0, tireStiffnessFactor=0.8), + dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), + flags=ToyotaFlags.NO_STOP_TIMER | ToyotaFlags.SNG_WITHOUT_DSU, + ) + TOYOTA_HIGHLANDER_TSS2 = ToyotaTSS2PlatformConfig( + [ + ToyotaCarDocs("Toyota Highlander 2020-23"), + ToyotaCarDocs("Toyota Highlander Hybrid 2020-23"), + ], + TOYOTA_HIGHLANDER.specs, + ) + TOYOTA_PRIUS = PlatformConfig( + [ + ToyotaCarDocs("Toyota Prius 2016", "Toyota Safety Sense P", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), + ToyotaCarDocs("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), + ToyotaCarDocs("Toyota Prius Prime 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), + ], + CarSpecs(mass=3045. * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.74, tireStiffnessFactor=0.6371), + dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), + ) + TOYOTA_PRIUS_V = PlatformConfig( + [ToyotaCarDocs("Toyota Prius v 2017", "Toyota Safety Sense P", min_enable_speed=MIN_ACC_SPEED)], + CarSpecs(mass=3340. * CV.LB_TO_KG, wheelbase=2.78, steerRatio=17.4, tireStiffnessFactor=0.5533), + dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), + flags=ToyotaFlags.NO_STOP_TIMER | ToyotaFlags.SNG_WITHOUT_DSU, + ) + TOYOTA_PRIUS_TSS2 = ToyotaTSS2PlatformConfig( + [ + ToyotaCarDocs("Toyota Prius 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"), + ToyotaCarDocs("Toyota Prius Prime 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"), + ], + CarSpecs(mass=3115. * CV.LB_TO_KG, wheelbase=2.70002, steerRatio=13.4, tireStiffnessFactor=0.6371), + ) + TOYOTA_RAV4 = PlatformConfig( + [ + ToyotaCarDocs("Toyota RAV4 2016", "Toyota Safety Sense P"), + ToyotaCarDocs("Toyota RAV4 2017-18") + ], + CarSpecs(mass=3650. * CV.LB_TO_KG, wheelbase=2.65, steerRatio=16.88, tireStiffnessFactor=0.5533), + dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), + ) + TOYOTA_RAV4H = PlatformConfig( + [ + ToyotaCarDocs("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", video_link="https://youtu.be/LhT5VzJVfNI?t=26"), + ToyotaCarDocs("Toyota RAV4 Hybrid 2017-18", video_link="https://youtu.be/LhT5VzJVfNI?t=26") + ], + TOYOTA_RAV4.specs, + dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), + # Note that the ICE RAV4 does not respect positive acceleration commands under 19 mph + flags=ToyotaFlags.NO_STOP_TIMER | ToyotaFlags.SNG_WITHOUT_DSU, + ) + TOYOTA_RAV4_TSS2 = ToyotaTSS2PlatformConfig( + [ + ToyotaCarDocs("Toyota RAV4 2019-21", video_link="https://www.youtube.com/watch?v=wJxjDd42gGA"), + ToyotaCarDocs("Toyota RAV4 Hybrid 2019-21"), + ], + CarSpecs(mass=3585. * CV.LB_TO_KG, wheelbase=2.68986, steerRatio=14.3, tireStiffnessFactor=0.7933), + ) + TOYOTA_RAV4_TSS2_2022 = ToyotaTSS2PlatformConfig( + [ + ToyotaCarDocs("Toyota RAV4 2022"), + ToyotaCarDocs("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"), + ], + TOYOTA_RAV4_TSS2.specs, + flags=ToyotaFlags.RADAR_ACC, + ) + TOYOTA_RAV4_TSS2_2023 = ToyotaTSS2PlatformConfig( + [ + ToyotaCarDocs("Toyota RAV4 2023-24"), + ToyotaCarDocs("Toyota RAV4 Hybrid 2023-24"), + ], + TOYOTA_RAV4_TSS2.specs, + flags=ToyotaFlags.RADAR_ACC | ToyotaFlags.ANGLE_CONTROL, + ) + TOYOTA_MIRAI = ToyotaTSS2PlatformConfig( # TSS 2.5 + [ToyotaCarDocs("Toyota Mirai 2021")], + CarSpecs(mass=4300. * CV.LB_TO_KG, wheelbase=2.91, steerRatio=14.8, tireStiffnessFactor=0.8), + ) + TOYOTA_SIENNA = PlatformConfig( + [ToyotaCarDocs("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", min_enable_speed=MIN_ACC_SPEED)], + CarSpecs(mass=4590. * CV.LB_TO_KG, wheelbase=3.03, steerRatio=15.5, tireStiffnessFactor=0.444), + dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), + flags=ToyotaFlags.NO_STOP_TIMER, + ) # Lexus - CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "Lexus Safety System+"), - CAR.LEXUS_ES: [ - ToyotaCarInfo("Lexus ES 2017-18"), - ToyotaCarInfo("Lexus ES Hybrid 2017-18"), - ], - CAR.LEXUS_ES_TSS2: [ - ToyotaCarInfo("Lexus ES 2019-24"), - ToyotaCarInfo("Lexus ES Hybrid 2019-24", video_link="https://youtu.be/BZ29osRVJeg?t=12"), - ], - CAR.LEXUS_IS: ToyotaCarInfo("Lexus IS 2017-19"), - CAR.LEXUS_IS_TSS2: ToyotaCarInfo("Lexus IS 2022-23"), - CAR.LEXUS_GS_F: ToyotaCarInfo("Lexus GS F 2016"), - CAR.LEXUS_NX: [ - ToyotaCarInfo("Lexus NX 2018-19"), - ToyotaCarInfo("Lexus NX Hybrid 2018-19"), - ], - CAR.LEXUS_NX_TSS2: [ - 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+"), - ToyotaCarInfo("Lexus RX 2017-19"), - # Hybrid platforms - ToyotaCarInfo("Lexus RX Hybrid 2016", "Lexus Safety System+"), - ToyotaCarInfo("Lexus RX Hybrid 2017-19"), - ], - CAR.LEXUS_RX_TSS2: [ - ToyotaCarInfo("Lexus RX 2020-22"), - ToyotaCarInfo("Lexus RX Hybrid 2020-22"), - ], -} + LEXUS_CTH = PlatformConfig( + [ToyotaCarDocs("Lexus CT Hybrid 2017-18", "Lexus Safety System+")], + CarSpecs(mass=3108. * CV.LB_TO_KG, wheelbase=2.6, steerRatio=18.6, tireStiffnessFactor=0.517), + dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), + ) + LEXUS_ES = PlatformConfig( + [ + ToyotaCarDocs("Lexus ES 2017-18"), + ToyotaCarDocs("Lexus ES Hybrid 2017-18"), + ], + CarSpecs(mass=3677. * CV.LB_TO_KG, wheelbase=2.8702, steerRatio=16.0, tireStiffnessFactor=0.444), + dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), + ) + LEXUS_ES_TSS2 = ToyotaTSS2PlatformConfig( + [ + ToyotaCarDocs("Lexus ES 2019-24"), + ToyotaCarDocs("Lexus ES Hybrid 2019-24", video_link="https://youtu.be/BZ29osRVJeg?t=12"), + ], + LEXUS_ES.specs, + ) + LEXUS_IS = PlatformConfig( + [ToyotaCarDocs("Lexus IS 2017-19")], + CarSpecs(mass=3736.8 * CV.LB_TO_KG, wheelbase=2.79908, steerRatio=13.3, tireStiffnessFactor=0.444), + dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), + flags=ToyotaFlags.UNSUPPORTED_DSU, + ) + LEXUS_IS_TSS2 = ToyotaTSS2PlatformConfig( + [ToyotaCarDocs("Lexus IS 2022-23")], + LEXUS_IS.specs, + ) + LEXUS_NX = PlatformConfig( + [ + ToyotaCarDocs("Lexus NX 2018-19"), + ToyotaCarDocs("Lexus NX Hybrid 2018-19"), + ], + CarSpecs(mass=4070. * CV.LB_TO_KG, wheelbase=2.66, steerRatio=14.7, tireStiffnessFactor=0.444), + dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), + ) + LEXUS_NX_TSS2 = ToyotaTSS2PlatformConfig( + [ + ToyotaCarDocs("Lexus NX 2020-21"), + ToyotaCarDocs("Lexus NX Hybrid 2020-21"), + ], + LEXUS_NX.specs, + ) + LEXUS_LC_TSS2 = ToyotaTSS2PlatformConfig( + [ToyotaCarDocs("Lexus LC 2024")], + CarSpecs(mass=4500. * CV.LB_TO_KG, wheelbase=2.87, steerRatio=13.0, tireStiffnessFactor=0.444), + ) + LEXUS_RC = PlatformConfig( + [ToyotaCarDocs("Lexus RC 2018-20")], + LEXUS_IS.specs, + dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), + flags=ToyotaFlags.UNSUPPORTED_DSU, + ) + LEXUS_RX = PlatformConfig( + [ + ToyotaCarDocs("Lexus RX 2016", "Lexus Safety System+"), + ToyotaCarDocs("Lexus RX 2017-19"), + # Hybrid platforms + ToyotaCarDocs("Lexus RX Hybrid 2016", "Lexus Safety System+"), + ToyotaCarDocs("Lexus RX Hybrid 2017-19"), + ], + CarSpecs(mass=4481. * CV.LB_TO_KG, wheelbase=2.79, steerRatio=16., tireStiffnessFactor=0.5533), + dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), + ) + LEXUS_RX_TSS2 = ToyotaTSS2PlatformConfig( + [ + ToyotaCarDocs("Lexus RX 2020-22"), + ToyotaCarDocs("Lexus RX Hybrid 2020-22"), + ], + LEXUS_RX.specs, + ) + LEXUS_GS_F = PlatformConfig( + [ToyotaCarDocs("Lexus GS F 2016")], + CarSpecs(mass=4034. * CV.LB_TO_KG, wheelbase=2.84988, steerRatio=13.3, tireStiffnessFactor=0.444), + dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), + flags=ToyotaFlags.UNSUPPORTED_DSU, + ) + # (addr, cars, bus, 1/freq*100, vl) STATIC_DSU_MSGS = [ - (0x128, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.AVALON), 1, 3, b'\xf4\x01\x90\x83\x00\x37'), - (0x128, (CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES), 1, 3, b'\x03\x00\x20\x00\x00\x52'), - (0x141, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, - CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 1, 2, b'\x00\x00\x00\x46'), - (0x160, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, - CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 1, 7, b'\x00\x00\x08\x12\x01\x31\x9c\x51'), - (0x161, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.AVALON, CAR.PRIUS_V), + (0x128, (CAR.TOYOTA_PRIUS, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.TOYOTA_RAV4, CAR.TOYOTA_COROLLA, CAR.TOYOTA_AVALON), \ + 1, 3, b'\xf4\x01\x90\x83\x00\x37'), + (0x128, (CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES), 1, 3, b'\x03\x00\x20\x00\x00\x52'), + (0x141, (CAR.TOYOTA_PRIUS, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.TOYOTA_RAV4, CAR.TOYOTA_COROLLA, CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_AVALON, + CAR.TOYOTA_SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.TOYOTA_PRIUS_V), 1, 2, b'\x00\x00\x00\x46'), + (0x160, (CAR.TOYOTA_PRIUS, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.TOYOTA_RAV4, CAR.TOYOTA_COROLLA, CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_AVALON, + CAR.TOYOTA_SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.TOYOTA_PRIUS_V), 1, 7, b'\x00\x00\x08\x12\x01\x31\x9c\x51'), + (0x161, (CAR.TOYOTA_PRIUS, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.TOYOTA_RAV4, CAR.TOYOTA_COROLLA, CAR.TOYOTA_AVALON, CAR.TOYOTA_PRIUS_V), 1, 7, b'\x00\x1e\x00\x00\x00\x80\x07'), - (0X161, (CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES), 1, 7, b'\x00\x1e\x00\xd4\x00\x00\x5b'), - (0x283, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, - CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 0, 3, b'\x00\x00\x00\x00\x00\x00\x8c'), - (0x2E6, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX), 0, 3, b'\xff\xf8\x00\x08\x7f\xe0\x00\x4e'), - (0x2E7, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX), 0, 3, b'\xa8\x9c\x31\x9c\x00\x00\x00\x02'), - (0x33E, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX), 0, 20, b'\x0f\xff\x26\x40\x00\x1f\x00'), - (0x344, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, - CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 0, 5, b'\x00\x00\x01\x00\x00\x00\x00\x50'), - (0x365, (CAR.PRIUS, CAR.LEXUS_NX, CAR.HIGHLANDER), 0, 20, b'\x00\x00\x00\x80\x03\x00\x08'), - (0x365, (CAR.RAV4, CAR.RAV4H, CAR.COROLLA, CAR.AVALON, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_RX, - CAR.PRIUS_V), 0, 20, b'\x00\x00\x00\x80\xfc\x00\x08'), - (0x366, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.HIGHLANDER), 0, 20, b'\x00\x00\x4d\x82\x40\x02\x00'), - (0x366, (CAR.RAV4, CAR.COROLLA, CAR.AVALON, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), + (0X161, (CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES), 1, 7, b'\x00\x1e\x00\xd4\x00\x00\x5b'), + (0x283, (CAR.TOYOTA_PRIUS, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.TOYOTA_RAV4, CAR.TOYOTA_COROLLA, CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_AVALON, + CAR.TOYOTA_SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.TOYOTA_PRIUS_V), 0, 3, b'\x00\x00\x00\x00\x00\x00\x8c'), + (0x2E6, (CAR.TOYOTA_PRIUS, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX), 0, 3, b'\xff\xf8\x00\x08\x7f\xe0\x00\x4e'), + (0x2E7, (CAR.TOYOTA_PRIUS, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX), 0, 3, b'\xa8\x9c\x31\x9c\x00\x00\x00\x02'), + (0x33E, (CAR.TOYOTA_PRIUS, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX), 0, 20, b'\x0f\xff\x26\x40\x00\x1f\x00'), + (0x344, (CAR.TOYOTA_PRIUS, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.TOYOTA_RAV4, CAR.TOYOTA_COROLLA, CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_AVALON, + CAR.TOYOTA_SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.TOYOTA_PRIUS_V), 0, 5, b'\x00\x00\x01\x00\x00\x00\x00\x50'), + (0x365, (CAR.TOYOTA_PRIUS, CAR.LEXUS_NX, CAR.TOYOTA_HIGHLANDER), 0, 20, b'\x00\x00\x00\x80\x03\x00\x08'), + (0x365, (CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H, CAR.TOYOTA_COROLLA, CAR.TOYOTA_AVALON, CAR.TOYOTA_SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_RX, + CAR.TOYOTA_PRIUS_V), 0, 20, b'\x00\x00\x00\x80\xfc\x00\x08'), + (0x366, (CAR.TOYOTA_PRIUS, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.TOYOTA_HIGHLANDER), 0, 20, b'\x00\x00\x4d\x82\x40\x02\x00'), + (0x366, (CAR.TOYOTA_RAV4, CAR.TOYOTA_COROLLA, CAR.TOYOTA_AVALON, CAR.TOYOTA_SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.TOYOTA_PRIUS_V), 0, 20, b'\x00\x72\x07\xff\x09\xfe\x00'), - (0x470, (CAR.PRIUS, CAR.LEXUS_RX), 1, 100, b'\x00\x00\x02\x7a'), - (0x470, (CAR.HIGHLANDER, CAR.RAV4H, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 1, 100, b'\x00\x00\x01\x79'), - (0x4CB, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, - CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 0, 100, b'\x0c\x00\x00\x00\x00\x00\x00\x00'), + (0x470, (CAR.TOYOTA_PRIUS, CAR.LEXUS_RX), 1, 100, b'\x00\x00\x02\x7a'), + (0x470, (CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_RAV4H, CAR.TOYOTA_SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.TOYOTA_PRIUS_V), 1, 100, b'\x00\x00\x01\x79'), + (0x4CB, (CAR.TOYOTA_PRIUS, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.TOYOTA_RAV4, CAR.TOYOTA_COROLLA, CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_AVALON, + CAR.TOYOTA_SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.TOYOTA_PRIUS_V), 0, 100, b'\x0c\x00\x00\x00\x00\x00\x00\x00'), ] @@ -296,7 +415,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, vin, offline_fw_versions) -> set[str]: candidates = set() for candidate, fws in offline_fw_versions.items(): @@ -375,62 +494,56 @@ FW_QUERY_CONFIG = FwQueryConfig( Request( [StdQueries.SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST_KWP], [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE_KWP], - whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.dsu, Ecu.abs, Ecu.eps, Ecu.epb, Ecu.telematics, - Ecu.srs, Ecu.combinationMeter, Ecu.transmission, Ecu.gateway, Ecu.hvac], + whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.dsu, Ecu.abs, Ecu.eps, Ecu.srs, Ecu.transmission, Ecu.hvac], bus=0, ), Request( [StdQueries.SHORT_TESTER_PRESENT_REQUEST, StdQueries.OBD_VERSION_REQUEST], [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, StdQueries.OBD_VERSION_RESPONSE], - whitelist_ecus=[Ecu.engine, Ecu.epb, Ecu.telematics, Ecu.hybrid, Ecu.srs, Ecu.combinationMeter, Ecu.transmission, - Ecu.gateway, Ecu.hvac], + whitelist_ecus=[Ecu.engine, Ecu.hybrid, Ecu.srs, Ecu.transmission, Ecu.hvac], bus=0, ), Request( [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.DEFAULT_DIAGNOSTIC_REQUEST, StdQueries.EXTENDED_DIAGNOSTIC_REQUEST, StdQueries.UDS_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.DEFAULT_DIAGNOSTIC_RESPONSE, StdQueries.EXTENDED_DIAGNOSTIC_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], - whitelist_ecus=[Ecu.engine, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.abs, Ecu.eps, Ecu.epb, Ecu.telematics, - Ecu.hybrid, Ecu.srs, Ecu.combinationMeter, Ecu.transmission, Ecu.gateway, Ecu.hvac], + whitelist_ecus=[Ecu.engine, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.abs, Ecu.eps, + Ecu.hybrid, Ecu.srs, Ecu.transmission, Ecu.hvac], bus=0, ), ], non_essential_ecus={ # FIXME: On some models, abs can sometimes be missing - Ecu.abs: [CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_IS, CAR.ALPHARD_TSS2], + Ecu.abs: [CAR.TOYOTA_RAV4, CAR.TOYOTA_COROLLA, CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_SIENNA, CAR.LEXUS_IS, CAR.TOYOTA_ALPHARD_TSS2], # On some models, the engine can show on two different addresses - Ecu.engine: [CAR.HIGHLANDER, CAR.CAMRY, CAR.COROLLA_TSS2, CAR.CHR, CAR.CHR_TSS2, CAR.LEXUS_IS, - CAR.LEXUS_RC, CAR.LEXUS_NX, CAR.LEXUS_NX_TSS2, CAR.LEXUS_RX, CAR.LEXUS_RX_TSS2], + Ecu.engine: [CAR.TOYOTA_HIGHLANDER, CAR.TOYOTA_CAMRY, CAR.TOYOTA_COROLLA_TSS2, CAR.TOYOTA_CHR, CAR.TOYOTA_CHR_TSS2, CAR.LEXUS_IS, + CAR.LEXUS_IS_TSS2, CAR.LEXUS_RC, CAR.LEXUS_NX, CAR.LEXUS_NX_TSS2, CAR.LEXUS_RX, CAR.LEXUS_RX_TSS2], }, extra_ecus=[ # All known ECUs on a late-model Toyota vehicle not queried here: # Responds to UDS: + # - Combination Meter (0x7c0) # - HV Battery (0x713, 0x747) # - Motor Generator (0x716, 0x724) # - 2nd ABS "Brake/EPB" (0x730) + # - Electronic Parking Brake ((0x750, 0x2c)) + # - Telematics ((0x750, 0xc7)) # Responds to KWP (0x1a8801): # - Steering Angle Sensor (0x7b3) # - EPS/EMPS (0x7a0, 0x7a1) + # - 2nd SRS Airbag (0x784) + # - Central Gateway ((0x750, 0x5f)) + # - Telematics ((0x750, 0xc7)) # Responds to KWP (0x1a8881): # - Body Control Module ((0x750, 0x40)) + # - Telematics ((0x750, 0xc7)) # Hybrid control computer can be on 0x7e2 (KWP) or 0x7d2 (UDS) depending on platform (Ecu.hybrid, 0x7e2, None), # Hybrid Control Assembly & Computer - # TODO: if these duplicate ECUs always exist together, remove one (Ecu.srs, 0x780, None), # SRS Airbag - (Ecu.srs, 0x784, None), # SRS Airbag 2 - # Likely only exists on cars where EPB isn't standard (e.g. Camry, Avalon (/Hybrid)) - # On some cars, EPB is controlled by the ABS module - (Ecu.epb, 0x750, 0x2c), # Electronic Parking Brake - # This isn't accessible on all cars - (Ecu.gateway, 0x750, 0x5f), - # On some cars, this only responds to b'\x1a\x88\x81', which is reflected by the b'\x1a\x88\x00' query - (Ecu.telematics, 0x750, 0xc7), # Transmission is combined with engine on some platforms, such as TSS-P RAV4 (Ecu.transmission, 0x701, None), # A few platforms have a tester present response on this address, add to log (Ecu.transmission, 0x7e1, None), - # On some cars, this only responds to b'\x1a\x88\x80' - (Ecu.combinationMeter, 0x7c0, None), (Ecu.hvac, 0x7c4, None), ], match_fw_to_car_fuzzy=match_fw_to_car_fuzzy, @@ -439,62 +552,24 @@ FW_QUERY_CONFIG = FwQueryConfig( STEER_THRESHOLD = 100 -DBC = { - CAR.RAV4H: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), - CAR.RAV4: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), - 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'), - CAR.CHR: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), - CAR.CHR_TSS2: dbc_dict('toyota_nodsu_pt_generated', None), - CAR.CAMRY: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), - CAR.CAMRY_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.HIGHLANDER: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), - CAR.HIGHLANDER_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.AVALON: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), - CAR.AVALON_2019: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), - CAR.AVALON_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.RAV4_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.RAV4_TSS2_2022: dbc_dict('toyota_nodsu_pt_generated', None), - CAR.RAV4_TSS2_2023: dbc_dict('toyota_nodsu_pt_generated', None), - CAR.COROLLA_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.LEXUS_ES: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), - CAR.LEXUS_ES_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.SIENNA: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), - CAR.LEXUS_IS: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), - CAR.LEXUS_IS_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.LEXUS_CTH: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), - CAR.LEXUS_NX: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), - CAR.LEXUS_NX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.PRIUS_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.MIRAI: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.ALPHARD_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.LEXUS_GS_F: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), -} - # These cars have non-standard EPS torque scale factors. All others are 73 -EPS_SCALE = defaultdict(lambda: 73, {CAR.PRIUS: 66, CAR.COROLLA: 88, CAR.LEXUS_IS: 77, CAR.LEXUS_RC: 77, CAR.LEXUS_CTH: 100, CAR.PRIUS_V: 100}) +EPS_SCALE = defaultdict(lambda: 73, + {CAR.TOYOTA_PRIUS: 66, CAR.TOYOTA_COROLLA: 88, CAR.LEXUS_IS: 77, CAR.LEXUS_RC: 77, CAR.LEXUS_CTH: 100, CAR.TOYOTA_PRIUS_V: 100}) # 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.LEXUS_LC_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, - CAR.CHR_TSS2} +TSS2_CAR = CAR.with_flags(ToyotaFlags.TSS2) -NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CAMRY} +NO_DSU_CAR = CAR.with_flags(ToyotaFlags.NO_DSU) # the DSU uses the AEB message for longitudinal on these cars -UNSUPPORTED_DSU_CAR = {CAR.LEXUS_IS, CAR.LEXUS_RC, CAR.LEXUS_GS_F} +UNSUPPORTED_DSU_CAR = CAR.with_flags(ToyotaFlags.UNSUPPORTED_DSU) # these cars have a radar which sends ACC messages instead of the camera -RADAR_ACC_CAR = {CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023, CAR.CHR_TSS2} +RADAR_ACC_CAR = CAR.with_flags(ToyotaFlags.RADAR_ACC) -# these cars use the Lane Tracing Assist (LTA) message for lateral control -ANGLE_CONTROL_CAR = {CAR.RAV4_TSS2_2023} +ANGLE_CONTROL_CAR = CAR.with_flags(ToyotaFlags.ANGLE_CONTROL) # no resume button press required -NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.PRIUS_V, CAR.RAV4H, CAR.HIGHLANDER, CAR.SIENNA} +NO_STOP_TIMER_CAR = CAR.with_flags(ToyotaFlags.NO_STOP_TIMER) + +DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/values.py b/selfdrive/car/values.py index 3e27870915..bf5d378ab4 100644 --- a/selfdrive/car/values.py +++ b/selfdrive/car/values.py @@ -1,4 +1,4 @@ -from typing import cast +from typing import get_args 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 @@ -6,13 +6,14 @@ 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.mock.values import CAR as MOCK 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] +Platform = BODY | CHRYSLER | FORD | GM | HONDA | HYUNDAI | MAZDA | MOCK | NISSAN | SUBARU | TESLA | TOYOTA | VOLKSWAGEN +BRANDS = get_args(Platform) -PLATFORMS: dict[str, Platform] = {str(platform): platform for brand in BRANDS for platform in cast(list[Platform], brand)} +PLATFORMS: dict[str, Platform] = {str(platform): platform for brand in BRANDS for platform in brand} diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index e668c35f7d..e8c9c58042 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -23,6 +23,7 @@ def get_vin(logcan, sendcan, buses, timeout=0.1, retry=2, debug=False): (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 + (StdQueries.UDS_VIN_REQUEST, StdQueries.UDS_VIN_RESPONSE, (0,), [0x74f], None, 0x6a), # Volkswagen fwdCamera ): if bus not in valid_buses: continue diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index 0cee2c7fce..a4e0c8946a 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -4,19 +4,21 @@ from openpilot.common.numpy_fast import clip from openpilot.common.conversions import Conversions as CV from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car import apply_driver_steer_torque_limits +from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.car.volkswagen import mqbcan, pqcan -from openpilot.selfdrive.car.volkswagen.values import CANBUS, PQ_CARS, CarControllerParams, VolkswagenFlags +from openpilot.selfdrive.car.volkswagen.values import CANBUS, CarControllerParams, VolkswagenFlags VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState -class CarController: +class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP self.CCP = CarControllerParams(CP) - self.CCS = pqcan if CP.carFingerprint in PQ_CARS else mqbcan + self.CCS = pqcan if CP.flags & VolkswagenFlags.PQ else mqbcan self.packer_pt = CANPacker(dbc_name) + self.ext_bus = CANBUS.pt if CP.networkLocation == car.CarParams.NetworkLocation.fwdCamera else CANBUS.cam self.apply_steer_last = 0 self.gra_acc_counter_last = None @@ -25,7 +27,7 @@ class CarController: self.hca_frame_timer_running = 0 self.hca_frame_same_torque = 0 - def update(self, CC, CS, ext_bus, now_nanos): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl can_sends = [] @@ -90,7 +92,7 @@ class CarController: hud_alert = 0 if hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw): hud_alert = self.CCP.LDW_MESSAGES["laneAssistTakeOver"] - can_sends.append(self.CCS.create_lka_hud_control(self.packer_pt, CANBUS.pt, CS.ldw_stock_values, CC.enabled, + can_sends.append(self.CCS.create_lka_hud_control(self.packer_pt, CANBUS.pt, CS.ldw_stock_values, CC.latActive, CS.out.steeringPressed, hud_alert, hud_control)) if self.frame % self.CCP.ACC_HUD_STEP == 0 and self.CP.openpilotLongitudinalControl: @@ -101,19 +103,19 @@ class CarController: # FIXME: follow the recent displayed-speed updates, also use mph_kmh toggle to fix display rounding problem? set_speed = hud_control.setSpeed * CV.MS_TO_KPH can_sends.append(self.CCS.create_acc_hud_control(self.packer_pt, CANBUS.pt, acc_hud_status, set_speed, - lead_distance)) + lead_distance, hud_control.leadDistanceBars)) # **** Stock ACC Button Controls **************************************** # gra_send_ready = self.CP.pcmCruise and CS.gra_stock_values["COUNTER"] != self.gra_acc_counter_last if gra_send_ready and (CC.cruiseControl.cancel or CC.cruiseControl.resume): - can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, ext_bus, CS.gra_stock_values, + can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, self.ext_bus, CS.gra_stock_values, cancel=CC.cruiseControl.cancel, resume=CC.cruiseControl.resume)) - new_actuators = actuators.copy() + new_actuators = actuators.as_builder() new_actuators.steer = self.apply_steer_last / self.CCP.STEER_MAX new_actuators.steerOutputCan = self.apply_steer_last self.gra_acc_counter_last = CS.gra_stock_values["COUNTER"] self.frame += 1 - return new_actuators, can_sends, self.eps_timer_soft_disable_alert + return new_actuators, can_sends diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 25c5bc04bc..ec6403496f 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -3,13 +3,15 @@ from cereal import car from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser -from openpilot.selfdrive.car.volkswagen.values import DBC, CANBUS, PQ_CARS, NetworkLocation, TransmissionType, GearShifter, \ +from openpilot.selfdrive.car.volkswagen.values import DBC, CANBUS, NetworkLocation, TransmissionType, GearShifter, \ CarControllerParams, VolkswagenFlags class CarState(CarStateBase): def __init__(self, CP): super().__init__(CP) + self.frame = 0 + self.eps_init_complete = False self.CCP = CarControllerParams(CP) self.button_states = {button.event_type: False for button in self.CCP.BUTTONS} self.esp_hold_confirmation = False @@ -31,7 +33,7 @@ class CarState(CarStateBase): return button_events def update(self, pt_cp, cam_cp, ext_cp, trans_type): - if self.CP.carFingerprint in PQ_CARS: + if self.CP.flags & VolkswagenFlags.PQ: return self.update_pq(pt_cp, cam_cp, ext_cp, trans_type) ret = car.CarState.new_message() @@ -47,18 +49,14 @@ class CarState(CarStateBase): ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.standstill = ret.vEgoRaw == 0 - # Update steering angle, rate, yaw rate, and driver input torque. VW send - # the sign/direction in a separate signal so they must be recombined. + # Update EPS position and state info. For signed values, VW sends the sign in a separate signal. ret.steeringAngleDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradwinkel"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradwinkel"])] ret.steeringRateDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradw_Geschw"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradw_Geschw"])] ret.steeringTorque = pt_cp.vl["LH_EPS_03"]["EPS_Lenkmoment"] * (1, -1)[int(pt_cp.vl["LH_EPS_03"]["EPS_VZ_Lenkmoment"])] ret.steeringPressed = abs(ret.steeringTorque) > self.CCP.STEER_DRIVER_ALLOWANCE ret.yawRate = pt_cp.vl["ESP_02"]["ESP_Gierrate"] * (1, -1)[int(pt_cp.vl["ESP_02"]["ESP_VZ_Gierrate"])] * CV.DEG_TO_RAD - - # Verify EPS readiness to accept steering commands hca_status = self.CCP.hca_status_values.get(pt_cp.vl["LH_EPS_03"]["EPS_HCA_Status"]) - ret.steerFaultPermanent = hca_status in ("DISABLED", "FAULT") - ret.steerFaultTemporary = hca_status in ("INITIALIZING", "REJECTED") + ret.steerFaultTemporary, ret.steerFaultPermanent = self.update_hca_state(hca_status) # VW Emergency Assist status tracking and mitigation self.eps_stock_values = pt_cp.vl["LH_EPS_03"] @@ -116,21 +114,24 @@ class CarState(CarStateBase): # Update ACC radar status. self.acc_type = ext_cp.vl["ACC_06"]["ACC_Typ"] - if pt_cp.vl["TSK_06"]["TSK_Status"] == 2: - # ACC okay and enabled, but not currently engaged - ret.cruiseState.available = True - ret.cruiseState.enabled = False - elif pt_cp.vl["TSK_06"]["TSK_Status"] in (3, 4, 5): - # ACC okay and enabled, currently regulating speed (3) or driver accel override (4) or brake only (5) - ret.cruiseState.available = True - ret.cruiseState.enabled = True + + # ACC okay but disabled (1), ACC ready (2), a radar visibility or other fault/disruption (6 or 7) + # currently regulating speed (3), driver accel override (4), brake only (5) + ret.cruiseState.available = pt_cp.vl["TSK_06"]["TSK_Status"] in (2, 3, 4, 5) + ret.cruiseState.enabled = pt_cp.vl["TSK_06"]["TSK_Status"] in (3, 4, 5) + + if self.CP.pcmCruise: + # Cruise Control mode; check for distance UI setting from the radar. + # ECM does not manage this, so do not need to check for openpilot longitudinal + ret.cruiseState.nonAdaptive = ext_cp.vl["ACC_02"]["ACC_Gesetzte_Zeitluecke"] == 0 else: - # ACC okay but disabled (1), or a radar visibility or other fault/disruption (6 or 7) - ret.cruiseState.available = False - ret.cruiseState.enabled = False + # Speed limiter mode; ECM faults if we command ACC while not pcmCruise + ret.cruiseState.nonAdaptive = bool(pt_cp.vl["TSK_06"]["TSK_Limiter_ausgewaehlt"]) + + ret.accFaulted = pt_cp.vl["TSK_06"]["TSK_Status"] in (6, 7) + self.esp_hold_confirmation = bool(pt_cp.vl["ESP_21"]["ESP_Haltebestaetigung"]) ret.cruiseState.standstill = self.CP.pcmCruise and self.esp_hold_confirmation - ret.accFaulted = pt_cp.vl["TSK_06"]["TSK_Status"] in (6, 7) # Update ACC setpoint. When the setpoint is zero or there's an error, the # radar sends a set-speed of ~90.69 m/s / 203mph. @@ -151,6 +152,7 @@ class CarState(CarStateBase): # Digital instrument clusters expect the ACC HUD lead car distance to be scaled differently self.upscale_lead_car_signal = bool(pt_cp.vl["Kombi_03"]["KBI_Variante"]) + self.frame += 1 return ret def update_pq(self, pt_cp, cam_cp, ext_cp, trans_type): @@ -168,18 +170,14 @@ class CarState(CarStateBase): ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.standstill = ret.vEgoRaw == 0 - # Update steering angle, rate, yaw rate, and driver input torque. VW send - # the sign/direction in a separate signal so they must be recombined. + # Update EPS position and state info. For signed values, VW sends the sign in a separate signal. ret.steeringAngleDeg = pt_cp.vl["Lenkhilfe_3"]["LH3_BLW"] * (1, -1)[int(pt_cp.vl["Lenkhilfe_3"]["LH3_BLWSign"])] ret.steeringRateDeg = pt_cp.vl["Lenkwinkel_1"]["Lenkradwinkel_Geschwindigkeit"] * (1, -1)[int(pt_cp.vl["Lenkwinkel_1"]["Lenkradwinkel_Geschwindigkeit_S"])] ret.steeringTorque = pt_cp.vl["Lenkhilfe_3"]["LH3_LM"] * (1, -1)[int(pt_cp.vl["Lenkhilfe_3"]["LH3_LMSign"])] ret.steeringPressed = abs(ret.steeringTorque) > self.CCP.STEER_DRIVER_ALLOWANCE ret.yawRate = pt_cp.vl["Bremse_5"]["Giergeschwindigkeit"] * (1, -1)[int(pt_cp.vl["Bremse_5"]["Vorzeichen_der_Giergeschwindigk"])] * CV.DEG_TO_RAD - - # Verify EPS readiness to accept steering commands hca_status = self.CCP.hca_status_values.get(pt_cp.vl["Lenkhilfe_2"]["LH2_Sta_HCA"]) - ret.steerFaultPermanent = hca_status in ("DISABLED", "FAULT") - ret.steerFaultTemporary = hca_status in ("INITIALIZING", "REJECTED") + ret.steerFaultTemporary, ret.steerFaultPermanent = self.update_hca_state(hca_status) # Update gas, brakes, and gearshift. ret.gas = pt_cp.vl["Motor_3"]["Fahrpedal_Rohsignal"] / 100.0 @@ -253,11 +251,20 @@ class CarState(CarStateBase): # Additional safety checks performed in CarInterface. ret.espDisabled = bool(pt_cp.vl["Bremse_1"]["ESP_Passiv_getastet"]) + self.frame += 1 return ret + def update_hca_state(self, hca_status): + # Treat INITIALIZING and FAULT as temporary for worst likely EPS recovery time, for cars without factory Lane Assist + # DISABLED means the EPS hasn't been configured to support Lane Assist + self.eps_init_complete = self.eps_init_complete or (hca_status in ("DISABLED", "READY", "ACTIVE") or self.frame > 600) + perm_fault = hca_status == "DISABLED" or (self.eps_init_complete and hca_status in ("INITIALIZING", "FAULT")) + temp_fault = hca_status in ("REJECTED", "PREEMPTED") or not self.eps_init_complete + return temp_fault, perm_fault + @staticmethod def get_can_parser(CP): - if CP.carFingerprint in PQ_CARS: + if CP.flags & VolkswagenFlags.PQ: return CarState.get_can_parser_pq(CP) messages = [ @@ -294,7 +301,7 @@ class CarState(CarStateBase): @staticmethod def get_cam_can_parser(CP): - if CP.carFingerprint in PQ_CARS: + if CP.flags & VolkswagenFlags.PQ: return CarState.get_cam_can_parser_pq(CP) messages = [] diff --git a/selfdrive/car/volkswagen/fingerprints.py b/selfdrive/car/volkswagen/fingerprints.py index eab2bc0090..fea530b29b 100644 --- a/selfdrive/car/volkswagen/fingerprints.py +++ b/selfdrive/car/volkswagen/fingerprints.py @@ -7,7 +7,7 @@ Ecu = car.CarParams.Ecu FW_VERSIONS = { - CAR.ARTEON_MK1: { + CAR.VOLKSWAGEN_ARTEON_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x873G0906259AH\xf1\x890001', b'\xf1\x873G0906259F \xf1\x890004', @@ -49,7 +49,7 @@ FW_VERSIONS = { b'\xf1\x875Q0907572R \xf1\x890771', ], }, - CAR.ATLAS_MK1: { + CAR.VOLKSWAGEN_ATLAS_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8703H906026AA\xf1\x899970', b'\xf1\x8703H906026AG\xf1\x899973', @@ -60,6 +60,7 @@ FW_VERSIONS = { b'\xf1\x8703H906026F \xf1\x896696', b'\xf1\x8703H906026F \xf1\x899970', b'\xf1\x8703H906026J \xf1\x896026', + b'\xf1\x8703H906026J \xf1\x899970', b'\xf1\x8703H906026J \xf1\x899971', b'\xf1\x8703H906026S \xf1\x896693', b'\xf1\x8703H906026S \xf1\x899970', @@ -80,6 +81,7 @@ FW_VERSIONS = { b'\xf1\x873Q0959655BN\xf1\x890713\xf1\x82\x0e2214152212001105141122052900', b'\xf1\x873Q0959655DB\xf1\x890720\xf1\x82\x0e1114151112001105111122052900', b'\xf1\x873Q0959655DB\xf1\x890720\xf1\x82\x0e2214152212001105141122052900', + b'\xf1\x873Q0959655DM\xf1\x890732\xf1\x82\x0e1114151112001105111122052J00', b'\xf1\x873Q0959655DM\xf1\x890732\xf1\x82\x0e1114151112001105161122052J00', b'\xf1\x873Q0959655DM\xf1\x890732\xf1\x82\x0e1115151112001105171122052J00', ], @@ -87,6 +89,7 @@ FW_VERSIONS = { b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B60924A1', b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6G920A1', b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6M921A1', + b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6N920A1', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6080105', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6090105', ], @@ -97,13 +100,26 @@ FW_VERSIONS = { b'\xf1\x875Q0907572H \xf1\x890620', b'\xf1\x875Q0907572J \xf1\x890654', b'\xf1\x875Q0907572P \xf1\x890682', + b'\xf1\x875Q0907572S \xf1\x890780', ], }, - CAR.CRAFTER_MK2: { + CAR.VOLKSWAGEN_CADDY_MK3: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906027T \xf1\x892363', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x872K5959655E \xf1\x890018\xf1\x82\x05000P037605', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\x0155', + ], + }, + CAR.VOLKSWAGEN_CRAFTER_MK2: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704L906056BP\xf1\x894729', b'\xf1\x8704L906056EK\xf1\x896391', b'\xf1\x8705L906023BC\xf1\x892688', + b'\xf1\x8705L906023MH\xf1\x892588', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AL\xf1\x890505\xf1\x82\x0e1411001413001203151311031100', @@ -113,6 +129,7 @@ FW_VERSIONS = { (Ecu.eps, 0x712, None): [ b'\xf1\x872N0909143D\x00\xf1\x897010\xf1\x82\x05183AZ306A2', b'\xf1\x872N0909143E \xf1\x897021\xf1\x82\x05163AZ306A2', + b'\xf1\x872N0909143H \xf1\x897045\xf1\x82\x05263AZ309A2', b'\xf1\x872N0909144K \xf1\x897045\xf1\x82\x05233AZ810A2', ], (Ecu.fwdRadar, 0x757, None): [ @@ -121,7 +138,7 @@ FW_VERSIONS = { b'\xf1\x872Q0907572M \xf1\x890233', ], }, - CAR.GOLF_MK7: { + CAR.VOLKSWAGEN_GOLF_MK7: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906016A \xf1\x897697', b'\xf1\x8704E906016AD\xf1\x895758', @@ -148,6 +165,7 @@ FW_VERSIONS = { b'\xf1\x8704L906056HE\xf1\x893758', b'\xf1\x8704L906056HN\xf1\x896590', b'\xf1\x8704L906056HT\xf1\x896591', + b'\xf1\x8704L997022N \xf1\x899459', b'\xf1\x870EA906016A \xf1\x898343', b'\xf1\x870EA906016E \xf1\x894219', b'\xf1\x870EA906016F \xf1\x894238', @@ -164,6 +182,7 @@ FW_VERSIONS = { b'\xf1\x875G0906259T \xf1\x890003', b'\xf1\x878V0906259H \xf1\x890002', b'\xf1\x878V0906259J \xf1\x890003', + b'\xf1\x878V0906259J \xf1\x890103', b'\xf1\x878V0906259K \xf1\x890001', b'\xf1\x878V0906259K \xf1\x890003', b'\xf1\x878V0906259P \xf1\x890001', @@ -198,17 +217,21 @@ FW_VERSIONS = { b'\xf1\x870D9300012 \xf1\x895046', b'\xf1\x870D9300014M \xf1\x895004', b'\xf1\x870D9300014Q \xf1\x895006', + b'\xf1\x870D9300018 \xf1\x895201', b'\xf1\x870D9300020J \xf1\x894902', b'\xf1\x870D9300020Q \xf1\x895201', b'\xf1\x870D9300020S \xf1\x895201', b'\xf1\x870D9300040A \xf1\x893613', b'\xf1\x870D9300040S \xf1\x894311', b'\xf1\x870D9300041H \xf1\x895220', + b'\xf1\x870D9300041N \xf1\x894512', b'\xf1\x870D9300041P \xf1\x894507', b'\xf1\x870DD300045K \xf1\x891120', b'\xf1\x870DD300046F \xf1\x891601', b'\xf1\x870GC300012A \xf1\x891401', b'\xf1\x870GC300012A \xf1\x891403', + b'\xf1\x870GC300012A \xf1\x891422', + b'\xf1\x870GC300012M \xf1\x892301', b'\xf1\x870GC300014B \xf1\x892401', b'\xf1\x870GC300014B \xf1\x892403', b'\xf1\x870GC300014B \xf1\x892405', @@ -227,6 +250,7 @@ FW_VERSIONS = { b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\x13141500111233003142114A2131219333313100', b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333423100', b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333463100', + b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x13141600111233003142115A2232229333463100', b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2251229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2252229333463100', @@ -235,6 +259,7 @@ FW_VERSIONS = { b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112', b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\x111413001113120006110417121A101A9113', + b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13271112111312--071104171825102591131211', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271112111312--071104171825102591131211', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271212111312--071104171838103891131211', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13272512111312--07110417182C102C91131211', @@ -285,6 +310,7 @@ FW_VERSIONS = { b'\xf1\x875QD909144B \xf1\x891072\xf1\x82\x0521A00507A1', b'\xf1\x875QM909144A \xf1\x891072\xf1\x82\x0521A20B03A1', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A00442A1', + b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A00642A1', b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A16A1', b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A17A1', b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A18A1', @@ -309,7 +335,7 @@ FW_VERSIONS = { b'\xf1\x875Q0907572S \xf1\x890780', ], }, - CAR.JETTA_MK7: { + CAR.VOLKSWAGEN_JETTA_MK7: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906024AK\xf1\x899937', b'\xf1\x8704E906024AS\xf1\x899912', @@ -317,6 +343,7 @@ FW_VERSIONS = { b'\xf1\x8704E906024BC\xf1\x899971', b'\xf1\x8704E906024BG\xf1\x891057', b'\xf1\x8704E906024C \xf1\x899970', + b'\xf1\x8704E906024C \xf1\x899971', b'\xf1\x8704E906024L \xf1\x895595', b'\xf1\x8704E906024L \xf1\x899970', b'\xf1\x8704E906027MS\xf1\x896223', @@ -360,7 +387,7 @@ FW_VERSIONS = { b'\xf1\x875Q0907572R \xf1\x890771', ], }, - CAR.PASSAT_MK8: { + CAR.VOLKSWAGEN_PASSAT_MK8: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8703N906026E \xf1\x892114', b'\xf1\x8704E906023AH\xf1\x893379', @@ -369,12 +396,15 @@ FW_VERSIONS = { b'\xf1\x8704L906026ET\xf1\x891990', b'\xf1\x8704L906026FP\xf1\x892012', b'\xf1\x8704L906026GA\xf1\x892013', + b'\xf1\x8704L906026GK\xf1\x899971', b'\xf1\x8704L906026KD\xf1\x894798', + b'\xf1\x8705L906022A \xf1\x890827', b'\xf1\x873G0906259 \xf1\x890004', b'\xf1\x873G0906259B \xf1\x890002', b'\xf1\x873G0906264 \xf1\x890004', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300041E \xf1\x891006', b'\xf1\x870CW300042H \xf1\x891601', b'\xf1\x870CW300042H \xf1\x891607', b'\xf1\x870CW300043H \xf1\x891601', @@ -389,6 +419,7 @@ FW_VERSIONS = { b'\xf1\x870DL300011H \xf1\x895201', b'\xf1\x870GC300042H \xf1\x891404', b'\xf1\x870GC300043 \xf1\x892301', + b'\xf1\x870GC300046P \xf1\x892805', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AE\xf1\x890195\xf1\x82\r56140056130012416612124111', @@ -404,6 +435,7 @@ FW_VERSIONS = { b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e5915005914001354701311542900', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011111200631145171716121691132111', + b'\xf1\x875QF959655S \xf1\x890639\xf1\x82\x13131100131300111111000120----2211114A48', ], (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00611A1', @@ -412,6 +444,7 @@ FW_VERSIONS = { b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0060803', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0080803', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526B0060905', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820531B0062105', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521B00606A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516B00501A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521B00603A1', @@ -428,9 +461,10 @@ FW_VERSIONS = { b'\xf1\x873Q0907572C \xf1\x890196', b'\xf1\x875Q0907572P \xf1\x890682', b'\xf1\x875Q0907572R \xf1\x890771', + b'\xf1\x875Q0907572S \xf1\x890780', ], }, - CAR.PASSAT_NMS: { + CAR.VOLKSWAGEN_PASSAT_NMS: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8706K906016C \xf1\x899609', b'\xf1\x8706K906016E \xf1\x899830', @@ -452,7 +486,7 @@ FW_VERSIONS = { b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\x0152', ], }, - CAR.POLO_MK6: { + CAR.VOLKSWAGEN_POLO_MK6: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704C906025H \xf1\x895177', b'\xf1\x8705C906032J \xf1\x891702', @@ -476,7 +510,7 @@ FW_VERSIONS = { b'\xf1\x872Q0907572R \xf1\x890372', ], }, - CAR.SHARAN_MK2: { + CAR.VOLKSWAGEN_SHARAN_MK2: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704L906016HE\xf1\x894635', ], @@ -487,7 +521,7 @@ FW_VERSIONS = { b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\x0153', ], }, - CAR.TAOS_MK1: { + CAR.VOLKSWAGEN_TAOS_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906025CK\xf1\x892228', b'\xf1\x8704E906027NJ\xf1\x891445', @@ -517,7 +551,7 @@ FW_VERSIONS = { b'\xf1\x872Q0907572T \xf1\x890383', ], }, - CAR.TCROSS_MK1: { + CAR.VOLKSWAGEN_TCROSS_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704C906025AK\xf1\x897053', ], @@ -534,7 +568,7 @@ FW_VERSIONS = { b'\xf1\x872Q0907572T \xf1\x890383', ], }, - CAR.TIGUAN_MK2: { + CAR.VOLKSWAGEN_TIGUAN_MK2: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8703N906026D \xf1\x893680', b'\xf1\x8704E906024AP\xf1\x891461', @@ -557,6 +591,7 @@ FW_VERSIONS = { b'\xf1\x8783A907115Q \xf1\x890001', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927158DS\xf1\x893699', b'\xf1\x8709G927158DT\xf1\x893698', b'\xf1\x8709G927158FM\xf1\x893757', b'\xf1\x8709G927158GC\xf1\x893821', @@ -564,6 +599,7 @@ FW_VERSIONS = { b'\xf1\x8709G927158GM\xf1\x893936', b'\xf1\x8709G927158GN\xf1\x893938', b'\xf1\x8709G927158HB\xf1\x894069', + b'\xf1\x8709G927158HC\xf1\x894070', b'\xf1\x870D9300043 \xf1\x895202', b'\xf1\x870DD300046K \xf1\x892302', b'\xf1\x870DL300011N \xf1\x892001', @@ -620,29 +656,37 @@ FW_VERSIONS = { b'\xf1\x872Q0907572T \xf1\x890383', ], }, - CAR.TOURAN_MK2: { + CAR.VOLKSWAGEN_TOURAN_MK2: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906025BE\xf1\x890720', + b'\xf1\x8704E906027HQ\xf1\x893746', b'\xf1\x8704L906026HM\xf1\x893017', b'\xf1\x8705E906018CQ\xf1\x890808', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300020A \xf1\x891936', b'\xf1\x870CW300041E \xf1\x891005', + b'\xf1\x870CW300041Q \xf1\x891606', b'\xf1\x870CW300051M \xf1\x891926', ], (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655AS\xf1\x890318\xf1\x82\x1336350021353335314132014730479333313100', b'\xf1\x875Q0959655AS\xf1\x890318\xf1\x82\x13363500213533353141324C4732479333313100', b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1336350021353336314740025250529333613100', + b'\xf1\x875QD959655AJ\xf1\x890421\xf1\x82\x1336350021313300314240023330339333663100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820531B0062105', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A8090400', + b'\xf1\x875QD909144F \xf1\x891082\xf1\x82\x0521A00642A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x873Q0907572C \xf1\x890195', + b'\xf1\x875Q0907572R \xf1\x890771', ], }, - CAR.TRANSPORTER_T61: { + CAR.VOLKSWAGEN_TRANSPORTER_T61: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704L906056AG\xf1\x899970', b'\xf1\x8704L906056AL\xf1\x899970', @@ -674,7 +718,7 @@ FW_VERSIONS = { b'\xf1\x872Q0907572R \xf1\x890372', ], }, - CAR.TROC_MK1: { + CAR.VOLKSWAGEN_TROC_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8705E906018AT\xf1\x899640', b'\xf1\x8705E906018CK\xf1\x890863', @@ -691,13 +735,16 @@ FW_VERSIONS = { b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1311110012333300314240681152119333463100', b'\xf1\x875Q0959655CF\xf1\x890421\xf1\x82\x1311110012333300314240021150119333613100', b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x13111100123333003142404M1152119333613100', + b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x13111100123333003142404M1154119333613100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521060403A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521060405A1', b'\xf1\x875WA907144M \xf1\x891051\xf1\x82\x001T06081T7N', + b'\xf1\x875WA907144Q \xf1\x891063\xf1\x82\x001O06081OOM', ], (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572M \xf1\x890233', b'\xf1\x872Q0907572T \xf1\x890383', ], @@ -842,28 +889,13 @@ FW_VERSIONS = { CAR.SEAT_ATECA_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906027KA\xf1\x893749', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870D9300014S \xf1\x895202', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1212001211001305121211052900', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571N60511A1', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572M \xf1\x890233', - ], - }, - CAR.SEAT_LEON_MK3: { - (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704L906021EL\xf1\x897542', b'\xf1\x8704L906026BP\xf1\x891198', b'\xf1\x8704L906026BP\xf1\x897608', b'\xf1\x8704L906056CR\xf1\x892181', b'\xf1\x8704L906056CR\xf1\x892797', b'\xf1\x8705E906018AS\xf1\x899596', + b'\xf1\x8781A906259B \xf1\x890003', b'\xf1\x878V0906264H \xf1\x890005', b'\xf1\x878V0907115E \xf1\x890002', ], @@ -871,25 +903,33 @@ FW_VERSIONS = { b'\xf1\x870CW300041D \xf1\x891004', b'\xf1\x870CW300041G \xf1\x891003', b'\xf1\x870CW300050J \xf1\x891908', + b'\xf1\x870D9300014S \xf1\x895202', b'\xf1\x870D9300042M \xf1\x895016', + b'\xf1\x870GC300014P \xf1\x892801', b'\xf1\x870GC300043A \xf1\x892304', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AC\xf1\x890189\xf1\x82\r11110011110011021511110200', b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11110011110011021511110200', b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r12110012120012021612110200', + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1212001211001305121211052900', b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1312001313001305171311052900', b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\x0e1312001313001305171311052900', b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\x0e1312001313001305171311052900', + b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100110200--1113121149', ], (Ecu.eps, 0x712, None): [ + b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571N60511A1', b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521N01842A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521N01342A1', b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0511N01805A0', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521N01309A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521N05808A1', + b'\xf1\x875WA907145M \xf1\x891051\xf1\x82\x0013N619137N', ], (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572M \xf1\x890233', b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\x0101', b'\xf1\x875Q0907572H \xf1\x890620', b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', @@ -916,15 +956,22 @@ FW_VERSIONS = { }, CAR.SKODA_KAMIQ_MK1: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704C906025AK\xf1\x897053', b'\xf1\x8705C906032M \xf1\x891333', + b'\xf1\x8705C906032M \xf1\x892365', b'\xf1\x8705E906013CK\xf1\x892540', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870CW300020 \xf1\x891906', + b'\xf1\x870CW300020 \xf1\x891907', b'\xf1\x870CW300020T \xf1\x892204', + b'\xf1\x870CW300050 \xf1\x891709', ], (Ecu.srs, 0x715, None): [ + b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1211110411110411--04040404131111112H14', + b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\x12111104111104112104040404111111112H14', b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\x122221042111042121040404042E2711152H14', + b'\xf1\x872Q0959655AS\xf1\x890411\xf1\x82\x1311150411110411210404040417151215391413', b'\xf1\x872Q0959655BJ\xf1\x890412\xf1\x82\x132223042111042121040404042B251215391423', ], (Ecu.eps, 0x712, None): [ @@ -933,11 +980,13 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572R \xf1\x890372', b'\xf1\x872Q0907572T \xf1\x890383', ], }, CAR.SKODA_KAROQ_MK1: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8705E906013CL\xf1\x892541', b'\xf1\x8705E906013H \xf1\x892407', b'\xf1\x8705E906018P \xf1\x895472', b'\xf1\x8705E906018P \xf1\x896020', @@ -957,6 +1006,7 @@ FW_VERSIONS = { (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563T6090500', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T6100500', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T6100600', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T6100700', ], (Ecu.fwdRadar, 0x757, None): [ @@ -977,7 +1027,9 @@ FW_VERSIONS = { b'\xf1\x8704L906026HT\xf1\x893617', b'\xf1\x8705E906018DJ\xf1\x890915', b'\xf1\x8705E906018DJ\xf1\x891903', + b'\xf1\x8705L906022GM\xf1\x893411', b'\xf1\x875NA906259E \xf1\x890003', + b'\xf1\x875NA907115D \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890005', b'\xf1\x8783A907115E \xf1\x890001', @@ -986,14 +1038,17 @@ FW_VERSIONS = { b'\xf1\x870D9300014S \xf1\x895201', b'\xf1\x870D9300043 \xf1\x895202', b'\xf1\x870DL300011N \xf1\x892014', + b'\xf1\x870DL300012G \xf1\x892006', b'\xf1\x870DL300012M \xf1\x892107', b'\xf1\x870DL300012N \xf1\x892110', b'\xf1\x870DL300013G \xf1\x892119', b'\xf1\x870GC300014N \xf1\x892801', + b'\xf1\x870GC300018S \xf1\x892803', b'\xf1\x870GC300019H \xf1\x892806', b'\xf1\x870GC300046Q \xf1\x892802', ], (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r11110011110011031111310311', b'\xf1\x873Q0959655AP\xf1\x890306\xf1\x82\r11110011110011421111314211', b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1213001211001205212111052100', b'\xf1\x873Q0959655BJ\xf1\x890703\xf1\x82\x0e1213001211001205212111052100', @@ -1002,6 +1057,7 @@ FW_VERSIONS = { b'\xf1\x873Q0959655CQ\xf1\x890720\xf1\x82\x0e1213111211001205212112052111', b'\xf1\x873Q0959655DJ\xf1\x890731\xf1\x82\x0e1513001511001205232113052J00', b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100010200--1121240749', + b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100010200--1121246149', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405', @@ -1009,6 +1065,7 @@ FW_VERSIONS = { b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6070405', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G500', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G600', + b'\xf1\x875TA907145F \xf1\x891063\xf1\x82\x0025T6BA25OM', b'\xf1\x875TA907145F \xf1\x891063\xf1\x82\x002LT61A2LOM', ], (Ecu.fwdRadar, 0x757, None): [ @@ -1027,8 +1084,10 @@ FW_VERSIONS = { b'\xf1\x8704E906027HD\xf1\x893742', b'\xf1\x8704E906027MH\xf1\x894786', b'\xf1\x8704L906021DT\xf1\x898127', + b'\xf1\x8704L906021ER\xf1\x898361', b'\xf1\x8704L906026BP\xf1\x897608', b'\xf1\x8704L906026BS\xf1\x891541', + b'\xf1\x8704L906026BT\xf1\x897612', b'\xf1\x875G0906259C \xf1\x890002', ], (Ecu.transmission, 0x7e1, None): [ @@ -1036,6 +1095,8 @@ FW_VERSIONS = { b'\xf1\x870CW300041N \xf1\x891605', b'\xf1\x870CW300043B \xf1\x891601', b'\xf1\x870CW300043P \xf1\x891605', + b'\xf1\x870D9300012H \xf1\x894518', + b'\xf1\x870D9300014T \xf1\x895221', b'\xf1\x870D9300041C \xf1\x894936', b'\xf1\x870D9300041H \xf1\x895220', b'\xf1\x870D9300041J \xf1\x894902', @@ -1056,11 +1117,13 @@ FW_VERSIONS = { b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A01513A1', b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521T00403A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521T00403A1', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521T00603A1', b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521T00601A1', b'\xf1\x875QD909144E \xf1\x891081\xf1\x82\x0521T00503A1', ], (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x875Q0907567P \xf1\x890100\xf1\x82\x0101', b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101', b'\xf1\x875Q0907572H \xf1\x890620', @@ -1070,29 +1133,6 @@ FW_VERSIONS = { b'\xf1\x875Q0907572R \xf1\x890771', ], }, - CAR.SKODA_SCALA_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704C906025AK\xf1\x897053', - b'\xf1\x8705C906032M \xf1\x892365', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300020 \xf1\x891907', - b'\xf1\x870CW300050 \xf1\x891709', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1211110411110411--04040404131111112H14', - b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\x12111104111104112104040404111111112H14', - b'\xf1\x872Q0959655AS\xf1\x890411\xf1\x82\x1311150411110411210404040417151215391413', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x872Q1909144AB\xf1\x896050', - b'\xf1\x872Q1909144M \xf1\x896041', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572AA\xf1\x890396', - b'\xf1\x872Q0907572R \xf1\x890372', - ], - }, CAR.SKODA_SUPERB_MK3: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906027BS\xf1\x892887', diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index fa68e81d0d..77e56875bf 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -2,7 +2,7 @@ from cereal import car from panda import Panda from openpilot.selfdrive.car import get_safety_config from openpilot.selfdrive.car.interfaces import CarInterfaceBase -from openpilot.selfdrive.car.volkswagen.values import PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter, VolkswagenFlags +from openpilot.selfdrive.car.volkswagen.values import CAR, CANBUS, CarControllerParams, NetworkLocation, TransmissionType, GearShifter, VolkswagenFlags ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -19,14 +19,12 @@ class CarInterface(CarInterfaceBase): self.ext_bus = CANBUS.cam self.cp_ext = self.cp_cam - self.eps_timer_soft_disable_alert = False - @staticmethod - def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): + def _get_params(ret, candidate: CAR, fingerprint, car_fw, experimental_long, docs): ret.carName = "volkswagen" ret.radarUnavailable = True - if candidate in PQ_CARS: + if ret.flags & VolkswagenFlags.PQ: # Set global PQ35/PQ46/NMS parameters ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.volkswagenPq)] ret.enableBsm = 0x3BA in fingerprint[0] # SWA_1 @@ -72,7 +70,7 @@ class CarInterface(CarInterfaceBase): # Global lateral tuning defaults, can be overridden per-vehicle ret.steerLimitTimer = 0.4 - if candidate in PQ_CARS: + if ret.flags & VolkswagenFlags.PQ: ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) else: @@ -98,13 +96,8 @@ class CarInterface(CarInterfaceBase): ret.stopAccel = -0.55 ret.vEgoStarting = 0.1 ret.vEgoStopping = 0.5 - ret.longitudinalTuning.kpV = [0.1] - ret.longitudinalTuning.kiV = [0.0] - - # Per-chassis tuning values, override tuning defaults here if desired - ret.autoResumeSng = ret.minEnableSpeed == -1 - ret.centerToFront = ret.wheelbase * 0.45 + return ret # returns a car.CarState @@ -116,7 +109,7 @@ class CarInterface(CarInterfaceBase): enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise)) # Low speed steer alert hysteresis logic - if self.CP.minSteerSpeed > 0. and ret.vEgo < (self.CP.minSteerSpeed + 1.): + if (self.CP.minSteerSpeed - 1e-3) > CarControllerParams.DEFAULT_MIN_STEER_SPEED and ret.vEgo < (self.CP.minSteerSpeed + 1.): self.low_speed_alert = True elif ret.vEgo > (self.CP.minSteerSpeed + 2.): self.low_speed_alert = False @@ -129,13 +122,10 @@ class CarInterface(CarInterfaceBase): if c.enabled and ret.vEgo < self.CP.minEnableSpeed: events.add(EventName.speedTooLow) - if self.eps_timer_soft_disable_alert: + if self.CC.eps_timer_soft_disable_alert: events.add(EventName.steerTimeLimit) ret.events = events.to_msg() return ret - def apply(self, c, now_nanos): - new_actuators, can_sends, self.eps_timer_soft_disable_alert = self.CC.update(c, self.CS, self.ext_bus, now_nanos) - return new_actuators, can_sends diff --git a/selfdrive/car/volkswagen/mqbcan.py b/selfdrive/car/volkswagen/mqbcan.py index 787c7de530..763908b6b2 100644 --- a/selfdrive/car/volkswagen/mqbcan.py +++ b/selfdrive/car/volkswagen/mqbcan.py @@ -28,7 +28,7 @@ def create_eps_update(packer, bus, eps_stock_values, ea_simulated_torque): return packer.make_can_msg("LH_EPS_03", bus, values) -def create_lka_hud_control(packer, bus, ldw_stock_values, enabled, steering_pressed, hud_alert, hud_control): +def create_lka_hud_control(packer, bus, ldw_stock_values, lat_active, steering_pressed, hud_alert, hud_control): values = {} if len(ldw_stock_values): values = {s: ldw_stock_values[s] for s in [ @@ -40,8 +40,8 @@ def create_lka_hud_control(packer, bus, ldw_stock_values, enabled, steering_pres ]} values.update({ - "LDW_Status_LED_gelb": 1 if enabled and steering_pressed else 0, - "LDW_Status_LED_gruen": 1 if enabled and not steering_pressed else 0, + "LDW_Status_LED_gelb": 1 if lat_active and steering_pressed else 0, + "LDW_Status_LED_gruen": 1 if lat_active and not steering_pressed else 0, "LDW_Lernmodus_links": 3 if hud_control.leftLaneDepart else 1 + hud_control.leftLaneVisible, "LDW_Lernmodus_rechts": 3 if hud_control.rightLaneDepart else 1 + hud_control.rightLaneVisible, "LDW_Texte": hud_alert, @@ -125,11 +125,11 @@ def create_acc_accel_control(packer, bus, acc_type, acc_enabled, accel, acc_cont return commands -def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_distance): +def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_distance, distance): values = { "ACC_Status_Anzeige": acc_hud_status, "ACC_Wunschgeschw_02": set_speed if set_speed < 250 else 327.36, - "ACC_Gesetzte_Zeitluecke": 3, + "ACC_Gesetzte_Zeitluecke": distance + 2, "ACC_Display_Prio": 3, "ACC_Abstandsindex": lead_distance, } diff --git a/selfdrive/car/volkswagen/pqcan.py b/selfdrive/car/volkswagen/pqcan.py index f42c3cf781..f8d161b970 100644 --- a/selfdrive/car/volkswagen/pqcan.py +++ b/selfdrive/car/volkswagen/pqcan.py @@ -9,7 +9,7 @@ def create_steering_control(packer, bus, apply_steer, lkas_enabled): return packer.make_can_msg("HCA_1", bus, values) -def create_lka_hud_control(packer, bus, ldw_stock_values, enabled, steering_pressed, hud_alert, hud_control): +def create_lka_hud_control(packer, bus, ldw_stock_values, lat_active, steering_pressed, hud_alert, hud_control): values = {} if len(ldw_stock_values): values = {s: ldw_stock_values[s] for s in [ @@ -21,8 +21,8 @@ def create_lka_hud_control(packer, bus, ldw_stock_values, enabled, steering_pres ]} values.update({ - "LDW_Lampe_gelb": 1 if enabled and steering_pressed else 0, - "LDW_Lampe_gruen": 1 if enabled and not steering_pressed else 0, + "LDW_Lampe_gelb": 1 if lat_active and steering_pressed else 0, + "LDW_Lampe_gruen": 1 if lat_active and not steering_pressed else 0, "LDW_Lernmodus_links": 3 if hud_control.leftLaneDepart else 1 + hud_control.leftLaneVisible, "LDW_Lernmodus_rechts": 3 if hud_control.rightLaneDepart else 1 + hud_control.rightLaneVisible, "LDW_Textbits": hud_alert, @@ -91,10 +91,10 @@ def create_acc_accel_control(packer, bus, acc_type, acc_enabled, accel, acc_cont return commands -def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_distance): +def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_distance, distance): values = { "ACA_StaACC": acc_hud_status, - "ACA_Zeitluecke": 2, + "ACA_Zeitluecke": distance + 2, "ACA_V_Wunsch": set_speed, "ACA_gemZeitl": lead_distance, "ACA_PrioDisp": 3, diff --git a/selfdrive/car/volkswagen/tests/test_volkswagen.py b/selfdrive/car/volkswagen/tests/test_volkswagen.py new file mode 100644 index 0000000000..0002578105 --- /dev/null +++ b/selfdrive/car/volkswagen/tests/test_volkswagen.py @@ -0,0 +1,60 @@ +import random +import re + +from cereal import car +from openpilot.selfdrive.car.volkswagen.values import CAR, FW_QUERY_CONFIG, WMI +from openpilot.selfdrive.car.volkswagen.fingerprints import FW_VERSIONS + +Ecu = car.CarParams.Ecu + +CHASSIS_CODE_PATTERN = re.compile('[A-Z0-9]{2}') +# TODO: determine the unknown groups +SPARE_PART_FW_PATTERN = re.compile(b'\xf1\x87(?P[0-9][0-9A-Z]{2})(?P[0-9][0-9A-Z][0-9])(?P[0-9A-Z]{2}[0-9])([A-Z0-9]| )') + + +class TestVolkswagenPlatformConfigs: + def test_spare_part_fw_pattern(self, subtests): + # Relied on for determining if a FW is likely VW + for platform, ecus in FW_VERSIONS.items(): + with subtests.test(platform=platform.value): + for fws in ecus.values(): + for fw in fws: + assert SPARE_PART_FW_PATTERN.match(fw) is not None, f"Bad FW: {fw}" + + def test_chassis_codes(self, subtests): + for platform in CAR: + with subtests.test(platform=platform.value): + assert len(platform.config.wmis) > 0, "WMIs not set" + assert len(platform.config.chassis_codes) > 0, "Chassis codes not set" + assert all(CHASSIS_CODE_PATTERN.match(cc) for cc in \ + platform.config.chassis_codes), "Bad chassis codes" + + # No two platforms should share chassis codes + for comp in CAR: + if platform == comp: + continue + assert set() == platform.config.chassis_codes & comp.config.chassis_codes, \ + f"Shared chassis codes: {comp}" + + def test_custom_fuzzy_fingerprinting(self, subtests): + all_radar_fw = list({fw for ecus in FW_VERSIONS.values() for fw in ecus[Ecu.fwdRadar, 0x757, None]}) + + for platform in CAR: + with subtests.test(platform=platform.name): + for wmi in WMI: + for chassis_code in platform.config.chassis_codes | {"00"}: + vin = ["0"] * 17 + vin[0:3] = wmi + vin[6:8] = chassis_code + vin = "".join(vin) + + # Check a few FW cases - expected, unexpected + for radar_fw in random.sample(all_radar_fw, 5) + [b'\xf1\x875Q0907572G \xf1\x890571', b'\xf1\x877H9907572AA\xf1\x890396']: + should_match = ((wmi in platform.config.wmis and chassis_code in platform.config.chassis_codes) and + radar_fw in all_radar_fw) + + live_fws = {(0x757, None): [radar_fw]} + matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fws, vin, FW_VERSIONS) + + expected_matches = {platform} if should_match else set() + assert expected_matches == matches, "Bad match" diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 4dff9205d6..8b58769b3f 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1,15 +1,15 @@ -from collections import namedtuple +from collections import defaultdict, namedtuple from dataclasses import dataclass, field -from enum import Enum, IntFlag +from enum import Enum, IntFlag, StrEnum from cereal import car from panda.python import uds from opendbc.can.can_define import CANDefine 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 +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column, \ + Device +from openpilot.selfdrive.car.fw_query_definitions import EcuAddrSubAddr, FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu NetworkLocation = car.CarParams.NetworkLocation @@ -34,13 +34,15 @@ class CarControllerParams: STEER_TIME_ALERT = STEER_TIME_MAX - 10 # If mitigation fails, time to soft disengage before EPS timer expires STEER_TIME_STUCK_TORQUE = 1.9 # EPS limits same torque to 6 seconds, reset timer 3x within that period + DEFAULT_MIN_STEER_SPEED = 0.4 # m/s, newer EPS racks fault below this speed, don't show a low speed alert + ACCEL_MAX = 2.0 # 2.0 m/s max acceleration ACCEL_MIN = -3.5 # 3.5 m/s max deceleration def __init__(self, CP): can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) - if CP.carFingerprint in PQ_CARS: + if CP.flags & VolkswagenFlags.PQ: self.LDW_STEP = 5 # LDW_1 message frequency 20Hz self.ACC_HUD_STEP = 4 # ACC_GRA_Anzeige frequency 25Hz self.STEER_DRIVER_ALLOWANCE = 80 # Driver intervention threshold 0.8 Nm @@ -109,20 +111,58 @@ class CANBUS: cam = 2 +class WMI(StrEnum): + VOLKSWAGEN_USA_SUV = "1V2" + VOLKSWAGEN_USA_CAR = "1VW" + VOLKSWAGEN_MEXICO_SUV = "3VV" + VOLKSWAGEN_MEXICO_CAR = "3VW" + VOLKSWAGEN_ARGENTINA = "8AW" + VOLKSWAGEN_BRASIL = "9BW" + SAIC_VOLKSWAGEN = "LSV" + SKODA = "TMB" + SEAT = "VSS" + AUDI_EUROPE_MPV = "WA1" + AUDI_GERMANY_CAR = "WAU" + MAN = "WMA" + AUDI_SPORT = "WUA" + VOLKSWAGEN_COMMERCIAL = "WV1" + VOLKSWAGEN_COMMERCIAL_BUS_VAN = "WV2" + VOLKSWAGEN_EUROPE_SUV = "WVG" + VOLKSWAGEN_EUROPE_CAR = "WVW" + VOLKSWAGEN_GROUP_RUS = "XW8" + + class VolkswagenFlags(IntFlag): + # Detected flags STOCK_HCA_PRESENT = 1 -@dataclass(frozen=True) + # Static flags + PQ = 2 + + +@dataclass class VolkswagenMQBPlatformConfig(PlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('vw_mqb_2010', None)) + # Volkswagen uses the VIN WMI and chassis code to match in the absence of the comma power + # on camera-integrated cars, as we lose too many ECUs to reliably identify the vehicle + chassis_codes: set[str] = field(default_factory=set) + wmis: set[WMI] = field(default_factory=set) -@dataclass(frozen=True) -class VolkswagenPQPlatformConfig(PlatformConfig): + +@dataclass +class VolkswagenPQPlatformConfig(VolkswagenMQBPlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('vw_golf_mk4', None)) + def init(self): + self.flags |= VolkswagenFlags.PQ + + @dataclass(frozen=True, kw_only=True) class VolkswagenCarSpecs(CarSpecs): - steerRatio: float = field(default=15.6) + centerToFrontRatio: float = 0.45 + steerRatio: float = 15.6 + minSteerSpeed: float = CarControllerParams.DEFAULT_MIN_STEER_SPEED + class Footnote(Enum): KAMIQ = CarFootnote( @@ -146,7 +186,7 @@ class Footnote(Enum): @dataclass -class VWCarInfo(CarInfo): +class VWCarDocs(CarDocs): package: str = "Adaptive Cruise Control (ACC) & Lane Assist" car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.j533])) @@ -155,205 +195,283 @@ class VWCarInfo(CarInfo): if "SKODA" in CP.carFingerprint: self.footnotes.append(Footnote.SKODA_HEATED_WINDSHIELD) - if CP.carFingerprint in (CAR.CRAFTER_MK2, CAR.TRANSPORTER_T61): + if CP.carFingerprint in (CAR.VOLKSWAGEN_CRAFTER_MK2, CAR.VOLKSWAGEN_TRANSPORTER_T61): self.car_parts = CarParts([Device.threex_angled_mount, CarHarness.j533]) + if abs(CP.minSteerSpeed - CarControllerParams.DEFAULT_MIN_STEER_SPEED) < 1e-3: + self.min_steer_speed = 0 + + # 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(Platforms): - ARTEON_MK1 = VolkswagenMQBPlatformConfig( - "VOLKSWAGEN ARTEON 1ST GEN", # Chassis AN + config: VolkswagenMQBPlatformConfig | VolkswagenPQPlatformConfig + + VOLKSWAGEN_ARTEON_MK1 = VolkswagenMQBPlatformConfig( [ - 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"), + VWCarDocs("Volkswagen Arteon 2018-23", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarDocs("Volkswagen Arteon R 2020-23", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarDocs("Volkswagen Arteon eHybrid 2020-23", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarDocs("Volkswagen Arteon Shooting Brake 2020-23", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarDocs("Volkswagen CC 2018-22", video_link="https://youtu.be/FAomFKPFlDA"), ], - specs=VolkswagenCarSpecs(mass=1733, wheelbase=2.84), + VolkswagenCarSpecs(mass=1733, wheelbase=2.84), + chassis_codes={"AN", "3H"}, + wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) - ATLAS_MK1 = VolkswagenMQBPlatformConfig( - "VOLKSWAGEN ATLAS 1ST GEN", # Chassis CA + VOLKSWAGEN_ATLAS_MK1 = VolkswagenMQBPlatformConfig( [ - 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"), + VWCarDocs("Volkswagen Atlas 2018-23"), + VWCarDocs("Volkswagen Atlas Cross Sport 2020-22"), + VWCarDocs("Volkswagen Teramont 2018-22"), + VWCarDocs("Volkswagen Teramont Cross Sport 2021-22"), + VWCarDocs("Volkswagen Teramont X 2021-22"), ], - specs=VolkswagenCarSpecs(mass=2011, wheelbase=2.98), + VolkswagenCarSpecs(mass=2011, wheelbase=2.98), + chassis_codes={"CA"}, + wmis={WMI.VOLKSWAGEN_USA_SUV, WMI.VOLKSWAGEN_EUROPE_SUV}, ) - CRAFTER_MK2 = VolkswagenMQBPlatformConfig( - "VOLKSWAGEN CRAFTER 2ND GEN", # Chassis SY/SZ + VOLKSWAGEN_CADDY_MK3 = VolkswagenPQPlatformConfig( [ - 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"), + VWCarDocs("Volkswagen Caddy 2019"), + VWCarDocs("Volkswagen Caddy Maxi 2019"), ], - specs=VolkswagenCarSpecs(mass=2100, wheelbase=3.64, minSteerSpeed=50 * CV.KPH_TO_MS), + VolkswagenCarSpecs(mass=1613, wheelbase=2.6, minSteerSpeed=21 * CV.KPH_TO_MS), + chassis_codes={"2K"}, + wmis={WMI.VOLKSWAGEN_COMMERCIAL_BUS_VAN}, ) - GOLF_MK7 = VolkswagenMQBPlatformConfig( - "VOLKSWAGEN GOLF 7TH GEN", # Chassis 5G/AU/BA/BE + VOLKSWAGEN_CRAFTER_MK2 = VolkswagenMQBPlatformConfig( [ - 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"), + VWCarDocs("Volkswagen Crafter 2017-24", video_link="https://youtu.be/4100gLeabmo"), + VWCarDocs("Volkswagen e-Crafter 2018-24", video_link="https://youtu.be/4100gLeabmo"), + VWCarDocs("Volkswagen Grand California 2019-24", video_link="https://youtu.be/4100gLeabmo"), + VWCarDocs("MAN TGE 2017-24", video_link="https://youtu.be/4100gLeabmo"), + VWCarDocs("MAN eTGE 2020-24", video_link="https://youtu.be/4100gLeabmo"), ], - specs=VolkswagenCarSpecs(mass=1397, wheelbase=2.62), + VolkswagenCarSpecs(mass=2100, wheelbase=3.64, minSteerSpeed=50 * CV.KPH_TO_MS), + chassis_codes={"SY", "SZ", "UY", "UZ"}, + wmis={WMI.VOLKSWAGEN_COMMERCIAL, WMI.MAN}, ) - JETTA_MK7 = VolkswagenMQBPlatformConfig( - "VOLKSWAGEN JETTA 7TH GEN", # Chassis BU + VOLKSWAGEN_GOLF_MK7 = VolkswagenMQBPlatformConfig( [ - VWCarInfo("Volkswagen Jetta 2018-24"), - VWCarInfo("Volkswagen Jetta GLI 2021-24"), + VWCarDocs("Volkswagen e-Golf 2014-20"), + VWCarDocs("Volkswagen Golf 2015-20", auto_resume=False), + VWCarDocs("Volkswagen Golf Alltrack 2015-19", auto_resume=False), + VWCarDocs("Volkswagen Golf GTD 2015-20"), + VWCarDocs("Volkswagen Golf GTE 2015-20"), + VWCarDocs("Volkswagen Golf GTI 2015-21", auto_resume=False), + VWCarDocs("Volkswagen Golf R 2015-19"), + VWCarDocs("Volkswagen Golf SportsVan 2015-20"), ], - specs=VolkswagenCarSpecs(mass=1328, wheelbase=2.71), + VolkswagenCarSpecs(mass=1397, wheelbase=2.62), + chassis_codes={"5G", "AU", "BA", "BE"}, + wmis={WMI.VOLKSWAGEN_MEXICO_CAR, WMI.VOLKSWAGEN_EUROPE_CAR}, ) - PASSAT_MK8 = VolkswagenMQBPlatformConfig( - "VOLKSWAGEN PASSAT 8TH GEN", # Chassis 3G + VOLKSWAGEN_JETTA_MK7 = VolkswagenMQBPlatformConfig( [ - VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.PASSAT]), - VWCarInfo("Volkswagen Passat Alltrack 2015-22"), - VWCarInfo("Volkswagen Passat GTE 2015-22"), + VWCarDocs("Volkswagen Jetta 2018-24"), + VWCarDocs("Volkswagen Jetta GLI 2021-24"), ], - specs=VolkswagenCarSpecs(mass=1551, wheelbase=2.79), + VolkswagenCarSpecs(mass=1328, wheelbase=2.71), + chassis_codes={"BU"}, + wmis={WMI.VOLKSWAGEN_MEXICO_CAR, WMI.VOLKSWAGEN_EUROPE_CAR}, ) - 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 + VOLKSWAGEN_PASSAT_MK8 = VolkswagenMQBPlatformConfig( [ - VWCarInfo("Volkswagen Polo 2018-23", footnotes=[Footnote.VW_MQB_A0]), - VWCarInfo("Volkswagen Polo GTI 2018-23", footnotes=[Footnote.VW_MQB_A0]), + VWCarDocs("Volkswagen Passat 2015-22", footnotes=[Footnote.PASSAT]), + VWCarDocs("Volkswagen Passat Alltrack 2015-22"), + VWCarDocs("Volkswagen Passat GTE 2015-22"), ], - specs=VolkswagenCarSpecs(mass=1230, wheelbase=2.55), + VolkswagenCarSpecs(mass=1551, wheelbase=2.79), + chassis_codes={"3C", "3G"}, + wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) - SHARAN_MK2 = VolkswagenPQPlatformConfig( - "VOLKSWAGEN SHARAN 2ND GEN", # Chassis 7N + VOLKSWAGEN_PASSAT_NMS = VolkswagenPQPlatformConfig( + [VWCarDocs("Volkswagen Passat NMS 2017-22")], + VolkswagenCarSpecs(mass=1503, wheelbase=2.80, minSteerSpeed=50 * CV.KPH_TO_MS, minEnableSpeed=20 * CV.KPH_TO_MS), + chassis_codes={"A3"}, + wmis={WMI.VOLKSWAGEN_USA_CAR}, + ) + VOLKSWAGEN_POLO_MK6 = VolkswagenMQBPlatformConfig( [ - VWCarInfo("Volkswagen Sharan 2018-22"), - VWCarInfo("SEAT Alhambra 2018-20"), + VWCarDocs("Volkswagen Polo 2018-23", footnotes=[Footnote.VW_MQB_A0]), + VWCarDocs("Volkswagen Polo GTI 2018-23", footnotes=[Footnote.VW_MQB_A0]), ], - specs=VolkswagenCarSpecs(mass=1639, wheelbase=2.92, minSteerSpeed=50*CV.KPH_TO_MS), + VolkswagenCarSpecs(mass=1230, wheelbase=2.55), + chassis_codes={"AW"}, + wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) - 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 + VOLKSWAGEN_SHARAN_MK2 = VolkswagenPQPlatformConfig( [ - VWCarInfo("Volkswagen Tiguan 2018-24"), - VWCarInfo("Volkswagen Tiguan eHybrid 2021-23"), + VWCarDocs("Volkswagen Sharan 2018-22"), + VWCarDocs("SEAT Alhambra 2018-20"), ], - specs=VolkswagenCarSpecs(mass=1715, wheelbase=2.74), + VolkswagenCarSpecs(mass=1639, wheelbase=2.92, minSteerSpeed=50 * CV.KPH_TO_MS), + chassis_codes={"7N"}, + wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) - TOURAN_MK2 = VolkswagenMQBPlatformConfig( - "VOLKSWAGEN TOURAN 2ND GEN", # Chassis 1T - VWCarInfo("Volkswagen Touran 2016-23"), - specs=VolkswagenCarSpecs(mass=1516, wheelbase=2.79), + VOLKSWAGEN_TAOS_MK1 = VolkswagenMQBPlatformConfig( + [VWCarDocs("Volkswagen Taos 2022-23")], + VolkswagenCarSpecs(mass=1498, wheelbase=2.69), + chassis_codes={"B2"}, + wmis={WMI.VOLKSWAGEN_MEXICO_SUV, WMI.VOLKSWAGEN_ARGENTINA}, ) - TRANSPORTER_T61 = VolkswagenMQBPlatformConfig( - "VOLKSWAGEN TRANSPORTER T6.1", # Chassis 7H/7L + VOLKSWAGEN_TCROSS_MK1 = VolkswagenMQBPlatformConfig( + [VWCarDocs("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0])], + VolkswagenCarSpecs(mass=1150, wheelbase=2.60), + chassis_codes={"C1"}, + wmis={WMI.VOLKSWAGEN_EUROPE_SUV}, + ) + VOLKSWAGEN_TIGUAN_MK2 = VolkswagenMQBPlatformConfig( [ - VWCarInfo("Volkswagen Caravelle 2020"), - VWCarInfo("Volkswagen California 2021-23"), + VWCarDocs("Volkswagen Tiguan 2018-24"), + VWCarDocs("Volkswagen Tiguan eHybrid 2021-23"), ], - specs=VolkswagenCarSpecs(mass=1926, wheelbase=3.00, minSteerSpeed=14.0), + VolkswagenCarSpecs(mass=1715, wheelbase=2.74), + chassis_codes={"5N", "AD", "AX", "BW"}, + wmis={WMI.VOLKSWAGEN_EUROPE_SUV, WMI.VOLKSWAGEN_MEXICO_SUV}, ) - 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), + VOLKSWAGEN_TOURAN_MK2 = VolkswagenMQBPlatformConfig( + [VWCarDocs("Volkswagen Touran 2016-23")], + VolkswagenCarSpecs(mass=1516, wheelbase=2.79), + chassis_codes={"1T"}, + wmis={WMI.VOLKSWAGEN_EUROPE_SUV}, + ) + VOLKSWAGEN_TRANSPORTER_T61 = VolkswagenMQBPlatformConfig( + [ + VWCarDocs("Volkswagen Caravelle 2020"), + VWCarDocs("Volkswagen California 2021-23"), + ], + VolkswagenCarSpecs(mass=1926, wheelbase=3.00, minSteerSpeed=14.0), + chassis_codes={"7H", "7L"}, + wmis={WMI.VOLKSWAGEN_COMMERCIAL_BUS_VAN}, + ) + VOLKSWAGEN_TROC_MK1 = VolkswagenMQBPlatformConfig( + [VWCarDocs("Volkswagen T-Roc 2018-23")], + VolkswagenCarSpecs(mass=1413, wheelbase=2.63), + chassis_codes={"A1"}, + wmis={WMI.VOLKSWAGEN_EUROPE_SUV}, ) 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"), + VWCarDocs("Audi A3 2014-19"), + VWCarDocs("Audi A3 Sportback e-tron 2017-18"), + VWCarDocs("Audi RS3 2018"), + VWCarDocs("Audi S3 2015-17"), ], - specs=VolkswagenCarSpecs(mass=1335, wheelbase=2.61), + VolkswagenCarSpecs(mass=1335, wheelbase=2.61), + chassis_codes={"8V", "FF"}, + wmis={WMI.AUDI_GERMANY_CAR, WMI.AUDI_SPORT}, ) AUDI_Q2_MK1 = VolkswagenMQBPlatformConfig( - "AUDI Q2 1ST GEN", # Chassis GA - VWCarInfo("Audi Q2 2018"), - specs=VolkswagenCarSpecs(mass=1205, wheelbase=2.61), + [VWCarDocs("Audi Q2 2018")], + VolkswagenCarSpecs(mass=1205, wheelbase=2.61), + chassis_codes={"GA"}, + wmis={WMI.AUDI_GERMANY_CAR}, ) AUDI_Q3_MK2 = VolkswagenMQBPlatformConfig( - "AUDI Q3 2ND GEN", # Chassis 8U/F3/FS - VWCarInfo("Audi Q3 2019-23"), - specs=VolkswagenCarSpecs(mass=1623, wheelbase=2.68), + [VWCarDocs("Audi Q3 2019-23")], + VolkswagenCarSpecs(mass=1623, wheelbase=2.68), + chassis_codes={"8U", "F3", "FS"}, + wmis={WMI.AUDI_EUROPE_MPV, WMI.AUDI_GERMANY_CAR}, ) 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), + [ + VWCarDocs("CUPRA Ateca 2018-23"), + VWCarDocs("SEAT Ateca 2016-23"), + VWCarDocs("SEAT Leon 2014-20"), + ], + VolkswagenCarSpecs(mass=1300, wheelbase=2.64), + chassis_codes={"5F"}, + wmis={WMI.SEAT}, ) 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), + [VWCarDocs("Škoda Fabia 2022-23", footnotes=[Footnote.VW_MQB_A0])], + VolkswagenCarSpecs(mass=1266, wheelbase=2.56), + chassis_codes={"PJ"}, + wmis={WMI.SKODA}, ) 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), + [ + VWCarDocs("Škoda Kamiq 2021-23", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), + VWCarDocs("Škoda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]), + ], + VolkswagenCarSpecs(mass=1230, wheelbase=2.66), + chassis_codes={"NW"}, + wmis={WMI.SKODA}, ) SKODA_KAROQ_MK1 = VolkswagenMQBPlatformConfig( - "SKODA KAROQ 1ST GEN", # Chassis NU - VWCarInfo("Škoda Karoq 2019-23"), - specs=VolkswagenCarSpecs(mass=1278, wheelbase=2.66), + [VWCarDocs("Škoda Karoq 2019-23")], + VolkswagenCarSpecs(mass=1278, wheelbase=2.66), + chassis_codes={"NU"}, + wmis={WMI.SKODA}, ) SKODA_KODIAQ_MK1 = VolkswagenMQBPlatformConfig( - "SKODA KODIAQ 1ST GEN", # Chassis NS - VWCarInfo("Škoda Kodiaq 2017-23"), - specs=VolkswagenCarSpecs(mass=1569, wheelbase=2.79), + [VWCarDocs("Škoda Kodiaq 2017-23")], + VolkswagenCarSpecs(mass=1569, wheelbase=2.79), + chassis_codes={"NS"}, + wmis={WMI.SKODA, WMI.VOLKSWAGEN_GROUP_RUS}, ) SKODA_OCTAVIA_MK3 = VolkswagenMQBPlatformConfig( - "SKODA OCTAVIA 3RD GEN", # Chassis NE [ - VWCarInfo("Škoda Octavia 2015-19"), - VWCarInfo("Škoda Octavia RS 2016"), + VWCarDocs("Škoda Octavia 2015-19"), + VWCarDocs("Škoda Octavia RS 2016"), + VWCarDocs("Škoda Octavia Scout 2017-19"), ], - 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), + VolkswagenCarSpecs(mass=1388, wheelbase=2.68), + chassis_codes={"NE"}, + wmis={WMI.SKODA}, ) SKODA_SUPERB_MK3 = VolkswagenMQBPlatformConfig( - "SKODA SUPERB 3RD GEN", # Chassis 3V/NP - VWCarInfo("Škoda Superb 2015-22"), - specs=VolkswagenCarSpecs(mass=1505, wheelbase=2.84), + [VWCarDocs("Škoda Superb 2015-22")], + VolkswagenCarSpecs(mass=1505, wheelbase=2.84), + chassis_codes={"3V", "NP"}, + wmis={WMI.SKODA}, ) -PQ_CARS = {CAR.PASSAT_NMS, CAR.SHARAN_MK2} + +def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str]: + candidates = set() + + # Compile all FW versions for each ECU + all_ecu_versions: dict[EcuAddrSubAddr, set[str]] = defaultdict(set) + for ecus in offline_fw_versions.values(): + for ecu, versions in ecus.items(): + all_ecu_versions[ecu] |= set(versions) + + # Check the WMI and chassis code to determine the platform + wmi = vin[:3] + chassis_code = vin[6:8] + + for platform in CAR: + valid_ecus = set() + for ecu in offline_fw_versions[platform]: + addr = ecu[1:] + if ecu[0] not in CHECK_FUZZY_ECUS: + continue + + # Sanity check that live FW is in the superset of all FW, Volkswagen ECU part numbers are commonly shared + found_versions = live_fw_versions.get(addr, []) + expected_versions = all_ecu_versions[ecu] + if not any(found_version in expected_versions for found_version in found_versions): + break + + valid_ecus.add(ecu[0]) + + if valid_ecus != CHECK_FUZZY_ECUS: + continue + + if wmi in platform.config.wmis and chassis_code in platform.config.chassis_codes: + candidates.add(platform) + + return {str(c) for c in candidates} + + +# These ECUs are required to match to gain a VIN match +# TODO: do we want to check camera when we add its FW? +CHECK_FUZZY_ECUS = {Ecu.fwdRadar} # 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. @@ -373,27 +491,26 @@ VOLKSWAGEN_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + VOLKSWAGEN_RX_OFFSET = 0x6a FW_QUERY_CONFIG = FwQueryConfig( - # 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, Ecu.fwdCamera], 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, ), ]], + non_essential_ecus={Ecu.eps: list(CAR)}, + extra_ecus=[(Ecu.fwdCamera, 0x74f, None)], + 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/controls/controlsd.py b/selfdrive/controls/controlsd.py index bd3dd08179..f147766c91 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -8,19 +8,17 @@ from typing import SupportsFloat import cereal.messaging as messaging from cereal import car, log -from cereal.visionipc import VisionIpcClient, VisionStreamType +from msgq.visionipc import VisionIpcClient, VisionStreamType -from panda import ALTERNATIVE_EXPERIENCE from openpilot.common.conversions import Conversions as CV +from openpilot.common.git import get_short_branch 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.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.car.car_helpers import get_car_interface, get_startup_event 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 @@ -32,7 +30,6 @@ 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 @@ -61,129 +58,22 @@ ACTIVE_STATES = (State.enabled, State.softDisabling, State.overriding) ENABLED_STATES = (State.preEnabled, *ACTIVE_STATES) -class CarD: - CI: CarInterfaceBase - CS: car.CarState - +class Controls: 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() if CI is None: - # wait for one pandaState and one CAN packet - print("Waiting for CAN messages...") - get_one_can(self.can_sock) + cloudlog.info("controlsd is waiting for CarParams") + with car.CarParams.from_bytes(self.params.get("CarParams", block=True)) as msg: + # TODO: this shouldn't need to be a builder + self.CP = msg.as_builder() + cloudlog.info("controlsd got CarParams") - num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates) - experimental_long_allowed = self.params.get_bool("ExperimentalLongitudinalEnabled") - self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], experimental_long_allowed, num_pandas) + # Uses car interface helper functions, altering state won't be considered by card for actuation + self.CI = get_car_interface(self.CP) else: self.CI, self.CP = CI, CI.CP - # set alternative experiences from parameters - disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") - self.CP.alternativeExperience = 0 - if not disengage_on_accelerator: - self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS - - 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() - safety_config.safetyModel = car.CarParams.SafetyModel.noOutput - self.CP.safetyConfigs = [safety_config] - - # Write previous route's CarParams - prev_cp = self.params.get("CarParamsPersistent") - if prev_cp is not None: - self.params.put("CarParamsPrevRoute", prev_cp) - - # Write CarParams for radard - cp_bytes = self.CP.to_bytes() - self.params.put("CarParams", cp_bytes) - 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() @@ -195,12 +85,17 @@ class Controls: self.log_sock = messaging.sub_sock('androidLog') - self.params = Params() + # TODO: de-couple controlsd with card/conflate on carState without introducing controls mismatches + self.car_state_sock = messaging.sub_sock('carState', timeout=20) + ignore = self.sensor_packets + ['testJoystick'] if SIMULATION: ignore += ['driverCameraState', 'managerState'] + if REPLAY: + # no vipc in replay will make them ignored anyways + ignore += ['roadCameraState', 'wideRoadCameraState'] self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', - 'driverMonitoringState', 'longitudinalPlan', 'liveLocationKalman', + 'carOutput', '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', ], @@ -209,25 +104,20 @@ class Controls: 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") if not self.CP.openpilotLongitudinalControl: self.params.remove("ExperimentalMode") - self.CC = car.CarControl.new_message() self.CS_prev = car.CarState.new_message() self.AM = AlertManager() self.events = Events() @@ -258,16 +148,16 @@ class Controls: self.current_alert_types = [ET.PERMANENT] self.logged_comm_issue = None self.not_running_prev = None - self.last_actuators = car.CarControl.Actuators.new_message() self.steer_limited = False self.desired_curvature = 0.0 self.experimental_mode = False + self.personality = self.read_personality_param() self.v_cruise_helper = VCruiseHelper(self.CP) self.recalibrating_seen = False self.can_log_mono_time = 0 - self.startup_event = get_startup_event(car_recognized, controller_available, len(self.CP.carFw) > 0) + self.startup_event = get_startup_event(car_recognized, not self.CP.passive, len(self.CP.carFw) > 0) if not sounds_available: self.events.add(EventName.soundsUnavailable, static=True) @@ -280,12 +170,12 @@ class Controls: elif self.CP.passive: self.events.add(EventName.dashcamMode, static=True) - # controlsd is driven by can recv, expected at 100Hz + # controlsd is driven by carState, expected at 100Hz self.rk = Ratekeeper(100, print_delay_threshold=None) def set_initial_state(self): if REPLAY: - controls_state = Params().get("ReplayControlsState") + controls_state = self.params.get("ReplayControlsState") if controls_state is not None: with log.ControlsState.from_bytes(controls_state) as controls_state: self.v_cruise_helper.v_cruise_kph = controls_state.vCruise @@ -322,18 +212,6 @@ class Controls: if not self.CP.pcmCruise and not self.v_cruise_helper.v_cruise_initialized and resume_pressed: self.events.add(EventName.resumeBlocked) - # Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0 - if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \ - (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \ - (CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)): - self.events.add(EventName.pedalPressed) - - if CS.brakePressed and CS.standstill: - self.events.add(EventName.preEnableStandstill) - - if CS.gasPressed: - self.events.add(EventName.gasPressedOverride) - if not self.CP.notCar: self.events.add_from_msg(self.sm['driverMonitoringState'].events) @@ -401,7 +279,7 @@ class Controls: else: safety_mismatch = pandaState.safetyModel not in IGNORED_SAFETY_MODES - # safety mismatch allows some time for boardd to set the safety mode and publish it back from panda + # safety mismatch allows some time for pandad 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) @@ -427,7 +305,7 @@ class Controls: self.events.add(EventName.cameraFrameRate) if not REPLAY and self.rk.lagging: self.events.add(EventName.controlsdLagging) - if len(self.sm['radarState'].radarErrors) or (not self.rk.lagging and not self.sm.all_checks(['radarState'])): + if len(self.sm['radarState'].radarErrors) or ((not self.rk.lagging or REPLAY) and not self.sm.all_checks(['radarState'])): self.events.add(EventName.radarFault) if not self.sm.valid['pandaStates']: self.events.add(EventName.usbError) @@ -439,19 +317,18 @@ class Controls: # generic catch-all. ideally, a more specific event should be added above instead 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 self.card.can_rcv_timeout) and no_system_errors: + if not self.sm.all_checks() and no_system_errors: if not self.sm.all_alive(): self.events.add(EventName.commIssue) elif not self.sm.all_freq_ok(): self.events.add(EventName.commIssueAvgFreq) - else: # invalid or can_rcv_timeout. + else: self.events.add(EventName.commIssue) logs = { 'invalid': [s for s, valid in self.sm.valid.items() if not valid], 'not_alive': [s for s, alive in self.sm.alive.items() if not alive], 'not_freq_ok': [s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok], - 'can_rcv_timeout': self.card.can_rcv_timeout, } if logs != self.logged_comm_issue: cloudlog.event("commIssue", error=True, **logs) @@ -484,7 +361,7 @@ class Controls: stock_long_is_braking = self.enabled and not self.CP.openpilotLongitudinalControl and CS.aEgo < -1.25 model_fcw = self.sm['modelV2'].meta.hardBrakePredicted and not CS.brakePressed and not stock_long_is_braking planner_fcw = self.sm['longitudinalPlan'].fcw and self.enabled - if planner_fcw or model_fcw: + if (planner_fcw or model_fcw) and not (self.CP.notCar and self.joystick_mode): self.events.add(EventName.fcw) for m in messaging.drain_sock(self.log_sock, wait_for_one=False): @@ -501,7 +378,7 @@ 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): + if not self.sm['liveLocationKalman'].gpsOK and self.sm['liveLocationKalman'].inputsOK and (self.distance_traveled > 1500): self.events.add(EventName.noGps) if self.sm['liveLocationKalman'].gpsOK: self.distance_traveled = 0 @@ -511,9 +388,10 @@ class Controls: self.events.add(EventName.modeldLagging) def data_sample(self): - """Receive data from sockets and update carState""" + """Receive data from sockets""" - CS = self.card.state_update(self.CC) + car_state = messaging.recv_one(self.car_state_sock) + CS = car_state.carState if car_state else self.CS_prev self.sm.update(0) @@ -527,12 +405,8 @@ class Controls: if VisionStreamType.VISION_STREAM_WIDE_ROAD not in available_streams: self.sm.ignore_alive.append('wideRoadCameraState') - if not self.CP.passive: - self.card.initialize() - self.initialized = True self.set_initial_state() - self.params.put_bool_nonblocking("ControlsReady", True) cloudlog.event( "controlsd.initialized", @@ -689,13 +563,12 @@ class Controls: if not CC.latActive: self.LaC.reset() if not CC.longActive: - self.LoC.reset(v_pid=CS.vEgo) + self.LoC.reset() 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.recv_frame['longitudinalPlan']) * DT_CTRL - actuators.accel = self.LoC.update(CC.longActive, CS, long_plan, pid_accel_limits, t_since_plan) + actuators.accel = self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits) # Steering PID loop and lateral MPC self.desired_curvature = clip_curvature(CS.vEgo, self.desired_curvature, model_v2.action.desiredCurvature) @@ -736,7 +609,7 @@ class Controls: undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2 turning = abs(lac_log.desiredLateralAccel) > 1.0 good_speed = CS.vEgo > 5 - max_torque = abs(self.last_actuators.steer) > 0.99 + max_torque = abs(self.sm['carOutput'].actuatorsOutput.steer) > 0.99 if undershooting and turning and good_speed and max_torque: lac_log.active and self.events.add(EventName.steerSaturated) elif lac_log.saturated: @@ -766,6 +639,12 @@ class Controls: cloudlog.error(f"actuators.{p} not finite {actuators.to_dict()}") setattr(actuators, p, 0.0) + # decrement personality on distance button press + if self.CP.openpilotLongitudinalControl: + if any(not be.pressed and be.type == ButtonType.gapAdjustCruise for be in CS.buttonEvents): + self.personality = (self.personality - 1) % 3 + self.params.put_nonblocking('LongitudinalPersonality', str(self.personality)) + return CC, lac_log def publish_logs(self, CS, start_time, CC, lac_log): @@ -794,6 +673,7 @@ class Controls: hudControl.speedVisible = self.enabled hudControl.lanesVisible = self.enabled hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead + hudControl.leadDistanceBars = self.personality + 1 hudControl.rightLaneVisible = True hudControl.leftLaneVisible = True @@ -833,13 +713,12 @@ class Controls: hudControl.visualAlert = current_alert.visual_alert if not self.CP.passive and self.initialized: - self.last_actuators = self.card.controls_update(CC) - CC.actuatorsOutput = self.last_actuators + CO = self.sm['carOutput'] if self.CP.steerControlType == car.CarParams.SteerControlType.angle: - self.steer_limited = abs(CC.actuators.steeringAngleDeg - CC.actuatorsOutput.steeringAngleDeg) > \ + self.steer_limited = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \ STEER_ANGLE_SATURATION_THRESHOLD else: - self.steer_limited = abs(CC.actuators.steer - CC.actuatorsOutput.steer) > 1e-2 + self.steer_limited = abs(CC.actuators.steer - CO.actuatorsOutput.steer) > 1e-2 force_decel = (self.sm['driverMonitoringState'].awarenessStatus < 0.) or \ (self.state == State.softDisabling) @@ -872,7 +751,6 @@ class Controls: controlsState.state = self.state controlsState.engageable = not self.events.contains(ET.NO_ENTRY) controlsState.longControlState = self.LoC.long_control_state - controlsState.vPid = float(self.LoC.v_pid) controlsState.vCruise = float(self.v_cruise_helper.v_cruise_kph) controlsState.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph) controlsState.upAccelCmd = float(self.LoC.pid.p) @@ -881,8 +759,8 @@ class Controls: controlsState.cumLagMs = -self.rk.remaining * 1000. controlsState.startMonoTime = int(start_time * 1e9) controlsState.forceDecel = bool(force_decel) - controlsState.canErrorCounter = self.card.can_rcv_cum_timeout_counter controlsState.experimentalMode = self.experimental_mode + controlsState.personality = self.personality lat_tuning = self.CP.lateralTuning.which() if self.joystick_mode: @@ -896,15 +774,11 @@ class Controls: self.pm.send('controlsState', dat) - car_events = self.events.to_msg() - - 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): ce_send = messaging.new_message('onroadEvents', len(self.events)) ce_send.valid = True - ce_send.onroadEvents = car_events + ce_send.onroadEvents = self.events.to_msg() self.pm.send('onroadEvents', ce_send) self.events_prev = self.events.names.copy() @@ -914,9 +788,6 @@ class Controls: cc_send.carControl = CC self.pm.send('carControl', cc_send) - # copy CarControl to pass to CarInterface on the next iteration - self.CC = CC - def step(self): start_time = time.monotonic() @@ -939,10 +810,17 @@ class Controls: self.CS_prev = CS + def read_personality_param(self): + try: + return int(self.params.get('LongitudinalPersonality')) + except (ValueError, TypeError): + return log.LongitudinalPersonality.standard + def params_thread(self, evt): while not evt.is_set(): self.is_metric = self.params.get_bool("IsMetric") self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl + self.personality = self.read_personality_param() if self.CP.notCar: self.joystick_mode = self.params.get_bool("JoystickDebugMode") time.sleep(0.1) @@ -955,12 +833,13 @@ class Controls: while True: self.step() self.rk.monitor_time() - except SystemExit: + finally: e.set() t.join() def main(): + config_realtime_process(4, Priority.CTRL_HIGH) controls = Controls() controls.controlsd_thread() diff --git a/selfdrive/controls/lib/alerts_offroad.json b/selfdrive/controls/lib/alerts_offroad.json index 35446ca22b..6fb6c569b0 100644 --- a/selfdrive/controls/lib/alerts_offroad.json +++ b/selfdrive/controls/lib/alerts_offroad.json @@ -1,7 +1,7 @@ { "Offroad_TemperatureTooHigh": { "text": "Device temperature too high. System cooling down before starting. Current internal component temperature: %1", - "severity": 1 + "severity": 0 }, "Offroad_ConnectivityNeededPrompt": { "text": "Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1", @@ -17,10 +17,6 @@ "severity": 1, "_comment": "Set extra field to the failed reason." }, - "Offroad_InvalidTime": { - "text": "Invalid date and time settings, system won't start. Connect to internet to set time.", - "severity": 1 - }, "Offroad_IsTakingSnapshot": { "text": "Taking camera snapshots. System won't start until finished.", "severity": 0 diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 6a5b22f686..cfc6374a1d 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -141,16 +141,6 @@ class VCruiseHelper: self.v_cruise_cluster_kph = self.v_cruise_kph -def apply_deadzone(error, deadzone): - if error > deadzone: - error -= deadzone - elif error < - deadzone: - error += deadzone - else: - error = 0. - return error - - def apply_center_deadzone(error, deadzone): if (error > - deadzone) and (error < deadzone): error = 0. diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index c5228ef7f2..b01818d704 100755 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import bisect import math import os from enum import IntEnum @@ -7,9 +8,9 @@ from collections.abc import Callable from cereal import log, car import cereal.messaging as messaging from openpilot.common.conversions import Conversions as CV +from openpilot.common.git import get_short_branch from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER -from openpilot.system.version import get_short_branch AlertSize = log.ControlsState.AlertSize AlertStatus = log.ControlsState.AlertStatus @@ -50,7 +51,7 @@ class Events: def __init__(self): self.events: list[int] = [] self.static_events: list[int] = [] - self.events_prev = dict.fromkeys(EVENTS.keys(), 0) + self.event_counters = dict.fromkeys(EVENTS.keys(), 0) @property def names(self) -> list[int]: @@ -61,11 +62,11 @@ class Events: def add(self, event_name: int, static: bool=False) -> None: if static: - self.static_events.append(event_name) - self.events.append(event_name) + bisect.insort(self.static_events, event_name) + bisect.insort(self.events, event_name) def clear(self) -> None: - self.events_prev = {k: (v + 1 if k in self.events else 0) for k, v in self.events_prev.items()} + self.event_counters = {k: (v + 1 if k in self.events else 0) for k, v in self.event_counters.items()} self.events = self.static_events.copy() def contains(self, event_type: str) -> bool: @@ -84,7 +85,7 @@ class Events: if not isinstance(alert, Alert): alert = alert(*callback_args) - if DT_CTRL * (self.events_prev[e] + 1) >= alert.creation_delay: + if DT_CTRL * (self.event_counters[e] + 1) >= alert.creation_delay: alert.alert_type = f"{EVENT_NAME[e]}/{et}" alert.event_type = et ret.append(alert) @@ -92,7 +93,7 @@ class Events: def add_from_msg(self, events): for e in events: - self.events.append(e.name.raw) + bisect.insort(self.events, e.name.raw) def to_msg(self): ret = [] @@ -251,13 +252,6 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2) -def no_gps_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: - return Alert( - "Poor GPS reception", - "Hardware malfunctioning if sky is visible", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=300.) - # *** debug alerts *** def out_of_space_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: @@ -337,6 +331,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { # ********** events with no alerts ********** EventName.stockFcw: {}, + EventName.actuatorsApiUnavailable: {}, # ********** events only containing alerts displayed in all states ********** @@ -433,7 +428,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.preDriverDistracted: { - ET.WARNING: Alert( + ET.PERMANENT: Alert( "Pay Attention", "", AlertStatus.normal, AlertSize.small, @@ -441,7 +436,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.promptDriverDistracted: { - ET.WARNING: Alert( + ET.PERMANENT: Alert( "Pay Attention", "Driver Distracted", AlertStatus.userPrompt, AlertSize.mid, @@ -449,7 +444,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.driverDistracted: { - ET.WARNING: Alert( + ET.PERMANENT: Alert( "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical, AlertSize.full, @@ -457,7 +452,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.preDriverUnresponsive: { - ET.WARNING: Alert( + ET.PERMANENT: Alert( "Touch Steering Wheel: No Face Detected", "", AlertStatus.normal, AlertSize.small, @@ -465,7 +460,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.promptDriverUnresponsive: { - ET.WARNING: Alert( + ET.PERMANENT: Alert( "Touch Steering Wheel", "Driver Unresponsive", AlertStatus.userPrompt, AlertSize.mid, @@ -473,7 +468,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.driverUnresponsive: { - ET.WARNING: Alert( + ET.PERMANENT: Alert( "DISENGAGE IMMEDIATELY", "Driver Unresponsive", AlertStatus.critical, AlertSize.full, @@ -493,7 +488,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { "Press Resume to Exit Standstill", "", AlertStatus.userPrompt, AlertSize.small, - Priority.MID, VisualAlert.none, AudibleAlert.none, .2), + Priority.LOW, VisualAlert.none, AudibleAlert.none, .2), }, EventName.belowSteerSpeed: { @@ -559,9 +554,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, # Unused - EventName.gpsMalfunction: { - ET.PERMANENT: NormalPermanentAlert("GPS Malfunction", "Likely Hardware Issue"), - }, EventName.locationdTemporaryError: { ET.NO_ENTRY: NoEntryAlert("locationd Temporary Error"), @@ -696,7 +688,11 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.noGps: { - ET.PERMANENT: no_gps_alert, + ET.PERMANENT: Alert( + "Poor GPS reception", + "Ensure device has a clear view of the sky", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=600.) }, EventName.soundsUnavailable: { diff --git a/selfdrive/controls/lib/lateral_mpc_lib/SConscript b/selfdrive/controls/lib/lateral_mpc_lib/SConscript index 49666abe69..73242cb8f9 100644 --- a/selfdrive/controls/lib/lateral_mpc_lib/SConscript +++ b/selfdrive/controls/lib/lateral_mpc_lib/SConscript @@ -1,4 +1,4 @@ -Import('env', 'envCython', 'arch') +Import('env', 'envCython', 'arch', 'msgq_python', 'common_python', 'opendbc_python') gen = "c_generated_code" @@ -60,6 +60,7 @@ lenv.Clean(generated_files, Dir(gen)) generated_lat = lenv.Command(generated_files, source_list, f"cd {Dir('.').abspath} && python3 lat_mpc.py") +lenv.Depends(generated_lat, [msgq_python, common_python, opendbc_python]) lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") diff --git a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py index 83ec3b3a13..ad60861088 100755 --- a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py +++ b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py @@ -128,7 +128,7 @@ def gen_lat_ocp(): return ocp -class LateralMpc(): +class LateralMpc: def __init__(self, x0=None): if x0 is None: x0 = np.zeros(X_DIM) diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index ee65c4a69e..e4841c705f 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -1,27 +1,19 @@ from cereal import car -from openpilot.common.numpy_fast import clip, interp +from openpilot.common.numpy_fast import clip from openpilot.common.realtime import DT_CTRL -from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, apply_deadzone +from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N from openpilot.selfdrive.controls.lib.pid import PIDController from openpilot.selfdrive.modeld.constants import ModelConstants +CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N] + LongCtrlState = car.CarControl.Actuators.LongControlState -def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, - v_target_1sec, brake_pressed, cruise_standstill): - # Ignore cruise standstill if car has a gas interceptor - cruise_standstill = cruise_standstill and not CP.enableGasInterceptor - accelerating = v_target_1sec > v_target - planned_stop = (v_target < CP.vEgoStopping and - v_target_1sec < CP.vEgoStopping and - not accelerating) - stay_stopped = (v_ego < CP.vEgoStopping and - (brake_pressed or cruise_standstill)) - stopping_condition = planned_stop or stay_stopped - - starting_condition = (v_target_1sec > CP.vEgoStarting and - accelerating and +def long_control_state_trans(CP, active, long_control_state, v_ego, + should_stop, brake_pressed, cruise_standstill): + stopping_condition = should_stop + starting_condition = (not should_stop and not cruise_standstill and not brake_pressed) started_condition = v_ego > CP.vEgoStarting @@ -34,7 +26,6 @@ def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, long_control_state = LongCtrlState.pid if stopping_condition: long_control_state = LongCtrlState.stopping - elif long_control_state == LongCtrlState.stopping: if starting_condition and CP.startingState: long_control_state = LongCtrlState.starting @@ -49,84 +40,45 @@ def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, return long_control_state - class LongControl: def __init__(self, CP): self.CP = CP - self.long_control_state = LongCtrlState.off # initialized to off + self.long_control_state = LongCtrlState.off self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV), (CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV), k_f=CP.longitudinalTuning.kf, rate=1 / DT_CTRL) - self.v_pid = 0.0 self.last_output_accel = 0.0 - def reset(self, v_pid): - """Reset PID controller and change setpoint""" + def reset(self): self.pid.reset() - self.v_pid = v_pid - def update(self, active, CS, long_plan, accel_limits, t_since_plan): + def update(self, active, CS, a_target, should_stop, accel_limits): """Update longitudinal control. This updates the state machine and runs a PID loop""" - # Interp control trajectory - speeds = long_plan.speeds - if len(speeds) == CONTROL_N: - v_target_now = interp(t_since_plan, ModelConstants.T_IDXS[:CONTROL_N], speeds) - a_target_now = interp(t_since_plan, ModelConstants.T_IDXS[:CONTROL_N], long_plan.accels) - - v_target_lower = interp(self.CP.longitudinalActuatorDelayLowerBound + t_since_plan, ModelConstants.T_IDXS[:CONTROL_N], speeds) - a_target_lower = 2 * (v_target_lower - v_target_now) / self.CP.longitudinalActuatorDelayLowerBound - a_target_now - - v_target_upper = interp(self.CP.longitudinalActuatorDelayUpperBound + t_since_plan, ModelConstants.T_IDXS[:CONTROL_N], speeds) - a_target_upper = 2 * (v_target_upper - v_target_now) / self.CP.longitudinalActuatorDelayUpperBound - a_target_now - - v_target = min(v_target_lower, v_target_upper) - a_target = min(a_target_lower, a_target_upper) - - v_target_1sec = interp(self.CP.longitudinalActuatorDelayUpperBound + t_since_plan + 1.0, ModelConstants.T_IDXS[:CONTROL_N], speeds) - else: - v_target = 0.0 - v_target_now = 0.0 - v_target_1sec = 0.0 - a_target = 0.0 - self.pid.neg_limit = accel_limits[0] self.pid.pos_limit = accel_limits[1] - output_accel = self.last_output_accel self.long_control_state = long_control_state_trans(self.CP, active, self.long_control_state, CS.vEgo, - v_target, v_target_1sec, CS.brakePressed, + should_stop, CS.brakePressed, CS.cruiseState.standstill) - if self.long_control_state == LongCtrlState.off: - self.reset(CS.vEgo) + self.reset() output_accel = 0. elif self.long_control_state == LongCtrlState.stopping: + output_accel = self.last_output_accel if output_accel > self.CP.stopAccel: output_accel = min(output_accel, 0.0) output_accel -= self.CP.stoppingDecelRate * DT_CTRL - self.reset(CS.vEgo) + self.reset() elif self.long_control_state == LongCtrlState.starting: output_accel = self.CP.startAccel - self.reset(CS.vEgo) + self.reset() - elif self.long_control_state == LongCtrlState.pid: - self.v_pid = v_target_now - - # Toyota starts braking more when it thinks you want to stop - # Freeze the integrator so we don't accelerate to compensate, and don't allow positive acceleration - # TODO too complex, needs to be simplified and tested on toyotas - prevent_overshoot = not self.CP.stoppingControl and CS.vEgo < 1.5 and v_target_1sec < 0.7 and v_target_1sec < self.v_pid - deadzone = interp(CS.vEgo, self.CP.longitudinalTuning.deadzoneBP, self.CP.longitudinalTuning.deadzoneV) - freeze_integrator = prevent_overshoot - - error = self.v_pid - CS.vEgo - error_deadzone = apply_deadzone(error, deadzone) - output_accel = self.pid.update(error_deadzone, speed=CS.vEgo, - feedforward=a_target, - freeze_integrator=freeze_integrator) + else: # LongCtrlState.pid + error = a_target - CS.aEgo + output_accel = self.pid.update(error, speed=CS.vEgo, + feedforward=a_target) self.last_output_accel = clip(output_accel, accel_limits[0], accel_limits[1]) - return self.last_output_accel diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript index 79afa1d918..592c1c2c2d 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript @@ -1,4 +1,4 @@ -Import('env', 'envCython', 'arch', 'messaging_python', 'common_python') +Import('env', 'envCython', 'arch', 'msgq_python', 'common_python', 'opendbc_python') gen = "c_generated_code" @@ -66,7 +66,7 @@ lenv.Clean(generated_files, Dir(gen)) generated_long = lenv.Command(generated_files, source_list, f"cd {Dir('.').abspath} && python3 long_mpc.py") -lenv.Depends(generated_long, [messaging_python, common_python]) +lenv.Depends(generated_long, [msgq_python, common_python, opendbc_python]) lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 39cfda4e8a..bad37add63 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -4,6 +4,7 @@ import time import numpy as np from cereal import log from openpilot.common.numpy_fast import clip +from openpilot.common.realtime import DT_MDL from openpilot.common.swaglog import cloudlog # WARNING: imports outside of constants will not trigger a rebuild from openpilot.selfdrive.modeld.constants import index_function @@ -220,8 +221,9 @@ def gen_long_ocp(): class LongitudinalMpc: - def __init__(self, mode='acc'): + def __init__(self, mode='acc', dt=DT_MDL): self.mode = mode + self.dt = dt self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.reset() self.source = SOURCES[2] @@ -440,7 +442,7 @@ class LongitudinalMpc: self.a_solution = self.x_sol[:,2] self.j_solution = self.u_sol[:,0] - self.prev_a = np.interp(T_IDXS + 0.05, T_IDXS, self.a_solution) + self.prev_a = np.interp(T_IDXS + self.dt, T_IDXS, self.a_solution) t = time.monotonic() if self.solution_status != 0: diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 018768f248..8772fadb47 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -2,8 +2,6 @@ import math import numpy as np from openpilot.common.numpy_fast import clip, interp -from openpilot.common.params import Params -from cereal import log import cereal.messaging as messaging from openpilot.common.conversions import Conversions as CV @@ -21,6 +19,7 @@ LON_MPC_STEP = 0.2 # first step is 0.2s A_CRUISE_MIN = -1.2 A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6] A_CRUISE_MAX_BP = [0., 10.0, 25., 40.] +CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N] # Lookup table for turns _A_TOTAL_MAX_V = [1.7, 3.2] @@ -36,7 +35,6 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): This function returns a limited long acceleration allowed, depending on the existing lateral acceleration this should avoid accelerating when losing the target in turns """ - # FIXME: This function to calculate lateral accel is incorrect and should use the VehicleModel # The lookup table for turns should also be updated if we do this a_total_max = interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V) @@ -46,10 +44,30 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): return [a_target[0], min(a_target[1], a_x_allowed)] +def get_accel_from_plan(CP, long_plan): + speeds = long_plan.speeds + if len(speeds) == CONTROL_N: + v_target_now = interp(DT_MDL, CONTROL_N_T_IDX, speeds) + a_target_now = interp(DT_MDL, CONTROL_N_T_IDX, long_plan.accels) + + v_target = interp(CP.longitudinalActuatorDelay + DT_MDL, CONTROL_N_T_IDX, speeds) + a_target = 2 * (v_target - v_target_now) / CP.longitudinalActuatorDelay - a_target_now + + v_target_1sec = interp(CP.longitudinalActuatorDelay + DT_MDL + 1.0, CONTROL_N_T_IDX, speeds) + else: + v_target = 0.0 + v_target_now = 0.0 + v_target_1sec = 0.0 + a_target = 0.0 + should_stop = (v_target < CP.vEgoStopping and + v_target_1sec < CP.vEgoStopping) + return a_target, should_stop + + class LongitudinalPlanner: def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL): self.CP = CP - self.mpc = LongitudinalMpc() + self.mpc = LongitudinalMpc(dt=dt) self.fcw = False self.dt = dt @@ -61,22 +79,12 @@ class LongitudinalPlanner: self.a_desired_trajectory = np.zeros(CONTROL_N) self.j_desired_trajectory = np.zeros(CONTROL_N) self.solverExecutionTime = 0.0 - self.params = Params() - self.param_read_counter = 0 - self.read_param() - self.personality = log.LongitudinalPersonality.standard - - def read_param(self): - try: - self.personality = int(self.params.get('LongitudinalPersonality')) - except (ValueError, TypeError): - self.personality = log.LongitudinalPersonality.standard @staticmethod def parse_model(model_msg, model_error): - if (len(model_msg.position.x) == 33 and - len(model_msg.velocity.x) == 33 and - len(model_msg.acceleration.x) == 33): + if (len(model_msg.position.x) == ModelConstants.IDX_N and + len(model_msg.velocity.x) == ModelConstants.IDX_N and + len(model_msg.acceleration.x) == ModelConstants.IDX_N): x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x) - model_error a = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.acceleration.x) @@ -89,9 +97,6 @@ class LongitudinalPlanner: return x, v, a, j def update(self, sm): - if self.param_read_counter % 50 == 0: - self.read_param() - self.param_read_counter += 1 self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc' v_ego = sm['carState'].vEgo @@ -130,11 +135,11 @@ class LongitudinalPlanner: accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05) accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05) - self.mpc.set_weights(prev_accel_constraint, personality=self.personality) + self.mpc.set_weights(prev_accel_constraint, personality=sm['controlsState'].personality) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) - self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=self.personality) + self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['controlsState'].personality) self.v_desired_trajectory_full = np.interp(ModelConstants.T_IDXS, T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory_full = np.interp(ModelConstants.T_IDXS, T_IDXS_MPC, self.mpc.a_solution) @@ -157,9 +162,14 @@ class LongitudinalPlanner: plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState']) + longitudinalPlan = plan_send.longitudinalPlan longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2'] longitudinalPlan.processingDelay = (plan_send.logMonoTime / 1e9) - sm.logMonoTime['modelV2'] + longitudinalPlan.solverExecutionTime = self.mpc.solve_time + + longitudinalPlan.allowBrake = True + longitudinalPlan.allowThrottle = True longitudinalPlan.speeds = self.v_desired_trajectory.tolist() longitudinalPlan.accels = self.a_desired_trajectory.tolist() @@ -169,7 +179,10 @@ class LongitudinalPlanner: longitudinalPlan.longitudinalPlanSource = self.mpc.source longitudinalPlan.fcw = self.fcw - longitudinalPlan.solverExecutionTime = self.mpc.solve_time - longitudinalPlan.personality = self.personality + a_target, should_stop = get_accel_from_plan(self.CP, longitudinalPlan) + longitudinalPlan.aTarget = a_target + longitudinalPlan.shouldStop = should_stop + longitudinalPlan.allowBrake = True + longitudinalPlan.allowThrottle = True pm.send('longitudinalPlan', plan_send) diff --git a/selfdrive/controls/lib/pid.py b/selfdrive/controls/lib/pid.py index f4ec7e5996..29c4d8bd46 100644 --- a/selfdrive/controls/lib/pid.py +++ b/selfdrive/controls/lib/pid.py @@ -4,7 +4,7 @@ from numbers import Number from openpilot.common.numpy_fast import clip, interp -class PIDController(): +class PIDController: def __init__(self, k_p, k_i, k_f=0., k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100): self._k_p = k_p self._k_i = k_i diff --git a/selfdrive/controls/lib/tests/test_alertmanager.py b/selfdrive/controls/lib/tests/test_alertmanager.py old mode 100755 new mode 100644 index dbd42858a0..8b9c18a9d4 --- a/selfdrive/controls/lib/tests/test_alertmanager.py +++ b/selfdrive/controls/lib/tests/test_alertmanager.py @@ -1,12 +1,10 @@ -#!/usr/bin/env python3 import random -import unittest from openpilot.selfdrive.controls.lib.events import Alert, EVENTS from openpilot.selfdrive.controls.lib.alertmanager import AlertManager -class TestAlertManager(unittest.TestCase): +class TestAlertManager: def test_duration(self): """ @@ -38,8 +36,4 @@ class TestAlertManager(unittest.TestCase): shown = current_alert is not None should_show = frame <= show_duration - self.assertEqual(shown, should_show, msg=f"{frame=} {add_duration=} {duration=}") - - -if __name__ == "__main__": - unittest.main() + assert shown == should_show, f"{frame=} {add_duration=} {duration=}" diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py old mode 100755 new mode 100644 index 866270ca60..81411edec1 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python3 -import unittest - from parameterized import parameterized from cereal import car, log @@ -15,9 +12,9 @@ from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel from openpilot.common.mock.generators import generate_liveLocationKalman -class TestLatControl(unittest.TestCase): +class TestLatControl: - @parameterized.expand([(HONDA.CIVIC, LatControlPID), (TOYOTA.RAV4, LatControlTorque), (NISSAN.LEAF, LatControlAngle)]) + @parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque), (NISSAN.NISSAN_LEAF, LatControlAngle)]) def test_saturation(self, car_name, controller): CarInterface, CarController, CarState = interfaces[car_name] CP = CarInterface.get_non_essential_params(car_name) @@ -36,8 +33,4 @@ class TestLatControl(unittest.TestCase): for _ in range(1000): _, _, lac_log = controller.update(True, CS, VM, params, False, 1, llk) - self.assertTrue(lac_log.saturated) - - -if __name__ == "__main__": - unittest.main() + assert lac_log.saturated diff --git a/selfdrive/controls/lib/tests/test_vehicle_model.py b/selfdrive/controls/lib/tests/test_vehicle_model.py old mode 100755 new mode 100644 index d016e87527..4d0e41805d --- a/selfdrive/controls/lib/tests/test_vehicle_model.py +++ b/selfdrive/controls/lib/tests/test_vehicle_model.py @@ -1,6 +1,5 @@ -#!/usr/bin/env python3 +import pytest import math -import unittest import numpy as np from control import StateSpace @@ -10,9 +9,9 @@ from openpilot.selfdrive.car.honda.values import CAR from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices -class TestVehicleModel(unittest.TestCase): - def setUp(self): - CP = CarInterface.get_non_essential_params(CAR.CIVIC) +class TestVehicleModel: + def setup_method(self): + CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC) self.VM = VehicleModel(CP) def test_round_trip_yaw_rate(self): @@ -23,7 +22,7 @@ class TestVehicleModel(unittest.TestCase): yr = self.VM.yaw_rate(sa, u, roll) new_sa = self.VM.get_steer_from_yaw_rate(yr, u, roll) - self.assertAlmostEqual(sa, new_sa) + assert sa == pytest.approx(new_sa) def test_dyn_ss_sol_against_yaw_rate(self): """Verify that the yaw_rate helper function matches the results @@ -38,7 +37,7 @@ class TestVehicleModel(unittest.TestCase): # Compute yaw rate using direct computations yr2 = self.VM.yaw_rate(sa, u, roll) - self.assertAlmostEqual(float(yr1[0]), yr2) + assert float(yr1[0]) == pytest.approx(yr2) def test_syn_ss_sol_simulate(self): """Verifies that dyn_ss_sol matches a simulation""" @@ -63,8 +62,3 @@ class TestVehicleModel(unittest.TestCase): x2 = dyn_ss_sol(sa, u, roll, self.VM) np.testing.assert_almost_equal(x1, x2, decimal=3) - - - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index eeeeda050e..681518be19 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -6,16 +6,6 @@ from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner import cereal.messaging as messaging -def publish_ui_plan(sm, pm, longitudinal_planner): - ui_send = messaging.new_message('uiPlan') - ui_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'modelV2']) - uiPlan = ui_send.uiPlan - uiPlan.frameId = sm['modelV2'].frameId - uiPlan.position.x = list(sm['modelV2'].position.x) - uiPlan.position.y = list(sm['modelV2'].position.y) - uiPlan.position.z = list(sm['modelV2'].position.z) - uiPlan.accel = longitudinal_planner.a_desired_trajectory_full.tolist() - pm.send('uiPlan', ui_send) def plannerd_thread(): config_realtime_process(5, Priority.CTRL_LOW) @@ -27,7 +17,7 @@ def plannerd_thread(): cloudlog.info("plannerd got CarParams: %s", CP.carName) longitudinal_planner = LongitudinalPlanner(CP) - pm = messaging.PubMaster(['longitudinalPlan', 'uiPlan']) + pm = messaging.PubMaster(['longitudinalPlan']) sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2'], poll='modelV2', ignore_avg_freq=['radarState']) @@ -36,7 +26,7 @@ def plannerd_thread(): if sm.updated['modelV2']: longitudinal_planner.update(sm) longitudinal_planner.publish(sm, pm) - publish_ui_plan(sm, pm, longitudinal_planner) + def main(): plannerd_thread() diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index 4de4208e9d..7420f666f7 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -2,7 +2,7 @@ import importlib import math from collections import deque -from typing import Any, Optional +from typing import Any import capnp from cereal import messaging, log, car @@ -133,7 +133,7 @@ def match_vision_to_track(v_ego: float, lead: capnp._DynamicStructReader, tracks prob_y = laplacian_pdf(c.yRel, -lead.y[0], lead.yStd[0]) prob_v = laplacian_pdf(c.vRel + v_ego, lead.v[0], lead.vStd[0]) - # This is isn't exactly right, but good heuristic + # This isn't exactly right, but it's a good heuristic return prob_d * prob_y * prob_v track = max(tracks.values(), key=prob) @@ -208,7 +208,7 @@ class RadarD: self.ready = False - def update(self, sm: messaging.SubMaster, rr: Optional[car.RadarData]): + def update(self, sm: messaging.SubMaster, rr): self.ready = sm.seen['modelV2'] self.current_time = 1e-9*max(sm.logMonoTime.values()) diff --git a/selfdrive/controls/tests/test_alerts.py b/selfdrive/controls/tests/test_alerts.py old mode 100755 new mode 100644 index 7b4fba0dce..38dc045949 --- a/selfdrive/controls/tests/test_alerts.py +++ b/selfdrive/controls/tests/test_alerts.py @@ -1,8 +1,6 @@ -#!/usr/bin/env python3 import copy import json import os -import unittest import random from PIL import Image, ImageDraw, ImageFont @@ -25,10 +23,10 @@ for event_types in EVENTS.values(): ALERTS.append(alert) -class TestAlerts(unittest.TestCase): +class TestAlerts: @classmethod - def setUpClass(cls): + def setup_class(cls): with open(OFFROAD_ALERTS_PATH) as f: cls.offroad_alerts = json.loads(f.read()) @@ -45,7 +43,7 @@ class TestAlerts(unittest.TestCase): for name, e in events.items(): if not name.endswith("DEPRECATED"): fail_msg = "%s @%d not in EVENTS" % (name, e) - self.assertTrue(e in EVENTS.keys(), msg=fail_msg) + assert e in EVENTS.keys(), fail_msg # ensure alert text doesn't exceed allowed width def test_alert_text_length(self): @@ -80,7 +78,7 @@ class TestAlerts(unittest.TestCase): left, _, right, _ = draw.textbbox((0, 0), txt, font) width = right - left msg = f"type: {alert.alert_type} msg: {txt}" - self.assertLessEqual(width, max_text_width, msg=msg) + assert width <= max_text_width, msg def test_alert_sanity_check(self): for event_types in EVENTS.values(): @@ -90,21 +88,21 @@ class TestAlerts(unittest.TestCase): continue if a.alert_size == AlertSize.none: - self.assertEqual(len(a.alert_text_1), 0) - self.assertEqual(len(a.alert_text_2), 0) + assert len(a.alert_text_1) == 0 + assert len(a.alert_text_2) == 0 elif a.alert_size == AlertSize.small: - self.assertGreater(len(a.alert_text_1), 0) - self.assertEqual(len(a.alert_text_2), 0) + assert len(a.alert_text_1) > 0 + assert len(a.alert_text_2) == 0 elif a.alert_size == AlertSize.mid: - self.assertGreater(len(a.alert_text_1), 0) - self.assertGreater(len(a.alert_text_2), 0) + assert len(a.alert_text_1) > 0 + assert len(a.alert_text_2) > 0 else: - self.assertGreater(len(a.alert_text_1), 0) + assert len(a.alert_text_1) > 0 - self.assertGreaterEqual(a.duration, 0.) + assert a.duration >= 0. if event_type not in (ET.WARNING, ET.PERMANENT, ET.PRE_ENABLE): - self.assertEqual(a.creation_delay, 0.) + assert a.creation_delay == 0. def test_offroad_alerts(self): params = Params() @@ -113,11 +111,11 @@ class TestAlerts(unittest.TestCase): alert = copy.copy(self.offroad_alerts[a]) set_offroad_alert(a, True) alert['extra'] = '' - self.assertTrue(json.dumps(alert) == params.get(a, encoding='utf8')) + assert json.dumps(alert) == params.get(a, encoding='utf8') # then delete it set_offroad_alert(a, False) - self.assertTrue(params.get(a) is None) + assert params.get(a) is None def test_offroad_alerts_extra_text(self): params = Params() @@ -128,8 +126,5 @@ class TestAlerts(unittest.TestCase): set_offroad_alert(a, True, extra_text="a"*i) written_alert = json.loads(params.get(a, encoding='utf8')) - self.assertTrue("a"*i == written_alert['extra']) - self.assertTrue(alert["text"] == written_alert['text']) - -if __name__ == "__main__": - unittest.main() + assert "a"*i == written_alert['extra'] + assert alert["text"] == written_alert['text'] diff --git a/selfdrive/controls/tests/test_cruise_speed.py b/selfdrive/controls/tests/test_cruise_speed.py old mode 100755 new mode 100644 index 76a2222e85..8f451a49bc --- a/selfdrive/controls/tests/test_cruise_speed.py +++ b/selfdrive/controls/tests/test_cruise_speed.py @@ -1,11 +1,9 @@ -#!/usr/bin/env python3 +import pytest import itertools import numpy as np -import unittest from parameterized import parameterized_class from cereal import log -from openpilot.common.params import Params from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT from cereal import car from openpilot.common.conversions import Conversions as CV @@ -15,7 +13,7 @@ ButtonEvent = car.CarState.ButtonEvent ButtonType = car.CarState.ButtonEvent.Type -def run_cruise_simulation(cruise, e2e, t_end=20.): +def run_cruise_simulation(cruise, e2e, personality, t_end=20.): man = Maneuver( '', duration=t_end, @@ -26,6 +24,7 @@ def run_cruise_simulation(cruise, e2e, t_end=20.): prob_lead_values=[0.0], breakpoints=[0.], e2e=e2e, + personality=personality, ) valid, output = man.evaluate() assert valid @@ -36,21 +35,19 @@ def run_cruise_simulation(cruise, e2e, t_end=20.): [True, False], # e2e log.LongitudinalPersonality.schema.enumerants, # personality [5,35])) # speed -class TestCruiseSpeed(unittest.TestCase): +class TestCruiseSpeed: def test_cruise_speed(self): - params = Params() - params.put("LongitudinalPersonality", str(self.personality)) print(f'Testing {self.speed} m/s') cruise_speed = float(self.speed) - simulation_steady_state = run_cruise_simulation(cruise_speed, self.e2e) - self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {self.speed} m/s') + simulation_steady_state = run_cruise_simulation(cruise_speed, self.e2e, self.personality) + assert simulation_steady_state == pytest.approx(cruise_speed, abs=.01), f'Did not reach {self.speed} m/s' # TODO: test pcmCruise @parameterized_class(('pcm_cruise',), [(False,)]) -class TestVCruiseHelper(unittest.TestCase): - def setUp(self): +class TestVCruiseHelper: + def setup_method(self): self.CP = car.CarParams(pcmCruise=self.pcm_cruise) self.v_cruise_helper = VCruiseHelper(self.CP) self.reset_cruise_speed_state() @@ -77,7 +74,7 @@ class TestVCruiseHelper(unittest.TestCase): CS.buttonEvents = [ButtonEvent(type=btn, pressed=pressed)] self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False) - self.assertEqual(pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) + assert pressed == (self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) def test_rising_edge_enable(self): """ @@ -96,7 +93,7 @@ class TestVCruiseHelper(unittest.TestCase): self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False) # Expected diff on enabling. Speed should not change on falling edge of pressed - self.assertEqual(not pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) + assert not pressed == self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last def test_resume_in_standstill(self): """ @@ -113,7 +110,7 @@ class TestVCruiseHelper(unittest.TestCase): # speed should only update if not at standstill and button falling edge should_equal = standstill or pressed - self.assertEqual(should_equal, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) + assert should_equal == (self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) def test_set_gas_pressed(self): """ @@ -137,7 +134,7 @@ class TestVCruiseHelper(unittest.TestCase): # TODO: fix skipping first run due to enabled on rising edge exception if v_ego == 0.0: continue - self.assertEqual(expected_v_cruise_kph, self.v_cruise_helper.v_cruise_kph) + assert expected_v_cruise_kph == self.v_cruise_helper.v_cruise_kph def test_initialize_v_cruise(self): """ @@ -147,12 +144,8 @@ class TestVCruiseHelper(unittest.TestCase): for experimental_mode in (True, False): for v_ego in np.linspace(0, 100, 101): self.reset_cruise_speed_state() - self.assertFalse(self.v_cruise_helper.v_cruise_initialized) + assert not self.v_cruise_helper.v_cruise_initialized self.enable(float(v_ego), experimental_mode) - self.assertTrue(V_CRUISE_INITIAL <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX) - self.assertTrue(self.v_cruise_helper.v_cruise_initialized) - - -if __name__ == "__main__": - unittest.main() + assert V_CRUISE_INITIAL <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX + assert self.v_cruise_helper.v_cruise_initialized diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py old mode 100755 new mode 100644 index 3b31632721..0fd543dd60 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -1,16 +1,14 @@ -#!/usr/bin/env python3 -import unittest +import pytest import itertools from parameterized import parameterized_class -from openpilot.common.params import Params from cereal import log from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance, get_T_FOLLOW from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver -def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): +def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False, personality=0): man = Maneuver( '', duration=t_end, @@ -20,6 +18,7 @@ def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): speed_lead_values=[v_lead], breakpoints=[0.], e2e=e2e, + personality=personality, ) valid, output = man.evaluate() assert valid @@ -32,16 +31,10 @@ def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): log.LongitudinalPersonality.standard, log.LongitudinalPersonality.aggressive], [0,10,35])) # speed -class TestFollowingDistance(unittest.TestCase): +class TestFollowingDistance: def test_following_distance(self): - params = Params() - params.put("LongitudinalPersonality", str(self.personality)) v_lead = float(self.speed) - simulation_steady_state = run_following_distance_simulation(v_lead, e2e=self.e2e) + simulation_steady_state = run_following_distance_simulation(v_lead, e2e=self.e2e, personality=self.personality) correct_steady_state = desired_follow_distance(v_lead, v_lead, get_T_FOLLOW(self.personality)) err_ratio = 0.2 if self.e2e else 0.1 - self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(err_ratio * correct_steady_state + .5)) - - -if __name__ == "__main__": - unittest.main() + assert simulation_steady_state == pytest.approx(correct_steady_state, abs=err_ratio * correct_steady_state + .5) diff --git a/selfdrive/controls/tests/test_lateral_mpc.py b/selfdrive/controls/tests/test_lateral_mpc.py index 8c09f46b60..3aa0fd1bce 100644 --- a/selfdrive/controls/tests/test_lateral_mpc.py +++ b/selfdrive/controls/tests/test_lateral_mpc.py @@ -1,4 +1,4 @@ -import unittest +import pytest import numpy as np from openpilot.selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import LateralMpc from openpilot.selfdrive.controls.lib.drive_helpers import CAR_ROTATION_RADIUS @@ -27,20 +27,20 @@ def run_mpc(lat_mpc=None, v_ref=30., x_init=0., y_init=0., psi_init=0., curvatur return lat_mpc.x_sol -class TestLateralMpc(unittest.TestCase): +class TestLateralMpc: def _assert_null(self, sol, curvature=1e-6): for i in range(len(sol)): - self.assertAlmostEqual(sol[0,i,1], 0., delta=curvature) - self.assertAlmostEqual(sol[0,i,2], 0., delta=curvature) - self.assertAlmostEqual(sol[0,i,3], 0., delta=curvature) + assert sol[0,i,1] == pytest.approx(0, abs=curvature) + assert sol[0,i,2] == pytest.approx(0, abs=curvature) + assert sol[0,i,3] == pytest.approx(0, abs=curvature) def _assert_simmetry(self, sol, curvature=1e-6): for i in range(len(sol)): - self.assertAlmostEqual(sol[0,i,1], -sol[1,i,1], delta=curvature) - self.assertAlmostEqual(sol[0,i,2], -sol[1,i,2], delta=curvature) - self.assertAlmostEqual(sol[0,i,3], -sol[1,i,3], delta=curvature) - self.assertAlmostEqual(sol[0,i,0], sol[1,i,0], delta=curvature) + assert sol[0,i,1] == pytest.approx(-sol[1,i,1], abs=curvature) + assert sol[0,i,2] == pytest.approx(-sol[1,i,2], abs=curvature) + assert sol[0,i,3] == pytest.approx(-sol[1,i,3], abs=curvature) + assert sol[0,i,0] == pytest.approx(sol[1,i,0], abs=curvature) def test_straight(self): sol = run_mpc() @@ -74,7 +74,7 @@ class TestLateralMpc(unittest.TestCase): y_init = 1. sol = run_mpc(y_init=y_init) for y in list(sol[:,1]): - self.assertGreaterEqual(y_init, abs(y)) + assert y_init >= abs(y) def test_switch_convergence(self): lat_mpc = LateralMpc() @@ -83,7 +83,3 @@ class TestLateralMpc(unittest.TestCase): sol = run_mpc(lat_mpc=lat_mpc, poly_shift=-3.0, v_ref=7.0) left_psi_deg = np.degrees(sol[:,2]) np.testing.assert_almost_equal(right_psi_deg, -left_psi_deg, decimal=3) - - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/controls/tests/test_leads.py b/selfdrive/controls/tests/test_leads.py old mode 100755 new mode 100644 index 268d9c47a7..f1f4449afd --- a/selfdrive/controls/tests/test_leads.py +++ b/selfdrive/controls/tests/test_leads.py @@ -1,13 +1,10 @@ -#!/usr/bin/env python3 -import unittest - import cereal.messaging as messaging from openpilot.selfdrive.test.process_replay import replay_process_with_name from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA -class TestLeads(unittest.TestCase): +class TestLeads: def test_radar_fault(self): # if there's no radar-related can traffic, radard should either not respond or respond with an error # this is tightly coupled with underlying car radar_interface implementation, but it's a good sanity check @@ -25,12 +22,8 @@ class TestLeads(unittest.TestCase): return msgs msgs = [m for _ in range(3) for m in single_iter_pkg()] - out = replay_process_with_name("radard", msgs, fingerprint=TOYOTA.COROLLA_TSS2) + out = replay_process_with_name("radard", msgs, fingerprint=TOYOTA.TOYOTA_COROLLA_TSS2) states = [m for m in out if m.which() == "radarState"] failures = [not state.valid and len(state.radarState.radarErrors) for state in states] - self.assertTrue(len(states) == 0 or all(failures)) - - -if __name__ == "__main__": - unittest.main() + assert len(states) == 0 or all(failures) diff --git a/selfdrive/controls/tests/test_startup.py b/selfdrive/controls/tests/test_startup.py index 34d14fbb39..14b0788a3d 100644 --- a/selfdrive/controls/tests/test_startup.py +++ b/selfdrive/controls/tests/test_startup.py @@ -4,12 +4,12 @@ from parameterized import parameterized from cereal import log, car import cereal.messaging as messaging from openpilot.common.params import Params -from openpilot.selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp +from openpilot.selfdrive.pandad.pandad_api_impl import can_list_to_can_capnp from openpilot.selfdrive.car.fingerprints import _FINGERPRINTS from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA from openpilot.selfdrive.car.mazda.values import CAR as MAZDA from openpilot.selfdrive.controls.lib.events import EVENT_NAME -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes EventName = car.CarEvent.EventName Ecu = car.CarParams.Ecu @@ -39,12 +39,12 @@ CX5_FW_VERSIONS = [ # TODO: test EventName.startup for release branches # officially supported car - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS, "toyota"), - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS, "toyota"), + (EventName.startupMaster, TOYOTA.TOYOTA_COROLLA, COROLLA_FW_VERSIONS, "toyota"), + (EventName.startupMaster, TOYOTA.TOYOTA_COROLLA, COROLLA_FW_VERSIONS, "toyota"), # dashcamOnly car - (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS, "mazda"), - (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS, "mazda"), + (EventName.startupNoControl, MAZDA.MAZDA_CX5, CX5_FW_VERSIONS, "mazda"), + (EventName.startupNoControl, MAZDA.MAZDA_CX5, CX5_FW_VERSIONS, "mazda"), # unrecognized car with no fw (EventName.startupNoFw, None, None, ""), @@ -55,8 +55,8 @@ CX5_FW_VERSIONS = [ (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1], "toyota"), # fuzzy match - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), + (EventName.startupMaster, TOYOTA.TOYOTA_COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), + (EventName.startupMaster, TOYOTA.TOYOTA_COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), ]) def test_startup_alert(expected_event, car_model, fw_versions, brand): controls_sock = messaging.sub_sock("controlsState") @@ -87,6 +87,7 @@ def test_startup_alert(expected_event, car_model, fw_versions, brand): os.environ['SKIP_FW_QUERY'] = '1' managed_processes['controlsd'].start() + managed_processes['card'].start() assert pm.wait_for_readers_to_update('can', 5) pm.send('can', can_list_to_can_capnp([[0, 0, b"", 0]])) @@ -104,7 +105,7 @@ def test_startup_alert(expected_event, car_model, fw_versions, brand): msgs = [[addr, 0, b'\x00'*length, 0] for addr, length in finger.items()] for _ in range(1000): - # controlsd waits for boardd to echo back that it has changed the multiplexing mode + # card waits for pandad to echo back that it has changed the multiplexing mode if not params.get_bool("ObdMultiplexingChanged"): params.put_bool("ObdMultiplexingChanged", True) diff --git a/selfdrive/controls/tests/test_state_machine.py b/selfdrive/controls/tests/test_state_machine.py old mode 100755 new mode 100644 index bdeed9fb7a..b6ec512dc4 --- a/selfdrive/controls/tests/test_state_machine.py +++ b/selfdrive/controls/tests/test_state_machine.py @@ -1,12 +1,10 @@ -#!/usr/bin/env python3 -import unittest - from cereal import car, log from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car.car_helpers import interfaces from openpilot.selfdrive.controls.controlsd import Controls, SOFT_DISABLE_TIME from openpilot.selfdrive.controls.lib.events import Events, ET, Alert, Priority, AlertSize, AlertStatus, VisualAlert, \ AudibleAlert, EVENTS +from openpilot.selfdrive.car.mock.values import CAR as MOCK State = log.ControlsState.OpenpilotState @@ -27,11 +25,11 @@ def make_event(event_types): return 0 -class TestStateMachine(unittest.TestCase): +class TestStateMachine: - def setUp(self): - CarInterface, CarController, CarState = interfaces["mock"] - CP = CarInterface.get_non_essential_params("mock") + def setup_method(self): + CarInterface, CarController, CarState = interfaces[MOCK.MOCK] + CP = CarInterface.get_non_essential_params(MOCK.MOCK) CI = CarInterface(CP, CarController, CarState) self.controlsd = Controls(CI=CI) @@ -45,7 +43,7 @@ class TestStateMachine(unittest.TestCase): self.controlsd.events.add(make_event([et, ET.IMMEDIATE_DISABLE])) self.controlsd.state = state self.controlsd.state_transition(self.CS) - self.assertEqual(State.disabled, self.controlsd.state) + assert State.disabled == self.controlsd.state self.controlsd.events.clear() def test_user_disable(self): @@ -54,7 +52,7 @@ class TestStateMachine(unittest.TestCase): self.controlsd.events.add(make_event([et, ET.USER_DISABLE])) self.controlsd.state = state self.controlsd.state_transition(self.CS) - self.assertEqual(State.disabled, self.controlsd.state) + assert State.disabled == self.controlsd.state self.controlsd.events.clear() def test_soft_disable(self): @@ -65,7 +63,7 @@ class TestStateMachine(unittest.TestCase): self.controlsd.events.add(make_event([et, ET.SOFT_DISABLE])) self.controlsd.state = state self.controlsd.state_transition(self.CS) - self.assertEqual(self.controlsd.state, State.disabled if state == State.disabled else State.softDisabling) + assert self.controlsd.state == State.disabled if state == State.disabled else State.softDisabling self.controlsd.events.clear() def test_soft_disable_timer(self): @@ -73,17 +71,17 @@ class TestStateMachine(unittest.TestCase): self.controlsd.events.add(make_event([ET.SOFT_DISABLE])) self.controlsd.state_transition(self.CS) for _ in range(int(SOFT_DISABLE_TIME / DT_CTRL)): - self.assertEqual(self.controlsd.state, State.softDisabling) + assert self.controlsd.state == State.softDisabling self.controlsd.state_transition(self.CS) - self.assertEqual(self.controlsd.state, State.disabled) + assert self.controlsd.state == State.disabled def test_no_entry(self): # Make sure noEntry keeps us disabled for et in ENABLE_EVENT_TYPES: self.controlsd.events.add(make_event([ET.NO_ENTRY, et])) self.controlsd.state_transition(self.CS) - self.assertEqual(self.controlsd.state, State.disabled) + assert self.controlsd.state == State.disabled self.controlsd.events.clear() def test_no_entry_pre_enable(self): @@ -91,7 +89,7 @@ class TestStateMachine(unittest.TestCase): self.controlsd.state = State.preEnabled self.controlsd.events.add(make_event([ET.NO_ENTRY, ET.PRE_ENABLE])) self.controlsd.state_transition(self.CS) - self.assertEqual(self.controlsd.state, State.preEnabled) + assert self.controlsd.state == State.preEnabled def test_maintain_states(self): # Given current state's event type, we should maintain state @@ -100,9 +98,5 @@ class TestStateMachine(unittest.TestCase): self.controlsd.state = state self.controlsd.events.add(make_event([et])) self.controlsd.state_transition(self.CS) - self.assertEqual(self.controlsd.state, state) + assert self.controlsd.state == state self.controlsd.events.clear() - - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/debug/check_can_parser_performance.py b/selfdrive/debug/check_can_parser_performance.py index c4b688ce29..604a1df124 100755 --- a/selfdrive/debug/check_can_parser_performance.py +++ b/selfdrive/debug/check_can_parser_performance.py @@ -25,11 +25,12 @@ if __name__ == '__main__': CC = car.CarControl.new_message() ets = [] for _ in tqdm(range(N_RUNS)): + msgs = [(m.as_builder().to_bytes(),) for m in tm.can_msgs] start_t = time.process_time_ns() - for msg in tm.can_msgs: + for msg in msgs: for cp in tm.CI.can_parsers: if cp is not None: - cp.update_strings((msg.as_builder().to_bytes(),)) + cp.update_strings(msg) ets.append((time.process_time_ns() - start_t) * 1e-6) print(f'{len(tm.can_msgs)} CAN packets, {N_RUNS} runs') diff --git a/selfdrive/debug/check_timings.py b/selfdrive/debug/check_timings.py index 3de6b8eb66..fc527cd81b 100755 --- a/selfdrive/debug/check_timings.py +++ b/selfdrive/debug/check_timings.py @@ -1,25 +1,36 @@ #!/usr/bin/env python3 - import sys import time import numpy as np +import datetime from collections.abc import MutableSequence -from collections import defaultdict, deque +from collections import defaultdict 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)) if __name__ == "__main__": - while True: - print() + ts: defaultdict[str, MutableSequence[float]] = defaultdict(list) + socks = {s: messaging.sub_sock(s, conflate=False) for s in sys.argv[1:]} + try: + st = time.monotonic() + while True: + print() + for s, sock in socks.items(): + msgs = messaging.drain_sock(sock) + for m in msgs: + ts[s].append(m.logMonoTime / 1e6) + + if len(ts[s]) > 2: + d = np.diff(ts[s])[-100:] + print(f"{s:25} {np.mean(d):7.2f} {np.std(d):7.2f} {np.max(d):7.2f} {np.min(d):7.2f}") + time.sleep(1) + except KeyboardInterrupt: + print("\n") + print("="*5, "timing summary", "="*5) for s, sock in socks.items(): msgs = messaging.drain_sock(sock) - for m in msgs: - ts[s].append(m.logMonoTime / 1e6) - if len(ts[s]) > 2: d = np.diff(ts[s]) - print(f"{s:25} {np.mean(d):.2f} {np.std(d):.2f} {np.max(d):.2f} {np.min(d):.2f}") - time.sleep(1) + print(f"{s:25} {np.mean(d):7.2f} {np.std(d):7.2f} {np.max(d):7.2f} {np.min(d):7.2f}") + print("="*5, datetime.timedelta(seconds=time.monotonic()-st), "="*5) diff --git a/selfdrive/debug/clear_dtc.py b/selfdrive/debug/clear_dtc.py index dea21331b7..a931c423a9 100755 --- a/selfdrive/debug/clear_dtc.py +++ b/selfdrive/debug/clear_dtc.py @@ -12,11 +12,11 @@ parser.add_argument('--debug', action='store_true') args = parser.parse_args() try: - check_output(["pidof", "boardd"]) - print("boardd is running, please kill openpilot before running this script! (aborted)") + check_output(["pidof", "pandad"]) + print("pandad is running, please kill openpilot before running this script! (aborted)") sys.exit(1) except CalledProcessError as e: - if e.returncode != 1: # 1 == no process found (boardd not running) + if e.returncode != 1: # 1 == no process found (pandad not running) raise e panda = Panda() diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py index ec9d02e8f4..234dfea3cc 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/selfdrive/debug/cpu_usage_stat.py @@ -9,10 +9,10 @@ System tools like top/htop can only show current cpu usage values, so I write th Calculate minumium/maximum/accumulated_average cpu usage as long term inspections. Monitor multiple processes simuteneously. Sample usage: - root@localhost:/data/openpilot$ python selfdrive/debug/cpu_usage_stat.py boardd,ubloxd - ('Add monitored proc:', './boardd') + root@localhost:/data/openpilot$ python selfdrive/debug/cpu_usage_stat.py pandad,ubloxd + ('Add monitored proc:', './pandad') ('Add monitored proc:', 'python locationd/ubloxd.py') - boardd: 1.96%, min: 1.96%, max: 1.96%, acc: 1.96% + pandad: 1.96%, min: 1.96%, max: 1.96%, acc: 1.96% ubloxd.py: 0.39%, min: 0.39%, max: 0.39%, acc: 0.39% ''' import psutil @@ -24,7 +24,7 @@ import argparse import re from collections import defaultdict -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes # Do statistics every 5 seconds PRINT_INTERVAL = 5 diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index 42561f70f0..93b0430c1e 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -8,7 +8,7 @@ from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car.honda.interface import CarInterface from openpilot.selfdrive.controls.lib.events import ET, Events from openpilot.selfdrive.controls.lib.alertmanager import AlertManager -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes EventName = car.CarEvent.EventName @@ -52,7 +52,7 @@ def cycle_alerts(duration=200, is_metric=False): cameras = ['roadCameraState', 'wideRoadCameraState', 'driverCameraState'] CS = car.CarState.new_message() - CP = CarInterface.get_non_essential_params("HONDA CIVIC 2016") + CP = CarInterface.get_non_essential_params("HONDA_CIVIC") sm = messaging.SubMaster(['deviceState', 'pandaStates', 'roadCameraState', 'modelV2', 'liveCalibration', 'driverMonitoringState', 'longitudinalPlan', 'liveLocationKalman', 'managerState'] + cameras) diff --git a/selfdrive/debug/debug_fw_fingerprinting_offline.py b/selfdrive/debug/debug_fw_fingerprinting_offline.py index d521ab2e18..3df2924738 100755 --- a/selfdrive/debug/debug_fw_fingerprinting_offline.py +++ b/selfdrive/debug/debug_fw_fingerprinting_offline.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 import argparse +from openpilot.tools.lib.live_logreader import live_logreader from openpilot.tools.lib.logreader import LogReader, ReadMode from panda.python import uds -def main(route: str, addrs: list[int]): +def main(route: str | None, addrs: list[int]): """ TODO: - highlight TX vs RX clearly @@ -13,7 +14,10 @@ def main(route: str, addrs: list[int]): - print as fixed width table, easier to read """ - lr = LogReader(route, default_mode=ReadMode.RLOG) + if route is None: + lr = live_logreader() + else: + lr = LogReader(route, default_mode=ReadMode.RLOG, sort_by_time=True) start_mono_time = None prev_mono_time = 0 @@ -28,18 +32,18 @@ def main(route: str, addrs: list[int]): if msg.which() in ("can", 'sendcan'): for can in getattr(msg, msg.which()): - if can.address in addrs: + if can.address in addrs or not len(addrs): if msg.logMonoTime != prev_mono_time: print() prev_mono_time = msg.logMonoTime - print(f"{msg.logMonoTime} rxaddr={can.address}, bus={can.src}, {round((msg.logMonoTime - start_mono_time) * 1e-6, 2)} ms, " + + print(f"{msg.which():>7}: rxaddr={can.address}, bus={can.src}, {round((msg.logMonoTime - start_mono_time) * 1e-6, 2)} ms, " + f"0x{can.dat.hex()}, {can.dat}, {len(can.dat)=}") if __name__ == "__main__": parser = argparse.ArgumentParser(description='View back and forth ISO-TP communication between various ECUs given an address') - parser.add_argument('route', help='Route name') - parser.add_argument('addrs', nargs='*', help='List of tx address to view (0x7e0 for engine)') + parser.add_argument('route', nargs='?', help='Route name, live if not specified') + parser.add_argument('--addrs', nargs='*', default=[], help='List of tx address to view (0x7e0 for engine)') args = parser.parse_args() addrs = [int(addr, base=16) if addr.startswith('0x') else int(addr) for addr in args.addrs] diff --git a/selfdrive/debug/dump_car_info.py b/selfdrive/debug/dump_car_docs.py similarity index 64% rename from selfdrive/debug/dump_car_info.py rename to selfdrive/debug/dump_car_docs.py index 6af328926b..f09c602cff 100755 --- a/selfdrive/debug/dump_car_info.py +++ b/selfdrive/debug/dump_car_docs.py @@ -2,12 +2,12 @@ import argparse import pickle -from openpilot.selfdrive.car.docs import get_all_car_info +from openpilot.selfdrive.car.docs import get_all_car_docs -def dump_car_info(path): +def dump_car_docs(path): with open(path, 'wb') as f: - pickle.dump(get_all_car_info(), f) + pickle.dump(get_all_car_docs(), f) print(f'Dumping car info to {path}') @@ -15,4 +15,4 @@ if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--path", required=True) args = parser.parse_args() - dump_car_info(args.path) + dump_car_docs(args.path) diff --git a/selfdrive/debug/get_fingerprint.py b/selfdrive/debug/get_fingerprint.py index f7f7a1604f..14f6c0e33c 100755 --- a/selfdrive/debug/get_fingerprint.py +++ b/selfdrive/debug/get_fingerprint.py @@ -4,7 +4,7 @@ # Instructions: # - connect to a Panda -# - run selfdrive/boardd/boardd +# - run selfdrive/pandad/pandad # - launching this script # Note: it's very important that the car is in stock mode, in order to collect a complete fingerprint # - since some messages are published at low frequency, keep this script running for at least 30s, diff --git a/selfdrive/debug/hyundai_enable_radar_points.py b/selfdrive/debug/hyundai_enable_radar_points.py index 7735391f4f..5e7080ef64 100755 --- a/selfdrive/debug/hyundai_enable_radar_points.py +++ b/selfdrive/debug/hyundai_enable_radar_points.py @@ -36,6 +36,9 @@ SUPPORTED_FW_VERSIONS = { default_config=b"\x00\x00\x00\x01\x00\x00", tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), # 2021 SONATA HYBRID + b"DNhe SCC FHCUP 1.00 1.00 99110-L5000\x19\x04&\x13' ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), b"DNhe SCC FHCUP 1.00 1.02 99110-L5000 \x01#\x15# ": ConfigValues( default_config=b"\x00\x00\x00\x01\x00\x00", tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), @@ -76,11 +79,11 @@ if __name__ == "__main__": args = parser.parse_args() try: - check_output(["pidof", "boardd"]) - print("boardd is running, please kill openpilot before running this script! (aborted)") + check_output(["pidof", "pandad"]) + print("pandad is running, please kill openpilot before running this script! (aborted)") sys.exit(1) except CalledProcessError as e: - if e.returncode != 1: # 1 == no process found (boardd not running) + if e.returncode != 1: # 1 == no process found (pandad not running) raise e confirm = input("power on the vehicle keeping the engine off (press start button twice) then type OK to continue: ").upper().strip() diff --git a/selfdrive/debug/internal/qlog_size.py b/selfdrive/debug/internal/qlog_size.py index b51cb3af2f..2d17a1ee28 100755 --- a/selfdrive/debug/internal/qlog_size.py +++ b/selfdrive/debug/internal/qlog_size.py @@ -5,24 +5,35 @@ from collections import defaultdict import matplotlib.pyplot as plt -from cereal.services import SERVICE_LIST from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.route import Route +from tqdm import tqdm MIN_SIZE = 0.5 # Percent size of total to show as separate entry def make_pie(msgs, typ): - compressed_length_by_type = {k: len(bz2.compress(b"".join(v))) for k, v in msgs.items()} + msgs_by_type = defaultdict(list) + for m in msgs: + msgs_by_type[m.which()].append(m.as_builder().to_bytes()) - total = sum(compressed_length_by_type.values()) + total = len(bz2.compress(b"".join([m.as_builder().to_bytes() for m in msgs]))) + uncompressed_total = len(b"".join([m.as_builder().to_bytes() for m in msgs])) + + length_by_type = {k: len(b"".join(v)) for k, v in msgs_by_type.items()} + # calculate compressed size by calculating diff when removed from the segment + compressed_length_by_type = {} + for k in tqdm(msgs_by_type.keys(), desc="Compressing"): + compressed_length_by_type[k] = total - len(bz2.compress(b"".join([m.as_builder().to_bytes() for m in msgs if m.which() != k]))) sizes = sorted(compressed_length_by_type.items(), key=lambda kv: kv[1]) - print(f"{typ} - Total {total / 1024:.2f} kB") + print("name - comp. size (uncomp. size)") for (name, sz) in sizes: - print(f"{name} - {sz / 1024:.2f} kB") + print(f"{name:<22} - {sz / 1024:.2f} kB ({length_by_type[name] / 1024:.2f} kB)") print() + print(f"{typ} - Real total {total / 1024:.2f} kB") + print(f"{typ} - Breakdown total {sum(compressed_length_by_type.values()) / 1024:.2f} kB") + print(f"{typ} - Uncompressed total {uncompressed_total / 1024 / 1024:.2f} MB") sizes_large = [(k, sz) for (k, sz) in sizes if sz >= total * MIN_SIZE / 100] sizes_large += [('other', sum(sz for (_, sz) in sizes if sz < total * MIN_SIZE / 100))] @@ -35,28 +46,11 @@ def make_pie(msgs, typ): if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Check qlog size based on a rlog') + parser = argparse.ArgumentParser(description='View log size breakdown by message type') parser.add_argument('route', help='route to use') - parser.add_argument('segment', type=int, help='segment number to use') args = parser.parse_args() - r = Route(args.route) - rlog = r.log_paths()[args.segment] - msgs = list(LogReader(rlog)) + msgs = list(LogReader(args.route)) - msgs_by_type = defaultdict(list) - for m in msgs: - msgs_by_type[m.which()].append(m.as_builder().to_bytes()) - - qlog_by_type = defaultdict(list) - for name, service in SERVICE_LIST.items(): - if service.decimation is None: - continue - - for i, msg in enumerate(msgs_by_type[name]): - if i % service.decimation == 0: - qlog_by_type[name].append(msg) - - make_pie(msgs_by_type, 'rlog') - make_pie(qlog_by_type, 'qlog') + make_pie(msgs, 'qlog') plt.show() diff --git a/selfdrive/debug/print_docs_diff.py b/selfdrive/debug/print_docs_diff.py index 3d35532496..7ef89a6eca 100755 --- a/selfdrive/debug/print_docs_diff.py +++ b/selfdrive/debug/print_docs_diff.py @@ -4,7 +4,7 @@ from collections import defaultdict import difflib import pickle -from openpilot.selfdrive.car.docs import get_all_car_info +from openpilot.selfdrive.car.docs import get_all_car_docs from openpilot.selfdrive.car.docs_definitions import Column FOOTNOTE_TAG = "{}" @@ -17,7 +17,7 @@ COLUMN_HEADER = "|---|---|---|{}|".format("|".join([":---:"] * (len(Column) - 3) ARROW_SYMBOL = "➡️" -def load_base_car_info(path): +def load_base_car_docs(path): with open(path, "rb") as f: return pickle.load(f) @@ -57,31 +57,31 @@ def format_row(builder): return "|" + "|".join(builder) + "|" -def print_car_info_diff(path): - base_car_info = defaultdict(list) - new_car_info = defaultdict(list) +def print_car_docs_diff(path): + base_car_docs = defaultdict(list) + new_car_docs = defaultdict(list) - for car in load_base_car_info(path): - base_car_info[car.car_fingerprint].append(car) - for car in get_all_car_info(): - new_car_info[car.car_fingerprint].append(car) + for car in load_base_car_docs(path): + base_car_docs[car.car_fingerprint].append(car) + for car in get_all_car_docs(): + new_car_docs[car.car_fingerprint].append(car) # Add new platforms to base cars so we can detect additions and removals in one pass - base_car_info.update({car: [] for car in new_car_info if car not in base_car_info}) + base_car_docs.update({car: [] for car in new_car_docs if car not in base_car_docs}) changes = defaultdict(list) - for base_car_model, base_cars in base_car_info.items(): + for base_car_model, base_cars in base_car_docs.items(): # Match car info changes, and get additions and removals - new_cars = new_car_info[base_car_model] + new_cars = new_car_docs[base_car_model] car_changes, car_additions, car_removals = match_cars(base_cars, new_cars) # Removals - for car_info in car_removals: - changes["removals"].append(format_row([car_info.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column])) + for car_docs in car_removals: + changes["removals"].append(format_row([car_docs.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column])) # Additions - for car_info in car_additions: - changes["additions"].append(format_row([car_info.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column])) + for car_docs in car_additions: + changes["additions"].append(format_row([car_docs.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column])) for new_car, base_car in car_changes: # Column changes @@ -117,4 +117,4 @@ if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--path", required=True) args = parser.parse_args() - print_car_info_diff(args.path) + print_car_docs_diff(args.path) diff --git a/selfdrive/debug/print_flags.py b/selfdrive/debug/print_flags.py new file mode 100755 index 0000000000..c69765550e --- /dev/null +++ b/selfdrive/debug/print_flags.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +from openpilot.selfdrive.car.values import BRANDS + +for brand in BRANDS: + all_flags = set() + for platform in brand: + if platform.config.flags != 0: + all_flags |= set(platform.config.flags) + + if len(all_flags): + print(brand.__module__.split('.')[-2].upper() + ':') + for flag in sorted(all_flags): + print(f' {flag.name:<24}:', {platform.name for platform in brand.with_flags(flag)}) + print() diff --git a/selfdrive/debug/read_dtc_status.py b/selfdrive/debug/read_dtc_status.py index 9ad5563975..2d27dd2bb2 100755 --- a/selfdrive/debug/read_dtc_status.py +++ b/selfdrive/debug/read_dtc_status.py @@ -13,11 +13,11 @@ parser.add_argument('--debug', action='store_true') args = parser.parse_args() try: - check_output(["pidof", "boardd"]) - print("boardd is running, please kill openpilot before running this script! (aborted)") + check_output(["pidof", "pandad"]) + print("pandad is running, please kill openpilot before running this script! (aborted)") sys.exit(1) except CalledProcessError as e: - if e.returncode != 1: # 1 == no process found (boardd not running) + if e.returncode != 1: # 1 == no process found (pandad not running) raise e panda = Panda() diff --git a/selfdrive/debug/run_process_on_route.py b/selfdrive/debug/run_process_on_route.py index 441de9ef57..90db14bc9a 100755 --- a/selfdrive/debug/run_process_on_route.py +++ b/selfdrive/debug/run_process_on_route.py @@ -27,4 +27,5 @@ if __name__ == "__main__": outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime) fn = f"{args.route.replace('/', '_')}_{args.process}.bz2" + print(f"Saved log to {fn}") save_log(fn, outputs) diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index cc6fc2ae17..e338110a7d 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -9,6 +9,7 @@ from tqdm import tqdm from openpilot.tools.lib.logreader import LogReader, ReadMode from openpilot.tools.lib.route import SegmentRange from openpilot.selfdrive.car.car_helpers import interface_names +from openpilot.selfdrive.car.fingerprints import MIGRATION from openpilot.selfdrive.car.fw_versions import VERSIONS, match_fw_to_car @@ -17,11 +18,6 @@ SUPPORTED_BRANDS = VERSIONS.keys() SUPPORTED_CARS = [brand for brand in SUPPORTED_BRANDS for brand in interface_names[brand]] UNKNOWN_BRAND = "unknown" -try: - from xx.pipeline.lib.fingerprint import MIGRATION -except ImportError: - MIGRATION = {} - if __name__ == "__main__": parser = argparse.ArgumentParser(description='Run FW fingerprint on Qlog of route or list of routes') parser.add_argument('route', help='Route or file with list of routes') @@ -82,8 +78,8 @@ if __name__ == "__main__": print("not in supported cars") break - _, exact_matches = match_fw_to_car(car_fw, allow_exact=True, allow_fuzzy=False) - _, fuzzy_matches = match_fw_to_car(car_fw, allow_exact=False, allow_fuzzy=True) + _, exact_matches = match_fw_to_car(car_fw, CP.carVin, allow_exact=True, allow_fuzzy=False) + _, fuzzy_matches = match_fw_to_car(car_fw, CP.carVin, allow_exact=False, allow_fuzzy=True) if (len(exact_matches) == 1) and (list(exact_matches)[0] == live_fingerprint): good_exact += 1 diff --git a/selfdrive/debug/uiview.py b/selfdrive/debug/uiview.py index f4440a912c..8e75769a85 100755 --- a/selfdrive/debug/uiview.py +++ b/selfdrive/debug/uiview.py @@ -3,13 +3,14 @@ import time from cereal import car, log, messaging from openpilot.common.params import Params -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes +from openpilot.system.hardware import HARDWARE if __name__ == "__main__": - CP = car.CarParams(notCar=True) + CP = car.CarParams(notCar=True, wheelbase=1, steerRatio=10) Params().put("CarParams", CP.to_bytes()) - procs = ['camerad', 'ui', 'modeld', 'calibrationd'] + procs = ['camerad', 'ui', 'modeld', 'calibrationd', 'plannerd', 'dmonitoringmodeld', 'dmonitoringd'] for p in procs: managed_processes[p].start() @@ -17,6 +18,7 @@ if __name__ == "__main__": msgs = {s: messaging.new_message(s) for s in ['controlsState', 'deviceState', 'carParams']} msgs['deviceState'].deviceState.started = True + msgs['deviceState'].deviceState.deviceType = HARDWARE.get_device_type() msgs['carParams'].carParams.openpilotLongitudinalControl = True msgs['pandaStates'] = messaging.new_message('pandaStates', 1) diff --git a/selfdrive/debug/vw_mqb_config.py b/selfdrive/debug/vw_mqb_config.py index 75409e3f87..6df1e57ce3 100755 --- a/selfdrive/debug/vw_mqb_config.py +++ b/selfdrive/debug/vw_mqb_config.py @@ -6,6 +6,7 @@ from enum import IntEnum from panda import Panda from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, SESSION_TYPE,\ DATA_IDENTIFIER_TYPE, ACCESS_TYPE +from datetime import date # TODO: extend UDS library to allow custom/vendor-defined data identifiers without ignoring type checks class VOLKSWAGEN_DATA_IDENTIFIER_TYPE(IntEnum): @@ -136,8 +137,10 @@ if __name__ == "__main__": # last two bytes, but not the VZ/importer or tester serial number # Can't seem to read it back, but we can read the calibration tester, # so fib a little and say that same tester did the programming - # TODO: encode the actual current date - prog_date = b'\x22\x02\x08' + current_date = date.today() + formatted_date = current_date.strftime('%y-%m-%d') + year, month, day = (int(part) for part in formatted_date.split('-')) + prog_date = bytes([year, month, day]) uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.PROGRAMMING_DATE, prog_date) tester_num = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER) uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER, tester_num) diff --git a/selfdrive/locationd/SConscript b/selfdrive/locationd/SConscript index 07555a6087..27cd4d5b40 100644 --- a/selfdrive/locationd/SConscript +++ b/selfdrive/locationd/SConscript @@ -1,6 +1,6 @@ -Import('env', 'arch', 'common', 'cereal', 'messaging', 'rednose', 'transformations') +Import('env', 'arch', 'common', 'messaging', 'rednose', 'transformations') -loc_libs = [cereal, messaging, 'zmq', common, 'capnp', 'kj', 'pthread', 'dl'] +loc_libs = [messaging, common, 'pthread', 'dl'] # build ekf models rednose_gen_dir = 'models/generated' diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 1050d68b9b..2ac392a778 100644 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -308,14 +308,12 @@ void Localizer::input_fake_gps_observations(double current_time) { } void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::Reader& log, const double sensor_time_offset) { - // ignore the message if the fix is invalid - bool gps_invalid_flag = (log.getFlags() % 2 == 0); - bool gps_unreasonable = (Vector2d(log.getAccuracy(), log.getVerticalAccuracy()).norm() >= SANE_GPS_UNCERTAINTY); + bool gps_unreasonable = (Vector2d(log.getHorizontalAccuracy(), log.getVerticalAccuracy()).norm() >= SANE_GPS_UNCERTAINTY); bool gps_accuracy_insane = ((log.getVerticalAccuracy() <= 0) || (log.getSpeedAccuracy() <= 0) || (log.getBearingAccuracyDeg() <= 0)); bool gps_lat_lng_alt_insane = ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK)); bool gps_vel_insane = (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK); - if (gps_invalid_flag || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane) { + if (!log.getHasFix() || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane) { //this->gps_valid = false; this->determine_gps_mode(current_time); return; @@ -331,7 +329,7 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R VectorXd ecef_pos = this->converter->ned2ecef({ 0.0, 0.0, 0.0 }).to_vector(); VectorXd ecef_vel = this->converter->ned2ecef({ log.getVNED()[0], log.getVNED()[1], log.getVNED()[2] }).to_vector() - ecef_pos; - float ecef_pos_std = std::sqrt(this->gps_variance_factor * std::pow(log.getAccuracy(), 2) + this->gps_vertical_variance_factor * std::pow(log.getVerticalAccuracy(), 2)); + float ecef_pos_std = std::sqrt(this->gps_variance_factor * std::pow(log.getHorizontalAccuracy(), 2) + this->gps_vertical_variance_factor * std::pow(log.getVerticalAccuracy(), 2)); MatrixXdr ecef_pos_R = Vector3d::Constant(std::pow(this->gps_std_factor * ecef_pos_std, 2)).asDiagonal(); MatrixXdr ecef_vel_R = Vector3d::Constant(std::pow(this->gps_std_factor * log.getSpeedAccuracy(), 2)).asDiagonal(); diff --git a/selfdrive/locationd/models/car_kf.py b/selfdrive/locationd/models/car_kf.py index 1f3e447a19..87a93cdd0c 100755 --- a/selfdrive/locationd/models/car_kf.py +++ b/selfdrive/locationd/models/car_kf.py @@ -28,7 +28,7 @@ def _slice(n): return s -class States(): +class States: # Vehicle model params STIFFNESS = _slice(1) # [-] STEER_RATIO = _slice(1) # [-] diff --git a/selfdrive/locationd/models/live_kf.py b/selfdrive/locationd/models/live_kf.py index c4933a6129..0cc3eca61d 100755 --- a/selfdrive/locationd/models/live_kf.py +++ b/selfdrive/locationd/models/live_kf.py @@ -20,7 +20,7 @@ def numpy2eigenstring(arr): return f"(Eigen::VectorXd({len(arr)}) << {arr_str}).finished()" -class States(): +class States: ECEF_POS = slice(0, 3) # x, y and z in ECEF in meters ECEF_ORIENTATION = slice(3, 7) # quat for pose of phone in ecef ECEF_VELOCITY = slice(7, 10) # ecef velocity in m/s @@ -39,7 +39,7 @@ class States(): ACC_BIAS_ERR = slice(18, 21) -class LiveKalman(): +class LiveKalman: name = 'live' initial_x = np.array([3.88e6, -3.37e6, 3.76e6, diff --git a/selfdrive/locationd/test/test_calibrationd.py b/selfdrive/locationd/test/test_calibrationd.py old mode 100755 new mode 100644 index e2db094397..df61b6a7c7 --- a/selfdrive/locationd/test/test_calibrationd.py +++ b/selfdrive/locationd/test/test_calibrationd.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 import random -import unittest import numpy as np @@ -31,7 +29,7 @@ def process_messages(c, cam_odo_calib, cycles, [0.0, 0.0, HEIGHT_INIT.item()], [cam_odo_height_std, cam_odo_height_std, cam_odo_height_std]) -class TestCalibrationd(unittest.TestCase): +class TestCalibrationd: def test_read_saved_params(self): msg = messaging.new_message('liveCalibration') @@ -43,13 +41,13 @@ class TestCalibrationd(unittest.TestCase): np.testing.assert_allclose(msg.liveCalibration.rpyCalib, c.rpy) np.testing.assert_allclose(msg.liveCalibration.height, c.height) - self.assertEqual(msg.liveCalibration.validBlocks, c.valid_blocks) + assert msg.liveCalibration.validBlocks == c.valid_blocks def test_calibration_basics(self): c = Calibrator(param_put=False) process_messages(c, [0.0, 0.0, 0.0], BLOCK_SIZE * INPUTS_WANTED) - self.assertEqual(c.valid_blocks, INPUTS_WANTED) + assert c.valid_blocks == INPUTS_WANTED np.testing.assert_allclose(c.rpy, np.zeros(3)) np.testing.assert_allclose(c.height, HEIGHT_INIT) c.reset() @@ -59,7 +57,7 @@ class TestCalibrationd(unittest.TestCase): c = Calibrator(param_put=False) process_messages(c, [0.0, 0.0, 0.0], BLOCK_SIZE * INPUTS_WANTED, cam_odo_speed=MIN_SPEED_FILTER - 1) process_messages(c, [0.0, 0.0, 0.0], BLOCK_SIZE * INPUTS_WANTED, carstate_speed=MIN_SPEED_FILTER - 1) - self.assertEqual(c.valid_blocks, 0) + assert c.valid_blocks == 0 np.testing.assert_allclose(c.rpy, np.zeros(3)) np.testing.assert_allclose(c.height, HEIGHT_INIT) @@ -67,7 +65,7 @@ class TestCalibrationd(unittest.TestCase): def test_calibration_yaw_rate_reject(self): c = Calibrator(param_put=False) process_messages(c, [0.0, 0.0, 0.0], BLOCK_SIZE * INPUTS_WANTED, cam_odo_yr=MAX_YAW_RATE_FILTER) - self.assertEqual(c.valid_blocks, 0) + assert c.valid_blocks == 0 np.testing.assert_allclose(c.rpy, np.zeros(3)) np.testing.assert_allclose(c.height, HEIGHT_INIT) @@ -75,43 +73,40 @@ class TestCalibrationd(unittest.TestCase): def test_calibration_speed_std_reject(self): c = Calibrator(param_put=False) process_messages(c, [0.0, 0.0, 0.0], BLOCK_SIZE * INPUTS_WANTED, cam_odo_speed_std=1e3) - self.assertEqual(c.valid_blocks, INPUTS_NEEDED) + assert c.valid_blocks == INPUTS_NEEDED np.testing.assert_allclose(c.rpy, np.zeros(3)) def test_calibration_speed_std_height_reject(self): c = Calibrator(param_put=False) process_messages(c, [0.0, 0.0, 0.0], BLOCK_SIZE * INPUTS_WANTED, cam_odo_height_std=1e3) - self.assertEqual(c.valid_blocks, INPUTS_NEEDED) + assert c.valid_blocks == INPUTS_NEEDED np.testing.assert_allclose(c.rpy, np.zeros(3)) def test_calibration_auto_reset(self): c = Calibrator(param_put=False) process_messages(c, [0.0, 0.0, 0.0], BLOCK_SIZE * INPUTS_NEEDED) - self.assertEqual(c.valid_blocks, INPUTS_NEEDED) + assert c.valid_blocks == INPUTS_NEEDED np.testing.assert_allclose(c.rpy, [0.0, 0.0, 0.0], atol=1e-3) process_messages(c, [0.0, MAX_ALLOWED_PITCH_SPREAD*0.9, MAX_ALLOWED_YAW_SPREAD*0.9], BLOCK_SIZE + 10) - self.assertEqual(c.valid_blocks, INPUTS_NEEDED + 1) - self.assertEqual(c.cal_status, log.LiveCalibrationData.Status.calibrated) + assert c.valid_blocks == INPUTS_NEEDED + 1 + assert c.cal_status == log.LiveCalibrationData.Status.calibrated c = Calibrator(param_put=False) process_messages(c, [0.0, 0.0, 0.0], BLOCK_SIZE * INPUTS_NEEDED) - self.assertEqual(c.valid_blocks, INPUTS_NEEDED) + assert c.valid_blocks == INPUTS_NEEDED np.testing.assert_allclose(c.rpy, [0.0, 0.0, 0.0]) process_messages(c, [0.0, MAX_ALLOWED_PITCH_SPREAD*1.1, 0.0], BLOCK_SIZE + 10) - self.assertEqual(c.valid_blocks, 1) - self.assertEqual(c.cal_status, log.LiveCalibrationData.Status.recalibrating) + assert c.valid_blocks == 1 + assert c.cal_status == log.LiveCalibrationData.Status.recalibrating np.testing.assert_allclose(c.rpy, [0.0, MAX_ALLOWED_PITCH_SPREAD*1.1, 0.0], atol=1e-2) c = Calibrator(param_put=False) process_messages(c, [0.0, 0.0, 0.0], BLOCK_SIZE * INPUTS_NEEDED) - self.assertEqual(c.valid_blocks, INPUTS_NEEDED) + assert c.valid_blocks == INPUTS_NEEDED np.testing.assert_allclose(c.rpy, [0.0, 0.0, 0.0]) process_messages(c, [0.0, 0.0, MAX_ALLOWED_YAW_SPREAD*1.1], BLOCK_SIZE + 10) - self.assertEqual(c.valid_blocks, 1) - self.assertEqual(c.cal_status, log.LiveCalibrationData.Status.recalibrating) + assert c.valid_blocks == 1 + assert c.cal_status == log.LiveCalibrationData.Status.recalibrating np.testing.assert_allclose(c.rpy, [0.0, 0.0, MAX_ALLOWED_YAW_SPREAD*1.1], atol=1e-2) - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/locationd/test/test_locationd.py b/selfdrive/locationd/test/test_locationd.py old mode 100755 new mode 100644 index 78de9216dc..74ac7d2962 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -1,7 +1,6 @@ -#!/usr/bin/env python3 +import pytest import json import random -import unittest import time import capnp @@ -10,16 +9,14 @@ from cereal.services import SERVICE_LIST from openpilot.common.params import Params from openpilot.common.transformations.coordinates import ecef2geodetic -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes -class TestLocationdProc(unittest.TestCase): +class TestLocationdProc: LLD_MSGS = ['gpsLocationExternal', 'cameraOdometry', 'carState', 'liveCalibration', 'accelerometer', 'gyroscope', 'magnetometer'] - def setUp(self): - random.seed(123489234) - + def setup_method(self): self.pm = messaging.PubMaster(self.LLD_MSGS) self.params = Params() @@ -27,7 +24,7 @@ class TestLocationdProc(unittest.TestCase): managed_processes['locationd'].prepare() managed_processes['locationd'].start() - def tearDown(self): + def teardown_method(self): managed_processes['locationd'].stop() def get_msg(self, name, t): @@ -38,6 +35,7 @@ class TestLocationdProc(unittest.TestCase): if name == "gpsLocationExternal": msg.gpsLocationExternal.flags = 1 + msg.gpsLocationExternal.hasFix = True msg.gpsLocationExternal.verticalAccuracy = 1.0 msg.gpsLocationExternal.speedAccuracy = 1.0 msg.gpsLocationExternal.bearingAccuracyDeg = 1.0 @@ -64,6 +62,7 @@ class TestLocationdProc(unittest.TestCase): return msg def test_params_gps(self): + random.seed(123489234) self.params.remove('LastGPSPosition') self.x = -2710700 + (random.random() * 1e5) @@ -85,10 +84,6 @@ class TestLocationdProc(unittest.TestCase): time.sleep(1) # wait for async params write lastGPS = json.loads(self.params.get('LastGPSPosition')) - self.assertAlmostEqual(lastGPS['latitude'], self.lat, places=3) - self.assertAlmostEqual(lastGPS['longitude'], self.lon, places=3) - self.assertAlmostEqual(lastGPS['altitude'], self.alt, places=3) - - -if __name__ == "__main__": - unittest.main() + assert lastGPS['latitude'] == pytest.approx(self.lat, abs=0.001) + assert lastGPS['longitude'] == pytest.approx(self.lon, abs=0.001) + assert lastGPS['altitude'] == pytest.approx(self.alt, abs=0.001) diff --git a/selfdrive/locationd/test/test_locationd_scenarios.py b/selfdrive/locationd/test/test_locationd_scenarios.py old mode 100755 new mode 100644 index f48c83ce46..ca52bffeea --- a/selfdrive/locationd/test/test_locationd_scenarios.py +++ b/selfdrive/locationd/test/test_locationd_scenarios.py @@ -1,11 +1,10 @@ -#!/usr/bin/env python3 import pytest -import unittest import numpy as np from collections import defaultdict from enum import Enum from openpilot.tools.lib.logreader import LogReader +from openpilot.selfdrive.test.process_replay.migration import migrate_all from openpilot.selfdrive.test.process_replay.process_replay import replay_process_with_name TEST_ROUTE = "ff2bd20623fcaeaa|2023-09-05--10-14-54/4" @@ -98,7 +97,7 @@ def run_scenarios(scenario, logs): @pytest.mark.xdist_group("test_locationd_scenarios") @pytest.mark.shared_download_cache -class TestLocationdScenarios(unittest.TestCase): +class TestLocationdScenarios: """ Test locationd with different scenarios. In all these scenarios, we expect the following: - locationd kalman filter should never go unstable (we care mostly about yaw_rate, roll, gpsOK, inputsOK, sensorsOK) @@ -106,8 +105,8 @@ class TestLocationdScenarios(unittest.TestCase): """ @classmethod - def setUpClass(cls): - cls.logs = list(LogReader(TEST_ROUTE)) + def setup_class(cls): + cls.logs = migrate_all(LogReader(TEST_ROUTE)) def test_base(self): """ @@ -117,8 +116,8 @@ class TestLocationdScenarios(unittest.TestCase): - roll: unchanged """ orig_data, replayed_data = run_scenarios(Scenario.BASE, self.logs) - self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) - self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) + assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)) + assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)) def test_gps_off(self): """ @@ -129,9 +128,9 @@ class TestLocationdScenarios(unittest.TestCase): - gpsOK: False """ orig_data, replayed_data = run_scenarios(Scenario.GPS_OFF, self.logs) - self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) - self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) - self.assertTrue(np.all(replayed_data['gps_flag'] == 0.0)) + assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)) + assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)) + assert np.all(replayed_data['gps_flag'] == 0.0) def test_gps_off_midway(self): """ @@ -142,9 +141,9 @@ class TestLocationdScenarios(unittest.TestCase): - gpsOK: True for the first half, False for the second half """ orig_data, replayed_data = run_scenarios(Scenario.GPS_OFF_MIDWAY, self.logs) - self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) - self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) - self.assertTrue(np.diff(replayed_data['gps_flag'])[512] == -1.0) + assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)) + assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)) + assert np.diff(replayed_data['gps_flag'])[512] == -1.0 def test_gps_on_midway(self): """ @@ -155,9 +154,9 @@ class TestLocationdScenarios(unittest.TestCase): - gpsOK: False for the first half, True for the second half """ orig_data, replayed_data = run_scenarios(Scenario.GPS_ON_MIDWAY, self.logs) - self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) - self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(1.5))) - self.assertTrue(np.diff(replayed_data['gps_flag'])[505] == 1.0) + assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)) + assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(1.5)) + assert np.diff(replayed_data['gps_flag'])[505] == 1.0 def test_gps_tunnel(self): """ @@ -168,10 +167,10 @@ class TestLocationdScenarios(unittest.TestCase): - gpsOK: False for the middle section, True for the rest """ orig_data, replayed_data = run_scenarios(Scenario.GPS_TUNNEL, self.logs) - self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) - self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) - self.assertTrue(np.diff(replayed_data['gps_flag'])[213] == -1.0) - self.assertTrue(np.diff(replayed_data['gps_flag'])[805] == 1.0) + assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)) + assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)) + assert np.diff(replayed_data['gps_flag'])[213] == -1.0 + assert np.diff(replayed_data['gps_flag'])[805] == 1.0 def test_gyro_off(self): """ @@ -182,9 +181,9 @@ class TestLocationdScenarios(unittest.TestCase): - sensorsOK: False """ _, replayed_data = run_scenarios(Scenario.GYRO_OFF, self.logs) - self.assertTrue(np.allclose(replayed_data['yaw_rate'], 0.0)) - self.assertTrue(np.allclose(replayed_data['roll'], 0.0)) - self.assertTrue(np.all(replayed_data['sensors_flag'] == 0.0)) + assert np.allclose(replayed_data['yaw_rate'], 0.0) + assert np.allclose(replayed_data['roll'], 0.0) + assert np.all(replayed_data['sensors_flag'] == 0.0) def test_gyro_spikes(self): """ @@ -195,10 +194,10 @@ class TestLocationdScenarios(unittest.TestCase): - inputsOK: False for some time after the spike, True for the rest """ orig_data, replayed_data = run_scenarios(Scenario.GYRO_SPIKE_MIDWAY, self.logs) - self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) - self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) - self.assertTrue(np.diff(replayed_data['inputs_flag'])[500] == -1.0) - self.assertTrue(np.diff(replayed_data['inputs_flag'])[694] == 1.0) + assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)) + assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)) + assert np.diff(replayed_data['inputs_flag'])[500] == -1.0 + assert np.diff(replayed_data['inputs_flag'])[694] == 1.0 def test_accel_off(self): """ @@ -209,9 +208,9 @@ class TestLocationdScenarios(unittest.TestCase): - sensorsOK: False """ _, replayed_data = run_scenarios(Scenario.ACCEL_OFF, self.logs) - self.assertTrue(np.allclose(replayed_data['yaw_rate'], 0.0)) - self.assertTrue(np.allclose(replayed_data['roll'], 0.0)) - self.assertTrue(np.all(replayed_data['sensors_flag'] == 0.0)) + assert np.allclose(replayed_data['yaw_rate'], 0.0) + assert np.allclose(replayed_data['roll'], 0.0) + assert np.all(replayed_data['sensors_flag'] == 0.0) def test_accel_spikes(self): """ @@ -220,9 +219,5 @@ class TestLocationdScenarios(unittest.TestCase): Expected Result: Right now, the kalman filter is not robust to small spikes like it is to gyroscope spikes. """ orig_data, replayed_data = run_scenarios(Scenario.ACCEL_SPIKE_MIDWAY, self.logs) - self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) - self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) - - -if __name__ == "__main__": - unittest.main() + assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2)) + assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5)) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 69bab8d1fa..06f9044738 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -159,8 +159,10 @@ class TorqueEstimator(ParameterEstimator): def handle_log(self, t, which, msg): if which == "carControl": self.raw_points["carControl_t"].append(t + self.lag) - self.raw_points["steer_torque"].append(-msg.actuatorsOutput.steer) self.raw_points["active"].append(msg.latActive) + elif which == "carOutput": + self.raw_points["carOutput_t"].append(t + self.lag) + self.raw_points["steer_torque"].append(-msg.actuatorsOutput.steer) elif which == "carState": self.raw_points["carState_t"].append(t + self.lag) self.raw_points["vego"].append(msg.vEgo) @@ -172,7 +174,7 @@ class TorqueEstimator(ParameterEstimator): active = np.interp(np.arange(t - MIN_ENGAGE_BUFFER, t, DT_MDL), self.raw_points['carControl_t'], self.raw_points['active']).astype(bool) steer_override = np.interp(np.arange(t - MIN_ENGAGE_BUFFER, t, DT_MDL), self.raw_points['carState_t'], self.raw_points['steer_override']).astype(bool) vego = np.interp(t, self.raw_points['carState_t'], self.raw_points['vego']) - steer = np.interp(t, self.raw_points['carControl_t'], self.raw_points['steer_torque']) + steer = np.interp(t, self.raw_points['carOutput_t'], self.raw_points['steer_torque']) lateral_acc = (vego * yaw_rate) - (np.sin(roll) * ACCELERATION_DUE_TO_GRAVITY) if all(active) and (not any(steer_override)) and (vego > MIN_VEL) and (abs(steer) > STEER_MIN_THRESHOLD) and (abs(lateral_acc) <= LAT_ACC_THRESHOLD): self.filtered_points.add_point(float(steer), float(lateral_acc)) @@ -218,7 +220,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', 'carOutput', 'carState', 'liveLocationKalman'], poll='liveLocationKalman') params = Params() with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP: diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 2d15223d1e..deb84d5952 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,3 +1,5 @@ +import glob + Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'transformations') lenv = env.Clone() lenvCython = envCython.Clone() @@ -50,11 +52,12 @@ lenvCython.Program('runners/runmodel_pyx.so', 'runners/runmodel_pyx.pyx', LIBS=c lenvCython.Program('runners/snpemodel_pyx.so', 'runners/snpemodel_pyx.pyx', LIBS=[snpemodel_lib, snpe_lib, *cython_libs], FRAMEWORKS=frameworks, RPATH=snpe_rpath) lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) +tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath)] + # Get model metadata fn = File("models/supercombo").abspath cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' -files = sum([lenv.Glob("#"+x) for x in open(File("#release/files_common").abspath).read().split("\n") if x.endswith("get_model_metadata.py")], []) -lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"]+files, cmd) +lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files, cmd) # Build thneed model if arch == "larch64" or GetOption('pc_thneed'): @@ -64,7 +67,6 @@ if arch == "larch64" or GetOption('pc_thneed'): tinygrad_opts += ["FLOAT16=1", "PYOPENCL_NO_CACHE=1"] cmd = f"cd {Dir('#').abspath}/tinygrad_repo && " + ' '.join(tinygrad_opts) + f" python3 openpilot/compile2.py {fn}.onnx {fn}.thneed" - tinygrad_files = sum([lenv.Glob("#"+x) for x in open(File("#release/files_common").abspath).read().split("\n") if x.startswith("tinygrad_repo/")], []) lenv.Command(fn + ".thneed", [fn + ".onnx"] + tinygrad_files, cmd) thneed_lib = env.SharedLibrary('thneed', thneed_src, LIBS=[gpucommon, common, 'zmq', 'OpenCL', 'dl']) diff --git a/selfdrive/modeld/constants.py b/selfdrive/modeld/constants.py index dda1ff5e33..ca81bffcc8 100644 --- a/selfdrive/modeld/constants.py +++ b/selfdrive/modeld/constants.py @@ -18,9 +18,6 @@ class ModelConstants: HISTORY_BUFFER_LEN = 99 DESIRE_LEN = 8 TRAFFIC_CONVENTION_LEN = 2 - NAV_FEATURE_LEN = 256 - NAV_INSTRUCTION_LEN = 150 - DRIVING_STYLE_LEN = 12 LAT_PLANNER_STATE_LEN = 4 LATERAL_CONTROL_PARAMS_LEN = 2 PREV_DESIRED_CURV_LEN = 1 diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index ef403b44fc..a388bf089c 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -9,7 +9,7 @@ from pathlib import Path from cereal import messaging from cereal.messaging import PubMaster, SubMaster -from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.realtime import set_realtime_priority diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index c39ec2da3d..f6a57f1a40 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -43,8 +43,7 @@ def fill_xyvat(builder, t, x, y, v, a, x_std=None, y_std=None, v_std=None, a_std 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, valid: bool) -> None: + timestamp_eof: int, model_execution_time: float, valid: bool) -> None: frame_age = frame_id - vipc_frame_id if frame_id > vipc_frame_id else 0 msg.valid = valid @@ -54,9 +53,7 @@ def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: dict[str, modelV2.frameAge = frame_age modelV2.frameDropPerc = frame_drop * 100 modelV2.timestampEof = timestamp_eof - modelV2.locationMonoTime = timestamp_llk modelV2.modelExecutionTime = model_execution_time - modelV2.navEnabled = nav_enabled # plan position = modelV2.position diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index e086b8aaf8..1e33b7b3f0 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -6,15 +6,16 @@ import numpy as np import cereal.messaging as messaging from cereal import car, log from pathlib import Path -from setproctitle import setproctitle +from openpilot.common.threadname import setthreadname from cereal.messaging import PubMaster, SubMaster -from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process +from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.common.transformations.model import get_warp_matrix -from openpilot.selfdrive import sentry +from openpilot.system import sentry from openpilot.selfdrive.car.car_helpers import get_demo_car_params from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime @@ -23,7 +24,7 @@ from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_ from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.modeld.models.commonmodel_pyx import ModelFrame, CLContext -PROCESS_NAME = "selfdrive.modeld.modeld" +THREAD_NAME = "selfdrive.modeld.modeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PATHS = { @@ -58,8 +59,6 @@ class ModelState: 'traffic_convention': np.zeros(ModelConstants.TRAFFIC_CONVENTION_LEN, dtype=np.float32), 'lateral_control_params': np.zeros(ModelConstants.LATERAL_CONTROL_PARAMS_LEN, dtype=np.float32), 'prev_desired_curv': np.zeros(ModelConstants.PREV_DESIRED_CURV_LEN * (ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), - 'nav_features': np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32), - 'nav_instructions': np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32), 'features_buffer': np.zeros(ModelConstants.HISTORY_BUFFER_LEN * ModelConstants.FEATURE_LEN, dtype=np.float32), } @@ -93,8 +92,6 @@ class ModelState: self.inputs['traffic_convention'][:] = inputs['traffic_convention'] self.inputs['lateral_control_params'][:] = inputs['lateral_control_params'] - self.inputs['nav_features'][:] = inputs['nav_features'] - self.inputs['nav_instructions'][:] = inputs['nav_instructions'] # if getCLBuffer is not None, frame will be None self.model.setInputBuffer("input_imgs", self.frame.prepare(buf, transform.flatten(), self.model.getCLBuffer("input_imgs"))) @@ -117,9 +114,9 @@ class ModelState: def main(demo=False): cloudlog.warning("modeld init") - sentry.set_tag("daemon", PROCESS_NAME) - cloudlog.bind(daemon=PROCESS_NAME) - setproctitle(PROCESS_NAME) + sentry.set_tag("daemon", THREAD_NAME) + cloudlog.bind(daemon=THREAD_NAME) + setthreadname("modeld") config_realtime_process(7, 54) cloudlog.warning("setting up CL context") @@ -153,7 +150,7 @@ def main(demo=False): # messaging pm = PubMaster(["modelV2", "cameraOdometry"]) - sm = SubMaster(["carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "navModel", "navInstruction", "carControl"]) + sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl"]) publish_state = PublishState() params = Params() @@ -167,8 +164,6 @@ def main(demo=False): model_transform_main = np.zeros((3, 3), dtype=np.float32) model_transform_extra = np.zeros((3, 3), dtype=np.float32) live_calib_seen = False - nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) - nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) buf_main, buf_extra = None, None meta_main = FrameMeta() meta_extra = FrameMeta() @@ -195,7 +190,7 @@ def main(demo=False): break if buf_main is None: - cloudlog.error("vipc_client_main no frame") + cloudlog.debug("vipc_client_main no frame") continue if use_extra_client: @@ -207,13 +202,12 @@ def main(demo=False): break if buf_extra is None: - cloudlog.error("vipc_client_extra no frame") + cloudlog.debug("vipc_client_extra no frame") continue if abs(meta_main.timestamp_sof - meta_extra.timestamp_sof) > 10000000: - cloudlog.error("frames out of sync! main: {} ({:.5f}), extra: {} ({:.5f})".format( - meta_main.frame_id, meta_main.timestamp_sof / 1e9, - meta_extra.frame_id, meta_extra.timestamp_sof / 1e9)) + cloudlog.error(f"frames out of sync! main: {meta_main.frame_id} ({meta_main.timestamp_sof / 1e9:.5f}),\ + extra: {meta_extra.frame_id} ({meta_extra.timestamp_sof / 1e9:.5f})") else: # Use single camera @@ -225,10 +219,11 @@ def main(demo=False): is_rhd = sm["driverMonitoringState"].isRHD frame_id = sm["roadCameraState"].frameId lateral_control_params = np.array([sm["carState"].vEgo, steer_delay], dtype=np.float32) - if sm.updated["liveCalibration"]: + if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) - model_transform_main = get_warp_matrix(device_from_calib_euler, main_wide_camera, False).astype(np.float32) - model_transform_extra = get_warp_matrix(device_from_calib_euler, True, True).astype(np.float32) + dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] + model_transform_main = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics if main_wide_camera else dc.fcam.intrinsics, False).astype(np.float32) + model_transform_extra = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics, True).astype(np.float32) live_calib_seen = True traffic_convention = np.zeros(2) @@ -238,30 +233,6 @@ def main(demo=False): if desire >= 0 and desire < ModelConstants.DESIRE_LEN: vec_desire[desire] = 1 - # Enable/disable nav features - timestamp_llk = sm["navModel"].locationMonoTime - nav_valid = sm.valid["navModel"] # and (nanos_since_boot() - timestamp_llk < 1e9) - nav_enabled = nav_valid and params.get_bool("ExperimentalMode") - - if not nav_enabled: - nav_features[:] = 0 - nav_instructions[:] = 0 - - if nav_enabled and sm.updated["navModel"]: - nav_features = np.array(sm["navModel"].features) - - if nav_enabled and sm.updated["navInstruction"]: - nav_instructions[:] = 0 - for maneuver in sm["navInstruction"].allManeuvers: - distance_idx = 25 + int(maneuver.distance / 20) - direction_idx = 0 - if maneuver.modifier in ("left", "slight left", "sharp left"): - direction_idx = 1 - if maneuver.modifier in ("right", "slight right", "sharp right"): - direction_idx = 2 - if 0 <= distance_idx < 50: - nav_instructions[distance_idx*3 + direction_idx] = 1 - # tracked dropped frames vipc_dropped_frames = max(0, meta_main.frame_id - last_vipc_frame_id - 1) frames_dropped = frame_dropped_filter.update(min(vipc_dropped_frames, 10)) @@ -279,8 +250,7 @@ def main(demo=False): 'desire': vec_desire, 'traffic_convention': traffic_convention, 'lateral_control_params': lateral_control_params, - 'nav_features': nav_features, - 'nav_instructions': nav_instructions} + } mt1 = time.perf_counter() model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only) @@ -291,7 +261,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, live_calib_seen) + meta_main.timestamp_eof, model_execution_time, live_calib_seen) desire_state = modelv2_send.modelV2.meta.desireState l_lane_change_prob = desire_state[log.Desire.laneChangeLeft] @@ -316,7 +286,7 @@ if __name__ == "__main__": args = parser.parse_args() main(demo=args.demo) except KeyboardInterrupt: - cloudlog.warning(f"child {PROCESS_NAME} got SIGINT") + cloudlog.warning(f"child {THREAD_NAME} got SIGINT") except Exception: sentry.capture_exception() raise diff --git a/selfdrive/modeld/models/README.md b/selfdrive/modeld/models/README.md index ff5c91fe0c..9e11ca8255 100644 --- a/selfdrive/modeld/models/README.md +++ b/selfdrive/modeld/models/README.md @@ -21,10 +21,6 @@ To view the architecture of the ONNX networks, you can use [netron](https://netr * one-hot encoded vector to tell model whether traffic is right-hand or left-hand traffic : 2 * **feature buffer** * A buffer of intermediate features that gets appended to the current feature to form a 5 seconds temporal context (at 20FPS) : 99 * 512 -* **nav features** - * 1 * 150 -* **nav instructions** - * 1 * 256 ### Supercombo output format (Full size: XXX x float32) diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc index 5e28e9b95d..523ce00e43 100644 --- a/selfdrive/modeld/models/commonmodel.cc +++ b/selfdrive/modeld/models/commonmodel.cc @@ -1,13 +1,10 @@ #include "selfdrive/modeld/models/commonmodel.h" -#include #include #include #include #include "common/clutil.h" -#include "common/mat.h" -#include "common/timing.h" ModelFrame::ModelFrame(cl_device_id device_id, cl_context context) { input_frames = std::make_unique(buf_size); @@ -52,21 +49,6 @@ ModelFrame::~ModelFrame() { CL_CHECK(clReleaseCommandQueue(q)); } -void softmax(const float* input, float* output, size_t len) { - const float max_val = *std::max_element(input, input + len); - float denominator = 0; - for (int i = 0; i < len; i++) { - float const v_exp = expf(input[i] - max_val); - denominator += v_exp; - output[i] = v_exp; - } - - const float inv_denominator = 1. / denominator; - for (int i = 0; i < len; i++) { - output[i] *= inv_denominator; - } -} - float sigmoid(float input) { return 1 / (1 + expf(-input)); } diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h index 1a079da055..2cf79094a6 100644 --- a/selfdrive/modeld/models/commonmodel.h +++ b/selfdrive/modeld/models/commonmodel.h @@ -13,20 +13,11 @@ #endif #include "common/mat.h" -#include "cereal/messaging/messaging.h" #include "selfdrive/modeld/transforms/loadyuv.h" #include "selfdrive/modeld/transforms/transform.h" -const bool send_raw_pred = getenv("SEND_RAW_PRED") != NULL; - -void softmax(const float* input, float* output, size_t len); float sigmoid(float input); -template -constexpr const kj::ArrayPtr to_kj_array_ptr(const std::array &arr) { - return kj::ArrayPtr(arr.data(), arr.size()); -} - class ModelFrame { public: ModelFrame(cl_device_id device_id, cl_context context); diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd index 57b79aeafb..7c3eb0b3d7 100644 --- a/selfdrive/modeld/models/commonmodel.pxd +++ b/selfdrive/modeld/models/commonmodel.pxd @@ -1,6 +1,6 @@ # distutils: language = c++ -from cereal.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem +from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem cdef extern from "common/mat.h": cdef struct mat3: diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd index 97e3914588..0bb798625b 100644 --- a/selfdrive/modeld/models/commonmodel_pyx.pxd +++ b/selfdrive/modeld/models/commonmodel_pyx.pxd @@ -1,7 +1,7 @@ # distutils: language = c++ -from cereal.visionipc.visionipc cimport cl_mem -from cereal.visionipc.visionipc_pyx cimport CLContext as BaseCLContext +from msgq.visionipc.visionipc cimport cl_mem +from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext cdef class CLContext(BaseCLContext): pass diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx index e33d301aff..e292bb0d2d 100644 --- a/selfdrive/modeld/models/commonmodel_pyx.pyx +++ b/selfdrive/modeld/models/commonmodel_pyx.pyx @@ -5,8 +5,8 @@ import numpy as np cimport numpy as cnp from libc.string cimport memcpy -from cereal.visionipc.visionipc cimport cl_mem -from cereal.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext +from msgq.visionipc.visionipc cimport cl_mem +from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context from .commonmodel cimport mat3, sigmoid as cppSigmoid, ModelFrame as cppModelFrame @@ -37,7 +37,11 @@ cdef class ModelFrame: def prepare(self, VisionBuf buf, float[:] projection, CLMem output): cdef mat3 cprojection memcpy(cprojection.v, &projection[0], 9*sizeof(float)) - cdef float * data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection, output.mem) + cdef float * data + if output is None: + data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection, NULL) + else: + data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection, output.mem) if not data: return None return np.asarray( data) diff --git a/selfdrive/modeld/models/dmonitoring_model.current b/selfdrive/modeld/models/dmonitoring_model.current index ed495df3a8..f935ab06b3 100644 --- a/selfdrive/modeld/models/dmonitoring_model.current +++ b/selfdrive/modeld/models/dmonitoring_model.current @@ -1,2 +1,2 @@ -60abed33-9f25-4e34-9937-aaf918d41dfc -50887963963a3022e85ac0c94b0801aef955a608 \ No newline at end of file +5ec97a39-0095-4cea-adfa-6d72b1966cc1 +26cac7a9757a27c783a365403040a1bd27ccdaea \ No newline at end of file diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 3e94e7d36a..dd3a6aba2e 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a667cbde6eca86c5b653f57853e3d33b9a875bceb557e193d1bef78c2df9c37 -size 16132779 +oid sha256:3dd3982940d823c4fbb0429b733a0b78b0688d7d67aa76ff7b754a3e2f3d8683 +size 16132780 diff --git a/selfdrive/modeld/models/dmonitoring_model_q.dlc b/selfdrive/modeld/models/dmonitoring_model_q.dlc index 041949ad94..fc285954d4 100644 --- a/selfdrive/modeld/models/dmonitoring_model_q.dlc +++ b/selfdrive/modeld/models/dmonitoring_model_q.dlc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f5729c457dd47f6c35e2a818eae14113526fd0bfac6417a809cbaf9d38697d9b -size 4488413 +oid sha256:7c26f13816b143f5bb29ac2980f8557bd5687a75729e4d895313fb9a5a1f0f46 +size 4488449 diff --git a/selfdrive/modeld/models/navmodel.onnx b/selfdrive/modeld/models/navmodel.onnx deleted file mode 100644 index 3b687b960a..0000000000 --- a/selfdrive/modeld/models/navmodel.onnx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8254b569878b7472e3f63ed9f3527a87bde785c9037aee3ed66f972e072b5899 -size 14166696 diff --git a/selfdrive/modeld/models/navmodel_q.dlc b/selfdrive/modeld/models/navmodel_q.dlc deleted file mode 100644 index d5e43abcfb..0000000000 --- a/selfdrive/modeld/models/navmodel_q.dlc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89fda8380efa3e421fbcdb6bb204c36a4991f137ee01d47f3d0380895aa7c036 -size 3630942 diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index ce346a406a..c0f8b16fb1 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:c11f99aab832242a604b1bb4c4cf391c9c3d5e90cbc07ab0d4c133473b56a3a4 -size 48193749 +oid sha256:b31b504bc0b440d3bc72967507a00eb4f112285626fbfb3135011500325ee6d6 +size 51452435 diff --git a/selfdrive/modeld/navmodeld.py b/selfdrive/modeld/navmodeld.py deleted file mode 100755 index 4672734681..0000000000 --- a/selfdrive/modeld/navmodeld.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 -import gc -import math -import time -import ctypes -import numpy as np -from pathlib import Path - -from cereal import messaging -from cereal.messaging import PubMaster, SubMaster -from cereal.visionipc import VisionIpcClient, VisionStreamType -from openpilot.common.swaglog import cloudlog -from openpilot.common.params import Params -from openpilot.common.realtime import set_realtime_priority -from openpilot.selfdrive.modeld.constants import ModelConstants -from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime - -NAV_INPUT_SIZE = 256*256 -NAV_FEATURE_LEN = 256 -NAV_DESIRE_LEN = 32 -NAV_OUTPUT_SIZE = 2*2*ModelConstants.IDX_N + NAV_DESIRE_LEN + NAV_FEATURE_LEN -MODEL_PATHS = { - ModelRunner.SNPE: Path(__file__).parent / 'models/navmodel_q.dlc', - ModelRunner.ONNX: Path(__file__).parent / 'models/navmodel.onnx'} - -class NavModelOutputXY(ctypes.Structure): - _fields_ = [ - ("x", ctypes.c_float), - ("y", ctypes.c_float)] - -class NavModelOutputPlan(ctypes.Structure): - _fields_ = [ - ("mean", NavModelOutputXY*ModelConstants.IDX_N), - ("std", NavModelOutputXY*ModelConstants.IDX_N)] - -class NavModelResult(ctypes.Structure): - _fields_ = [ - ("plan", NavModelOutputPlan), - ("desire_pred", ctypes.c_float*NAV_DESIRE_LEN), - ("features", ctypes.c_float*NAV_FEATURE_LEN)] - -class ModelState: - inputs: dict[str, np.ndarray] - output: np.ndarray - model: ModelRunner - - def __init__(self): - assert ctypes.sizeof(NavModelResult) == NAV_OUTPUT_SIZE * ctypes.sizeof(ctypes.c_float) - self.output = np.zeros(NAV_OUTPUT_SIZE, dtype=np.float32) - self.inputs = {'input_img': np.zeros(NAV_INPUT_SIZE, dtype=np.uint8)} - 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]: - self.inputs['input_img'][:] = buf - - t1 = time.perf_counter() - self.model.setInputBuffer("input_img", self.inputs['input_img'].view(np.float32)) - self.model.execute() - t2 = time.perf_counter() - return self.output, t2 - t1 - -def get_navmodel_packet(model_output: np.ndarray, valid: bool, frame_id: int, location_ts: int, execution_time: float, dsp_execution_time: float): - model_result = ctypes.cast(model_output.ctypes.data, ctypes.POINTER(NavModelResult)).contents - msg = messaging.new_message('navModel') - msg.valid = valid - msg.navModel.frameId = frame_id - msg.navModel.locationMonoTime = location_ts - msg.navModel.modelExecutionTime = execution_time - msg.navModel.dspExecutionTime = dsp_execution_time - msg.navModel.features = model_result.features[:] - msg.navModel.desirePrediction = model_result.desire_pred[:] - msg.navModel.position.x = [p.x for p in model_result.plan.mean] - msg.navModel.position.y = [p.y for p in model_result.plan.mean] - msg.navModel.position.xStd = [math.exp(p.x) for p in model_result.plan.std] - msg.navModel.position.yStd = [math.exp(p.y) for p in model_result.plan.std] - return msg - - -def main(): - gc.disable() - set_realtime_priority(1) - - # there exists a race condition when two processes try to create a - # SNPE model runner at the same time, wait for dmonitoringmodeld to finish - cloudlog.warning("waiting for dmonitoringmodeld to initialize") - if not Params().get_bool("DmModelInitialized", True): - return - - model = ModelState() - cloudlog.warning("models loaded, navmodeld starting") - - vipc_client = VisionIpcClient("navd", VisionStreamType.VISION_STREAM_MAP, True) - while not vipc_client.connect(False): - time.sleep(0.1) - assert vipc_client.is_connected() - cloudlog.warning(f"connected with buffer size: {vipc_client.buffer_len}") - - sm = SubMaster(["navInstruction"]) - pm = PubMaster(["navModel"]) - - while True: - buf = vipc_client.recv() - if buf is None: - continue - - sm.update(0) - t1 = time.perf_counter() - model_output, dsp_execution_time = model.run(buf.data[:buf.uv_offset]) - t2 = time.perf_counter() - - valid = vipc_client.valid and sm.valid["navInstruction"] - pm.send("navModel", get_navmodel_packet(model_output, valid, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, dsp_execution_time)) - - -if __name__ == "__main__": - main() diff --git a/selfdrive/modeld/runners/snpemodel.pxd b/selfdrive/modeld/runners/snpemodel.pxd index 1f928da332..a911b43584 100644 --- a/selfdrive/modeld/runners/snpemodel.pxd +++ b/selfdrive/modeld/runners/snpemodel.pxd @@ -2,7 +2,7 @@ from libcpp.string cimport string -from cereal.visionipc.visionipc cimport cl_context +from msgq.visionipc.visionipc cimport cl_context cdef extern from "selfdrive/modeld/runners/snpemodel.h": cdef cppclass SNPEModel: diff --git a/selfdrive/modeld/runners/thneedmodel.pxd b/selfdrive/modeld/runners/thneedmodel.pxd index 90af972865..79e24dbdd6 100644 --- a/selfdrive/modeld/runners/thneedmodel.pxd +++ b/selfdrive/modeld/runners/thneedmodel.pxd @@ -2,7 +2,7 @@ from libcpp.string cimport string -from cereal.visionipc.visionipc cimport cl_context +from msgq.visionipc.visionipc cimport cl_context cdef extern from "selfdrive/modeld/runners/thneedmodel.h": cdef cppclass ThneedModel: diff --git a/selfdrive/modeld/tests/test_modeld.py b/selfdrive/modeld/tests/test_modeld.py old mode 100755 new mode 100644 index 257a9bc878..1e19a591bf --- a/selfdrive/modeld/tests/test_modeld.py +++ b/selfdrive/modeld/tests/test_modeld.py @@ -1,27 +1,26 @@ -#!/usr/bin/env python3 -import unittest import numpy as np import random import cereal.messaging as messaging -from cereal.visionipc import VisionIpcServer, VisionStreamType -from openpilot.common.transformations.camera import tici_f_frame_size +from msgq.visionipc import VisionIpcServer, VisionStreamType +from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.car.car_helpers import write_car_param -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state -IMG = np.zeros(int(tici_f_frame_size[0]*tici_f_frame_size[1]*(3/2)), dtype=np.uint8) +CAM = DEVICE_CAMERAS[("tici", "ar0231")].fcam +IMG = np.zeros(int(CAM.width*CAM.height*(3/2)), dtype=np.uint8) IMG_BYTES = IMG.flatten().tobytes() -class TestModeld(unittest.TestCase): +class TestModeld: - def setUp(self): + def setup_method(self): self.vipc_server = VisionIpcServer("camerad") - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, False, *tici_f_frame_size) - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, *tici_f_frame_size) - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, *tici_f_frame_size) + self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, False, CAM.width, CAM.height) + self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, CAM.width, CAM.height) + self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, CAM.width, CAM.height) self.vipc_server.start_listener() write_car_param() @@ -31,7 +30,7 @@ class TestModeld(unittest.TestCase): managed_processes['modeld'].start() self.pm.wait_for_readers_to_update("roadCameraState", 10) - def tearDown(self): + def teardown_method(self): managed_processes['modeld'].stop() del self.vipc_server @@ -64,15 +63,15 @@ class TestModeld(unittest.TestCase): self._wait() mdl = self.sm['modelV2'] - self.assertEqual(mdl.frameId, n) - self.assertEqual(mdl.frameIdExtra, n) - self.assertEqual(mdl.timestampEof, cs.timestampEof) - self.assertEqual(mdl.frameAge, 0) - self.assertEqual(mdl.frameDropPerc, 0) + assert mdl.frameId == n + assert mdl.frameIdExtra == n + assert mdl.timestampEof == cs.timestampEof + assert mdl.frameAge == 0 + assert mdl.frameDropPerc == 0 odo = self.sm['cameraOdometry'] - self.assertEqual(odo.frameId, n) - self.assertEqual(odo.timestampEof, cs.timestampEof) + assert odo.frameId == n + assert odo.timestampEof == cs.timestampEof def test_dropped_frames(self): """ @@ -94,13 +93,9 @@ class TestModeld(unittest.TestCase): mdl = self.sm['modelV2'] odo = self.sm['cameraOdometry'] - self.assertEqual(mdl.frameId, frame_id) - self.assertEqual(mdl.frameIdExtra, frame_id) - self.assertEqual(odo.frameId, frame_id) + assert mdl.frameId == frame_id + assert mdl.frameIdExtra == frame_id + assert odo.frameId == frame_id if n != frame_id: - self.assertFalse(self.sm.updated['modelV2']) - self.assertFalse(self.sm.updated['cameraOdometry']) - - -if __name__ == "__main__": - unittest.main() + assert not self.sm.updated['modelV2'] + assert not self.sm.updated['cameraOdometry'] diff --git a/selfdrive/modeld/tests/timing/benchmark.py b/selfdrive/modeld/tests/timing/benchmark.py index 4ab0afacbc..c629ec2fff 100755 --- a/selfdrive/modeld/tests/timing/benchmark.py +++ b/selfdrive/modeld/tests/timing/benchmark.py @@ -6,7 +6,7 @@ import time import numpy as np import cereal.messaging as messaging -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes N = int(os.getenv("N", "5")) diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 579e79093f..80af7b71d0 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -2,11 +2,9 @@ import gc import cereal.messaging as messaging -from cereal import car from openpilot.common.params import Params from openpilot.common.realtime import set_realtime_priority -from openpilot.selfdrive.controls.lib.events import Events -from openpilot.selfdrive.monitoring.driver_monitor import DriverStatus +from openpilot.selfdrive.monitoring.helpers import DriverMonitoring def dmonitoringd_thread(): @@ -17,70 +15,32 @@ def dmonitoringd_thread(): pm = messaging.PubMaster(['driverMonitoringState']) sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'controlsState', 'modelV2'], poll='driverStateV2') - driver_status = DriverStatus(rhd_saved=params.get_bool("IsRhdDetected")) + DM = DriverMonitoring(rhd_saved=params.get_bool("IsRhdDetected"), always_on=params.get_bool("AlwaysOnDM")) - v_cruise_last = 0 - driver_engaged = False - - # 10Hz <- dmonitoringmodeld + # 20Hz <- dmonitoringmodeld while True: sm.update() if not sm.updated['driverStateV2']: + # iterate when model has new output continue - # Get interaction - if sm.updated['carState']: - v_cruise = sm['carState'].cruiseState.speed - driver_engaged = len(sm['carState'].buttonEvents) > 0 or \ - v_cruise != v_cruise_last or \ - sm['carState'].steeringPressed or \ - sm['carState'].gasPressed - v_cruise_last = v_cruise + valid = sm.all_checks() + if valid: + DM.run_step(sm) - if sm.updated['modelV2']: - driver_status.set_policy(sm['modelV2'], sm['carState'].vEgo) - - # Get data from dmonitoringmodeld - events = Events() - - 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 - if driver_status.terminal_alert_cnt >= driver_status.settings._MAX_TERMINAL_ALERTS or \ - driver_status.terminal_time >= driver_status.settings._MAX_TERMINAL_DURATION: - events.add(car.CarEvent.EventName.tooDistracted) - - # Update events from driver state - driver_status.update_events(events, driver_engaged, sm['controlsState'].enabled, sm['carState'].standstill) - - # build driverMonitoringState packet - dat = messaging.new_message('driverMonitoringState', valid=sm.all_checks()) - dat.driverMonitoringState = { - "events": events.to_msg(), - "faceDetected": driver_status.face_detected, - "isDistracted": driver_status.driver_distracted, - "distractedType": sum(driver_status.distracted_types), - "awarenessStatus": driver_status.awareness, - "posePitchOffset": driver_status.pose.pitch_offseter.filtered_stat.mean(), - "posePitchValidCount": driver_status.pose.pitch_offseter.filtered_stat.n, - "poseYawOffset": driver_status.pose.yaw_offseter.filtered_stat.mean(), - "poseYawValidCount": driver_status.pose.yaw_offseter.filtered_stat.n, - "stepChange": driver_status.step_change, - "awarenessActive": driver_status.awareness_active, - "awarenessPassive": driver_status.awareness_passive, - "isLowStd": driver_status.pose.low_std, - "hiStdCount": driver_status.hi_stds, - "isActiveMode": driver_status.active_monitoring_mode, - "isRHD": driver_status.wheel_on_right, - } + # publish + dat = DM.get_state_packet(valid=valid) pm.send('driverMonitoringState', dat) + # load live always-on toggle + if sm['driverStateV2'].frameId % 40 == 1: + DM.always_on = params.get_bool("AlwaysOnDM") + # save rhd virtual toggle every 5 mins if (sm['driverStateV2'].frameId % 6000 == 0 and - driver_status.wheelpos_learner.filtered_stat.n > driver_status.settings._WHEELPOS_FILTER_MIN_COUNT and - driver_status.wheel_on_right == (driver_status.wheelpos_learner.filtered_stat.M > driver_status.settings._WHEELPOS_THRESHOLD)): - params.put_bool_nonblocking("IsRhdDetected", driver_status.wheel_on_right) + DM.wheelpos_learner.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and + DM.wheel_on_right == (DM.wheelpos_learner.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)): + params.put_bool_nonblocking("IsRhdDetected", DM.wheel_on_right) def main(): dmonitoringd_thread() diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/helpers.py similarity index 76% rename from selfdrive/monitoring/driver_monitor.py rename to selfdrive/monitoring/helpers.py index 2279002f35..dfb35182b2 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/helpers.py @@ -1,11 +1,13 @@ from math import atan2 from cereal import car +import cereal.messaging as messaging +from openpilot.selfdrive.controls.lib.events import Events from openpilot.common.numpy_fast import interp from openpilot.common.realtime import DT_DMON from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.stat_live import RunningStatFilter -from openpilot.common.transformations.camera import tici_d_frame_size +from openpilot.common.transformations.camera import DEVICE_CAMERAS EventName = car.CarEvent.EventName @@ -15,7 +17,7 @@ EventName = car.CarEvent.EventName # We recommend that you do not change these numbers from the defaults. # ****************************************************************************************** -class DRIVER_MONITOR_SETTINGS(): +class DRIVER_MONITOR_SETTINGS: def __init__(self): self._DT_DMON = DT_DMON # ref (page15-16): https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:42018X1947&rid=2 @@ -31,8 +33,8 @@ class DRIVER_MONITOR_SETTINGS(): self._SG_THRESHOLD = 0.9 self._BLINK_THRESHOLD = 0.865 - self._EE_THRESH11 = 0.241 - self._EE_THRESH12 = 4.7 + self._EE_THRESH11 = 0.25 + self._EE_THRESH12 = 7.5 self._EE_MAX_OFFSET1 = 0.06 self._EE_MIN_OFFSET1 = 0.025 self._EE_THRESH21 = 0.01 @@ -55,6 +57,7 @@ class DRIVER_MONITOR_SETTINGS(): self._POSESTD_THRESHOLD = 0.3 self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz + self._ALWAYS_ON_ALERT_MIN_SPEED = 7 self._POSE_CALIB_MIN_SPEED = 13 # 30 mph self._POSE_OFFSET_MIN_COUNT = int(60 / self._DT_DMON) # valid data counts before calibration completes, 1min cumulative @@ -70,16 +73,37 @@ class DRIVER_MONITOR_SETTINGS(): self._MAX_TERMINAL_ALERTS = 3 # not allowed to engage after 3 terminal alerts self._MAX_TERMINAL_DURATION = int(30 / self._DT_DMON) # not allowed to engage after 30s of terminal alerts +class DistractedType: + NOT_DISTRACTED = 0 + DISTRACTED_POSE = 1 << 0 + DISTRACTED_BLINK = 1 << 1 + DISTRACTED_E2E = 1 << 2 + +class DriverPose: + def __init__(self, max_trackable): + self.yaw = 0. + self.pitch = 0. + self.roll = 0. + self.yaw_std = 0. + self.pitch_std = 0. + self.roll_std = 0. + self.pitch_offseter = RunningStatFilter(max_trackable=max_trackable) + self.yaw_offseter = RunningStatFilter(max_trackable=max_trackable) + self.calibrated = False + self.low_std = True + self.cfactor_pitch = 1. + self.cfactor_yaw = 1. + +class DriverBlink: + def __init__(self): + self.left = 0. + self.right = 0. + # model output refers to center of undistorted+leveled image EFL = 598.0 # focal length in K -W, H = tici_d_frame_size # corrected image has same size as raw - -class DistractedType: - NOT_DISTRACTED = 0 - DISTRACTED_POSE = 1 - DISTRACTED_BLINK = 2 - DISTRACTED_E2E = 4 +cam = DEVICE_CAMERAS[("tici", "ar0231")] # corrected image has same size as raw +W, H = (cam.dcam.width, cam.dcam.height) # corrected image has same size as raw def face_orientation_from_net(angles_desc, pos_desc, rpy_calib): # the output of these angles are in device frame @@ -99,27 +123,9 @@ def face_orientation_from_net(angles_desc, pos_desc, rpy_calib): yaw -= rpy_calib[2] return roll_net, pitch, yaw -class DriverPose(): - def __init__(self, max_trackable): - self.yaw = 0. - self.pitch = 0. - self.roll = 0. - self.yaw_std = 0. - self.pitch_std = 0. - self.roll_std = 0. - self.pitch_offseter = RunningStatFilter(max_trackable=max_trackable) - self.yaw_offseter = RunningStatFilter(max_trackable=max_trackable) - self.low_std = True - self.cfactor_pitch = 1. - self.cfactor_yaw = 1. -class DriverBlink(): - def __init__(self): - self.left_blink = 0. - self.right_blink = 0. - -class DriverStatus(): - def __init__(self, rhd_saved=False, settings=None): +class DriverMonitoring: + def __init__(self, rhd_saved=False, settings=None, always_on=False): if settings is None: settings = DRIVER_MONITOR_SETTINGS() # init policy settings @@ -128,7 +134,6 @@ class DriverStatus(): # init driver status self.wheelpos_learner = RunningStatFilter() self.pose = DriverPose(self.settings._POSE_OFFSET_MAX_COUNT) - self.pose_calibrated = False self.blink = DriverBlink() self.eev1 = 0. self.eev2 = 1. @@ -137,9 +142,7 @@ class DriverStatus(): self.ee1_calibrated = False self.ee2_calibrated = False - self.awareness = 1. - self.awareness_active = 1. - self.awareness_passive = 1. + self.always_on = always_on self.distracted_types = [] self.driver_distracted = False self.driver_distraction_filter = FirstOrderFilter(0., self.settings._DISTRACTED_FILTER_TS, self.settings._DT_DMON) @@ -156,13 +159,18 @@ class DriverStatus(): self.threshold_pre = self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME self.threshold_prompt = self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME + self._reset_awareness() self._set_timers(active_monitoring=True) + self._reset_events() def _reset_awareness(self): self.awareness = 1. self.awareness_active = 1. self.awareness_passive = 1. + def _reset_events(self): + self.current_events = Events() + def _set_timers(self, active_monitoring): if self.active_monitoring_mode and self.awareness <= self.threshold_prompt: if active_monitoring: @@ -193,41 +201,7 @@ class DriverStatus(): self.step_change = self.settings._DT_DMON / self.settings._AWARENESS_TIME self.active_monitoring_mode = False - def _get_distracted_types(self): - distracted_types = [] - - if not self.pose_calibrated: - pitch_error = self.pose.pitch - self.settings._PITCH_NATURAL_OFFSET - yaw_error = self.pose.yaw - self.settings._YAW_NATURAL_OFFSET - else: - pitch_error = self.pose.pitch - min(max(self.pose.pitch_offseter.filtered_stat.mean(), - self.settings._PITCH_MIN_OFFSET), self.settings._PITCH_MAX_OFFSET) - yaw_error = self.pose.yaw - min(max(self.pose.yaw_offseter.filtered_stat.mean(), - self.settings._YAW_MIN_OFFSET), self.settings._YAW_MAX_OFFSET) - pitch_error = 0 if pitch_error > 0 else abs(pitch_error) # no positive pitch limit - yaw_error = abs(yaw_error) - if pitch_error > (self.settings._POSE_PITCH_THRESHOLD*self.pose.cfactor_pitch if self.pose_calibrated else self.settings._PITCH_NATURAL_THRESHOLD) or \ - yaw_error > self.settings._POSE_YAW_THRESHOLD*self.pose.cfactor_yaw: - distracted_types.append(DistractedType.DISTRACTED_POSE) - - if (self.blink.left_blink + self.blink.right_blink)*0.5 > self.settings._BLINK_THRESHOLD: - distracted_types.append(DistractedType.DISTRACTED_BLINK) - - if self.ee1_calibrated: - ee1_dist = self.eev1 > max(min(self.ee1_offseter.filtered_stat.M, self.settings._EE_MAX_OFFSET1), self.settings._EE_MIN_OFFSET1) \ - * self.settings._EE_THRESH12 - else: - ee1_dist = self.eev1 > self.settings._EE_THRESH11 - # if self.ee2_calibrated: - # ee2_dist = self.eev2 < self.ee2_offseter.filtered_stat.M * self.settings._EE_THRESH22 - # else: - # ee2_dist = self.eev2 < self.settings._EE_THRESH21 - if ee1_dist: - distracted_types.append(DistractedType.DISTRACTED_E2E) - - return distracted_types - - def set_policy(self, model_data, car_speed): + def _set_policy(self, model_data, car_speed): bp = model_data.meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s k1 = max(-0.00156*((car_speed-16)**2)+0.6, 0.2) bp_normal = max(min(bp / k1, 0.5),0) @@ -238,7 +212,37 @@ class DriverStatus(): [self.settings._POSE_YAW_THRESHOLD_SLACK, self.settings._POSE_YAW_THRESHOLD_STRICT]) / self.settings._POSE_YAW_THRESHOLD - def update_states(self, driver_state, cal_rpy, car_speed, op_engaged): + def _get_distracted_types(self): + distracted_types = [] + + if not self.pose.calibrated: + pitch_error = self.pose.pitch - self.settings._PITCH_NATURAL_OFFSET + yaw_error = self.pose.yaw - self.settings._YAW_NATURAL_OFFSET + else: + pitch_error = self.pose.pitch - min(max(self.pose.pitch_offseter.filtered_stat.mean(), + self.settings._PITCH_MIN_OFFSET), self.settings._PITCH_MAX_OFFSET) + yaw_error = self.pose.yaw - min(max(self.pose.yaw_offseter.filtered_stat.mean(), + self.settings._YAW_MIN_OFFSET), self.settings._YAW_MAX_OFFSET) + pitch_error = 0 if pitch_error > 0 else abs(pitch_error) # no positive pitch limit + yaw_error = abs(yaw_error) + if pitch_error > (self.settings._POSE_PITCH_THRESHOLD*self.pose.cfactor_pitch if self.pose.calibrated else self.settings._PITCH_NATURAL_THRESHOLD) or \ + yaw_error > self.settings._POSE_YAW_THRESHOLD*self.pose.cfactor_yaw: + distracted_types.append(DistractedType.DISTRACTED_POSE) + + if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD: + distracted_types.append(DistractedType.DISTRACTED_BLINK) + + if self.ee1_calibrated: + ee1_dist = self.eev1 > max(min(self.ee1_offseter.filtered_stat.M, self.settings._EE_MAX_OFFSET1), self.settings._EE_MIN_OFFSET1) \ + * self.settings._EE_THRESH12 + else: + ee1_dist = self.eev1 > self.settings._EE_THRESH11 + if ee1_dist: + distracted_types.append(DistractedType.DISTRACTED_E2E) + + return distracted_types + + def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged): rhd_pred = driver_state.wheelOnRightProb # calibrates only when there's movement and either face detected if car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or @@ -266,9 +270,9 @@ class DriverStatus(): self.pose.yaw_std = driver_data.faceOrientationStd[1] model_std_max = max(self.pose.pitch_std, self.pose.yaw_std) self.pose.low_std = model_std_max < self.settings._POSESTD_THRESHOLD - self.blink.left_blink = driver_data.leftBlinkProb * (driver_data.leftEyeProb > self.settings._EYE_THRESHOLD) \ + self.blink.left = driver_data.leftBlinkProb * (driver_data.leftEyeProb > self.settings._EYE_THRESHOLD) \ * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) - self.blink.right_blink = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ + self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) self.eev1 = driver_data.notReadyProb[0] self.eev2 = driver_data.readyProb[0] @@ -287,7 +291,7 @@ class DriverStatus(): self.ee1_offseter.push_and_update(self.eev1) self.ee2_offseter.push_and_update(self.eev2) - self.pose_calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ + self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT self.ee1_calibrated = self.ee1_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT self.ee2_calibrated = self.ee2_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT @@ -299,8 +303,19 @@ class DriverStatus(): elif self.face_detected and self.pose.low_std: self.hi_stds = 0 - def update_events(self, events, driver_engaged, ctrl_active, standstill): - if (driver_engaged and self.awareness > 0 and not self.active_monitoring_mode) or not ctrl_active: # reset only when on disengagement if red reached + def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear, car_speed): + self._reset_events() + # Block engaging after max number of distrations or when alert active + if self.terminal_alert_cnt >= self.settings._MAX_TERMINAL_ALERTS or \ + self.terminal_time >= self.settings._MAX_TERMINAL_DURATION or \ + self.always_on and self.awareness <= self.threshold_prompt: + self.current_events.add(EventName.tooDistracted) + + always_on_valid = self.always_on and not wrong_gear + if (driver_engaged and self.awareness > 0 and not self.active_monitoring_mode) or \ + (not always_on_valid and not op_engaged) or \ + (always_on_valid and not op_engaged and self.awareness <= 0): + # always reset on disengage with normal mode; disengage resets only on red if always on self._reset_awareness() return @@ -320,12 +335,19 @@ class DriverStatus(): if self.awareness > self.threshold_prompt: return - standstill_exemption = standstill and self.awareness - self.step_change <= self.threshold_prompt + _reaching_audible = self.awareness - self.step_change <= self.threshold_prompt + _reaching_terminal = self.awareness - self.step_change <= 0 + standstill_exemption = standstill and _reaching_audible + always_on_red_exemption = always_on_valid and not op_engaged and _reaching_terminal + always_on_lowspeed_exemption = always_on_valid and not op_engaged and car_speed < self.settings._ALWAYS_ON_ALERT_MIN_SPEED and _reaching_audible + certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected maybe_distracted = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME or not self.face_detected + if certainly_distracted or maybe_distracted: - # should always be counting if distracted unless at standstill and reaching orange - if not standstill_exemption: + # should always be counting if distracted unless at standstill (lowspeed for always-on) and reaching orange + # also will not be reaching 0 if DM is active when not engaged + if not (standstill_exemption or always_on_red_exemption or always_on_lowspeed_exemption): self.awareness = max(self.awareness - self.step_change, -0.1) alert = None @@ -343,4 +365,52 @@ class DriverStatus(): alert = EventName.preDriverDistracted if self.active_monitoring_mode else EventName.preDriverUnresponsive if alert is not None: - events.add(alert) + self.current_events.add(alert) + + + def get_state_packet(self, valid=True): + # build driverMonitoringState packet + dat = messaging.new_message('driverMonitoringState', valid=valid) + dat.driverMonitoringState = { + "events": self.current_events.to_msg(), + "faceDetected": self.face_detected, + "isDistracted": self.driver_distracted, + "distractedType": sum(self.distracted_types), + "awarenessStatus": self.awareness, + "posePitchOffset": self.pose.pitch_offseter.filtered_stat.mean(), + "posePitchValidCount": self.pose.pitch_offseter.filtered_stat.n, + "poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(), + "poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n, + "stepChange": self.step_change, + "awarenessActive": self.awareness_active, + "awarenessPassive": self.awareness_passive, + "isLowStd": self.pose.low_std, + "hiStdCount": self.hi_stds, + "isActiveMode": self.active_monitoring_mode, + "isRHD": self.wheel_on_right, + } + return dat + + def run_step(self, sm): + # Set strictness + self._set_policy( + model_data=sm['modelV2'], + car_speed=sm['carState'].vEgo + ) + + # Parse data from dmonitoringmodeld + self._update_states( + driver_state=sm['driverStateV2'], + cal_rpy=sm['liveCalibration'].rpyCalib, + car_speed=sm['carState'].vEgo, + op_engaged=sm['controlsState'].enabled + ) + + # Update distraction events + self._update_events( + driver_engaged=sm['carState'].steeringPressed or sm['carState'].gasPressed, + op_engaged=sm['controlsState'].enabled, + standstill=sm['carState'].standstill, + wrong_gear=sm['carState'].gearShifter in [car.CarState.GearShifter.reverse, car.CarState.GearShifter.park], + car_speed=sm['carState'].vEgo + ) diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py old mode 100755 new mode 100644 index c02d44849f..f750437dc3 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -1,11 +1,8 @@ -#!/usr/bin/env python3 -import unittest import numpy as np from cereal import car, log from openpilot.common.realtime import DT_DMON -from openpilot.selfdrive.controls.lib.events import Events -from openpilot.selfdrive.monitoring.driver_monitor import DriverStatus, DRIVER_MONITOR_SETTINGS +from openpilot.selfdrive.monitoring.helpers import DriverMonitoring, DRIVER_MONITOR_SETTINGS EventName = car.CarEvent.EventName dm_settings = DRIVER_MONITOR_SETTINGS() @@ -52,24 +49,22 @@ always_distracted = [msg_DISTRACTED] * int(TEST_TIMESPAN / DT_DMON) always_true = [True] * int(TEST_TIMESPAN / DT_DMON) always_false = [False] * int(TEST_TIMESPAN / DT_DMON) -# TODO: this only tests DriverStatus -class TestMonitoring(unittest.TestCase): +class TestMonitoring: def _run_seq(self, msgs, interaction, engaged, standstill): - DS = DriverStatus() + DM = DriverMonitoring() events = [] for idx in range(len(msgs)): - e = Events() - DS.update_states(msgs[idx], [0, 0, 0], 0, engaged[idx]) + DM._update_states(msgs[idx], [0, 0, 0], 0, engaged[idx]) # cal_rpy and car_speed don't matter here # evaluate events at 10Hz for tests - DS.update_events(e, interaction[idx], engaged[idx], standstill[idx]) - events.append(e) + DM._update_events(interaction[idx], engaged[idx], standstill[idx], 0, 0) + events.append(DM.current_events) assert len(events) == len(msgs), f"got {len(events)} for {len(msgs)} driverState input msgs" - return events, DS + return events, DM def _assert_no_events(self, events): - self.assertTrue(all(not len(e) for e in events)) + assert all(not len(e) for e in events) # engaged, driver is attentive all the time def test_fully_aware_driver(self): @@ -79,27 +74,27 @@ class TestMonitoring(unittest.TestCase): # engaged, driver is distracted and does nothing def test_fully_distracted_driver(self): events, d_status = self._run_seq(always_distracted, always_false, always_true, always_false) - self.assertEqual(len(events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]), 0) - self.assertEqual(events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL + - ((d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0], - EventName.preDriverDistracted) - self.assertEqual(events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL + - ((d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0], EventName.promptDriverDistracted) - self.assertEqual(events[int((d_status.settings._DISTRACTED_TIME + - ((TEST_TIMESPAN-10-d_status.settings._DISTRACTED_TIME)/2))/DT_DMON)].names[0], EventName.driverDistracted) - self.assertIs(type(d_status.awareness), float) + assert len(events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]) == 0 + assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL + \ + ((d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == \ + EventName.preDriverDistracted + assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL + \ + ((d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.promptDriverDistracted + assert events[int((d_status.settings._DISTRACTED_TIME + \ + ((TEST_TIMESPAN-10-d_status.settings._DISTRACTED_TIME)/2))/DT_DMON)].names[0] == EventName.driverDistracted + assert isinstance(d_status.awareness, float) # engaged, no face detected the whole time, no action def test_fully_invisible_driver(self): events, d_status = self._run_seq(always_no_face, always_false, always_true, always_false) - self.assertTrue(len(events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]) == 0) - self.assertEqual(events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL + - ((d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0], - EventName.preDriverUnresponsive) - self.assertEqual(events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL + - ((d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0], EventName.promptDriverUnresponsive) - self.assertEqual(events[int((d_status.settings._AWARENESS_TIME + - ((TEST_TIMESPAN-10-d_status.settings._AWARENESS_TIME)/2))/DT_DMON)].names[0], EventName.driverUnresponsive) + assert len(events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]) == 0 + assert events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL + \ + ((d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == \ + EventName.preDriverUnresponsive + assert events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL + \ + ((d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.promptDriverUnresponsive + assert events[int((d_status.settings._AWARENESS_TIME + \ + ((TEST_TIMESPAN-10-d_status.settings._AWARENESS_TIME)/2))/DT_DMON)].names[0] == EventName.driverUnresponsive # engaged, down to orange, driver pays attention, back to normal; then down to orange, driver touches wheel # - should have short orange recovery time and no green afterwards; wheel touch only recovers when paying attention @@ -111,12 +106,12 @@ class TestMonitoring(unittest.TestCase): interaction_vector = [car_interaction_NOT_DETECTED] * int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON) + \ [car_interaction_DETECTED] * (int(TEST_TIMESPAN/DT_DMON)-int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON)) events, _ = self._run_seq(ds_vector, interaction_vector, always_true, always_false) - self.assertEqual(len(events[int(DISTRACTED_SECONDS_TO_ORANGE*0.5/DT_DMON)]), 0) - self.assertEqual(events[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0], EventName.promptDriverDistracted) - self.assertEqual(len(events[int(DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)]), 0) - self.assertEqual(events[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)].names[0], EventName.promptDriverDistracted) - self.assertEqual(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)].names[0], EventName.promptDriverDistracted) - self.assertEqual(len(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+2.5)/DT_DMON)]), 0) + assert len(events[int(DISTRACTED_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 + assert events[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.promptDriverDistracted + assert len(events[int(DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)]) == 0 + assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)].names[0] == EventName.promptDriverDistracted + assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)].names[0] == EventName.promptDriverDistracted + assert len(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+2.5)/DT_DMON)]) == 0 # engaged, down to orange, driver dodges camera, then comes back still distracted, down to red, \ # driver dodges, and then touches wheel to no avail, disengages and reengages @@ -135,10 +130,10 @@ class TestMonitoring(unittest.TestCase): op_vector[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+2.5)/DT_DMON):int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3)/DT_DMON)] \ = [False] * int(0.5/DT_DMON) events, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) - self.assertEqual(events[int((DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)].names[0], EventName.promptDriverDistracted) - self.assertEqual(events[int((DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)].names[0], EventName.driverDistracted) - self.assertEqual(events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)].names[0], EventName.driverDistracted) - self.assertTrue(len(events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3.5)/DT_DMON)]) == 0) + assert events[int((DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)].names[0] == EventName.promptDriverDistracted + assert events[int((DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)].names[0] == EventName.driverDistracted + assert events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)].names[0] == EventName.driverDistracted + assert len(events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3.5)/DT_DMON)]) == 0 # engaged, invisible driver, down to orange, driver touches wheel; then down to orange again, driver appears # - both actions should clear the alert, but momentary appearance should not @@ -150,15 +145,15 @@ class TestMonitoring(unittest.TestCase): [msg_ATTENTIVE] * int(_visible_time/DT_DMON) interaction_vector[int((INVISIBLE_SECONDS_TO_ORANGE)/DT_DMON):int((INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON)] = [True] * int(1/DT_DMON) events, _ = self._run_seq(ds_vector, interaction_vector, 2*always_true, 2*always_false) - self.assertTrue(len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0) - self.assertEqual(events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0], EventName.promptDriverUnresponsive) - self.assertTrue(len(events[int((INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)]) == 0) + assert len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 + assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive + assert len(events[int((INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)]) == 0 if _visible_time == 0.5: - self.assertEqual(events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0], EventName.promptDriverUnresponsive) - self.assertEqual(events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)].names[0], EventName.preDriverUnresponsive) + assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive + assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)].names[0] == EventName.preDriverUnresponsive elif _visible_time == 10: - self.assertEqual(events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0], EventName.promptDriverUnresponsive) - self.assertTrue(len(events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)]) == 0) + assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive + assert len(events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)]) == 0 # engaged, invisible driver, down to red, driver appears and then touches wheel, then disengages/reengages # - only disengage will clear the alert @@ -171,18 +166,18 @@ class TestMonitoring(unittest.TestCase): interaction_vector[int((INVISIBLE_SECONDS_TO_RED+_visible_time)/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON)] = [True] * int(1/DT_DMON) op_vector[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)] = [False] * int(0.5/DT_DMON) events, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) - self.assertTrue(len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0) - self.assertEqual(events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0], EventName.promptDriverUnresponsive) - self.assertEqual(events[int((INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)].names[0], EventName.driverUnresponsive) - self.assertEqual(events[int((INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)].names[0], EventName.driverUnresponsive) - self.assertEqual(events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)].names[0], EventName.driverUnresponsive) - self.assertTrue(len(events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1+0.1)/DT_DMON)]) == 0) + assert len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 + assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive + assert events[int((INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive + assert events[int((INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)].names[0] == EventName.driverUnresponsive + assert events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)].names[0] == EventName.driverUnresponsive + assert len(events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1+0.1)/DT_DMON)]) == 0 # disengaged, always distracted driver # - dm should stay quiet when not engaged def test_pure_dashcam_user(self): events, _ = self._run_seq(always_distracted, always_false, always_false, always_false) - self.assertTrue(sum(len(event) for event in events) == 0) + assert sum(len(event) for event in events) == 0 # engaged, car stops at traffic light, down to orange, no action, then car starts moving # - should only reach green when stopped, but continues counting down on launch @@ -191,10 +186,10 @@ class TestMonitoring(unittest.TestCase): standstill_vector = always_true[:] standstill_vector[int(_redlight_time/DT_DMON):] = [False] * int((TEST_TIMESPAN-_redlight_time)/DT_DMON) events, d_status = self._run_seq(always_distracted, always_false, always_true, standstill_vector) - self.assertEqual(events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL+1)/DT_DMON)].names[0], - EventName.preDriverDistracted) - self.assertEqual(events[int((_redlight_time-0.1)/DT_DMON)].names[0], EventName.preDriverDistracted) - self.assertEqual(events[int((_redlight_time+0.5)/DT_DMON)].names[0], EventName.promptDriverDistracted) + assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL+1)/DT_DMON)].names[0] == \ + EventName.preDriverDistracted + assert events[int((_redlight_time-0.1)/DT_DMON)].names[0] == EventName.preDriverDistracted + assert events[int((_redlight_time+0.5)/DT_DMON)].names[0] == EventName.promptDriverDistracted # engaged, model is somehow uncertain and driver is distracted # - should fall back to wheel touch after uncertain alert @@ -202,13 +197,10 @@ class TestMonitoring(unittest.TestCase): ds_vector = [msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN] * int(TEST_TIMESPAN/DT_DMON) interaction_vector = always_false[:] events, d_status = self._run_seq(ds_vector, interaction_vector, always_true, always_false) - self.assertTrue(EventName.preDriverUnresponsive in - events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME-0.1)/DT_DMON)].names) - self.assertTrue(EventName.promptDriverUnresponsive in - events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names) - self.assertTrue(EventName.driverUnresponsive in - events[int((INVISIBLE_SECONDS_TO_RED-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names) + assert EventName.preDriverUnresponsive in \ + events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME-0.1)/DT_DMON)].names + assert EventName.promptDriverUnresponsive in \ + events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names + assert EventName.driverUnresponsive in \ + events[int((INVISIBLE_SECONDS_TO_RED-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/navd/README.md b/selfdrive/navd/README.md index 3047b7f8eb..81de8ae39f 100644 --- a/selfdrive/navd/README.md +++ b/selfdrive/navd/README.md @@ -8,7 +8,7 @@ This directory contains two daemons, `navd` and `mapsd`, which support navigatio ### map renderer -The map renderer listens for the `navRoute` and publishes a rendered map view over VisionIPC for the navigation model, which lives in `selfdrive/modeld/`. The rendered maps look like this: +The map renderer listens for the `navRoute` and publishes a simplified rendered map view over VisionIPC. The rendered maps look like this: ![](https://i.imgur.com/oZLfmwq.png) diff --git a/selfdrive/navd/SConscript b/selfdrive/navd/SConscript index 5a173c0351..295e8127db 100644 --- a/selfdrive/navd/SConscript +++ b/selfdrive/navd/SConscript @@ -1,8 +1,8 @@ -Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'cereal', 'transformations') +Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') map_env = qt_env.Clone() -libs = ['qt_widgets', 'qt_util', 'QMapLibre', common, messaging, cereal, visionipc, transformations, - 'zmq', 'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread', 'json11'] + map_env["LIBS"] +libs = ['qt_widgets', 'qt_util', 'QMapLibre', common, messaging, visionipc, transformations, + 'm', 'OpenCL', 'ssl', 'crypto', 'pthread', 'json11'] + map_env["LIBS"] if arch == 'larch64': libs.append(':libEGL_mesa.so.0') @@ -17,4 +17,4 @@ if arch in ['larch64', 'aarch64', 'x86_64']: map_env["RPATH"].append(Dir('.').abspath) map_env["LIBPATH"].append(Dir('.').abspath) maplib = map_env.SharedLibrary("maprender", ["map_renderer.cc"], LIBS=libs) - map_env.Program("mapsd", ["main.cc", ], LIBS=[maplib[0].get_path(), ] + libs) + # map_env.Program("mapsd", ["main.cc", ], LIBS=[maplib[0].get_path(), ] + libs) diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index d52ee162bd..1e57ad3e7c 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -41,6 +41,8 @@ MapRenderer::MapRenderer(const QMapLibre::Settings &settings, bool online) : m_s QSurfaceFormat fmt; fmt.setRenderableType(QSurfaceFormat::OpenGLES); + m_settings.setMapMode(QMapLibre::Settings::MapMode::Static); + ctx = std::make_unique(); ctx->setFormat(fmt); ctx->create(); @@ -87,6 +89,18 @@ MapRenderer::MapRenderer(const QMapLibre::Settings &settings, bool online) : m_s LOGE("Map loading failed with %d: '%s'\n", err_code, reason.toStdString().c_str()); }); + QObject::connect(m_map.data(), &QMapLibre::Map::staticRenderFinished, [=](const QString &error) { + rendering = false; + + if (!error.isEmpty()) { + LOGE("Static map rendering failed with error: '%s'\n", error.toStdString().c_str()); + } else if (vipc_server != nullptr) { + double end_render_t = millis_since_boot(); + publish((end_render_t - start_render_t) / 1000.0, true); + last_llk_rendered = (*sm)["liveLocationKalman"].getLogMonoTime(); + } + }); + if (online) { vipc_server.reset(new VisionIpcServer("navd")); vipc_server->create_buffers(VisionStreamType::VISION_STREAM_MAP, NUM_VIPC_BUFFERS, false, WIDTH, HEIGHT); @@ -114,22 +128,16 @@ void MapRenderer::msgUpdate() { float bearing = RAD2DEG(orientation.getValue()[2]); updatePosition(get_point_along_line(pos.getValue()[0], pos.getValue()[1], bearing, MAP_OFFSET), bearing); - // TODO: use the static rendering mode instead - // retry render a few times - for (int i = 0; i < 5 && !rendered(); i++) { - QApplication::processEvents(QEventLoop::AllEvents, 100); + if (!rendering) { update(); - if (rendered()) { - LOGW("rendered after %d retries", i+1); - break; - } } - // fallback to sending a blank frame if (!rendered()) { publish(0, false); } } + + } if (sm->updated("navRoute")) { @@ -157,7 +165,9 @@ void MapRenderer::updatePosition(QMapLibre::Coordinate position, float bearing) m_map->setCoordinate(position); m_map->setBearing(bearing); m_map->setZoom(zoom); - update(); + if (!rendering) { + update(); + } } bool MapRenderer::loaded() { @@ -165,16 +175,10 @@ bool MapRenderer::loaded() { } void MapRenderer::update() { - double start_t = millis_since_boot(); + rendering = true; gl_functions->glClear(GL_COLOR_BUFFER_BIT); - m_map->render(); - gl_functions->glFlush(); - double end_t = millis_since_boot(); - - if ((vipc_server != nullptr) && loaded()) { - publish((end_t - start_t) / 1000.0, true); - last_llk_rendered = (*sm)["liveLocationKalman"].getLogMonoTime(); - } + start_render_t = millis_since_boot(); + m_map->startStaticRender(); } void MapRenderer::sendThumbnail(const uint64_t ts, const kj::Array &buf) { diff --git a/selfdrive/navd/map_renderer.h b/selfdrive/navd/map_renderer.h index fd5922b668..956c1d54bc 100644 --- a/selfdrive/navd/map_renderer.h +++ b/selfdrive/navd/map_renderer.h @@ -12,7 +12,7 @@ #include #include -#include "cereal/visionipc/visionipc_server.h" +#include "msgq/visionipc/visionipc_server.h" #include "cereal/messaging/messaging.h" @@ -43,8 +43,10 @@ private: void initLayers(); + double start_render_t; uint32_t frame_id = 0; uint64_t last_llk_rendered = 0; + bool rendering = false; bool rendered() { return last_llk_rendered == (*sm)["liveLocationKalman"].getLogMonoTime(); } diff --git a/selfdrive/navd/map_renderer.py b/selfdrive/navd/map_renderer.py index 8d525ac73e..e44b883436 100755 --- a/selfdrive/navd/map_renderer.py +++ b/selfdrive/navd/map_renderer.py @@ -31,7 +31,8 @@ void map_renderer_free_image(void *inst, uint8_t *buf); return ffi, ffi.dlopen(lib) -def wait_ready(lib, renderer): +def wait_ready(lib, renderer, timeout=None): + st = time.time() while not lib.map_renderer_loaded(renderer): lib.map_renderer_update(renderer) @@ -40,6 +41,9 @@ def wait_ready(lib, renderer): time.sleep(0.01) + if timeout is not None and time.time() - st > timeout: + raise TimeoutError("Timeout waiting for map renderer to be ready") + def get_image(lib, renderer): buf = lib.map_renderer_get_image(renderer) diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index 2be703cdb2..8cfc495f27 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -48,15 +48,14 @@ class RouteEngine: self.reroute_counter = 0 + + self.api = None + self.mapbox_token = None if "MAPBOX_TOKEN" in os.environ: self.mapbox_token = os.environ["MAPBOX_TOKEN"] self.mapbox_host = "https://api.mapbox.com" else: - try: - self.mapbox_token = Api(self.params.get("DongleId", encoding='utf8')).get_token(expiry_hours=4 * 7 * 24) - except FileNotFoundError: - cloudlog.exception("Failed to generate mapbox token due to missing private key. Ensure device is registered.") - self.mapbox_token = "" + self.api = Api(self.params.get("DongleId", encoding='utf8')) self.mapbox_host = "https://maps.comma.ai" def update(self): @@ -122,8 +121,12 @@ class RouteEngine: if lang is not None: lang = lang.replace('main_', '') + token = self.mapbox_token + if token is None: + token = self.api.get_token() + params = { - 'access_token': self.mapbox_token, + 'access_token': token, 'annotations': 'maxspeed', 'geometries': 'geojson', 'overview': 'full', diff --git a/selfdrive/navd/tests/test_map_renderer.py b/selfdrive/navd/tests/test_map_renderer.py old mode 100755 new mode 100644 index b5f186dbb0..04363883b2 --- a/selfdrive/navd/tests/test_map_renderer.py +++ b/selfdrive/navd/tests/test_map_renderer.py @@ -1,16 +1,14 @@ -#!/usr/bin/env python3 import time import numpy as np import os import pytest -import unittest import requests import threading import http.server import cereal.messaging as messaging from typing import Any -from cereal.visionipc import VisionIpcClient, VisionStreamType +from msgq.visionipc import VisionIpcClient, VisionStreamType from openpilot.common.mock.generators import LLK_DECIMATION, LOCATION1, LOCATION2, generate_liveLocationKalman from openpilot.selfdrive.test.helpers import with_processes @@ -65,11 +63,12 @@ class MapBoxInternetDisabledServer(threading.Thread): MapBoxInternetDisabledRequestHandler.INTERNET_ACTIVE = True -class TestMapRenderer(unittest.TestCase): +@pytest.mark.skip(reason="not used") +class TestMapRenderer: server: MapBoxInternetDisabledServer @classmethod - def setUpClass(cls): + def setup_class(cls): assert "MAPBOX_TOKEN" in os.environ cls.original_token = os.environ["MAPBOX_TOKEN"] cls.server = MapBoxInternetDisabledServer() @@ -77,10 +76,10 @@ class TestMapRenderer(unittest.TestCase): time.sleep(0.5) # wait for server to startup @classmethod - def tearDownClass(cls) -> None: + def teardown_class(cls) -> None: cls.server.stop() - def setUp(self): + def setup_method(self): self.server.enable_internet() os.environ['MAPS_HOST'] = f'http://localhost:{self.server.port}' @@ -135,7 +134,7 @@ class TestMapRenderer(unittest.TestCase): invalid_and_not_previously_valid = (expect_valid and not self.sm.valid['mapRenderState'] and not prev_valid) valid_and_not_previously_invalid = (not expect_valid and self.sm.valid['mapRenderState'] and prev_valid) - if (invalid_and_not_previously_valid or valid_and_not_previously_invalid) and frames_since_test_start < 5: + if (invalid_and_not_previously_valid or valid_and_not_previously_invalid) and frames_since_test_start < 20: continue # check output @@ -202,15 +201,12 @@ class TestMapRenderer(unittest.TestCase): def assert_stat(stat, nominal, tol=0.3): tol = (nominal / (1+tol)), (nominal * (1+tol)) - self.assertTrue(tol[0] < stat < tol[1], f"{stat} not in tolerance {tol}") + assert tol[0] < stat < tol[1], f"{stat} not in tolerance {tol}" assert_stat(_mean, 0.030) assert_stat(_median, 0.027) assert_stat(_stddev, 0.0078) - self.assertLess(_max, 0.065) - self.assertGreater(_min, 0.015) + assert _max < 0.065 + assert _min > 0.015 - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/navd/tests/test_navd.py b/selfdrive/navd/tests/test_navd.py old mode 100755 new mode 100644 index 61be6cc387..b6580acff1 --- a/selfdrive/navd/tests/test_navd.py +++ b/selfdrive/navd/tests/test_navd.py @@ -1,22 +1,20 @@ -#!/usr/bin/env python3 import json 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 +from openpilot.system.manager.process_config import managed_processes -class TestNavd(unittest.TestCase): - def setUp(self): +class TestNavd: + def setup_method(self): self.params = Params() self.sm = messaging.SubMaster(['navRoute', 'navInstruction']) - def tearDown(self): + def teardown_method(self): managed_processes['navd'].stop() def _check_route(self, start, end, check_coords=True): @@ -57,7 +55,3 @@ class TestNavd(unittest.TestCase): 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__": - unittest.main() diff --git a/selfdrive/pandad/.gitignore b/selfdrive/pandad/.gitignore new file mode 100644 index 0000000000..f7226cdb87 --- /dev/null +++ b/selfdrive/pandad/.gitignore @@ -0,0 +1,3 @@ +pandad +pandad_api_impl.cpp +tests/test_pandad_usbprotocol diff --git a/selfdrive/pandad/SConscript b/selfdrive/pandad/SConscript new file mode 100644 index 0000000000..63a2c1e650 --- /dev/null +++ b/selfdrive/pandad/SConscript @@ -0,0 +1,11 @@ +Import('env', 'envCython', 'common', 'messaging') + +libs = ['usb-1.0', common, messaging, 'pthread'] +panda = env.Library('panda', ['panda.cc', 'panda_comms.cc', 'spi.cc']) + +env.Program('pandad', ['main.cc', 'pandad.cc'], LIBS=[panda] + libs) +env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) + +envCython.Program('pandad_api_impl.so', 'pandad_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) +if GetOption('extras'): + env.Program('tests/test_pandad_usbprotocol', ['tests/test_pandad_usbprotocol.cc'], LIBS=[panda] + libs) diff --git a/selfdrive/boardd/boardd.py b/selfdrive/pandad/__init__.py similarity index 81% rename from selfdrive/boardd/boardd.py rename to selfdrive/pandad/__init__.py index 0cdaf5e912..8081a62dd0 100644 --- a/selfdrive/boardd/boardd.py +++ b/selfdrive/pandad/__init__.py @@ -1,5 +1,5 @@ # Cython, now uses scons to build -from openpilot.selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp +from openpilot.selfdrive.pandad.pandad_api_impl import can_list_to_can_capnp assert can_list_to_can_capnp def can_capnp_to_can_list(can, src_filter=None): diff --git a/selfdrive/boardd/can_list_to_can_capnp.cc b/selfdrive/pandad/can_list_to_can_capnp.cc similarity index 95% rename from selfdrive/boardd/can_list_to_can_capnp.cc rename to selfdrive/pandad/can_list_to_can_capnp.cc index 72ca72688a..9fc2648da2 100644 --- a/selfdrive/boardd/can_list_to_can_capnp.cc +++ b/selfdrive/pandad/can_list_to_can_capnp.cc @@ -1,5 +1,5 @@ #include "cereal/messaging/messaging.h" -#include "selfdrive/boardd/panda.h" +#include "selfdrive/pandad/panda.h" void can_list_to_can_capnp_cpp(const std::vector &can_list, std::string &out, bool sendCan, bool valid) { MessageBuilder msg; diff --git a/selfdrive/boardd/main.cc b/selfdrive/pandad/main.cc similarity index 71% rename from selfdrive/boardd/main.cc rename to selfdrive/pandad/main.cc index cb17a584bd..b63d884a45 100644 --- a/selfdrive/boardd/main.cc +++ b/selfdrive/pandad/main.cc @@ -1,22 +1,22 @@ #include -#include "selfdrive/boardd/boardd.h" +#include "selfdrive/pandad/pandad.h" #include "common/swaglog.h" #include "common/util.h" #include "system/hardware/hw.h" int main(int argc, char *argv[]) { - LOGW("starting boardd"); + LOGW("starting pandad"); if (!Hardware::PC()) { int err; err = util::set_realtime_priority(54); assert(err == 0); - err = util::set_core_affinity({4}); + err = util::set_core_affinity({3}); assert(err == 0); } std::vector serials(argv + 1, argv + argc); - boardd_main_thread(serials); + pandad_main_thread(serials); return 0; } diff --git a/selfdrive/boardd/panda.cc b/selfdrive/pandad/panda.cc similarity index 82% rename from selfdrive/boardd/panda.cc rename to selfdrive/pandad/panda.cc index e075887a4d..a404ad3880 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/pandad/panda.cc @@ -1,4 +1,4 @@ -#include "selfdrive/boardd/panda.h" +#include "selfdrive/pandad/panda.h" #include @@ -25,10 +25,6 @@ Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { } hw_type = get_hw_type(); - has_rtc = (hw_type == cereal::PandaState::PandaType::UNO) || - (hw_type == cereal::PandaState::PandaType::DOS) || - (hw_type == cereal::PandaState::PandaType::TRES); - can_reset_communications(); return; @@ -77,41 +73,6 @@ cereal::PandaState::PandaType Panda::get_hw_type() { return (cereal::PandaState::PandaType)(hw_query[0]); } -void Panda::set_rtc(struct tm sys_time) { - // tm struct has year defined as years since 1900 - handle->control_write(0xa1, (uint16_t)(1900 + sys_time.tm_year), 0); - handle->control_write(0xa2, (uint16_t)(1 + sys_time.tm_mon), 0); - handle->control_write(0xa3, (uint16_t)sys_time.tm_mday, 0); - // handle->control_write(0xa4, (uint16_t)(1 + sys_time.tm_wday), 0); - handle->control_write(0xa5, (uint16_t)sys_time.tm_hour, 0); - handle->control_write(0xa6, (uint16_t)sys_time.tm_min, 0); - handle->control_write(0xa7, (uint16_t)sys_time.tm_sec, 0); -} - -struct tm Panda::get_rtc() { - struct __attribute__((packed)) timestamp_t { - uint16_t year; // Starts at 0 - uint8_t month; - uint8_t day; - uint8_t weekday; - uint8_t hour; - uint8_t minute; - uint8_t second; - } rtc_time = {0}; - - handle->control_read(0xa0, 0, 0, (unsigned char*)&rtc_time, sizeof(rtc_time)); - - struct tm new_time = { 0 }; - new_time.tm_year = rtc_time.year - 1900; // tm struct has year defined as years since 1900 - new_time.tm_mon = rtc_time.month - 1; - new_time.tm_mday = rtc_time.day; - new_time.tm_hour = rtc_time.hour; - new_time.tm_min = rtc_time.minute; - new_time.tm_sec = rtc_time.second; - - return new_time; -} - void Panda::set_fan_speed(uint16_t fan_speed) { handle->control_write(0xb1, fan_speed, 0); } @@ -211,7 +172,7 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data for (auto cmsg : can_data_list) { // check if the message is intended for this panda uint8_t bus = cmsg.getSrc(); - if (bus < bus_offset || bus >= (bus_offset + PANDA_BUS_CNT)) { + if (bus < bus_offset || bus >= (bus_offset + PANDA_BUS_OFFSET)) { continue; } auto can_data = cmsg.getDat(); @@ -259,12 +220,18 @@ bool Panda::can_receive(std::vector& out_vec) { if (!comms_healthy()) { return false; } - if (recv == RECV_SIZE) { - LOGW("Panda receive buffer full"); - } - receive_buffer_size += recv; - return (recv <= 0) ? true : unpack_can_buffer(receive_buffer, receive_buffer_size, out_vec); + if (getenv("PANDAD_MAXOUT") != NULL) { + static uint8_t junk[RECV_SIZE]; + handle->bulk_read(0xab, junk, RECV_SIZE - recv); + } + + bool ret = true; + if (recv > 0) { + receive_buffer_size += recv; + ret = unpack_can_buffer(receive_buffer, receive_buffer_size, out_vec); + } + return ret; } void Panda::can_reset_communications() { @@ -284,6 +251,13 @@ bool Panda::unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector #include @@ -120,7 +120,7 @@ std::vector PandaUsbHandle::list() { libusb_close(handle); if (ret < 0) { goto finish; } - serials.push_back(std::string((char *)desc_serial, ret).c_str()); + serials.push_back(std::string((char *)desc_serial, ret)); } } diff --git a/selfdrive/boardd/panda_comms.h b/selfdrive/pandad/panda_comms.h similarity index 93% rename from selfdrive/boardd/panda_comms.h rename to selfdrive/pandad/panda_comms.h index e61d25402f..9c452faf6d 100644 --- a/selfdrive/boardd/panda_comms.h +++ b/selfdrive/pandad/panda_comms.h @@ -56,6 +56,13 @@ private: }; #ifndef __APPLE__ +struct __attribute__((packed)) spi_header { + uint8_t sync; + uint8_t endpoint; + uint16_t tx_len; + uint16_t max_rx_len; +}; + class PandaSpiHandle : public PandaCommsHandle { public: PandaSpiHandle(std::string serial); @@ -78,5 +85,9 @@ private: int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len, unsigned int timeout); int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout); int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout); + int lltransfer(spi_ioc_transfer &t); + + spi_header header; + uint32_t xfer_count = 0; }; #endif diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/pandad/pandad.cc similarity index 90% rename from selfdrive/boardd/boardd.cc rename to selfdrive/pandad/pandad.cc index b2b59d3752..095a7893b4 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/pandad/pandad.cc @@ -1,4 +1,4 @@ -#include "selfdrive/boardd/boardd.h" +#include "selfdrive/pandad/pandad.h" #include #include @@ -25,7 +25,7 @@ // - The internal panda will always be the first panda // - Consecutive pandas will be sorted based on panda type, and then serial number // Connecting: -// - If a panda connection is dropped, boardd will reconnect to all pandas +// - If a panda connection is dropped, pandad will reconnect to all pandas // - If a panda is added, we will only reconnect when we are offroad // CAN buses: // - Each panda will have it's block of 4 buses. E.g.: the second panda will use @@ -49,12 +49,6 @@ std::atomic ignition(false); ExitHandler do_exit; -static std::string get_time_str(const struct tm &time) { - char s[30] = {'\0'}; - std::strftime(s, std::size(s), "%Y-%m-%d %H:%M:%S", &time); - return s; -} - bool check_all_connected(const std::vector &pandas) { for (const auto& panda : pandas) { if (!panda->connected()) { @@ -65,36 +59,6 @@ bool check_all_connected(const std::vector &pandas) { return true; } -enum class SyncTimeDir { TO_PANDA, FROM_PANDA }; - -void sync_time(Panda *panda, SyncTimeDir dir) { - if (!panda->has_rtc) return; - - setenv("TZ", "UTC", 1); - struct tm sys_time = util::get_time(); - struct tm rtc_time = panda->get_rtc(); - - if (dir == SyncTimeDir::TO_PANDA) { - if (util::time_valid(sys_time)) { - // Write time to RTC if it looks reasonable - double seconds = difftime(mktime(&rtc_time), mktime(&sys_time)); - if (std::abs(seconds) > 1.1) { - panda->set_rtc(sys_time); - LOGW("Updating panda RTC. dt = %.2f System: %s RTC: %s", - seconds, get_time_str(sys_time).c_str(), get_time_str(rtc_time).c_str()); - } - } - } else if (dir == SyncTimeDir::FROM_PANDA) { - LOGW("System time: %s, RTC time: %s", get_time_str(sys_time).c_str(), get_time_str(rtc_time).c_str()); - - if (!util::time_valid(sys_time) && util::time_valid(rtc_time)) { - const struct timeval tv = {mktime(&rtc_time), 0}; - settimeofday(&tv, 0); - LOGE("System time wrong, setting from RTC."); - } - } -} - bool safety_setter_thread(std::vector pandas) { LOGD("Starting safety setter thread"); @@ -180,7 +144,7 @@ bool safety_setter_thread(std::vector pandas) { Panda *connect(std::string serial="", uint32_t index=0) { std::unique_ptr panda; try { - panda = std::make_unique(serial, (index * PANDA_BUS_CNT)); + panda = std::make_unique(serial, (index * PANDA_BUS_OFFSET)); } catch (std::exception &e) { return nullptr; } @@ -195,12 +159,11 @@ Panda *connect(std::string serial="", uint32_t index=0) { throw std::runtime_error("Panda firmware out of date. Run pandad.py to update."); } - sync_time(panda.get(), SyncTimeDir::FROM_PANDA); return panda.release(); } void can_send_thread(std::vector pandas, bool fake_send) { - util::set_thread_name("boardd_can_send"); + util::set_thread_name("pandad_can_send"); AlignedBuffer aligned_buf; std::unique_ptr context(Context::create()); @@ -235,12 +198,12 @@ void can_send_thread(std::vector pandas, bool fake_send) { } void can_recv_thread(std::vector pandas) { - util::set_thread_name("boardd_can_recv"); + util::set_thread_name("pandad_can_recv"); PubMaster pm({"can"}); // run at 100Hz - RateKeeper rk("boardd_can_recv", 100); + RateKeeper rk("pandad_can_recv", 100); std::vector raw_can_data; while (!do_exit && check_all_connected(pandas)) { @@ -353,7 +316,6 @@ std::optional send_panda_states(PubMaster *pm, const std::vector ps.setControlsAllowed(health.controls_allowed_pkt); ps.setTxBufferOverflow(health.tx_buffer_overflow_pkt); ps.setRxBufferOverflow(health.rx_buffer_overflow_pkt); - ps.setGmlanSendErrs(health.gmlan_send_errs_pkt); ps.setPandaType(panda->hw_type); ps.setSafetyModel(cereal::CarParams::SafetyModel(health.safety_mode_pkt)); ps.setSafetyParam(health.safety_param_pkt); @@ -443,7 +405,7 @@ void send_peripheral_state(PubMaster *pm, Panda *panda) { } void panda_state_thread(std::vector pandas, bool spoofing_started) { - util::set_thread_name("boardd_panda_state"); + util::set_thread_name("pandad_panda_state"); Params params; SubMaster sm({"controlsState"}); @@ -533,7 +495,7 @@ void panda_state_thread(std::vector pandas, bool spoofing_started) { void peripheral_control_thread(Panda *panda, bool no_fan_control) { - util::set_thread_name("boardd_peripheral_control"); + util::set_thread_name("pandad_peripheral_control"); SubMaster sm({"deviceState", "driverCameraState"}); @@ -581,16 +543,11 @@ void peripheral_control_thread(Panda *panda, bool no_fan_control) { panda->set_ir_pwr(ir_pwr); prev_ir_pwr = ir_pwr; } - - // Write to rtc once per minute when no ignition present - if (!ignition && (sm.frame % 120 == 1)) { - sync_time(panda, SyncTimeDir::TO_PANDA); - } } } -void boardd_main_thread(std::vector serials) { - LOGW("launching boardd"); +void pandad_main_thread(std::vector serials) { + LOGW("launching pandad"); if (serials.size() == 0) { serials = Panda::list(); diff --git a/selfdrive/boardd/boardd.h b/selfdrive/pandad/pandad.h similarity index 53% rename from selfdrive/boardd/boardd.h rename to selfdrive/pandad/pandad.h index 0646fc6189..9d35949a8f 100644 --- a/selfdrive/boardd/boardd.h +++ b/selfdrive/pandad/pandad.h @@ -3,7 +3,7 @@ #include #include -#include "selfdrive/boardd/panda.h" +#include "selfdrive/pandad/panda.h" bool safety_setter_thread(std::vector pandas); -void boardd_main_thread(std::vector serials); +void pandad_main_thread(std::vector serials); diff --git a/selfdrive/boardd/pandad.py b/selfdrive/pandad/pandad.py similarity index 83% rename from selfdrive/boardd/pandad.py rename to selfdrive/pandad/pandad.py index 988d1a2409..12accdbf5e 100755 --- a/selfdrive/boardd/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -1,16 +1,14 @@ #!/usr/bin/env python3 -# simple boardd wrapper that updates the panda first +# simple pandad wrapper that updates the panda first import os import usb1 import time import subprocess from typing import NoReturn -from functools import cmp_to_key from panda import Panda, PandaDFU, PandaProtocolMismatch, FW_PATH from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params -from openpilot.selfdrive.boardd.set_time import set_time from openpilot.system.hardware import HARDWARE from openpilot.common.swaglog import cloudlog @@ -63,24 +61,6 @@ def flash_panda(panda_serial: str) -> Panda: return panda -def panda_sort_cmp(a: Panda, b: Panda): - a_type = a.get_type() - b_type = b.get_type() - - # make sure the internal one is always first - if a.is_internal() and not b.is_internal(): - return -1 - if not a.is_internal() and b.is_internal(): - return 1 - - # sort by hardware type - if a_type != b_type: - return a_type < b_type - - # last resort: sort by serial number - return a.get_usb_serial() < b.get_usb_serial() - - def main() -> NoReturn: count = 0 first_run = True @@ -137,7 +117,10 @@ def main() -> NoReturn: no_internal_panda_count = 0 # sort pandas to have deterministic order - pandas.sort(key=cmp_to_key(panda_sort_cmp)) + # * the internal one is always first + # * then sort by hardware type + # * as a last resort, sort by serial number + pandas.sort(key=lambda x: (not x.is_internal(), x.get_type(), x.get_usb_serial())) panda_serials = [p.get_usb_serial() for p in pandas] # log panda fw versions @@ -154,16 +137,9 @@ def main() -> NoReturn: cloudlog.event("panda.som_reset_triggered", health=health, serial=panda.get_usb_serial()) if first_run: - if panda.is_internal(): - # update time from RTC - set_time(cloudlog) - # reset panda to ensure we're in a good state cloudlog.info(f"Resetting panda {panda.get_usb_serial()}") - if panda.is_internal(): - HARDWARE.reset_internal_panda() - else: - panda.reset(reconnect=False) + panda.reset(reconnect=True) for p in pandas: p.close() @@ -181,10 +157,10 @@ def main() -> NoReturn: first_run = False - # run boardd with all connected serials as arguments - os.environ['MANAGER_DAEMON'] = 'boardd' - os.chdir(os.path.join(BASEDIR, "selfdrive/boardd")) - subprocess.run(["./boardd", *panda_serials], check=True) + # run pandad with all connected serials as arguments + os.environ['MANAGER_DAEMON'] = 'pandad' + os.chdir(os.path.join(BASEDIR, "selfdrive/pandad")) + subprocess.run(["./pandad", *panda_serials], check=True) if __name__ == "__main__": main() diff --git a/selfdrive/boardd/boardd_api_impl.pyx b/selfdrive/pandad/pandad_api_impl.pyx similarity index 93% rename from selfdrive/boardd/boardd_api_impl.pyx rename to selfdrive/pandad/pandad_api_impl.pyx index 6a552bb447..cbac8cc5a3 100644 --- a/selfdrive/boardd/boardd_api_impl.pyx +++ b/selfdrive/pandad/pandad_api_impl.pyx @@ -15,16 +15,17 @@ cdef extern from "can_list_to_can_capnp.cc": void can_list_to_can_capnp_cpp(const vector[can_frame] &can_list, string &out, bool sendCan, bool valid) def can_list_to_can_capnp(can_msgs, msgtype='can', valid=True): + cdef can_frame *f cdef vector[can_frame] can_list - can_list.reserve(len(can_msgs)) - cdef can_frame f + can_list.reserve(len(can_msgs)) for can_msg in can_msgs: + f = &(can_list.emplace_back()) f.address = can_msg[0] f.busTime = can_msg[1] f.dat = can_msg[2] f.src = can_msg[3] - can_list.push_back(f) + cdef string out can_list_to_can_capnp_cpp(can_list, out, msgtype == 'sendcan', valid) return out diff --git a/selfdrive/boardd/spi.cc b/selfdrive/pandad/spi.cc similarity index 74% rename from selfdrive/boardd/spi.cc rename to selfdrive/pandad/spi.cc index d11e955c49..8f1e29689b 100644 --- a/selfdrive/boardd/spi.cc +++ b/selfdrive/pandad/spi.cc @@ -13,7 +13,7 @@ #include "common/timing.h" #include "common/swaglog.h" #include "panda/board/comms_definitions.h" -#include "selfdrive/boardd/panda_comms.h" +#include "selfdrive/pandad/panda_comms.h" #define SPI_SYNC 0x5AU @@ -28,13 +28,6 @@ enum SpiError { ACK_TIMEOUT = -3, }; -struct __attribute__((packed)) spi_header { - uint8_t sync; - uint8_t endpoint; - uint16_t tx_len; - uint16_t max_rx_len; -}; - const unsigned int SPI_ACK_TIMEOUT = 500; // milliseconds const std::string SPI_DEVICE = "/dev/spidev0.0"; @@ -55,6 +48,12 @@ private: std::recursive_mutex &m; }; +#define SPILOG(fn, fmt, ...) do { \ + fn(fmt, ## __VA_ARGS__); \ + fn(" %d / 0x%x / %d / %d / tx: %s", \ + xfer_count, header.endpoint, header.tx_len, header.max_rx_len, \ + util::hexdump(tx_buf, std::min((int)header.tx_len, 8)).c_str()); \ + } while (0) PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) { int ret; @@ -178,7 +177,7 @@ int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t t } if (d < 0) { - LOGE("SPI: bulk transfer failed with %d", d); + SPILOG(LOGE, "SPI: bulk transfer failed with %d", d); comms_healthy = false; return d; } @@ -240,6 +239,7 @@ int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint1 // due to full TX buffers nack_count += 1; if (nack_count > 3) { + SPILOG(LOGE, "NACK sleep %d", nack_count); usleep(std::clamp(nack_count*10, 200, 2000)); } } @@ -247,7 +247,7 @@ int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint1 } while (ret < 0 && connected && !timed_out); if (ret < 0) { - LOGE("transfer failed, after %d tries, %.2fms", timeout_count, millis_since_boot() - start_time); + SPILOG(LOGE, "transfer failed, after %d tries, %.2fms", timeout_count, millis_since_boot() - start_time); } return ret; @@ -258,32 +258,32 @@ int PandaSpiHandle::wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout, if (timeout == 0) { timeout = SPI_ACK_TIMEOUT; } - timeout = std::clamp(timeout, 100U, SPI_ACK_TIMEOUT); + timeout = std::clamp(timeout, 20U, SPI_ACK_TIMEOUT); spi_ioc_transfer transfer = { .tx_buf = (uint64_t)tx_buf, .rx_buf = (uint64_t)rx_buf, - .len = length + .len = length, }; - tx_buf[0] = tx; + memset(tx_buf, tx, length); while (true) { - int ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + int ret = lltransfer(transfer); if (ret < 0) { - LOGE("SPI: failed to send ACK request"); + SPILOG(LOGE, "SPI: failed to send ACK request"); return ret; } if (rx_buf[0] == ack) { break; } else if (rx_buf[0] == SPI_NACK) { - LOGD("SPI: got NACK"); + SPILOG(LOGD, "SPI: got NACK, waiting for 0x%x", ack); return SpiError::NACK; } // handle timeout if (millis_since_boot() - start_millis > timeout) { - LOGD("SPI: timed out waiting for ACK"); + SPILOG(LOGW, "SPI: timed out waiting for ACK, waiting for 0x%x", ack); return SpiError::ACK_TIMEOUT; } } @@ -291,6 +291,40 @@ int PandaSpiHandle::wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout, return 0; } +int PandaSpiHandle::lltransfer(spi_ioc_transfer &t) { + static const double err_prob = std::stod(util::getenv("SPI_ERR_PROB", "-1")); + + if (err_prob > 0) { + if ((static_cast(rand()) / RAND_MAX) < err_prob) { + printf("transfer len error\n"); + t.len = rand() % SPI_BUF_SIZE; + } + if ((static_cast(rand()) / RAND_MAX) < err_prob && t.tx_buf != (uint64_t)NULL) { + printf("corrupting TX\n"); + for (int i = 0; i < t.len; i++) { + if ((static_cast(rand()) / RAND_MAX) > 0.9) { + ((uint8_t*)t.tx_buf)[i] = (uint8_t)(rand() % 256); + } + } + } + } + + int ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &t); + + if (err_prob > 0) { + if ((static_cast(rand()) / RAND_MAX) < err_prob && t.rx_buf != (uint64_t)NULL) { + printf("corrupting RX\n"); + for (int i = 0; i < t.len; i++) { + if ((static_cast(rand()) / RAND_MAX) > 0.9) { + ((uint8_t*)t.rx_buf)[i] = (uint8_t)(rand() % 256); + } + } + } + } + + return ret; +} + int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout) { int ret; uint16_t rx_data_len; @@ -300,7 +334,8 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx assert(tx_len < SPI_BUF_SIZE); assert(max_rx_len < SPI_BUF_SIZE); - spi_header header = { + xfer_count++; + header = { .sync = SPI_SYNC, .endpoint = endpoint, .tx_len = tx_len, @@ -316,16 +351,16 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx memcpy(tx_buf, &header, sizeof(header)); add_checksum(tx_buf, sizeof(header)); transfer.len = sizeof(header) + 1; - ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + ret = lltransfer(transfer); if (ret < 0) { - LOGE("SPI: failed to send header"); - goto transfer_fail; + SPILOG(LOGE, "SPI: failed to send header"); + goto fail; } // Wait for (N)ACK ret = wait_for_ack(SPI_HACK, 0x11, timeout, 1); if (ret < 0) { - goto transfer_fail; + goto fail; } // Send data @@ -334,35 +369,35 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx } add_checksum(tx_buf, tx_len); transfer.len = tx_len + 1; - ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + ret = lltransfer(transfer); if (ret < 0) { - LOGE("SPI: failed to send data"); - goto transfer_fail; + SPILOG(LOGE, "SPI: failed to send data"); + goto fail; } // Wait for (N)ACK ret = wait_for_ack(SPI_DACK, 0x13, timeout, 3); if (ret < 0) { - goto transfer_fail; + goto fail; } // Read data rx_data_len = *(uint16_t *)(rx_buf+1); if (rx_data_len >= SPI_BUF_SIZE) { - LOGE("SPI: RX data len larger than buf size %d", rx_data_len); - goto transfer_fail; + SPILOG(LOGE, "SPI: RX data len larger than buf size %d", rx_data_len); + goto fail; } transfer.len = rx_data_len + 1; transfer.rx_buf = (uint64_t)(rx_buf + 2 + 1); - ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + ret = lltransfer(transfer); if (ret < 0) { - LOGE("SPI: failed to read rx data"); - goto transfer_fail; + SPILOG(LOGE, "SPI: failed to read rx data"); + goto fail; } if (!check_checksum(rx_buf, rx_data_len + 4)) { - LOGE("SPI: bad checksum"); - goto transfer_fail; + SPILOG(LOGE, "SPI: bad checksum"); + goto fail; } if (rx_data != NULL) { @@ -371,7 +406,19 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx return rx_data_len; -transfer_fail: +fail: + // ensure slave is in a consistent state + // and ready for the next transfer + int nack_cnt = 0; + while (nack_cnt < 3) { + if (wait_for_ack(SPI_NACK, 0x14, 1, SPI_BUF_SIZE/2) == 0) { + nack_cnt += 1; + } else { + nack_cnt = 0; + } + } + + if (ret > 0) ret = -1; return ret; } #endif diff --git a/selfdrive/athena/tests/__init__.py b/selfdrive/pandad/tests/__init__.py similarity index 100% rename from selfdrive/athena/tests/__init__.py rename to selfdrive/pandad/tests/__init__.py diff --git a/selfdrive/boardd/tests/bootstub.panda.bin b/selfdrive/pandad/tests/bootstub.panda.bin similarity index 100% rename from selfdrive/boardd/tests/bootstub.panda.bin rename to selfdrive/pandad/tests/bootstub.panda.bin diff --git a/selfdrive/boardd/tests/bootstub.panda_h7.bin b/selfdrive/pandad/tests/bootstub.panda_h7.bin similarity index 100% rename from selfdrive/boardd/tests/bootstub.panda_h7.bin rename to selfdrive/pandad/tests/bootstub.panda_h7.bin diff --git a/selfdrive/boardd/tests/bootstub.panda_h7_spiv0.bin b/selfdrive/pandad/tests/bootstub.panda_h7_spiv0.bin similarity index 100% rename from selfdrive/boardd/tests/bootstub.panda_h7_spiv0.bin rename to selfdrive/pandad/tests/bootstub.panda_h7_spiv0.bin diff --git a/selfdrive/boardd/tests/test_pandad.py b/selfdrive/pandad/tests/test_pandad.py old mode 100755 new mode 100644 similarity index 67% rename from selfdrive/boardd/tests/test_pandad.py rename to selfdrive/pandad/tests/test_pandad.py index 21cc0b5f37..467c7f04c9 --- a/selfdrive/boardd/tests/test_pandad.py +++ b/selfdrive/pandad/tests/test_pandad.py @@ -1,14 +1,12 @@ -#!/usr/bin/env python3 import os import pytest import time -import unittest import cereal.messaging as messaging from cereal import log from openpilot.common.gpio import gpio_set, gpio_init from panda import Panda, PandaDFU, PandaProtocolMismatch -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes from openpilot.system.hardware import HARDWARE from openpilot.system.hardware.tici.pins import GPIO @@ -16,29 +14,34 @@ HERE = os.path.dirname(os.path.realpath(__file__)) @pytest.mark.tici -class TestPandad(unittest.TestCase): +class TestPandad: - def setUp(self): + def setup_method(self): # ensure panda is up if len(Panda.list()) == 0: self._run_test(60) - def tearDown(self): + self.spi = HARDWARE.get_device_type() != 'tici' + + def teardown_method(self): managed_processes['pandad'].stop() - def _run_test(self, timeout=30): + def _run_test(self, timeout=30) -> float: + st = time.monotonic() + sm = messaging.SubMaster(['pandaStates']) + managed_processes['pandad'].start() - - sm = messaging.SubMaster(['peripheralState']) - for _ in range(timeout*10): + while (time.monotonic() - st) < timeout: sm.update(100) - if sm['peripheralState'].pandaType != log.PandaState.PandaType.unknown: + if len(sm['pandaStates']) and sm['pandaStates'][0].pandaType != log.PandaState.PandaType.unknown: break - + dt = time.monotonic() - st managed_processes['pandad'].stop() - if sm['peripheralState'].pandaType == log.PandaState.PandaType.unknown: - raise Exception("boardd failed to start") + if len(sm['pandaStates']) == 0 or sm['pandaStates'][0].pandaType == log.PandaState.PandaType.unknown: + raise Exception("pandad failed to start") + + return dt def _go_to_dfu(self): HARDWARE.recover_internal_panda() @@ -60,7 +63,7 @@ class TestPandad(unittest.TestCase): assert Panda.wait_for_panda(None, 10) if expect_mismatch: - with self.assertRaises(PandaProtocolMismatch): + with pytest.raises(PandaProtocolMismatch): Panda() else: with Panda() as p: @@ -88,15 +91,25 @@ class TestPandad(unittest.TestCase): assert any(Panda(s).is_internal() for s in Panda.list()) def test_best_case_startup_time(self): - # run once so we're setup + # run once so we're up to date self._run_test(60) - # should be fast this time - self._run_test(8) + ts = [] + for _ in range(10): + # should be nearly instant this time + dt = self._run_test(5) + ts.append(dt) + + # 5s for USB (due to enumeration) + # - 0.2s pandad -> pandad + # - plus some buffer + assert 0.1 < (sum(ts)/len(ts)) < (0.5 if self.spi else 5.0) + print("startup times", ts, sum(ts) / len(ts)) + def test_protocol_version_check(self): - if HARDWARE.get_device_type() == 'tici': - raise unittest.SkipTest("SPI test") + if not self.spi: + pytest.skip("SPI test") # flash old fw fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin") self._flash_bootstub_and_test(fn, expect_mismatch=True) @@ -113,7 +126,3 @@ class TestPandad(unittest.TestCase): self._assert_no_panda() self._run_test(60) - - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/pandad/tests/test_pandad_loopback.py b/selfdrive/pandad/tests/test_pandad_loopback.py new file mode 100644 index 0000000000..d2b6d047d5 --- /dev/null +++ b/selfdrive/pandad/tests/test_pandad_loopback.py @@ -0,0 +1,114 @@ +import os +import copy +import random +import time +import pytest +from collections import defaultdict +from pprint import pprint + +import cereal.messaging as messaging +from cereal import car, log +from openpilot.common.retry import retry +from openpilot.common.params import Params +from openpilot.common.timeout import Timeout +from openpilot.selfdrive.pandad import can_list_to_can_capnp +from openpilot.selfdrive.car import make_can_msg +from openpilot.system.hardware import TICI +from openpilot.selfdrive.test.helpers import phone_only, with_processes + + +@retry(attempts=3) +def setup_pandad(num_pandas): + params = Params() + params.clear_all() + params.put_bool("IsOnroad", False) + + sm = messaging.SubMaster(['pandaStates']) + with Timeout(90, "pandad didn't start"): + 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) + + found_pandas = len(sm['pandaStates']) + assert num_pandas == found_pandas, "connected pandas ({found_pandas}) doesn't match expected panda count ({num_pandas}). \ + connect another panda for multipanda tests." + + # pandad safety setting relies on these params + cp = car.CarParams.new_message() + + safety_config = car.CarParams.SafetyConfig.new_message() + safety_config.safetyModel = car.CarParams.SafetyModel.allOutput + cp.safetyConfigs = [safety_config]*num_pandas + + params.put_bool("IsOnroad", True) + params.put_bool("FirmwareQueryDone", True) + params.put_bool("ControlsReady", True) + params.put("CarParams", cp.to_bytes()) + + with Timeout(90, "pandad didn't set safety mode"): + while any(ps.safetyModel != car.CarParams.SafetyModel.allOutput for ps in sm['pandaStates']): + sm.update(1000) + +def send_random_can_messages(sendcan, count, num_pandas=1): + sent_msgs = defaultdict(set) + for _ in range(count): + to_send = [] + for __ in range(random.randrange(20)): + bus = random.choice([b for b in range(3*num_pandas) if b % 4 != 3]) + addr = random.randrange(1, 1<<29) + dat = bytes(random.getrandbits(8) for _ in range(random.randrange(1, 9))) + if (addr, dat) in sent_msgs[bus]: + continue + sent_msgs[bus].add((addr, dat)) + to_send.append(make_can_msg(addr, dat, bus)) + sendcan.send(can_list_to_can_capnp(to_send, msgtype='sendcan')) + return sent_msgs + + +@pytest.mark.tici +class TestBoarddLoopback: + @classmethod + def setup_class(cls): + os.environ['STARTED'] = '1' + os.environ['BOARDD_LOOPBACK'] = '1' + + @phone_only + @with_processes(['pandad']) + def test_loopback(self): + num_pandas = 2 if TICI and "SINGLE_PANDA" not in os.environ else 1 + setup_pandad(num_pandas) + + sendcan = messaging.pub_sock('sendcan') + can = messaging.sub_sock('can', conflate=False, timeout=100) + sm = messaging.SubMaster(['pandaStates']) + time.sleep(1) + + n = 200 + for i in range(n): + print(f"pandad loopback {i}/{n}") + + sent_msgs = send_random_can_messages(sendcan, random.randrange(20, 100), num_pandas) + + sent_loopback = copy.deepcopy(sent_msgs) + sent_loopback.update({k+128: copy.deepcopy(v) for k, v in sent_msgs.items()}) + sent_total = {k: len(v) for k, v in sent_loopback.items()} + for _ in range(100 * 5): + sm.update(0) + recvd = messaging.drain_sock(can, wait_for_one=True) + for msg in recvd: + for m in msg.can: + key = (m.address, m.dat) + assert key in sent_loopback[m.src], f"got unexpected msg: {m.src=} {m.address=} {m.dat=}" + sent_loopback[m.src].discard(key) + + if all(len(v) == 0 for v in sent_loopback.values()): + break + + # if a set isn't empty, messages got dropped + pprint(sent_msgs) + pprint(sent_loopback) + print({k: len(x) for k, x in sent_loopback.items()}) + print(sum([len(x) for x in sent_loopback.values()])) + pprint(sm['pandaStates']) # may drop messages due to RX buffer overflow + for bus in sent_loopback.keys(): + assert not len(sent_loopback[bus]), f"loop {i}: bus {bus} missing {len(sent_loopback[bus])} out of {sent_total[bus]} messages" diff --git a/selfdrive/pandad/tests/test_pandad_spi.py b/selfdrive/pandad/tests/test_pandad_spi.py new file mode 100644 index 0000000000..9c5990cd3f --- /dev/null +++ b/selfdrive/pandad/tests/test_pandad_spi.py @@ -0,0 +1,106 @@ +import os +import time +import numpy as np +import pytest +import random + +import cereal.messaging as messaging +from cereal.services import SERVICE_LIST +from openpilot.system.hardware import HARDWARE +from openpilot.selfdrive.test.helpers import phone_only, with_processes +from openpilot.selfdrive.pandad.tests.test_pandad_loopback import setup_pandad, send_random_can_messages + +JUNGLE_SPAM = "JUNGLE_SPAM" in os.environ + +@pytest.mark.tici +class TestBoarddSpi: + @classmethod + def setup_class(cls): + if HARDWARE.get_device_type() == 'tici': + pytest.skip("only for spi pandas") + os.environ['STARTED'] = '1' + os.environ['SPI_ERR_PROB'] = '0.001' + if not JUNGLE_SPAM: + os.environ['BOARDD_LOOPBACK'] = '1' + + @phone_only + @with_processes(['pandad']) + def test_spi_corruption(self, subtests): + setup_pandad(1) + + sendcan = messaging.pub_sock('sendcan') + socks = {s: messaging.sub_sock(s, conflate=False, timeout=100) for s in ('can', 'pandaStates', 'peripheralState')} + time.sleep(2) + for s in socks.values(): + messaging.drain_sock_raw(s) + + total_recv_count = 0 + total_sent_count = 0 + sent_msgs = {bus: list() for bus in range(3)} + + st = time.monotonic() + ts = {s: list() for s in socks.keys()} + for _ in range(int(os.getenv("TEST_TIME", "20"))): + # send some CAN messages + if not JUNGLE_SPAM: + sent = send_random_can_messages(sendcan, random.randrange(2, 20)) + for k, v in sent.items(): + sent_msgs[k].extend(list(v)) + total_sent_count += len(v) + + for service, sock in socks.items(): + for m in messaging.drain_sock(sock): + ts[service].append(m.logMonoTime) + + # sanity check for corruption + assert m.valid or (service == "can") + if service == "can": + for msg in m.can: + if JUNGLE_SPAM: + # PandaJungle.set_generated_can(True) + i = msg.address - 0x200 + assert msg.address >= 0x200 + assert msg.src == (i%3) + assert msg.dat == b"\xff"*(i%8) + total_recv_count += 1 + continue + + if msg.src > 4: + continue + key = (msg.address, msg.dat) + assert key in sent_msgs[msg.src], f"got unexpected msg: {msg.src=} {msg.address=} {msg.dat=}" + # TODO: enable this + #sent_msgs[msg.src].remove(key) + total_recv_count += 1 + elif service == "pandaStates": + assert len(m.pandaStates) == 1 + ps = m.pandaStates[0] + assert ps.uptime < 1000 + assert ps.pandaType == "tres" + assert ps.ignitionLine + assert not ps.ignitionCan + assert 4000 < ps.voltage < 14000 + elif service == "peripheralState": + ps = m.peripheralState + assert ps.pandaType == "tres" + assert 4000 < ps.voltage < 14000 + assert 50 < ps.current < 1000 + assert ps.fanSpeedRpm < 10000 + + time.sleep(0.5) + et = time.monotonic() - st + + print("\n======== timing report ========") + for service, times in ts.items(): + dts = np.diff(times)/1e6 + print(service.ljust(17), f"{np.mean(dts):7.2f} {np.min(dts):7.2f} {np.max(dts):7.2f}") + with subtests.test(msg="timing check", service=service): + edt = 1e3 / SERVICE_LIST[service].frequency + assert edt*0.9 < np.mean(dts) < edt*1.1 + assert np.max(dts) < edt*8 + assert np.min(dts) < edt + assert len(dts) >= ((et-0.5)*SERVICE_LIST[service].frequency*0.8) + + with subtests.test(msg="CAN traffic"): + print(f"Sent {total_sent_count} CAN messages, got {total_recv_count} back. {total_recv_count/(total_sent_count+1e-4):.2%} received") + assert total_recv_count > 20 diff --git a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc b/selfdrive/pandad/tests/test_pandad_usbprotocol.cc similarity index 98% rename from selfdrive/boardd/tests/test_boardd_usbprotocol.cc rename to selfdrive/pandad/tests/test_pandad_usbprotocol.cc index 86476d05cd..11f7184efd 100644 --- a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc +++ b/selfdrive/pandad/tests/test_pandad_usbprotocol.cc @@ -4,7 +4,7 @@ #include "catch2/catch.hpp" #include "cereal/messaging/messaging.h" #include "common/util.h" -#include "selfdrive/boardd/panda.h" +#include "selfdrive/pandad/panda.h" struct PandaTest : public Panda { PandaTest(uint32_t bus_offset, int can_list_size, cereal::PandaState::PandaType hw_type); @@ -40,7 +40,7 @@ PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState uint32_t id = util::random_int(0, std::size(dlc_to_len) - 1); const std::string &dat = test_data[dlc_to_len[id]]; can.setAddress(i); - can.setSrc(util::random_int(0, 3) + bus_offset); + can.setSrc(util::random_int(0, 2) + bus_offset); can.setDat(kj::ArrayPtr((uint8_t *)dat.data(), dat.size())); total_pakets_size += sizeof(can_header) + dat.size(); } diff --git a/selfdrive/test/docker_build.sh b/selfdrive/test/docker_build.sh index 5f77ceb10d..4d58a1507c 100755 --- a/selfdrive/test/docker_build.sh +++ b/selfdrive/test/docker_build.sh @@ -17,7 +17,7 @@ fi source $SCRIPT_DIR/docker_common.sh $1 "$TAG_SUFFIX" -DOCKER_BUILDKIT=1 docker buildx build --provenance false --pull --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR +DOCKER_BUILDKIT=1 docker buildx build --provenance false --pull --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $DOCKER_IMAGE:latest -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR if [ -n "$PUSH_IMAGE" ]; then docker push $REMOTE_TAG diff --git a/selfdrive/test/docker_common.sh b/selfdrive/test/docker_common.sh index f8a423762d..2887fff74b 100644 --- a/selfdrive/test/docker_common.sh +++ b/selfdrive/test/docker_common.sh @@ -1,9 +1,6 @@ if [ "$1" = "base" ]; then export DOCKER_IMAGE=openpilot-base export DOCKER_FILE=Dockerfile.openpilot_base -elif [ "$1" = "sim" ]; then - export DOCKER_IMAGE=openpilot-sim - export DOCKER_FILE=tools/sim/Dockerfile.sim elif [ "$1" = "prebuilt" ]; then export DOCKER_IMAGE=openpilot-prebuilt export DOCKER_FILE=Dockerfile.openpilot diff --git a/selfdrive/test/fuzzy_generation.py b/selfdrive/test/fuzzy_generation.py index 26c35c0c18..94eb0dfaa6 100644 --- a/selfdrive/test/fuzzy_generation.py +++ b/selfdrive/test/fuzzy_generation.py @@ -2,6 +2,7 @@ import capnp import hypothesis.strategies as st from typing import Any from collections.abc import Callable +from functools import cache from cereal import log @@ -11,67 +12,62 @@ DrawType = Callable[[st.SearchStrategy], Any] class FuzzyGenerator: def __init__(self, draw: DrawType, real_floats: bool): self.draw = draw - self.real_floats = real_floats + self.native_type_map = FuzzyGenerator._get_native_type_map(real_floats) 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 - return st.floats(**kwargs, allow_nan=allow_nan, allow_infinity=allow_infinity) - - if field == 'bool': - return st.booleans() - elif field == 'int8': - return st.integers(min_value=-2**7, max_value=2**7-1) - elif field == 'int16': - return st.integers(min_value=-2**15, max_value=2**15-1) - elif field == 'int32': - return st.integers(min_value=-2**31, max_value=2**31-1) - elif field == 'int64': - return st.integers(min_value=-2**63, max_value=2**63-1) - elif field == 'uint8': - return st.integers(min_value=0, max_value=2**8-1) - elif field == 'uint16': - return st.integers(min_value=0, max_value=2**16-1) - elif field == 'uint32': - return st.integers(min_value=0, max_value=2**32-1) - elif field == 'uint64': - return st.integers(min_value=0, max_value=2**64-1) - elif field == 'float32': - return floats(width=32) - elif field == 'float64': - return floats(width=64) - elif field == 'text': - return st.text(max_size=1000) - elif field == 'data': - return st.binary(max_size=1000) - elif field == 'anyPointer': - return st.text() + value_func = self.native_type_map.get(field) + if value_func is not None: + return value_func else: - raise NotImplementedError(f'Invalid type : {field}') + raise NotImplementedError(f'Invalid type: {field}') def generate_field(self, field: capnp.lib.capnp._StructSchemaField) -> st.SearchStrategy: def rec(field_type: capnp.lib.capnp._DynamicStructReader) -> st.SearchStrategy: - if field_type.which() == 'struct': + type_which = field_type.which() + if type_which == 'struct': return self.generate_struct(field.schema.elementType if base_type == 'list' else field.schema) - elif field_type.which() == 'list': + elif type_which == 'list': return st.lists(rec(field_type.list.elementType)) - elif field_type.which() == 'enum': + elif type_which == 'enum': schema = field.schema.elementType if base_type == 'list' else field.schema return st.sampled_from(list(schema.enumerants.keys())) else: - return self.generate_native_type(field_type.which()) + return self.generate_native_type(type_which) - if 'slot' in field.proto.to_dict(): - base_type = field.proto.slot.type.which() - return rec(field.proto.slot.type) - else: + try: + if hasattr(field.proto, 'slot'): + slot_type = field.proto.slot.type + base_type = slot_type.which() + return rec(slot_type) + else: + return self.generate_struct(field.schema) + except capnp.lib.capnp.KjException: return self.generate_struct(field.schema) 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}) + single_fill: tuple[str, ...] = (event,) if event else (self.draw(st.sampled_from(schema.union_fields)),) if schema.union_fields else () + fields_to_generate = schema.non_union_fields + single_fill + return st.fixed_dictionaries({field: self.generate_field(schema.fields[field]) for field in fields_to_generate if not field.endswith('DEPRECATED')}) + + @staticmethod + @cache + def _get_native_type_map(real_floats: bool) -> dict[str, st.SearchStrategy]: + return { + 'bool': st.booleans(), + 'int8': st.integers(min_value=-2**7, max_value=2**7-1), + 'int16': st.integers(min_value=-2**15, max_value=2**15-1), + 'int32': st.integers(min_value=-2**31, max_value=2**31-1), + 'int64': st.integers(min_value=-2**63, max_value=2**63-1), + 'uint8': st.integers(min_value=0, max_value=2**8-1), + 'uint16': st.integers(min_value=0, max_value=2**16-1), + 'uint32': st.integers(min_value=0, max_value=2**32-1), + 'uint64': st.integers(min_value=0, max_value=2**64-1), + 'float32': st.floats(width=32, allow_nan=not real_floats, allow_infinity=not real_floats), + 'float64': st.floats(width=64, allow_nan=not real_floats, allow_infinity=not real_floats), + 'text': st.text(max_size=1000), + 'data': st.binary(max_size=1000), + 'anyPointer': st.text(), # Note: No need to define a separate function for anyPointer + } @classmethod def get_random_msg(cls, draw: DrawType, struct: capnp.lib.capnp._StructModule, real_floats: bool = False) -> dict[str, Any]: diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index 210a283699..b7f5bb183b 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -1,19 +1,21 @@ +import contextlib import http.server import os import threading import time +import pytest from functools import wraps import cereal.messaging as messaging from openpilot.common.params import Params -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes from openpilot.system.hardware import PC from openpilot.system.version import training_version, terms_version def set_params_enabled(): - os.environ['FINGERPRINT'] = "TOYOTA COROLLA TSS2 2019" + os.environ['FINGERPRINT'] = "TOYOTA_COROLLA_TSS2" os.environ['LOGPRINT'] = "debug" params = Params() @@ -31,39 +33,46 @@ def phone_only(f): @wraps(f) def wrap(self, *args, **kwargs): if PC: - self.skipTest("This test is not meant to run on PC") - f(self, *args, **kwargs) + pytest.skip("This test is not meant to run on PC") + return f(self, *args, **kwargs) return wrap def release_only(f): @wraps(f) def wrap(self, *args, **kwargs): if "RELEASE" not in os.environ: - self.skipTest("This test is only for release branches") + pytest.skip("This test is only for release branches") f(self, *args, **kwargs) return wrap -def with_processes(processes, init_time=0, ignore_stopped=None): + +@contextlib.contextmanager +def processes_context(processes, init_time=0, ignore_stopped=None): ignore_stopped = [] if ignore_stopped is None else ignore_stopped + # start and assert started + for n, p in enumerate(processes): + managed_processes[p].start() + if n < len(processes) - 1: + time.sleep(init_time) + + assert all(managed_processes[name].proc.exitcode is None for name in processes) + + try: + yield [managed_processes[name] for name in processes] + # assert processes are still started + assert all(managed_processes[name].proc.exitcode is None for name in processes if name not in ignore_stopped) + finally: + for p in processes: + managed_processes[p].stop() + + +def with_processes(processes, init_time=0, ignore_stopped=None): def wrapper(func): @wraps(func) def wrap(*args, **kwargs): - # start and assert started - for n, p in enumerate(processes): - managed_processes[p].start() - if n < len(processes) - 1: - time.sleep(init_time) - assert all(managed_processes[name].proc.exitcode is None for name in processes) - - # call the function - try: - func(*args, **kwargs) - # assert processes are still started - assert all(managed_processes[name].proc.exitcode is None for name in processes if name not in ignore_stopped) - finally: - for p in processes: - managed_processes[p].stop() + with processes_context(processes, init_time, ignore_stopped): + return func(*args, **kwargs) return wrap return wrapper @@ -80,23 +89,49 @@ def read_segment_list(segment_list_path): return [(platform[2:], segment) for platform, segment in zip(seg_list[::2], seg_list[1::2], strict=True)] +@contextlib.contextmanager +def http_server_context(handler, setup=None): + 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: + yield (host, port) + finally: + server.shutdown() + server.server_close() + t.join() + + 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() - + with http_server_context(handler, setup) as (host, port): + return func(*args, f"http://{host}:{port}", **kwargs) return inner + + +def DirectoryHttpServer(directory) -> type[http.server.SimpleHTTPRequestHandler]: + # creates an http server that serves files from directory + class Handler(http.server.SimpleHTTPRequestHandler): + API_NO_RESPONSE = False + API_BAD_RESPONSE = False + + def do_GET(self): + if self.API_NO_RESPONSE: + return + + if self.API_BAD_RESPONSE: + self.send_response(500, "") + return + super().do_GET() + + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=str(directory), **kwargs) + + return Handler diff --git a/selfdrive/test/longitudinal_maneuvers/maneuver.py b/selfdrive/test/longitudinal_maneuvers/maneuver.py index 000225ab77..6c8495cc3b 100644 --- a/selfdrive/test/longitudinal_maneuvers/maneuver.py +++ b/selfdrive/test/longitudinal_maneuvers/maneuver.py @@ -19,6 +19,7 @@ class Maneuver: self.ensure_start = kwargs.get("ensure_start", False) self.enabled = kwargs.get("enabled", True) self.e2e = kwargs.get("e2e", False) + self.personality = kwargs.get("personality", 0) self.force_decel = kwargs.get("force_decel", False) self.duration = duration @@ -33,6 +34,7 @@ class Maneuver: only_lead2=self.only_lead2, only_radar=self.only_radar, e2e=self.e2e, + personality=self.personality, force_decel=self.force_decel, ) diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index bb935fdc8e..daf7cec32b 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -15,7 +15,7 @@ class Plant: messaging_initialized = False def __init__(self, lead_relevancy=False, speed=0.0, distance_lead=2.0, - enabled=True, only_lead2=False, only_radar=False, e2e=False, force_decel=False): + enabled=True, only_lead2=False, only_radar=False, e2e=False, personality=0, force_decel=False): self.rate = 1. / DT_MDL if not Plant.messaging_initialized: @@ -39,6 +39,7 @@ class Plant: self.only_lead2 = only_lead2 self.only_radar = only_radar self.e2e = e2e + self.personality = personality self.force_decel = force_decel self.rk = Ratekeeper(self.rate, print_delay_threshold=100.0) @@ -49,7 +50,7 @@ class Plant: from openpilot.selfdrive.car.honda.values import CAR from openpilot.selfdrive.car.honda.interface import CarInterface - self.planner = LongitudinalPlanner(CarInterface.get_non_essential_params(CAR.CIVIC), init_v=self.speed) + self.planner = LongitudinalPlanner(CarInterface.get_non_essential_params(CAR.HONDA_CIVIC), init_v=self.speed) @property def current_time(self): @@ -82,7 +83,7 @@ class Plant: lead = log.RadarState.LeadData.new_message() lead.dRel = float(d_rel) - lead.yRel = float(0.0) + lead.yRel = 0.0 lead.vRel = float(v_rel) lead.aRel = float(a_lead - self.acceleration) lead.vLead = float(v_lead) @@ -112,6 +113,7 @@ class Plant: control.controlsState.longControlState = LongCtrlState.pid if self.enabled else LongCtrlState.off control.controlsState.vCruise = float(v_cruise * 3.6) control.controlsState.experimentalMode = self.e2e + control.controlsState.personality = self.personality control.controlsState.forceDecel = self.force_decel car_state.carState.vEgo = float(self.speed) car_state.carState.standstill = self.speed < 0.01 diff --git a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py old mode 100755 new mode 100644 index 713b7801f8..62a95babeb --- a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 import itertools -import unittest from parameterized import parameterized_class from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE @@ -144,17 +142,13 @@ def create_maneuvers(kwargs): @parameterized_class(("e2e", "force_decel"), itertools.product([True, False], repeat=2)) -class LongitudinalControl(unittest.TestCase): +class TestLongitudinalControl: e2e: bool force_decel: bool - def test_maneuver(self): + def test_maneuver(self, subtests): for maneuver in create_maneuvers({"e2e": self.e2e, "force_decel": self.force_decel}): - with self.subTest(title=maneuver.title, e2e=maneuver.e2e, force_decel=maneuver.force_decel): + with subtests.test(title=maneuver.title, e2e=maneuver.e2e, force_decel=maneuver.force_decel): print(maneuver.title, f'in {"e2e" if maneuver.e2e else "acc"} mode') valid, _ = maneuver.evaluate() - self.assertTrue(valid) - - -if __name__ == "__main__": - unittest.main(failfast=True) + assert valid diff --git a/selfdrive/test/process_replay/.gitignore b/selfdrive/test/process_replay/.gitignore index 63c37e64e1..a35cd58d41 100644 --- a/selfdrive/test/process_replay/.gitignore +++ b/selfdrive/test/process_replay/.gitignore @@ -1,2 +1 @@ fakedata/ -debayer_diff.txt diff --git a/selfdrive/test/process_replay/debayer_replay_ref_commit b/selfdrive/test/process_replay/debayer_replay_ref_commit deleted file mode 100644 index 551fc680ba..0000000000 --- a/selfdrive/test/process_replay/debayer_replay_ref_commit +++ /dev/null @@ -1 +0,0 @@ -8f9ba7540b4549b4a57312129b8ff678d045f70f \ No newline at end of file diff --git a/selfdrive/test/process_replay/imgproc_replay_ref_hash b/selfdrive/test/process_replay/imgproc_replay_ref_hash new file mode 100644 index 0000000000..defcb3681c --- /dev/null +++ b/selfdrive/test/process_replay/imgproc_replay_ref_hash @@ -0,0 +1 @@ +707434c540e685bbe2886b3ff7c82fd61939d362 \ No newline at end of file diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index ef74314172..0715fcf7b4 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -1,15 +1,20 @@ from collections import defaultdict from cereal import messaging +from openpilot.selfdrive.car.fingerprints import MIGRATION from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_encode_index from openpilot.selfdrive.car.toyota.values import EPS_SCALE -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes from panda import Panda +# TODO: message migration should happen in-place def migrate_all(lr, old_logtime=False, manager_states=False, panda_states=False, camera_states=False): msgs = migrate_sensorEvents(lr, old_logtime) msgs = migrate_carParams(msgs, old_logtime) + msgs = migrate_gpsLocation(msgs) + msgs = migrate_deviceState(msgs) + msgs = migrate_carOutput(msgs) if manager_states: msgs = migrate_managerState(msgs) if panda_states: @@ -35,13 +40,60 @@ def migrate_managerState(lr): return all_msgs +def migrate_gpsLocation(lr): + all_msgs = [] + for msg in lr: + if msg.which() in ('gpsLocation', 'gpsLocationExternal'): + new_msg = msg.as_builder() + g = getattr(new_msg, new_msg.which()) + # hasFix is a newer field + if not g.hasFix and g.flags == 1: + g.hasFix = True + all_msgs.append(new_msg.as_reader()) + else: + all_msgs.append(msg) + return all_msgs + + +def migrate_deviceState(lr): + all_msgs = [] + dt = None + for msg in lr: + if msg.which() == 'initData': + dt = msg.initData.deviceType + if msg.which() == 'deviceState': + n = msg.as_builder() + n.deviceState.deviceType = dt + all_msgs.append(n.as_reader()) + else: + all_msgs.append(msg) + return all_msgs + + +def migrate_carOutput(lr): + # migration needed only for routes before carOutput + if any(msg.which() == 'carOutput' for msg in lr): + return lr + + all_msgs = [] + for msg in lr: + if msg.which() == 'carControl': + co = messaging.new_message('carOutput') + co.valid = msg.valid + co.logMonoTime = msg.logMonoTime + co.carOutput.actuatorsOutput = msg.carControl.actuatorsOutputDEPRECATED + all_msgs.append(co.as_reader()) + all_msgs.append(msg) + return all_msgs + + def migrate_pandaStates(lr): all_msgs = [] # TODO: safety param migration should be handled automatically safety_param_migration = { - "TOYOTA PRIUS 2017": EPS_SCALE["TOYOTA PRIUS 2017"] | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL, - "TOYOTA RAV4 2017": EPS_SCALE["TOYOTA RAV4 2017"] | Panda.FLAG_TOYOTA_ALT_BRAKE | Panda.FLAG_TOYOTA_GAS_INTERCEPTOR, - "KIA EV6 2022": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_CANFD_HDA2, + "TOYOTA_PRIUS": EPS_SCALE["TOYOTA_PRIUS"] | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL, + "TOYOTA_RAV4": EPS_SCALE["TOYOTA_RAV4"] | Panda.FLAG_TOYOTA_ALT_BRAKE, + "KIA_EV6": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_CANFD_HDA2, } # Migrate safety param base on carState @@ -149,9 +201,8 @@ def migrate_carParams(lr, old_logtime=False): all_msgs = [] for msg in lr: if msg.which() == 'carParams': - CP = messaging.new_message('carParams') - CP.valid = True - CP.carParams = msg.carParams.as_builder() + CP = msg.as_builder() + CP.carParams.carFingerprint = MIGRATION.get(CP.carParams.carFingerprint, CP.carParams.carFingerprint) for car_fw in CP.carParams.carFw: car_fw.brand = CP.carParams.carName if old_logtime: diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 97b7c7c46c..cba2edfd58 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -1,18 +1,14 @@ #!/usr/bin/env python3 import os import sys -import time from collections import defaultdict from typing import Any -import cereal.messaging as messaging -from openpilot.common.params import Params +from openpilot.common.git import get_commit from openpilot.system.hardware import PC -from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.tools.lib.openpilotci import BASE_URL, get_url from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff from openpilot.selfdrive.test.process_replay.process_replay import get_process_config, replay_process -from openpilot.system.version import get_commit from openpilot.tools.lib.framereader import FrameReader from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.helpers import save_log @@ -20,9 +16,7 @@ from openpilot.tools.lib.helpers import save_log TEST_ROUTE = "2f4452b03ccb98f0|2022-12-03--13-45-30" SEGMENT = 6 MAX_FRAMES = 100 if PC else 600 -NAV_FRAMES = 50 -NO_NAV = "NO_NAV" in os.environ NO_MODEL = "NO_MODEL" in os.environ SEND_EXTRA_INPUTS = bool(int(os.getenv("SEND_EXTRA_INPUTS", "0"))) @@ -50,71 +44,21 @@ def trim_logs_to_max_frames(logs, max_frames, frs_types, include_all_types): return all_msgs -def nav_model_replay(lr): - sm = messaging.SubMaster(['navModel', 'navThumbnail', 'mapRenderState']) - pm = messaging.PubMaster(['liveLocationKalman', 'navRoute']) - - nav = [m for m in lr if m.which() == 'navRoute'] - llk = [m for m in lr if m.which() == 'liveLocationKalman'] - assert len(nav) > 0 and len(llk) >= NAV_FRAMES and nav[0].logMonoTime < llk[-NAV_FRAMES].logMonoTime - - log_msgs = [] - try: - assert "MAPBOX_TOKEN" in os.environ - os.environ['MAP_RENDER_TEST_MODE'] = '1' - Params().put_bool('DmModelInitialized', True) - managed_processes['mapsd'].start() - managed_processes['navmodeld'].start() - - # setup position and route - for _ in range(10): - for s in (llk[-NAV_FRAMES], nav[0]): - pm.send(s.which(), s.as_builder().to_bytes()) - sm.update(1000) - if sm.updated['navModel']: - break - time.sleep(1) - - if not sm.updated['navModel']: - raise Exception("no navmodeld outputs, failed to initialize") - - # drain - time.sleep(2) - sm.update(0) - - # run replay - for n in range(len(llk) - NAV_FRAMES, len(llk)): - pm.send(llk[n].which(), llk[n].as_builder().to_bytes()) - m = messaging.recv_one(sm.sock['navThumbnail']) - assert m is not None, f"no navThumbnail, frame={n}" - log_msgs.append(m) - - m = messaging.recv_one(sm.sock['mapRenderState']) - assert m is not None, f"no mapRenderState, frame={n}" - log_msgs.append(m) - - m = messaging.recv_one(sm.sock['navModel']) - assert m is not None, f"no navModel response, frame={n}" - log_msgs.append(m) - finally: - managed_processes['mapsd'].stop() - managed_processes['navmodeld'].stop() - - return log_msgs - - def model_replay(lr, frs): # modeld is using frame pairs modeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"roadCameraState", "wideRoadCameraState"}, {"roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}) dmodeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"driverCameraState"}, {"driverEncodeIdx", "carParams"}) + if not SEND_EXTRA_INPUTS: - modeld_logs = [msg for msg in modeld_logs if msg.which() not in ["liveCalibration",]] - dmodeld_logs = [msg for msg in dmodeld_logs if msg.which() not in ["liveCalibration",]] - # initial calibration - cal_msg = next(msg for msg in lr if msg.which() == "liveCalibration").as_builder() - cal_msg.logMonoTime = lr[0].logMonoTime - modeld_logs.insert(0, cal_msg.as_reader()) - dmodeld_logs.insert(0, cal_msg.as_reader()) + modeld_logs = [msg for msg in modeld_logs if msg.which() != 'liveCalibration'] + dmodeld_logs = [msg for msg in dmodeld_logs if msg.which() != 'liveCalibration'] + + # initial setup + for s in ('liveCalibration', 'deviceState'): + msg = next(msg for msg in lr if msg.which() == s).as_builder() + msg.logMonoTime = lr[0].logMonoTime + modeld_logs.insert(1, msg.as_reader()) + dmodeld_logs.insert(1, msg.as_reader()) modeld = get_process_config("modeld") dmonitoringmodeld = get_process_config("dmonitoringmodeld") @@ -166,8 +110,6 @@ if __name__ == "__main__": # run replays if not NO_MODEL: log_msgs += model_replay(lr, frs) - if not NO_NAV: - log_msgs += nav_model_replay(lr) # get diff failed = False @@ -179,28 +121,19 @@ if __name__ == "__main__": all_logs = list(LogReader(BASE_URL + log_fn)) cmp_log = [] - # logs are ordered based on type: modelV2, driverStateV2, nav messages (navThumbnail, mapRenderState, navModel) + # logs are ordered based on type: modelV2, driverStateV2 if not NO_MODEL: model_start_index = next(i for i, m in enumerate(all_logs) if m.which() in ("modelV2", "cameraOdometry")) cmp_log += all_logs[model_start_index:model_start_index + MAX_FRAMES*2] dmon_start_index = next(i for i, m in enumerate(all_logs) if m.which() == "driverStateV2") cmp_log += all_logs[dmon_start_index:dmon_start_index + MAX_FRAMES] - if not NO_NAV: - nav_start_index = next(i for i, m in enumerate(all_logs) if m.which() in ["navThumbnail", "mapRenderState", "navModel"]) - nav_logs = all_logs[nav_start_index:nav_start_index + NAV_FRAMES*3] - cmp_log += nav_logs ignore = [ 'logMonoTime', 'modelV2.frameDropPerc', 'modelV2.modelExecutionTime', 'driverStateV2.modelExecutionTime', - 'driverStateV2.dspExecutionTime', - 'navModel.dspExecutionTime', - 'navModel.modelExecutionTime', - 'navThumbnail.timestampEof', - 'mapRenderState.locationMonoTime', - 'mapRenderState.renderTime', + 'driverStateV2.dspExecutionTime' ] if PC: ignore += [ @@ -218,7 +151,8 @@ if __name__ == "__main__": results[TEST_ROUTE]["models"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore) diff_short, diff_long, failed = format_diff(results, log_paths, ref_commit) - print(diff_long) + if "CI" in os.environ: + print(diff_long) print('-------------\n'*5) print(diff_short) with open("model_diff.txt", "w") as f: diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 3b7b04df80..87f1adfada 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -fd6421f7551573c549480f9d29bb0dee4678344d +73eb11111fb6407fa307c3f2bdd3331f2514c707 diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 5119be0a8c..a55396002b 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -6,7 +6,7 @@ import json import heapq import signal import platform -from collections import OrderedDict +from collections import Counter, OrderedDict from dataclasses import dataclass, field from typing import Any from collections.abc import Callable, Iterable @@ -16,14 +16,14 @@ import capnp import cereal.messaging as messaging from cereal import car from cereal.services import SERVICE_LIST -from cereal.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name +from msgq.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.timeout import Timeout from openpilot.common.realtime import DT_CTRL from panda.python import ALTERNATIVE_EXPERIENCE from openpilot.selfdrive.car.car_helpers import get_car, interfaces -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from openpilot.selfdrive.test.process_replay.migration import migrate_all from openpilot.selfdrive.test.process_replay.capture import ProcessOutputCapture @@ -317,12 +317,12 @@ class ProcessContainer: return output_msgs -def controlsd_fingerprint_callback(rc, pm, msgs, fingerprint): +def card_fingerprint_callback(rc, pm, msgs, fingerprint): print("start fingerprinting") params = Params() canmsgs = [msg for msg in msgs if msg.which() == "can"][:300] - # controlsd expects one arbitrary can and pandaState + # card expects one arbitrary can and pandaState rc.send_sync(pm, "can", messaging.new_message("can", 1)) pm.send("pandaStates", messaging.new_message("pandaStates", 1)) rc.send_sync(pm, "can", messaging.new_message("can", 1)) @@ -356,12 +356,20 @@ def get_car_params_callback(rc, pm, msgs, fingerprint): for m in canmsgs[:300]: can.send(m.as_builder().to_bytes()) _, CP = get_car(can, sendcan, Params().get_bool("ExperimentalLongitudinalEnabled")) + + if not params.get_bool("DisengageOnAccelerator"): + CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS + params.put("CarParams", CP.to_bytes()) return CP def controlsd_rcv_callback(msg, cfg, frame): - # no sendcan until controlsd is initialized + return (frame - 1) == 0 or msg.which() == 'carState' + + +def card_rcv_callback(msg, cfg, frame): + # no sendcan until card is initialized if msg.which() != "can": return False @@ -461,18 +469,28 @@ CONFIGS = [ ProcessConfig( proc_name="controlsd", pubs=[ - "can", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState", + "carState", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState", "longitudinalPlan", "liveLocationKalman", "liveParameters", "radarState", "modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState", - "testJoystick", "liveTorqueParameters", "accelerometer", "gyroscope" + "testJoystick", "liveTorqueParameters", "accelerometer", "gyroscope", "carOutput" ], - subs=["controlsState", "carState", "carControl", "sendcan", "onroadEvents", "carParams"], + subs=["controlsState", "carControl", "onroadEvents"], ignore=["logMonoTime", "controlsState.startMonoTime", "controlsState.cumLagMs"], config_callback=controlsd_config_callback, - init_callback=controlsd_fingerprint_callback, + init_callback=get_car_params_callback, should_recv_callback=controlsd_rcv_callback, tolerance=NUMPY_TOLERANCE, processing_time=0.004, + ), + ProcessConfig( + proc_name="card", + pubs=["pandaStates", "carControl", "onroadEvents", "can"], + subs=["sendcan", "carState", "carParams", "carOutput"], + ignore=["logMonoTime", "carState.cumLagMs"], + init_callback=card_fingerprint_callback, + should_recv_callback=card_rcv_callback, + tolerance=NUMPY_TOLERANCE, + processing_time=0.004, main_pub="can", ), ProcessConfig( @@ -487,7 +505,7 @@ CONFIGS = [ ProcessConfig( proc_name="plannerd", pubs=["modelV2", "carControl", "carState", "controlsState", "radarState"], - subs=["longitudinalPlan", "uiPlan"], + subs=["longitudinalPlan"], ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime"], init_callback=get_car_params_callback, should_recv_callback=FrequencyBasedRcvCallback("modelV2"), @@ -537,7 +555,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="torqued", - pubs=["liveLocationKalman", "carState", "carControl"], + pubs=["liveLocationKalman", "carState", "carControl", "carOutput"], subs=["liveTorqueParameters"], ignore=["logMonoTime"], init_callback=get_car_params_callback, @@ -546,7 +564,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="modeld", - pubs=["roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState"], + pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState"], subs=["modelV2", "cameraOdometry"], ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime"], should_recv_callback=ModeldCameraSyncRcvCallback(), @@ -798,3 +816,18 @@ def check_openpilot_enabled(msgs: LogIterable) -> bool: max_enabled_count = max(max_enabled_count, cur_enabled_count) return max_enabled_count > int(10. / DT_CTRL) + + +def check_most_messages_valid(msgs: LogIterable, threshold: float = 0.9) -> bool: + msgs_counts = Counter(msg.which() for msg in msgs) + msgs_valid_counts = Counter(msg.which() for msg in msgs if msg.valid) + + most_valid_for_service = {} + for msg_type in msgs_counts.keys(): + valid_share = msgs_valid_counts.get(msg_type, 0) / msgs_counts[msg_type] + ok = valid_share >= threshold + if not ok: + print(f"WARNING: Service {msg_type} has {valid_share * 100:.2f}% valid messages, which is below threshold of {threshold * 100:.2f}%") + most_valid_for_service[msg_type] = ok + + return all(most_valid_for_service.values()) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 9c627fb7c9..96161a42de 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -47609e372bf616932c4dca74d2616c3d97fa2443 +8737e368e17f859291164286feb4246e00c0b4a5 diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index 8e882207b5..bf7a4bfd97 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -9,8 +9,8 @@ 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 -from openpilot.selfdrive.test.process_replay.vision_meta import DRIVER_FRAME_SIZES + check_openpilot_enabled, check_most_messages_valid, get_custom_params_from_lr +from openpilot.selfdrive.test.process_replay.vision_meta import DRIVER_CAMERA_FRAME_SIZES from openpilot.selfdrive.test.update_ci_routes import upload_route from openpilot.tools.lib.route import Route from openpilot.tools.lib.framereader import FrameReader, BaseFrameReader, FrameType @@ -37,7 +37,7 @@ class DummyFrameReader(BaseFrameReader): @staticmethod def zero_dcamera(): - return DummyFrameReader(*DRIVER_FRAME_SIZES["tici"], 1200, 0) + return DummyFrameReader(*DRIVER_CAMERA_FRAME_SIZES[("tici", "ar0231")], 1200, 0) def regen_segment( @@ -129,6 +129,8 @@ def regen_and_save( if not check_openpilot_enabled(output_logs): raise Exception("Route did not engage for long enough") + if not check_most_messages_valid(output_logs): + raise Exception("Route has too many invalid messages") if upload: upload_route(rel_log_dir) diff --git a/selfdrive/test/process_replay/test_debayer.py b/selfdrive/test/process_replay/test_debayer.py deleted file mode 100755 index edf2cbd469..0000000000 --- a/selfdrive/test/process_replay/test_debayer.py +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import bz2 -import numpy as np - -import pyopencl as cl # install with `PYOPENCL_CL_PRETEND_VERSION=2.0 pip install pyopencl` - -from openpilot.system.hardware import PC, TICI -from openpilot.common.basedir import BASEDIR -from openpilot.tools.lib.openpilotci import BASE_URL -from openpilot.system.version import get_commit -from openpilot.system.camerad.snapshot.snapshot import yuv_to_rgb -from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.filereader import FileReader - -TEST_ROUTE = "8345e3b82948d454|2022-05-04--13-45-33/0" - -FRAME_WIDTH = 1928 -FRAME_HEIGHT = 1208 -FRAME_STRIDE = 2896 - -UV_WIDTH = FRAME_WIDTH // 2 -UV_HEIGHT = FRAME_HEIGHT // 2 -UV_SIZE = UV_WIDTH * UV_HEIGHT - - -def get_frame_fn(ref_commit, test_route, tici=True): - return f"{test_route}_debayer{'_tici' if tici else ''}_{ref_commit}.bz2" - - -def bzip_frames(frames): - data = b'' - for y, u, v in frames: - data += y.tobytes() - data += u.tobytes() - data += v.tobytes() - return bz2.compress(data) - - -def unbzip_frames(url): - with FileReader(url) as f: - dat = f.read() - - data = bz2.decompress(dat) - - res = [] - for y_start in range(0, len(data), FRAME_WIDTH * FRAME_HEIGHT + UV_SIZE * 2): - u_start = y_start + FRAME_WIDTH * FRAME_HEIGHT - v_start = u_start + UV_SIZE - - y = np.frombuffer(data[y_start: u_start], dtype=np.uint8).reshape((FRAME_HEIGHT, FRAME_WIDTH)) - u = np.frombuffer(data[u_start: v_start], dtype=np.uint8).reshape((UV_HEIGHT, UV_WIDTH)) - v = np.frombuffer(data[v_start: v_start + UV_SIZE], dtype=np.uint8).reshape((UV_HEIGHT, UV_WIDTH)) - - res.append((y, u, v)) - - return res - - -def init_kernels(frame_offset=0): - ctx = cl.create_some_context(interactive=False) - - with open(os.path.join(BASEDIR, 'system/camerad/cameras/real_debayer.cl')) as f: - build_args = ' -cl-fast-relaxed-math -cl-denorms-are-zero -cl-single-precision-constant' + \ - f' -DFRAME_STRIDE={FRAME_STRIDE} -DRGB_WIDTH={FRAME_WIDTH} -DRGB_HEIGHT={FRAME_HEIGHT} -DFRAME_OFFSET={frame_offset} -DCAM_NUM=0' - if PC: - build_args += ' -DHALF_AS_FLOAT=1 -cl-std=CL2.0' - debayer_prg = cl.Program(ctx, f.read()).build(options=build_args) - - return ctx, debayer_prg - -def debayer_frame(ctx, debayer_prg, data, rgb=False): - q = cl.CommandQueue(ctx) - - yuv_buff = np.empty(FRAME_WIDTH * FRAME_HEIGHT + UV_SIZE * 2, dtype=np.uint8) - - cam_g = cl.Buffer(ctx, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=data) - yuv_g = cl.Buffer(ctx, cl.mem_flags.WRITE_ONLY, FRAME_WIDTH * FRAME_HEIGHT + UV_SIZE * 2) - - local_worksize = (20, 20) if TICI else (4, 4) - ev1 = debayer_prg.debayer10(q, (UV_WIDTH, UV_HEIGHT), local_worksize, cam_g, yuv_g) - cl.enqueue_copy(q, yuv_buff, yuv_g, wait_for=[ev1]).wait() - cl.enqueue_barrier(q) - - y = yuv_buff[:FRAME_WIDTH*FRAME_HEIGHT].reshape((FRAME_HEIGHT, FRAME_WIDTH)) - u = yuv_buff[FRAME_WIDTH*FRAME_HEIGHT:FRAME_WIDTH*FRAME_HEIGHT+UV_SIZE].reshape((UV_HEIGHT, UV_WIDTH)) - v = yuv_buff[FRAME_WIDTH*FRAME_HEIGHT+UV_SIZE:].reshape((UV_HEIGHT, UV_WIDTH)) - - if rgb: - return yuv_to_rgb(y, u, v) - else: - return y, u, v - - -def debayer_replay(lr): - ctx, debayer_prg = init_kernels() - - frames = [] - for m in lr: - if m.which() == 'roadCameraState': - cs = m.roadCameraState - if cs.image: - data = np.frombuffer(cs.image, dtype=np.uint8) - img = debayer_frame(ctx, debayer_prg, data) - - frames.append(img) - - return frames - - -if __name__ == "__main__": - update = "--update" in sys.argv - replay_dir = os.path.dirname(os.path.abspath(__file__)) - ref_commit_fn = os.path.join(replay_dir, "debayer_replay_ref_commit") - - # load logs - lr = list(LogReader(TEST_ROUTE)) - - # run replay - frames = debayer_replay(lr) - - # get diff - failed = False - diff = '' - yuv_i = ['y', 'u', 'v'] - if not update: - with open(ref_commit_fn) as f: - ref_commit = f.read().strip() - frame_fn = get_frame_fn(ref_commit, TEST_ROUTE, tici=TICI) - - try: - cmp_frames = unbzip_frames(BASE_URL + frame_fn) - - if len(frames) != len(cmp_frames): - failed = True - diff += 'amount of frames not equal\n' - - for i, (frame, cmp_frame) in enumerate(zip(frames, cmp_frames, strict=True)): - for j in range(3): - fr = frame[j] - cmp_f = cmp_frame[j] - if fr.shape != cmp_f.shape: - failed = True - diff += f'frame shapes not equal for ({i}, {yuv_i[j]})\n' - diff += f'{ref_commit}: {cmp_f.shape}\n' - diff += f'HEAD: {fr.shape}\n' - elif not np.array_equal(fr, cmp_f): - failed = True - if np.allclose(fr, cmp_f, atol=1): - diff += f'frames not equal for ({i}, {yuv_i[j]}), but are all close\n' - else: - diff += f'frames not equal for ({i}, {yuv_i[j]})\n' - - frame_diff = np.abs(np.subtract(fr, cmp_f)) - diff_len = len(np.nonzero(frame_diff)[0]) - if diff_len > 10000: - diff += f'different at a large amount of pixels ({diff_len})\n' - else: - diff += 'different at (frame, yuv, pixel, ref, HEAD):\n' - for k in zip(*np.nonzero(frame_diff), strict=True): - diff += f'{i}, {yuv_i[j]}, {k}, {cmp_f[k]}, {fr[k]}\n' - - if failed: - print(diff) - with open("debayer_diff.txt", "w") as f: - f.write(diff) - except Exception as e: - print(str(e)) - failed = True - - # upload new refs - if update or (failed and TICI): - from openpilot.tools.lib.openpilotci import upload_file - - print("Uploading new refs") - - frames_bzip = bzip_frames(frames) - - new_commit = get_commit() - frame_fn = os.path.join(replay_dir, get_frame_fn(new_commit, TEST_ROUTE, tici=TICI)) - with open(frame_fn, "wb") as f2: - f2.write(frames_bzip) - - try: - upload_file(frame_fn, os.path.basename(frame_fn)) - except Exception as e: - print("failed to upload", e) - - if update: - with open(ref_commit_fn, 'w') as f: - f.write(str(new_commit)) - - print("\nNew ref commit: ", new_commit) - - sys.exit(int(failed)) diff --git a/selfdrive/test/process_replay/test_fuzzy.py b/selfdrive/test/process_replay/test_fuzzy.py old mode 100755 new mode 100644 index adff06f88a..c802d9c573 --- a/selfdrive/test/process_replay/test_fuzzy.py +++ b/selfdrive/test/process_replay/test_fuzzy.py @@ -1,9 +1,7 @@ -#!/usr/bin/env python3 import copy from hypothesis import given, HealthCheck, Phase, settings import hypothesis.strategies as st from parameterized import parameterized -import unittest from cereal import log from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA @@ -13,11 +11,11 @@ import openpilot.selfdrive.test.process_replay.process_replay as pr # These processes currently fail because of unrealistic data breaking assumptions # that openpilot makes causing error with NaN, inf, int size, array indexing ... # TODO: Make each one testable -NOT_TESTED = ['controlsd', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'dmonitoringmodeld', 'modeld'] +NOT_TESTED = ['controlsd', 'card', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'dmonitoringmodeld', 'modeld'] TEST_CASES = [(cfg.proc_name, copy.deepcopy(cfg)) for cfg in pr.CONFIGS if cfg.proc_name not in NOT_TESTED] -class TestFuzzProcesses(unittest.TestCase): +class TestFuzzProcesses: # TODO: make this faster and increase examples @parameterized.expand(TEST_CASES) @@ -27,7 +25,4 @@ class TestFuzzProcesses(unittest.TestCase): msgs = FuzzyGenerator.get_random_event_msg(data.draw, events=cfg.pubs, real_floats=True) lr = [log.Event.new_message(**m).as_reader() for m in msgs] cfg.timeout = 5 - pr.replay_process(cfg, lr, fingerprint=TOYOTA.COROLLA_TSS2, disable_progress=True) - -if __name__ == "__main__": - unittest.main() + pr.replay_process(cfg, lr, fingerprint=TOYOTA.TOYOTA_COROLLA_TSS2, disable_progress=True) diff --git a/selfdrive/test/process_replay/test_imgproc.py b/selfdrive/test/process_replay/test_imgproc.py new file mode 100644 index 0000000000..27d0541a50 --- /dev/null +++ b/selfdrive/test/process_replay/test_imgproc.py @@ -0,0 +1,98 @@ +import os +import numpy as np +import hashlib + +import pyopencl as cl # install with `PYOPENCL_CL_PRETEND_VERSION=2.0 pip install pyopencl` + +from openpilot.system.hardware import PC, TICI +from openpilot.common.basedir import BASEDIR +from openpilot.common.transformations.camera import DEVICE_CAMERAS +from openpilot.system.camerad.snapshot.snapshot import yuv_to_rgb +from openpilot.tools.lib.logreader import LogReader + +# TODO: check all sensors +TEST_ROUTE = "8345e3b82948d454|2022-05-04--13-45-33/0" + +cam = DEVICE_CAMERAS[("tici", "ar0231")] +FRAME_WIDTH, FRAME_HEIGHT = (cam.dcam.width, cam.dcam.height) +FRAME_STRIDE = FRAME_WIDTH * 12 // 8 + 4 + +UV_WIDTH = FRAME_WIDTH // 2 +UV_HEIGHT = FRAME_HEIGHT // 2 +UV_SIZE = UV_WIDTH * UV_HEIGHT + + +def init_kernels(frame_offset=0): + ctx = cl.create_some_context(interactive=False) + + with open(os.path.join(BASEDIR, 'system/camerad/cameras/process_raw.cl')) as f: + build_args = f' -cl-fast-relaxed-math -cl-denorms-are-zero -cl-single-precision-constant -I{BASEDIR}/system/camerad/sensors ' + \ + f' -DFRAME_WIDTH={FRAME_WIDTH} -DFRAME_HEIGHT={FRAME_WIDTH} -DFRAME_STRIDE={FRAME_STRIDE} -DFRAME_OFFSET={frame_offset} ' + \ + f' -DRGB_WIDTH={FRAME_WIDTH} -DRGB_HEIGHT={FRAME_HEIGHT} -DYUV_STRIDE={FRAME_WIDTH} -DUV_OFFSET={FRAME_WIDTH*FRAME_HEIGHT}' + \ + ' -DSENSOR_ID=1 -DVIGNETTING=0 ' + if PC: + build_args += ' -DHALF_AS_FLOAT=1 -cl-std=CL2.0' + imgproc_prg = cl.Program(ctx, f.read()).build(options=build_args) + + return ctx, imgproc_prg + +def proc_frame(ctx, imgproc_prg, data, rgb=False): + q = cl.CommandQueue(ctx) + + yuv_buff = np.empty(FRAME_WIDTH * FRAME_HEIGHT + UV_SIZE * 2, dtype=np.uint8) + + cam_g = cl.Buffer(ctx, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=data) + yuv_g = cl.Buffer(ctx, cl.mem_flags.WRITE_ONLY, FRAME_WIDTH * FRAME_HEIGHT + UV_SIZE * 2) + + krn = imgproc_prg.process_raw + krn.set_scalar_arg_dtypes([None, None, np.int32]) + local_worksize = (20, 20) if TICI else (4, 4) + + ev1 = krn(q, (FRAME_WIDTH//2, FRAME_HEIGHT//2), local_worksize, cam_g, yuv_g, 1) + cl.enqueue_copy(q, yuv_buff, yuv_g, wait_for=[ev1]).wait() + cl.enqueue_barrier(q) + + y = yuv_buff[:FRAME_WIDTH*FRAME_HEIGHT].reshape((FRAME_HEIGHT, FRAME_WIDTH)) + u = yuv_buff[FRAME_WIDTH*FRAME_HEIGHT::2].reshape((UV_HEIGHT, UV_WIDTH)) + v = yuv_buff[FRAME_WIDTH*FRAME_HEIGHT+1::2].reshape((UV_HEIGHT, UV_WIDTH)) + + if rgb: + return yuv_to_rgb(y, u, v) + else: + return y, u, v + + +def imgproc_replay(lr): + ctx, imgproc_prg = init_kernels() + + frames = [] + for m in lr: + if m.which() == 'roadCameraState': + cs = m.roadCameraState + if cs.image: + data = np.frombuffer(cs.image, dtype=np.uint8) + img = proc_frame(ctx, imgproc_prg, data) + + frames.append(img) + + return frames + + +if __name__ == "__main__": + # load logs + lr = list(LogReader(TEST_ROUTE)) + # run replay + out_frames = imgproc_replay(lr) + + all_pix = np.concatenate([np.concatenate([d.flatten() for d in f]) for f in out_frames]) + pix_hash = hashlib.sha1(all_pix).hexdigest() + + with open('imgproc_replay_ref_hash') as f: + ref_hash = f.read() + + if pix_hash != ref_hash: + print("result changed! please check kernel") + print(f"ref: {ref_hash}") + print(f"new: {pix_hash}") + else: + print("test passed") diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 2b917b0f61..533ab125f9 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -7,57 +7,58 @@ from collections import defaultdict from tqdm import tqdm from typing import Any +from openpilot.common.git import get_commit from openpilot.selfdrive.car.car_helpers import interface_names from openpilot.tools.lib.openpilotci import get_url, upload_file from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff -from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, check_openpilot_enabled, replay_process -from openpilot.system.version import get_commit +from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, replay_process, \ + check_openpilot_enabled, check_most_messages_valid from openpilot.tools.lib.filereader import FileReader from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.helpers import save_log source_segments = [ - ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY - ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA - ("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.KIA_EV6 (+ QCOM GPS) - ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS - ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 - ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 - ("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.CIVIC (NIDEC) - ("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.ACCORD (BOSCH) - ("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.PACIFICA_2018_HYBRID - ("RAM", "17fc16d840fe9d21|2023-04-26--13-28-44--5"), # CHRYSLER.RAM_1500 - ("SUBARU", "341dccd5359e3c97|2022-09-12--10-35-33--3"), # SUBARU.OUTBACK - ("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.VOLT - ("GM2", "376bf99325883932|2022-10-27--13-41-22--1"), # GM.BOLT_EUV - ("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.XTRAIL - ("VOLKSWAGEN", "de9592456ad7d144|2021-06-29--11-00-15--6"), # VOLKSWAGEN.GOLF - ("MAZDA", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.CX9_2021 - ("FORD", "54827bf84c38b14f|2023-01-26--21-59-07--4"), # FORD.BRONCO_SPORT_MK1 + ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.COMMA_BODY + ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.HYUNDAI_SONATA + ("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.HYUNDAI_KIA_EV6 (+ QCOM GPS) + ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.TOYOTA_PRIUS + ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.TOYOTA_RAV4 + ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.TOYOTA_COROLLA_TSS2 + ("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.HONDA_CIVIC (NIDEC) + ("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.HONDA_ACCORD (BOSCH) + ("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID + ("RAM", "17fc16d840fe9d21|2023-04-26--13-28-44--5"), # CHRYSLER.RAM_1500_5TH_GEN + ("SUBARU", "341dccd5359e3c97|2022-09-12--10-35-33--3"), # SUBARU.SUBARU_OUTBACK + ("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.CHEVROLET_VOLT + ("GM2", "376bf99325883932|2022-10-27--13-41-22--1"), # GM.CHEVROLET_BOLT_EUV + ("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.NISSAN_XTRAIL + ("VOLKSWAGEN", "de9592456ad7d144|2021-06-29--11-00-15--6"), # VOLKSWAGEN.VOLKSWAGEN_GOLF + ("MAZDA", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.MAZDA_CX9_2021 + ("FORD", "54827bf84c38b14f|2023-01-26--21-59-07--4"), # FORD.FORD_BRONCO_SPORT_MK1 # Enable when port is tested and dashcamOnly is no longer set - #("TESLA", "bb50caf5f0945ab1|2021-06-19--17-20-18--3"), # TESLA.AP2_MODELS - #("VOLKSWAGEN2", "3cfdec54aa035f3f|2022-07-19--23-45-10--2"), # VOLKSWAGEN.PASSAT_NMS + #("TESLA", "bb50caf5f0945ab1|2021-06-19--17-20-18--3"), # TESLA.TESLA_AP2_MODELS + #("VOLKSWAGEN2", "3cfdec54aa035f3f|2022-07-19--23-45-10--2"), # VOLKSWAGEN.VOLKSWAGEN_PASSAT_NMS ] segments = [ - ("BODY", "regen997DF2697CB|2023-10-30--23-14-29--0"), - ("HYUNDAI", "regen2A9D2A8E0B4|2023-10-30--23-13-34--0"), - ("HYUNDAI2", "regen6CA24BC3035|2023-10-30--23-14-28--0"), - ("TOYOTA", "regen5C019D76307|2023-10-30--23-13-31--0"), - ("TOYOTA2", "regen5DCADA88A96|2023-10-30--23-14-57--0"), - ("TOYOTA3", "regen7204CA3A498|2023-10-30--23-15-55--0"), - ("HONDA", "regen048F8FA0B24|2023-10-30--23-15-53--0"), - ("HONDA2", "regen7D2D3F82D5B|2023-10-30--23-15-55--0"), - ("CHRYSLER", "regen7125C42780C|2023-10-30--23-16-21--0"), - ("RAM", "regen2731F3213D2|2023-10-30--23-18-11--0"), - ("SUBARU", "regen86E4C1B4DDD|2023-10-30--23-18-14--0"), - ("GM", "regenF6393D64745|2023-10-30--23-17-18--0"), - ("GM2", "regen220F830C05B|2023-10-30--23-18-39--0"), - ("NISSAN", "regen4F671F7C435|2023-10-30--23-18-40--0"), - ("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"), + ("BODY", "regen29FD9FF7760|2024-05-21--06-58-51--0"), + ("HYUNDAI", "regen0B1B76A1C27|2024-05-21--06-57-53--0"), + ("HYUNDAI2", "regen3BB55FA5E20|2024-05-21--06-59-03--0"), + ("TOYOTA", "regenF6FB954C1E2|2024-05-21--06-57-53--0"), + ("TOYOTA2", "regen0AC637CE7BA|2024-05-21--06-57-54--0"), + ("TOYOTA3", "regenC7BE3FAE496|2024-05-21--06-59-01--0"), + ("HONDA", "regen58E9F8B695A|2024-05-21--06-57-55--0"), + ("HONDA2", "regen8695608EB15|2024-05-21--06-57-55--0"), + ("CHRYSLER", "regenB0F8C25C902|2024-05-21--06-59-47--0"), + ("RAM", "regenB3B2C7A105B|2024-05-21--07-00-47--0"), + ("SUBARU", "regen860FD736DCC|2024-05-21--07-00-50--0"), + ("GM", "regen8CB3048DEB9|2024-05-21--06-59-49--0"), + ("GM2", "regen379D446541D|2024-05-21--07-00-51--0"), + ("NISSAN", "regen24871108F80|2024-05-21--07-00-38--0"), + ("VOLKSWAGEN", "regenF390392F275|2024-05-21--07-00-52--0"), + ("MAZDA", "regenE5A36020581|2024-05-21--07-01-51--0"), + ("FORD", "regenDC288ED0D78|2024-05-21--07-02-18--0"), ] # dashcamOnly makes don't need to be tested until a full port is done @@ -107,9 +108,15 @@ 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): - # 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 + return f"Route did not enable at all or for long enough: {new_log_path}", log_msgs + if not check_most_messages_valid(log_msgs): + return f"Route did not have enough valid messages: {new_log_path}", log_msgs + + if cfg.proc_name != 'ubloxd' or segment != 'regen3BB55FA5E20|2024-05-21--06-59-03--0': + seen_msgs = {m.which() for m in log_msgs} + expected_msgs = set(cfg.subs) + if seen_msgs != expected_msgs: + return f"Expected messages: {expected_msgs}, but got: {seen_msgs}", log_msgs try: return compare_logs(ref_log_msgs, log_msgs, ignore_fields + cfg.ignore, ignore_msgs, cfg.tolerance), log_msgs @@ -156,7 +163,8 @@ if __name__ == "__main__": assert full_test, "Need to run full test when updating refs" try: - ref_commit = open(REF_COMMIT_FN).read().strip() + with open(REF_COMMIT_FN) as f: + ref_commit = f.read().strip() except FileNotFoundError: print("Couldn't find reference commit") sys.exit(1) diff --git a/selfdrive/test/process_replay/test_regen.py b/selfdrive/test/process_replay/test_regen.py old mode 100755 new mode 100644 index 41d67ea376..17fefcb497 --- a/selfdrive/test/process_replay/test_regen.py +++ b/selfdrive/test/process_replay/test_regen.py @@ -1,7 +1,3 @@ -#!/usr/bin/env python3 - -import unittest - from parameterized import parameterized from openpilot.selfdrive.test.process_replay.regen import regen_segment, DummyFrameReader @@ -11,7 +7,7 @@ from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.framereader import FrameReader TESTED_SEGMENTS = [ - ("PRIUS_C2", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA PRIUS 2017: NEO, pandaStateDEPRECATED, no peripheralState, sensorEventsDEPRECATED + ("PRIUS_C2", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.TOYOTA_PRIUS: NEO, pandaStateDEPRECATED, no peripheralState, sensorEventsDEPRECATED # Enable these once regen on CI becomes faster or use them for different tests running controlsd in isolation # ("MAZDA_C3", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.CX9_2021: TICI, incomplete managerState # ("FORD_C3", "54827bf84c38b14f|2023-01-26--21-59-07--4"), # FORD.BRONCO_SPORT_MK1: TICI @@ -30,7 +26,7 @@ def ci_setup_data_readers(route, sidx): return lr, frs -class TestRegen(unittest.TestCase): +class TestRegen: @parameterized.expand(TESTED_SEGMENTS) def test_engaged(self, case_name, segment): route, sidx = segment.rsplit("--", 1) @@ -38,8 +34,4 @@ class TestRegen(unittest.TestCase): output_logs = regen_segment(lr, frs, disable_tqdm=True) engaged = check_openpilot_enabled(output_logs) - self.assertTrue(engaged, f"openpilot not engaged in {case_name}") - - -if __name__=='__main__': - unittest.main() + assert engaged, f"openpilot not engaged in {case_name}" diff --git a/selfdrive/test/process_replay/vision_meta.py b/selfdrive/test/process_replay/vision_meta.py index b3c3dc0c9c..12deb58724 100644 --- a/selfdrive/test/process_replay/vision_meta.py +++ b/selfdrive/test/process_replay/vision_meta.py @@ -1,17 +1,17 @@ from collections import namedtuple -from cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionStreamType from openpilot.common.realtime import DT_MDL, DT_DMON -from openpilot.common.transformations.camera import tici_f_frame_size, tici_d_frame_size, tici_e_frame_size, eon_f_frame_size, eon_d_frame_size +from openpilot.common.transformations.camera import DEVICE_CAMERAS VideoStreamMeta = namedtuple("VideoStreamMeta", ["camera_state", "encode_index", "stream", "dt", "frame_sizes"]) -ROAD_CAMERA_FRAME_SIZES = {"tici": tici_f_frame_size, "tizi": tici_f_frame_size, "neo": eon_f_frame_size} -WIDE_ROAD_CAMERA_FRAME_SIZES = {"tici": tici_e_frame_size, "tizi": tici_e_frame_size} -DRIVER_FRAME_SIZES = {"tici": tici_d_frame_size, "tizi": tici_d_frame_size, "neo": eon_d_frame_size} +ROAD_CAMERA_FRAME_SIZES = {k: (v.dcam.width, v.dcam.height) for k, v in DEVICE_CAMERAS.items()} +WIDE_ROAD_CAMERA_FRAME_SIZES = {k: (v.ecam.width, v.ecam.height) for k, v in DEVICE_CAMERAS.items() if v.ecam is not None} +DRIVER_CAMERA_FRAME_SIZES = {k: (v.dcam.width, v.dcam.height) for k, v in DEVICE_CAMERAS.items()} VIPC_STREAM_METADATA = [ # metadata: (state_msg_type, encode_msg_type, stream_type, dt, frame_sizes) ("roadCameraState", "roadEncodeIdx", VisionStreamType.VISION_STREAM_ROAD, DT_MDL, ROAD_CAMERA_FRAME_SIZES), ("wideRoadCameraState", "wideRoadEncodeIdx", VisionStreamType.VISION_STREAM_WIDE_ROAD, DT_MDL, WIDE_ROAD_CAMERA_FRAME_SIZES), - ("driverCameraState", "driverEncodeIdx", VisionStreamType.VISION_STREAM_DRIVER, DT_DMON, DRIVER_FRAME_SIZES), + ("driverCameraState", "driverEncodeIdx", VisionStreamType.VISION_STREAM_DRIVER, DT_DMON, DRIVER_CAMERA_FRAME_SIZES), ] diff --git a/selfdrive/test/profiling/lib.py b/selfdrive/test/profiling/lib.py index 7f3b0126ff..93ba215386 100644 --- a/selfdrive/test/profiling/lib.py +++ b/selfdrive/test/profiling/lib.py @@ -8,7 +8,7 @@ class ReplayDone(Exception): pass -class SubSocket(): +class SubSocket: def __init__(self, msgs, trigger): self.i = 0 self.trigger = trigger @@ -28,7 +28,7 @@ class SubSocket(): return msg -class PubSocket(): +class PubSocket: def send(self, data): pass diff --git a/selfdrive/test/profiling/profiler.py b/selfdrive/test/profiling/profiler.py index 6571825418..2cd547171a 100755 --- a/selfdrive/test/profiling/profiler.py +++ b/selfdrive/test/profiling/profiler.py @@ -16,8 +16,8 @@ from openpilot.selfdrive.car.volkswagen.values import CAR as VW BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" CARS = { - 'toyota': ("0982d79ebb0de295|2021-01-03--20-03-36/6", TOYOTA.RAV4), - 'honda': ("0982d79ebb0de295|2021-01-08--10-13-10/6", HONDA.CIVIC), + 'toyota': ("0982d79ebb0de295|2021-01-03--20-03-36/6", TOYOTA.TOYOTA_RAV4), + 'honda': ("0982d79ebb0de295|2021-01-08--10-13-10/6", HONDA.HONDA_CIVIC), "vw": ("ef895f46af5fd73f|2021-05-22--14-06-35/6", VW.AUDI_A3_MK3), } diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index 5c85312f1a..84dae25821 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -70,6 +70,7 @@ safe_checkout() { git checkout $GIT_COMMIT git clean -xdff git submodule sync + git submodule foreach --recursive "git reset --hard && git clean -xdff" git submodule update --init --recursive git submodule foreach --recursive "git reset --hard && git clean -xdff" @@ -95,6 +96,7 @@ unsafe_checkout() { git reset --hard $GIT_COMMIT git clean -df git submodule sync + git submodule foreach --recursive "git reset --hard && git clean -df" git submodule update --init --recursive git submodule foreach --recursive "git reset --hard && git clean -df" diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py old mode 100755 new mode 100644 index 8f8c93ecff..7b44a3b80e --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import bz2 import math import json @@ -10,7 +9,6 @@ import shutil import subprocess import time import numpy as np -import unittest from collections import Counter, defaultdict from functools import cached_property from pathlib import Path @@ -27,35 +25,41 @@ from openpilot.selfdrive.test.helpers import set_params_enabled, release_only from openpilot.system.hardware.hw import Paths from openpilot.tools.lib.logreader import LogReader -# Baseline CPU usage by process +""" +CPU usage budget +* each process is entitled to at least 8% +* total CPU usage of openpilot (sum(PROCS.values()) + should not exceed MAX_TOTAL_CPU +""" +MAX_TOTAL_CPU = 250. # total for all 8 cores PROCS = { - "selfdrive.controls.controlsd": 41.0, - "./loggerd": 14.0, - "./encoderd": 17.0, - "./camerad": 14.5, - "./locationd": 11.0, - "./mapsd": (0.5, 10.0), + # Baseline CPU usage by process + "selfdrive.controls.controlsd": 32.0, + "selfdrive.car.card": 22.0, + "loggerd": 14.0, + "encoderd": 17.0, + "camerad": 14.5, + "locationd": 11.0, "selfdrive.controls.plannerd": 11.0, - "./ui": 18.0, + "ui": 18.0, "selfdrive.locationd.paramsd": 9.0, - "./sensord": 7.0, + "sensord": 7.0, "selfdrive.controls.radard": 7.0, - "selfdrive.modeld.modeld": 13.0, + "modeld": 13.0, "selfdrive.modeld.dmonitoringmodeld": 8.0, - "selfdrive.modeld.navmodeld": 1.0, - "selfdrive.thermald.thermald": 3.87, + "system.hardware.hardwared": 3.87, "selfdrive.locationd.calibrationd": 2.0, "selfdrive.locationd.torqued": 5.0, "selfdrive.ui.soundd": 3.5, "selfdrive.monitoring.dmonitoringd": 4.0, - "./proclogd": 1.54, + "proclogd": 1.54, "system.logmessaged": 0.2, - "selfdrive.tombstoned": 0, - "./logcatd": 0, + "system.tombstoned": 0, + "logcatd": 0, "system.micd": 6.0, "system.timed": 0, - "selfdrive.boardd.pandad": 0, - "selfdrive.statsd": 0.4, + "selfdrive.pandad.pandad": 0, + "system.statsd": 0.4, "selfdrive.navd.navd": 0.4, "system.loggerd.uploader": (0.5, 15.0), "system.loggerd.deleter": 0.1, @@ -63,12 +67,12 @@ PROCS = { PROCS.update({ "tici": { - "./boardd": 4.0, - "./ubloxd": 0.02, - "system.sensord.pigeond": 6.0, + "pandad": 4.0, + "ubloxd": 0.02, + "system.ubloxd.pigeond": 6.0, }, "tizi": { - "./boardd": 19.0, + "pandad": 19.0, "system.qcomgpsd.qcomgpsd": 1.0, } }.get(HARDWARE.get_device_type(), {})) @@ -87,8 +91,6 @@ TIMINGS = { "driverCameraState": [2.5, 0.35], "modelV2": [2.5, 0.35], "driverStateV2": [2.5, 0.40], - "navModel": [2.5, 0.35], - "mapRenderState": [2.5, 0.35], "liveLocationKalman": [2.5, 0.35], "wideRoadCameraState": [1.5, 0.35], } @@ -99,10 +101,10 @@ def cputime_total(ct): @pytest.mark.tici -class TestOnroad(unittest.TestCase): +class TestOnroad: @classmethod - def setUpClass(cls): + def setup_class(cls): if "DEBUG" in os.environ: segs = filter(lambda x: os.path.exists(os.path.join(x, "rlog")), Path(Paths.log_root()).iterdir()) segs = sorted(segs, key=lambda x: x.stat().st_mtime) @@ -122,7 +124,7 @@ class TestOnroad(unittest.TestCase): # start manager and run openpilot for a minute proc = None try: - manager_path = os.path.join(BASEDIR, "selfdrive/manager/manager.py") + manager_path = os.path.join(BASEDIR, "system/manager/manager.py") proc = subprocess.Popen(["python", manager_path]) sm = messaging.SubMaster(['carState']) @@ -178,7 +180,7 @@ class TestOnroad(unittest.TestCase): msgs[m.which()].append(m) return msgs - def test_service_frequencies(self): + def test_service_frequencies(self, subtests): for s, msgs in self.service_msgs.items(): if s in ('initData', 'sentinel'): continue @@ -187,18 +189,18 @@ class TestOnroad(unittest.TestCase): if s in ('ubloxGnss', 'ubloxRaw', 'gnssMeasurements', 'gpsLocation', 'gpsLocationExternal', 'qcomGnss'): continue - with self.subTest(service=s): + with subtests.test(service=s): assert len(msgs) >= math.floor(SERVICE_LIST[s].frequency*55) def test_cloudlog_size(self): msgs = [m for m in self.lr if m.which() == 'logMessage'] total_size = sum(len(m.as_builder().to_bytes()) for m in msgs) - self.assertLess(total_size, 3.5e5) + assert total_size < 3.5e5 cnt = Counter(json.loads(m.logMessage)['filename'] for m in msgs) big_logs = [f for f, n in cnt.most_common(3) if n / sum(cnt.values()) > 30.] - self.assertEqual(len(big_logs), 0, f"Log spam: {big_logs}") + assert len(big_logs) == 0, f"Log spam: {big_logs}" def test_log_sizes(self): for f, sz in self.log_sizes.items(): @@ -227,15 +229,15 @@ class TestOnroad(unittest.TestCase): result += "------------------------------------------------\n" print(result) - self.assertLess(max(ts), 250.) - self.assertLess(np.mean(ts), 10.) + assert max(ts) < 250. + assert np.mean(ts) < 10. #self.assertLess(np.std(ts), 5.) # some slow frames are expected since camerad/modeld can preempt ui veryslow = [x for x in ts if x > 40.] assert len(veryslow) < 5, f"Too many slow frame draw times: {veryslow}" - def test_cpu_usage(self): + def test_cpu_usage(self, subtests): result = "\n" result += "------------------------------------------------\n" result += "------------------ CPU Usage -------------------\n" @@ -245,8 +247,7 @@ class TestOnroad(unittest.TestCase): for pl in self.service_msgs['procLog']: for x in pl.procLog.procs: if len(x.cmdline) > 0: - n = list(x.cmdline)[0] - plogs_by_proc[n].append(x) + plogs_by_proc[x.name].append(x) print(plogs_by_proc.keys()) cpu_ok = True @@ -254,8 +255,9 @@ class TestOnroad(unittest.TestCase): for proc_name, expected_cpu in PROCS.items(): err = "" + exp = "???" cpu_usage = 0. - x = plogs_by_proc[proc_name] + x = plogs_by_proc[proc_name[-15:]] if len(x) > 2: cpu_time = cputime_total(x[-1]) - cputime_total(x[0]) cpu_usage = cpu_time / dt * 100. @@ -278,17 +280,25 @@ class TestOnroad(unittest.TestCase): result += f"{proc_name.ljust(35)} {cpu_usage:5.2f}% ({exp}%) {err}\n" if len(err) > 0: cpu_ok = False + result += "------------------------------------------------\n" # Ensure there's no missing procs all_procs = {p.name for p in self.service_msgs['managerState'][0].managerState.processes if p.shouldBeRunning} for p in all_procs: - with self.subTest(proc=p): + with subtests.test(proc=p): assert any(p in pp for pp in PROCS.keys()), f"Expected CPU usage missing for {p}" - result += "------------------------------------------------\n" + # total CPU check + procs_tot = sum([(max(x) if isinstance(x, tuple) else x) for x in PROCS.values()]) + with subtests.test(name="total CPU"): + assert procs_tot < MAX_TOTAL_CPU, "Total CPU budget exceeded" + result += "------------------------------------------------\n" + result += f"Total allocated CPU usage is {procs_tot}%, budget is {MAX_TOTAL_CPU}%, {MAX_TOTAL_CPU-procs_tot:.1f}% left\n" + result += "------------------------------------------------\n" + print(result) - self.assertTrue(cpu_ok) + assert cpu_ok def test_memory_usage(self): mems = [m.deviceState.memoryUsagePercent for m in self.service_msgs['deviceState']] @@ -296,26 +306,26 @@ class TestOnroad(unittest.TestCase): # check for big leaks. note that memory usage is # expected to go up while the MSGQ buffers fill up - self.assertLessEqual(max(mems) - min(mems), 3.0) + assert max(mems) - min(mems) <= 3.0 def test_gpu_usage(self): - self.assertEqual(self.gpu_procs, {"weston", "ui", "camerad", "selfdrive.modeld.modeld"}) + assert self.gpu_procs == {"weston", "ui", "camerad", "modeld"} def test_camera_processing_time(self): result = "\n" result += "------------------------------------------------\n" - result += "-------------- Debayer Timing ------------------\n" + result += "-------------- ImgProc Timing ------------------\n" result += "------------------------------------------------\n" ts = [getattr(m, m.which()).processingTime for m in self.lr if 'CameraState' in m.which()] - self.assertLess(min(ts), 0.025, f"high execution time: {min(ts)}") + assert min(ts) < 0.025, f"high execution time: {min(ts)}" result += f"execution time: min {min(ts):.5f}s\n" result += f"execution time: max {max(ts):.5f}s\n" result += f"execution time: mean {np.mean(ts):.5f}s\n" result += "------------------------------------------------\n" print(result) - @unittest.skip("TODO: enable once timings are fixed") + @pytest.mark.skip("TODO: enable once timings are fixed") def test_camera_frame_timings(self): result = "\n" result += "------------------------------------------------\n" @@ -325,7 +335,7 @@ class TestOnroad(unittest.TestCase): ts = [getattr(m, m.which()).timestampSof for m in self.lr if name in m.which()] d_ms = np.diff(ts) / 1e6 d50 = np.abs(d_ms-50) - self.assertLess(max(d50), 1.0, f"high sof delta vs 50ms: {max(d50)}") + assert max(d50) < 1.0, f"high sof delta vs 50ms: {max(d50)}" result += f"{name} sof delta vs 50ms: min {min(d50):.5f}s\n" result += f"{name} sof delta vs 50ms: max {max(d50):.5f}s\n" result += f"{name} sof delta vs 50ms: mean {d50.mean():.5f}s\n" @@ -341,8 +351,8 @@ class TestOnroad(unittest.TestCase): cfgs = [("longitudinalPlan", 0.05, 0.05),] for (s, instant_max, avg_max) in cfgs: ts = [getattr(m, s).solverExecutionTime for m in self.service_msgs[s]] - self.assertLess(max(ts), instant_max, f"high '{s}' execution time: {max(ts)}") - self.assertLess(np.mean(ts), avg_max, f"high avg '{s}' execution time: {np.mean(ts)}") + assert max(ts) < instant_max, f"high '{s}' execution time: {max(ts)}" + assert np.mean(ts) < avg_max, f"high avg '{s}' execution time: {np.mean(ts)}" result += f"'{s}' execution time: min {min(ts):.5f}s\n" result += f"'{s}' execution time: max {max(ts):.5f}s\n" result += f"'{s}' execution time: mean {np.mean(ts):.5f}s\n" @@ -361,8 +371,8 @@ class TestOnroad(unittest.TestCase): ] for (s, instant_max, avg_max) in cfgs: ts = [getattr(m, s).modelExecutionTime for m in self.service_msgs[s]] - self.assertLess(max(ts), instant_max, f"high '{s}' execution time: {max(ts)}") - self.assertLess(np.mean(ts), avg_max, f"high avg '{s}' execution time: {np.mean(ts)}") + assert max(ts) < instant_max, f"high '{s}' execution time: {max(ts)}" + assert np.mean(ts) < avg_max, f"high avg '{s}' execution time: {np.mean(ts)}" result += f"'{s}' execution time: min {min(ts):.5f}s\n" result += f"'{s}' execution time: max {max(ts):.5f}s\n" result += f"'{s}' execution time: mean {np.mean(ts):.5f}s\n" @@ -398,7 +408,7 @@ class TestOnroad(unittest.TestCase): result += f"{''.ljust(40)} {np.max(np.absolute([np.max(ts)/dt, np.min(ts)/dt]))} {np.std(ts)/dt}\n" result += "="*67 print(result) - self.assertTrue(passed) + assert passed @release_only def test_startup(self): @@ -409,7 +419,7 @@ class TestOnroad(unittest.TestCase): startup_alert = msg.controlsState.alertText1 break expected = EVENTS[car.CarEvent.EventName.startup][ET.PERMANENT].alert_text_1 - self.assertEqual(startup_alert, expected, "wrong startup alert") + assert startup_alert == expected, "wrong startup alert" def test_engagable(self): no_entries = Counter() @@ -421,7 +431,3 @@ class TestOnroad(unittest.TestCase): eng = [m.controlsState.engageable for m in self.service_msgs['controlsState']] assert all(eng), \ f"Not engageable for whole segment:\n- controlsState.engageable: {Counter(eng)}\n- No entry events: {no_entries}" - - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/test/test_time_to_onroad.py b/selfdrive/test/test_time_to_onroad.py old mode 100755 new mode 100644 index a3f803e221..e08d0e676c --- a/selfdrive/test/test_time_to_onroad.py +++ b/selfdrive/test/test_time_to_onroad.py @@ -1,24 +1,26 @@ -#!/usr/bin/env python3 import os import pytest import time import subprocess +from cereal import car import cereal.messaging as messaging from openpilot.common.basedir import BASEDIR from openpilot.common.timeout import Timeout from openpilot.selfdrive.test.helpers import set_params_enabled +EventName = car.CarEvent.EventName + @pytest.mark.tici def test_time_to_onroad(): # launch set_params_enabled() - manager_path = os.path.join(BASEDIR, "selfdrive/manager/manager.py") + manager_path = os.path.join(BASEDIR, "system/manager/manager.py") proc = subprocess.Popen(["python", manager_path]) start_time = time.monotonic() - sm = messaging.SubMaster(['controlsState', 'deviceState', 'onroadEvents', 'sendcan']) + sm = messaging.SubMaster(['controlsState', 'deviceState', 'onroadEvents']) try: # wait for onroad. timeout assumes panda is up to date with Timeout(10, "timed out waiting to go onroad"): @@ -28,15 +30,14 @@ def test_time_to_onroad(): # wait for engageability try: with Timeout(10, "timed out waiting for engageable"): - sendcan_frame = None + initialized = False 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 sm.seen['onroadEvents'] and not any(EventName.controlsInitializing == e.name for e in sm['onroadEvents']): + initialized = True - if sendcan_frame is not None and sm.recv_frame['sendcan'] > sendcan_frame: + if initialized: sm.update(100) assert sm['controlsState'].engageable, f"events: {sm['onroadEvents']}" break diff --git a/selfdrive/test/test_updated.py b/selfdrive/test/test_updated.py old mode 100755 new mode 100644 index dd79e03de4..ea945d94c2 --- a/selfdrive/test/test_updated.py +++ b/selfdrive/test/test_updated.py @@ -1,10 +1,8 @@ -#!/usr/bin/env python3 import datetime import os import pytest import time import tempfile -import unittest import shutil import signal import subprocess @@ -15,9 +13,9 @@ from openpilot.common.params import Params @pytest.mark.tici -class TestUpdated(unittest.TestCase): +class TestUpdated: - def setUp(self): + def setup_method(self): self.updated_proc = None self.tmp_dir = tempfile.TemporaryDirectory() @@ -59,7 +57,7 @@ class TestUpdated(unittest.TestCase): self.params.clear_all() os.sync() - def tearDown(self): + def teardown_method(self): try: if self.updated_proc is not None: self.updated_proc.terminate() @@ -90,7 +88,7 @@ class TestUpdated(unittest.TestCase): os.environ["UPDATER_STAGING_ROOT"] = self.staging_dir os.environ["UPDATER_NEOS_VERSION"] = self.neos_version os.environ["UPDATER_NEOSUPDATE_DIR"] = self.neosupdate_dir - updated_path = os.path.join(self.basedir, "selfdrive/updated.py") + updated_path = os.path.join(self.basedir, "system/updated.py") return subprocess.Popen(updated_path, env=os.environ) def _start_updater(self, offroad=True, nosleep=False): @@ -166,8 +164,8 @@ class TestUpdated(unittest.TestCase): # make sure LastUpdateTime is recent t = self._read_param("LastUpdateTime") last_update_time = datetime.datetime.fromisoformat(t) - td = datetime.datetime.utcnow() - last_update_time - self.assertLess(td.total_seconds(), 10) + td = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - last_update_time + assert td.total_seconds() < 10 self.params.remove("LastUpdateTime") # wait a bit for the rest of the params to be written @@ -175,13 +173,13 @@ class TestUpdated(unittest.TestCase): # check params update = self._read_param("UpdateAvailable") - self.assertEqual(update == "1", update_available, f"UpdateAvailable: {repr(update)}") - self.assertEqual(self._read_param("UpdateFailedCount"), "0") + assert update == "1" == update_available, f"UpdateAvailable: {repr(update)}" + assert self._read_param("UpdateFailedCount") == "0" # TODO: check that the finalized update actually matches remote # check the .overlay_init and .overlay_consistent flags - self.assertTrue(os.path.isfile(os.path.join(self.basedir, ".overlay_init"))) - self.assertEqual(os.path.isfile(os.path.join(self.finalized_dir, ".overlay_consistent")), update_available) + assert os.path.isfile(os.path.join(self.basedir, ".overlay_init")) + assert os.path.isfile(os.path.join(self.finalized_dir, ".overlay_consistent")) == update_available # *** test cases *** @@ -214,7 +212,7 @@ class TestUpdated(unittest.TestCase): self._check_update_state(True) # Let the updater run for 10 cycles, and write an update every cycle - @unittest.skip("need to make this faster") + @pytest.mark.skip("need to make this faster") def test_update_loop(self): self._start_updater() @@ -243,12 +241,12 @@ class TestUpdated(unittest.TestCase): # run another cycle, should have a new mtime self._wait_for_update(clear_param=True) second_mtime = os.path.getmtime(overlay_init_fn) - self.assertTrue(first_mtime != second_mtime) + assert first_mtime != second_mtime # run another cycle, mtime should be same as last cycle self._wait_for_update(clear_param=True) new_mtime = os.path.getmtime(overlay_init_fn) - self.assertTrue(second_mtime == new_mtime) + assert second_mtime == new_mtime # Make sure updated exits if another instance is running def test_multiple_instances(self): @@ -260,7 +258,7 @@ class TestUpdated(unittest.TestCase): # start another instance second_updated = self._get_updated_proc() ret_code = second_updated.wait(timeout=5) - self.assertTrue(ret_code is not None) + assert ret_code is not None # *** test cases with NEOS updates *** @@ -277,10 +275,10 @@ class TestUpdated(unittest.TestCase): self._start_updater() self._wait_for_update(clear_param=True) self._check_update_state(False) - self.assertFalse(os.path.isdir(self.neosupdate_dir)) + assert not os.path.isdir(self.neosupdate_dir) # Let the updater run with no update for a cycle, then write an update - @unittest.skip("TODO: only runs on device") + @pytest.mark.skip("TODO: only runs on device") def test_update_with_neos_update(self): # bump the NEOS version and commit it self._run([ @@ -295,8 +293,4 @@ class TestUpdated(unittest.TestCase): self._check_update_state(True) # TODO: more comprehensive check - self.assertTrue(os.path.isdir(self.neosupdate_dir)) - - -if __name__ == "__main__": - unittest.main() + assert os.path.isdir(self.neosupdate_dir) diff --git a/selfdrive/test/test_valgrind_replay.py b/selfdrive/test/test_valgrind_replay.py deleted file mode 100755 index 75520df91b..0000000000 --- a/selfdrive/test/test_valgrind_replay.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 -import os -import threading -import time -import unittest -import subprocess -import signal - -if "CI" in os.environ: - def tqdm(x): - return x -else: - from tqdm import tqdm # type: ignore - -import cereal.messaging as messaging -from collections import namedtuple -from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.openpilotci import get_url -from openpilot.common.basedir import BASEDIR - -ProcessConfig = namedtuple('ProcessConfig', ['proc_name', 'pub_sub', 'ignore', 'command', 'path', 'segment', 'wait_for_response']) - -CONFIGS = [ - ProcessConfig( - proc_name="ubloxd", - pub_sub={ - "ubloxRaw": ["ubloxGnss", "gpsLocationExternal"], - }, - ignore=[], - command="./ubloxd", - path="system/ubloxd", - segment="0375fdf7b1ce594d|2019-06-13--08-32-25--3", - wait_for_response=True - ), -] - - -class TestValgrind(unittest.TestCase): - def extract_leak_sizes(self, log): - if "All heap blocks were freed -- no leaks are possible" in log: - return (0,0,0) - - log = log.replace(",","") # fixes casting to int issue with large leaks - err_lost1 = log.split("definitely lost: ")[1] - err_lost2 = log.split("indirectly lost: ")[1] - err_lost3 = log.split("possibly lost: ")[1] - definitely_lost = int(err_lost1.split(" ")[0]) - indirectly_lost = int(err_lost2.split(" ")[0]) - possibly_lost = int(err_lost3.split(" ")[0]) - return (definitely_lost, indirectly_lost, possibly_lost) - - def valgrindlauncher(self, arg, cwd): - os.chdir(os.path.join(BASEDIR, cwd)) - # Run valgrind on a process - command = "valgrind --leak-check=full " + arg - p = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True, preexec_fn=os.setsid) - - while not self.replay_done: - time.sleep(0.1) - - # Kill valgrind and extract leak output - os.killpg(os.getpgid(p.pid), signal.SIGINT) - _, err = p.communicate() - error_msg = str(err, encoding='utf-8') - with open(os.path.join(BASEDIR, "selfdrive/test/valgrind_logs.txt"), "a") as f: - f.write(error_msg) - f.write(5 * "\n") - definitely_lost, indirectly_lost, possibly_lost = self.extract_leak_sizes(error_msg) - if max(definitely_lost, indirectly_lost, possibly_lost) > 0: - self.leak = True - print("LEAKS from", arg, "\nDefinitely lost:", definitely_lost, "\nIndirectly lost", indirectly_lost, "\nPossibly lost", possibly_lost) - else: - self.leak = False - - def replay_process(self, config, logreader): - pub_sockets = list(config.pub_sub.keys()) # We dump data from logs here - sub_sockets = [s for _, sub in config.pub_sub.items() for s in sub] # We get responses here - pm = messaging.PubMaster(pub_sockets) - sm = messaging.SubMaster(sub_sockets) - - print("Sorting logs") - all_msgs = sorted(logreader, key=lambda msg: msg.logMonoTime) - pub_msgs = [msg for msg in all_msgs if msg.which() in list(config.pub_sub.keys())] - - thread = threading.Thread(target=self.valgrindlauncher, args=(config.command, config.path)) - thread.daemon = True - thread.start() - - while not all(pm.all_readers_updated(s) for s in config.pub_sub.keys()): - time.sleep(0) - - for msg in tqdm(pub_msgs): - pm.send(msg.which(), msg.as_builder()) - if config.wait_for_response: - sm.update(100) - - self.replay_done = True - - def test_config(self): - open(os.path.join(BASEDIR, "selfdrive/test/valgrind_logs.txt"), "w").close() - - for cfg in CONFIGS: - self.leak = None - self.replay_done = False - - r, n = cfg.segment.rsplit("--", 1) - lr = LogReader(get_url(r, n)) - self.replay_process(cfg, lr) - - while self.leak is None: - time.sleep(0.1) # Wait for the valgrind to finish - - self.assertFalse(self.leak) - - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/thermald/tests/__init__.py b/selfdrive/thermald/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/selfdrive/thermald/tests/test_fan_controller.py b/selfdrive/thermald/tests/test_fan_controller.py deleted file mode 100755 index 7081e1353e..0000000000 --- a/selfdrive/thermald/tests/test_fan_controller.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -import unittest -from unittest.mock import Mock, patch -from parameterized import parameterized - -from openpilot.selfdrive.thermald.fan_controller import TiciFanController - -ALL_CONTROLLERS = [(TiciFanController,)] - -def patched_controller(controller_class): - with patch("os.system", new=Mock()): - return controller_class() - -class TestFanController(unittest.TestCase): - def wind_up(self, controller, ignition=True): - for _ in range(1000): - controller.update(100, ignition) - - def wind_down(self, controller, ignition=False): - for _ in range(1000): - controller.update(10, ignition) - - @parameterized.expand(ALL_CONTROLLERS) - def test_hot_onroad(self, controller_class): - controller = patched_controller(controller_class) - self.wind_up(controller) - self.assertGreaterEqual(controller.update(100, True), 70) - - @parameterized.expand(ALL_CONTROLLERS) - def test_offroad_limits(self, controller_class): - controller = patched_controller(controller_class) - self.wind_up(controller) - self.assertLessEqual(controller.update(100, False), 30) - - @parameterized.expand(ALL_CONTROLLERS) - def test_no_fan_wear(self, controller_class): - controller = patched_controller(controller_class) - self.wind_down(controller) - self.assertEqual(controller.update(10, False), 0) - - @parameterized.expand(ALL_CONTROLLERS) - def test_limited(self, controller_class): - controller = patched_controller(controller_class) - self.wind_up(controller, True) - self.assertEqual(controller.update(100, True), 100) - - @parameterized.expand(ALL_CONTROLLERS) - def test_windup_speed(self, controller_class): - controller = patched_controller(controller_class) - self.wind_down(controller, True) - for _ in range(10): - controller.update(90, True) - self.assertGreaterEqual(controller.update(90, True), 60) - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/thermald/tests/test_power_monitoring.py b/selfdrive/thermald/tests/test_power_monitoring.py deleted file mode 100755 index c3a890f068..0000000000 --- a/selfdrive/thermald/tests/test_power_monitoring.py +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env python3 -import unittest -from unittest.mock import patch - -from openpilot.common.params import Params -from openpilot.selfdrive.thermald.power_monitoring import PowerMonitoring, CAR_BATTERY_CAPACITY_uWh, \ - CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S - - -# Create fake time -ssb = 0. -def mock_time_monotonic(): - global ssb - ssb += 1. - return ssb - -TEST_DURATION_S = 50 -GOOD_VOLTAGE = 12 * 1e3 -VOLTAGE_BELOW_PAUSE_CHARGING = (VBATT_PAUSE_CHARGING - 1) * 1e3 - -def pm_patch(name, value, constant=False): - if constant: - return patch(f"openpilot.selfdrive.thermald.power_monitoring.{name}", value) - return patch(f"openpilot.selfdrive.thermald.power_monitoring.{name}", return_value=value) - - -@patch("time.monotonic", new=mock_time_monotonic) -class TestPowerMonitoring(unittest.TestCase): - def setUp(self): - self.params = Params() - - # Test to see that it doesn't do anything when pandaState is None - def test_pandaState_present(self): - pm = PowerMonitoring() - for _ in range(10): - pm.calculate(None, None) - self.assertEqual(pm.get_power_used(), 0) - self.assertEqual(pm.get_car_battery_capacity(), (CAR_BATTERY_CAPACITY_uWh / 10)) - - # Test to see that it doesn't integrate offroad when ignition is True - def test_offroad_ignition(self): - pm = PowerMonitoring() - for _ in range(10): - pm.calculate(GOOD_VOLTAGE, True) - self.assertEqual(pm.get_power_used(), 0) - - # Test to see that it integrates with discharging battery - def test_offroad_integration_discharging(self): - POWER_DRAW = 4 - with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): - pm = PowerMonitoring() - for _ in range(TEST_DURATION_S + 1): - pm.calculate(GOOD_VOLTAGE, False) - expected_power_usage = ((TEST_DURATION_S/3600) * POWER_DRAW * 1e6) - self.assertLess(abs(pm.get_power_used() - expected_power_usage), 10) - - # Test to check positive integration of car_battery_capacity - def test_car_battery_integration_onroad(self): - POWER_DRAW = 4 - with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): - pm = PowerMonitoring() - pm.car_battery_capacity_uWh = 0 - for _ in range(TEST_DURATION_S + 1): - pm.calculate(GOOD_VOLTAGE, True) - expected_capacity = ((TEST_DURATION_S/3600) * CAR_CHARGING_RATE_W * 1e6) - self.assertLess(abs(pm.get_car_battery_capacity() - expected_capacity), 10) - - # Test to check positive integration upper limit - def test_car_battery_integration_upper_limit(self): - POWER_DRAW = 4 - with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): - pm = PowerMonitoring() - pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh - 1000 - for _ in range(TEST_DURATION_S + 1): - pm.calculate(GOOD_VOLTAGE, True) - estimated_capacity = CAR_BATTERY_CAPACITY_uWh + (CAR_CHARGING_RATE_W / 3600 * 1e6) - self.assertLess(abs(pm.get_car_battery_capacity() - estimated_capacity), 10) - - # Test to check negative integration of car_battery_capacity - def test_car_battery_integration_offroad(self): - POWER_DRAW = 4 - with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): - pm = PowerMonitoring() - pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh - for _ in range(TEST_DURATION_S + 1): - pm.calculate(GOOD_VOLTAGE, False) - expected_capacity = CAR_BATTERY_CAPACITY_uWh - ((TEST_DURATION_S/3600) * POWER_DRAW * 1e6) - self.assertLess(abs(pm.get_car_battery_capacity() - expected_capacity), 10) - - # Test to check negative integration lower limit - def test_car_battery_integration_lower_limit(self): - POWER_DRAW = 4 - with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): - pm = PowerMonitoring() - pm.car_battery_capacity_uWh = 1000 - for _ in range(TEST_DURATION_S + 1): - pm.calculate(GOOD_VOLTAGE, False) - estimated_capacity = 0 - ((1/3600) * POWER_DRAW * 1e6) - self.assertLess(abs(pm.get_car_battery_capacity() - estimated_capacity), 10) - - # Test to check policy of stopping charging after MAX_TIME_OFFROAD_S - def test_max_time_offroad(self): - MOCKED_MAX_OFFROAD_TIME = 3600 - POWER_DRAW = 0 # To stop shutting down for other reasons - with pm_patch("MAX_TIME_OFFROAD_S", MOCKED_MAX_OFFROAD_TIME, constant=True), pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): - pm = PowerMonitoring() - pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh - start_time = ssb - ignition = False - while ssb <= start_time + MOCKED_MAX_OFFROAD_TIME: - pm.calculate(GOOD_VOLTAGE, ignition) - if (ssb - start_time) % 1000 == 0 and ssb < start_time + MOCKED_MAX_OFFROAD_TIME: - self.assertFalse(pm.should_shutdown(ignition, True, start_time, False)) - self.assertTrue(pm.should_shutdown(ignition, True, start_time, False)) - - def test_car_voltage(self): - POWER_DRAW = 0 # To stop shutting down for other reasons - TEST_TIME = 350 - VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 50 - with pm_patch("VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S", VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S, constant=True), \ - pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): - pm = PowerMonitoring() - pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh - ignition = False - start_time = ssb - for i in range(TEST_TIME): - pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) - if i % 10 == 0: - self.assertEqual(pm.should_shutdown(ignition, True, start_time, True), - (pm.car_voltage_mV < VBATT_PAUSE_CHARGING * 1e3 and - (ssb - start_time) > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S and - (ssb - start_time) > DELAY_SHUTDOWN_TIME_S)) - self.assertTrue(pm.should_shutdown(ignition, True, start_time, True)) - - # Test to check policy of not stopping charging when DisablePowerDown is set - def test_disable_power_down(self): - POWER_DRAW = 0 # To stop shutting down for other reasons - TEST_TIME = 100 - self.params.put_bool("DisablePowerDown", True) - with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): - pm = PowerMonitoring() - pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh - ignition = False - for i in range(TEST_TIME): - pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) - if i % 10 == 0: - self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) - self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) - - # Test to check policy of not stopping charging when ignition - def test_ignition(self): - POWER_DRAW = 0 # To stop shutting down for other reasons - TEST_TIME = 100 - with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): - pm = PowerMonitoring() - pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh - ignition = True - for i in range(TEST_TIME): - pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) - if i % 10 == 0: - self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) - self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) - - # Test to check policy of not stopping charging when harness is not connected - def test_harness_connection(self): - POWER_DRAW = 0 # To stop shutting down for other reasons - TEST_TIME = 100 - with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): - pm = PowerMonitoring() - pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh - - ignition = False - for i in range(TEST_TIME): - pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) - if i % 10 == 0: - self.assertFalse(pm.should_shutdown(ignition, False, ssb, False)) - self.assertFalse(pm.should_shutdown(ignition, False, ssb, False)) - - def test_delay_shutdown_time(self): - pm = PowerMonitoring() - pm.car_battery_capacity_uWh = 0 - ignition = False - in_car = True - offroad_timestamp = ssb - started_seen = True - pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) - - while ssb < offroad_timestamp + DELAY_SHUTDOWN_TIME_S: - self.assertFalse(pm.should_shutdown(ignition, in_car, - offroad_timestamp, - started_seen), - f"Should not shutdown before {DELAY_SHUTDOWN_TIME_S} seconds offroad time") - self.assertTrue(pm.should_shutdown(ignition, in_car, - offroad_timestamp, - started_seen), - f"Should shutdown after {DELAY_SHUTDOWN_TIME_S} seconds offroad time") - - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index ea5734fe6e..e181cb9abd 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,10 +1,9 @@ import os import json -Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', - 'cereal', 'transformations') +Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') -base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', - 'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] +base_libs = [common, messaging, visionipc, transformations, + 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == 'larch64': base_libs.append('EGL') @@ -36,10 +35,12 @@ widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) Export('widgets') qt_libs = [widgets, qt_util] + base_libs -qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc", +qt_src = ["main.cc", "qt/sidebar.cc", "qt/body.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", "qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc", - "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc"] + "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc", + "qt/onroad/onroad_home.cc", "qt/onroad/annotated_camera.cc", + "qt/onroad/buttons.cc", "qt/onroad/alerts.cc"] # build translation files with open(File("translations/languages.json").abspath) as f: @@ -125,4 +126,4 @@ if GetOption('extras') and arch != "Darwin": # build watch3 if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'): - qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11', 'zmq', 'visionipc', 'messaging']) + qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11', 'msgq', 'visionipc']) diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc index 0e321d4e10..6889b40e51 100644 --- a/selfdrive/ui/qt/api.cc +++ b/selfdrive/ui/qt/api.cc @@ -1,6 +1,5 @@ #include "selfdrive/ui/qt/api.h" -#include #include #include @@ -8,38 +7,41 @@ #include #include #include -#include #include #include +#include #include -#include "common/params.h" #include "common/util.h" #include "system/hardware/hw.h" #include "selfdrive/ui/qt/util.h" namespace CommaApi { -QByteArray rsa_sign(const QByteArray &data) { - static std::string key = util::read_file(Path::rsa_file()); - if (key.empty()) { - qDebug() << "No RSA private key found, please run manager.py or registration.py"; - return {}; +RSA *get_rsa_private_key() { + static std::unique_ptr rsa_private(nullptr, RSA_free); + if (!rsa_private) { + FILE *fp = fopen(Path::rsa_file().c_str(), "rb"); + if (!fp) { + qDebug() << "No RSA private key found, please run manager.py or registration.py"; + return nullptr; + } + rsa_private.reset(PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL)); + fclose(fp); } + return rsa_private.get(); +} - BIO* mem = BIO_new_mem_buf(key.data(), key.size()); - assert(mem); - RSA* rsa_private = PEM_read_bio_RSAPrivateKey(mem, NULL, NULL, NULL); - assert(rsa_private); - auto sig = QByteArray(); - sig.resize(RSA_size(rsa_private)); +QByteArray rsa_sign(const QByteArray &data) { + RSA *rsa_private = get_rsa_private_key(); + if (!rsa_private) return {}; + + QByteArray sig(RSA_size(rsa_private), Qt::Uninitialized); unsigned int sig_len; int ret = RSA_sign(NID_sha256, (unsigned char*)data.data(), data.size(), (unsigned char*)sig.data(), &sig_len, rsa_private); assert(ret == 1); - assert(sig_len == sig.size()); - BIO_free(mem); - RSA_free(rsa_private); + assert(sig.size() == sig_len); return sig; } @@ -57,9 +59,7 @@ QString create_jwt(const QJsonObject &payloads, int expiry) { QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts); auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256); - auto sig = rsa_sign(hash); - jwt += '.' + sig.toBase64(b64_opts); - return jwt; + return jwt + "." + rsa_sign(hash).toBase64(b64_opts); } } // namespace CommaApi diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index c6032852a1..f60b80b21a 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -10,7 +10,7 @@ #include "common/params.h" #include "selfdrive/ui/qt/offroad/driverview.h" #include "selfdrive/ui/qt/body.h" -#include "selfdrive/ui/qt/onroad.h" +#include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/sidebar.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index a5644bae4f..490eb118ca 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -5,6 +5,7 @@ #include +#include "common/swaglog.h" #include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/ui.h" @@ -74,7 +75,7 @@ void MapWindow::initLayers() { QVariantMap transition; transition["duration"] = 400; // ms - m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiState()->scene.navigate_on_openpilot)); + m_map->setPaintProperty("navLayer", "line-color", QColor("#31a1ee")); m_map->setPaintProperty("navLayer", "line-color-transition", transition); m_map->setPaintProperty("navLayer", "line-width", 7.5); m_map->setLayoutProperty("navLayer", "line-cap", "round"); @@ -118,32 +119,28 @@ void MapWindow::updateState(const UIState &s) { const SubMaster &sm = *(s.sm); update(); - if (sm.updated("modelV2")) { - // set path color on change, and show map on rising edge of navigate on openpilot - bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabled() && - sm["controlsState"].getControlsState().getEnabled(); - if (nav_enabled != uiState()->scene.navigate_on_openpilot) { - if (loaded_once) { - m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled)); - } - if (nav_enabled) { - emit requestVisible(true); - } - } - uiState()->scene.navigate_on_openpilot = nav_enabled; + // on rising edge of a valid system time, reinitialize the map to set a new token + if (sm.valid("clocks") && !prev_time_valid) { + LOGW("Time is now valid, reinitializing map"); + m_settings.setApiKey(get_mapbox_token()); + initializeGL(); } + prev_time_valid = sm.valid("clocks"); if (sm.updated("liveLocationKalman")) { auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman(); auto locationd_pos = locationd_location.getPositionGeodetic(); auto locationd_orientation = locationd_location.getCalibratedOrientationNED(); auto locationd_velocity = locationd_location.getVelocityCalibrated(); + auto locationd_ecef = locationd_location.getPositionECEF(); - // Check std norm - auto pos_ecef_std = locationd_location.getPositionECEF().getStd(); - bool pos_accurate_enough = sqrt(pow(pos_ecef_std[0], 2) + pow(pos_ecef_std[1], 2) + pow(pos_ecef_std[2], 2)) < 100; - - locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && pos_accurate_enough); + locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && locationd_ecef.getValid()); + if (locationd_valid) { + // Check std norm + auto pos_ecef_std = locationd_ecef.getStd(); + bool pos_accurate_enough = sqrt(pow(pos_ecef_std[0], 2) + pow(pos_ecef_std[1], 2) + pow(pos_ecef_std[2], 2)) < 100; + locationd_valid = pos_accurate_enough; + } if (locationd_valid) { last_position = QMapLibre::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]); @@ -274,6 +271,10 @@ void MapWindow::initializeGL() { loaded_once = true; } }); + + 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()); + }); } void MapWindow::paintGL() { @@ -365,7 +366,6 @@ void MapWindow::pinchTriggered(QPinchGesture *gesture) { void MapWindow::offroadTransition(bool offroad) { if (offroad) { clearRoute(); - uiState()->scene.navigate_on_openpilot = false; routing_problem = false; } else { auto dest = coordinate_from_param("NavDestination"); diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 81ba50037a..31a44f27b1 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -51,6 +51,7 @@ private: void setError(const QString &err_str); bool loaded_once = false; + bool prev_time_valid = true; // Panning QPointF m_lastPos; @@ -69,11 +70,6 @@ private: MapInstructions* map_instructions; MapETA* map_eta; - // Blue with normal nav, green when nav is input into the model - QColor getNavPathColor(bool nav_enabled) { - return nav_enabled ? QColor("#31ee73") : QColor("#31a1ee"); - } - void clearRoute(); void updateDestinationMarker(); uint64_t route_rcv_frame = 0; diff --git a/selfdrive/ui/qt/maps/map_eta.cc b/selfdrive/ui/qt/maps/map_eta.cc index 161844c618..0eb77e36ce 100644 --- a/selfdrive/ui/qt/maps/map_eta.cc +++ b/selfdrive/ui/qt/maps/map_eta.cc @@ -38,10 +38,13 @@ void MapETA::updateETA(float s, float s_typical, float d) { auto remaining = s < 3600 ? std::pair{QString::number(int(s / 60)), tr("min")} : std::pair{QString("%1:%2").arg((int)s / 3600).arg(((int)s % 3600) / 60, 2, 10, QLatin1Char('0')), tr("hr")}; QString color = "#25DA6E"; - if (s / s_typical > 1.5) - color = "#DA3025"; - else if (s / s_typical > 1.2) - color = "#DAA725"; + if (std::abs(s_typical) > 1e-5) { + if (s / s_typical > 1.5) { + color = "#DA3025"; + } else if (s / s_typical > 1.2) { + color = "#DAA725"; + } + } // Distance auto distance = map_format_distance(d, uiState()->scene.is_metric); diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc index 3907ff7b05..16923f7a43 100644 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -137,17 +137,17 @@ std::optional coordinate_from_param(const std::string &pa // return {distance, unit} std::pair map_format_distance(float d, bool is_metric) { - auto round_distance = [](float d) -> float { - return (d > 10) ? std::nearbyint(d) : std::nearbyint(d * 10) / 10.0; + auto round_distance = [](float d) -> QString { + return (d > 10) ? QString::number(std::nearbyint(d)) : QString::number(std::nearbyint(d * 10) / 10.0, 'f', 1); }; d = std::max(d, 0.0f); if (is_metric) { - return (d > 500) ? std::pair{QString::number(round_distance(d / 1000)), QObject::tr("km")} + return (d > 500) ? std::pair{round_distance(d / 1000), QObject::tr("km")} : std::pair{QString::number(50 * std::nearbyint(d / 50)), QObject::tr("m")}; } else { float feet = d * METER_TO_FOOT; - return (feet > 500) ? std::pair{QString::number(round_distance(d * METER_TO_MILE)), QObject::tr("mi")} + return (feet > 500) ? std::pair{round_distance(d * METER_TO_MILE), QObject::tr("mi")} : std::pair{QString::number(50 * std::nearbyint(d / 50)), QObject::tr("ft")}; } } diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index 5354a01fa0..d7cdddff44 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -82,11 +82,11 @@ void Networking::connectToNetwork(const Network n) { if (wifi->isKnownConnection(n.ssid)) { wifi->activateWifiConnection(n.ssid); } else if (n.security_type == SecurityType::OPEN) { - wifi->connect(n); + wifi->connect(n, false); } else if (n.security_type == SecurityType::WPA) { QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8); if (!pass.isEmpty()) { - wifi->connect(n, pass); + wifi->connect(n, false, pass); } } } @@ -96,7 +96,7 @@ void Networking::wrongPassword(const QString &ssid) { const Network &n = wifi->seenNetworks.value(ssid); QString pass = InputDialog::getText(tr("Wrong password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8); if (!pass.isEmpty()) { - wifi->connect(n, pass); + wifi->connect(n, false, pass); } } } @@ -192,9 +192,9 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid hidden_network.ssid = ssid.toUtf8(); if (!pass.isEmpty()) { hidden_network.security_type = SecurityType::WPA; - wifi->connect(hidden_network, pass); + wifi->connect(hidden_network, true, pass); } else { - wifi->connect(hidden_network); + wifi->connect(hidden_network, true); } emit requestWifiScreen(); } diff --git a/selfdrive/ui/qt/network/networkmanager.h b/selfdrive/ui/qt/network/networkmanager.h index 2896b0fff7..8bdeaf3bbd 100644 --- a/selfdrive/ui/qt/network/networkmanager.h +++ b/selfdrive/ui/qt/network/networkmanager.h @@ -32,6 +32,7 @@ const QString NM_DBUS_INTERFACE_IP4_CONFIG = "org.freedesktop.NetworkMa const QString NM_DBUS_SERVICE = "org.freedesktop.NetworkManager"; +const int NM_DEVICE_STATE_UNKNOWN = 0; const int NM_DEVICE_STATE_ACTIVATED = 100; const int NM_DEVICE_STATE_NEED_AUTH = 60; const int NM_DEVICE_TYPE_WIFI = 2; diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc index ebb5cb8736..717da47096 100644 --- a/selfdrive/ui/qt/network/wifi_manager.cc +++ b/selfdrive/ui/qt/network/wifi_manager.cc @@ -1,5 +1,7 @@ #include "selfdrive/ui/qt/network/wifi_manager.h" +#include + #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/prime.h" @@ -14,9 +16,15 @@ bool compare_by_strength(const Network &a, const Network &b) { template T call(const QString &path, const QString &interface, const QString &method, Args &&...args) { - QDBusInterface nm = QDBusInterface(NM_DBUS_SERVICE, path, interface, QDBusConnection::systemBus()); + QDBusInterface nm(NM_DBUS_SERVICE, path, interface, QDBusConnection::systemBus()); nm.setTimeout(DBUS_TIMEOUT); - QDBusMessage response = nm.call(method, args...); + + QDBusMessage response = nm.call(method, std::forward(args)...); + if (response.type() == QDBusMessage::ErrorMessage) { + qCritical() << "DBus call error:" << response.errorMessage(); + return T(); + } + if constexpr (std::is_same_v) { return response; } else if (response.arguments().count() >= 1) { @@ -98,11 +106,21 @@ void WifiManager::refreshFinished(QDBusPendingCallWatcher *watcher) { ipv4_address = getIp4Address(); seenNetworks.clear(); - const QDBusReply> wather_reply = *watcher; - for (const QDBusObjectPath &path : wather_reply.value()) { - QDBusReply replay = call(path.path(), NM_DBUS_INTERFACE_PROPERTIES, "GetAll", NM_DBUS_INTERFACE_ACCESS_POINT); - auto properties = replay.value(); + const QDBusReply> watcher_reply = *watcher; + if (!watcher_reply.isValid()) { + qCritical() << "Failed to refresh"; + watcher->deleteLater(); + return; + } + for (const QDBusObjectPath &path : watcher_reply.value()) { + QDBusReply reply = call(path.path(), NM_DBUS_INTERFACE_PROPERTIES, "GetAll", NM_DBUS_INTERFACE_ACCESS_POINT); + if (!reply.isValid()) { + qCritical() << "Failed to retrieve properties for path:" << path.path(); + continue; + } + + auto properties = reply.value(); const QByteArray ssid = properties["Ssid"].toByteArray(); if (ssid.isEmpty()) continue; @@ -166,7 +184,7 @@ SecurityType WifiManager::getSecurityType(const QVariantMap &properties) { } } -void WifiManager::connect(const Network &n, const QString &password, const QString &username) { +void WifiManager::connect(const Network &n, const bool is_hidden, const QString &password, const QString &username) { setCurrentConnecting(n.ssid); forgetConnection(n.ssid); // Clear all connections that may already exist to the network we are connecting Connection connection; @@ -176,6 +194,7 @@ void WifiManager::connect(const Network &n, const QString &password, const QStri connection["connection"]["autoconnect-retries"] = 0; connection["802-11-wireless"]["ssid"] = n.ssid; + connection["802-11-wireless"]["hidden"] = is_hidden; connection["802-11-wireless"]["mode"] = "infrastructure"; if (n.security_type == SecurityType::WPA) { @@ -209,16 +228,8 @@ void WifiManager::deactivateConnection(const QDBusObjectPath &path) { } QVector WifiManager::getActiveConnections() { - QVector conns; - QDBusObjectPath path; - const QDBusArgument &arr = call(NM_DBUS_PATH, NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE, "ActiveConnections"); - arr.beginArray(); - while (!arr.atEnd()) { - arr >> path; - conns.push_back(path); - } - arr.endArray(); - return conns; + auto result = call(NM_DBUS_PATH, NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE, "ActiveConnections"); + return qdbus_cast>(result); } bool WifiManager::isKnownConnection(const QString &ssid) { @@ -427,7 +438,10 @@ void WifiManager::addTetheringConnection() { connection["ipv4"]["route-metric"] = 1100; connection["ipv6"]["method"] = "ignore"; - call(NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "AddConnection", QVariant::fromValue(connection)); + auto path = call(NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "AddConnection", QVariant::fromValue(connection)); + if (!path.path().isEmpty()) { + knownConnections[path] = tethering_ssid; + } } void WifiManager::tetheringActivated(QDBusPendingCallWatcher *call) { diff --git a/selfdrive/ui/qt/network/wifi_manager.h b/selfdrive/ui/qt/network/wifi_manager.h index 7debffa452..2f6a1829d7 100644 --- a/selfdrive/ui/qt/network/wifi_manager.h +++ b/selfdrive/ui/qt/network/wifi_manager.h @@ -52,7 +52,7 @@ public: std::optional activateWifiConnection(const QString &ssid); NetworkType currentNetworkType(); void updateGsmSettings(bool roaming, QString apn, bool metered); - void connect(const Network &ssid, const QString &password = {}, const QString &username = {}); + void connect(const Network &ssid, const bool is_hidden = false, const QString &password = {}, const QString &username = {}); // Tethering functions void setTetheringEnabled(bool enabled); @@ -63,7 +63,7 @@ public: private: QString adapter; // Path to network manager wifi-device QTimer timer; - unsigned int raw_adapter_state; // Connection status https://developer.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NMDeviceState + unsigned int raw_adapter_state = NM_DEVICE_STATE_UNKNOWN; // Connection status https://developer.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NMDeviceState QString connecting_to_network; QString tethering_ssid; const QString defaultTetheringPassword = "swagswagcomma"; diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 85393cabc0..96fe6585cc 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -1,5 +1,3 @@ -#include "selfdrive/ui/qt/offroad/settings.h" - #include #include #include @@ -8,20 +6,14 @@ #include -#include "selfdrive/ui/qt/network/networking.h" - -#include "common/params.h" #include "common/watchdog.h" #include "common/util.h" -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/widgets/input.h" +#include "selfdrive/ui/qt/network/networking.h" +#include "selfdrive/ui/qt/offroad/settings.h" +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/widgets/prime.h" #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/widgets/ssh_keys.h" -#include "selfdrive/ui/qt/widgets/toggle.h" -#include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/qt_window.h" TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // param, title, desc, icon @@ -59,6 +51,12 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."), "../assets/offroad/icon_warning.png", }, + { + "AlwaysOnDM", + tr("Always-On Driver Monitoring"), + tr("Enable driver monitoring even when openpilot is not engaged."), + "../assets/offroad/icon_monitoring.png", + }, { "RecordFront", tr("Record and Upload Driver Camera"), @@ -91,9 +89,14 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { std::vector longi_button_texts{tr("Aggressive"), tr("Standard"), tr("Relaxed")}; long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), tr("Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " - "In relaxed mode openpilot will stay further away from lead cars."), + "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " + "your steering wheel distance button."), "../assets/offroad/icon_speed_limit.png", longi_button_texts); + + // set up uiState update for personality setting + QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState); + for (auto &[param, title, desc, icon] : toggle_defs) { auto toggle = new ParamControl(param, title, desc, icon, this); @@ -119,6 +122,18 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }); } +void TogglesPanel::updateState(const UIState &s) { + const SubMaster &sm = *(s.sm); + + if (sm.updated("controlsState")) { + auto personality = sm["controlsState"].getControlsState().getPersonality(); + if (personality != s.scene.personality && s.scene.started && isVisible()) { + long_personality_setting->setCheckedButton(static_cast(personality)); + } + uiState()->scene.personality = personality; + } +} + void TogglesPanel::expandToggleDescription(const QString ¶m) { toggles[param.toStdString()]->showDescription(); } @@ -134,21 +149,14 @@ void TogglesPanel::updateToggles() { "

    %2


    " "%3
    " "

    %4


    " - "%5
    " - "

    %6


    " - "%7") + "%5
    ") .arg(tr("openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. Experimental features are listed below:")) .arg(tr("End-to-End Longitudinal Control")) .arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " "mistakes should be expected.")) - .arg(tr("Navigate on openpilot")) - .arg(tr("When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right " - "appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around " - "exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc.")) .arg(tr("New Driving Visualization")) - .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. " - "When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green.")); + .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.")); const bool is_release = params.getBool("IsReleaseBranch"); auto cp_bytes = params.get("CarParamsPersistent"); @@ -198,6 +206,14 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A")))); addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str())); + pair_device = new ButtonControl(tr("Pair Device"), tr("PAIR"), + tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.")); + connect(pair_device, &ButtonControl::clicked, [=]() { + PairingPopup popup(this); + popup.exec(); + }); + addItem(pair_device); + // offroad-only buttons auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"), @@ -245,9 +261,14 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { }); addItem(translateBtn); + QObject::connect(uiState(), &UIState::primeTypeChanged, [this] (PrimeType type) { + pair_device->setVisible(type == PrimeType::UNPAIRED); + }); QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { for (auto btn : findChildren()) { - btn->setEnabled(offroad); + if (btn != pair_device) { + btn->setEnabled(offroad); + } } }); @@ -328,6 +349,11 @@ void DevicePanel::poweroff() { } } +void DevicePanel::showEvent(QShowEvent *event) { + pair_device->setVisible(uiState()->primeType() == PrimeType::UNPAIRED); + ListWidget::showEvent(event); +} + void SettingsWindow::showEvent(QShowEvent *event) { setCurrentPanel(0); } diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index a5dd25b14f..35a044bdd2 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -10,7 +10,7 @@ #include #include - +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/controls.h" @@ -42,6 +42,8 @@ class DevicePanel : public ListWidget { Q_OBJECT public: explicit DevicePanel(SettingsWindow *parent); + void showEvent(QShowEvent *event) override; + signals: void reviewTrainingGuide(); void showDriverView(); @@ -53,6 +55,7 @@ private slots: private: Params params; + ButtonControl *pair_device; }; class TogglesPanel : public ListWidget { @@ -64,6 +67,9 @@ public: public slots: void expandToggleDescription(const QString ¶m); +private slots: + void updateState(const UIState &s); + private: Params params; std::map toggles; diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 15c022db9a..c8245205ce 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -17,7 +17,7 @@ void SoftwarePanel::checkForUpdates() { - std::system("pkill -SIGUSR1 -f selfdrive.updated"); + std::system("pkill -SIGUSR1 -f system.updated.updated"); } SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { @@ -36,7 +36,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { if (downloadBtn->text() == tr("CHECK")) { checkForUpdates(); } else { - std::system("pkill -SIGHUP -f selfdrive.updated"); + std::system("pkill -SIGHUP -f system.updated.updated"); } }); addItem(downloadBtn); diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h deleted file mode 100644 index b3ba411453..0000000000 --- a/selfdrive/ui/qt/onroad.h +++ /dev/null @@ -1,142 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -#include "common/util.h" -#include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/widgets/cameraview.h" - - -const int btn_size = 192; -const int img_size = (btn_size / 4) * 3; - - -// ***** onroad widgets ***** -class OnroadAlerts : public QWidget { - Q_OBJECT - -public: - OnroadAlerts(QWidget *parent = 0) : QWidget(parent) {} - void updateAlert(const Alert &a); - -protected: - void paintEvent(QPaintEvent*) override; - -private: - QColor bg; - Alert alert = {}; -}; - -class ExperimentalButton : public QPushButton { - Q_OBJECT - -public: - explicit ExperimentalButton(QWidget *parent = 0); - void updateState(const UIState &s); - -private: - void paintEvent(QPaintEvent *event) override; - void changeMode(); - - Params params; - QPixmap engage_img; - QPixmap experimental_img; - bool experimental_mode; - bool engageable; -}; - - -class MapSettingsButton : public QPushButton { - Q_OBJECT - -public: - explicit MapSettingsButton(QWidget *parent = 0); - -private: - void paintEvent(QPaintEvent *event) override; - - QPixmap settings_img; -}; - -// container window for the NVG UI -class AnnotatedCameraWidget : public CameraWidget { - Q_OBJECT - -public: - explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0); - void updateState(const UIState &s); - - MapSettingsButton *map_settings_btn; - -private: - void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255); - - QVBoxLayout *main_layout; - ExperimentalButton *experimental_btn; - QPixmap dm_img; - float speed; - QString speedUnit; - float setSpeed; - float speedLimit; - bool is_cruise_set = false; - bool is_metric = false; - bool dmActive = false; - bool hideBottomIcons = false; - bool rightHandDM = false; - float dm_fade_state = 1.0; - bool has_us_speed_limit = false; - bool has_eu_speed_limit = false; - bool v_ego_cluster_seen = false; - int status = STATUS_DISENGAGED; - std::unique_ptr pm; - - int skip_frame_count = 0; - bool wide_cam_requested = false; - -protected: - void paintGL() override; - void initializeGL() override; - void showEvent(QShowEvent *event) override; - void updateFrameMat() override; - void drawLaneLines(QPainter &painter, const UIState *s); - void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd); - void drawHud(QPainter &p); - void drawDriverState(QPainter &painter, const UIState *s); - inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); } - inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); } - inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); } - - double prev_draw_t = 0; - FirstOrderFilter fps_filter; -}; - -// container for all onroad widgets -class OnroadWindow : public QWidget { - Q_OBJECT - -public: - OnroadWindow(QWidget* parent = 0); - bool isMapVisible() const { return map && map->isVisible(); } - void showMapPanel(bool show) { if (map) map->setVisible(show); } - -signals: - void mapPanelRequested(); - -private: - void paintEvent(QPaintEvent *event); - void mousePressEvent(QMouseEvent* e) override; - OnroadAlerts *alerts; - AnnotatedCameraWidget *nvg; - QColor bg = bg_colors[STATUS_DISENGAGED]; - QWidget *map = nullptr; - QHBoxLayout* split; - -private slots: - void offroadTransition(bool offroad); - void primeChanged(bool prime); - void updateState(const UIState &s); -}; diff --git a/selfdrive/ui/qt/onroad/alerts.cc b/selfdrive/ui/qt/onroad/alerts.cc new file mode 100644 index 0000000000..0235c5ff42 --- /dev/null +++ b/selfdrive/ui/qt/onroad/alerts.cc @@ -0,0 +1,112 @@ +#include "selfdrive/ui/qt/onroad/alerts.h" + +#include +#include + +#include "selfdrive/ui/qt/util.h" + +void OnroadAlerts::updateState(const UIState &s) { + Alert a = getAlert(*(s.sm), s.scene.started_frame); + if (!alert.equal(a)) { + alert = a; + update(); + } +} + +void OnroadAlerts::clear() { + alert = {}; + update(); +} + +OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, uint64_t started_frame) { + const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState(); + const uint64_t controls_frame = sm.rcv_frame("controlsState"); + + Alert a = {}; + if (controls_frame >= started_frame) { // Don't get old alert. + a = {cs.getAlertText1().cStr(), cs.getAlertText2().cStr(), + cs.getAlertType().cStr(), cs.getAlertSize(), cs.getAlertStatus()}; + } + + if (!sm.updated("controlsState") && (sm.frame - started_frame) > 5 * UI_FREQ) { + const int CONTROLS_TIMEOUT = 5; + const int controls_missing = (nanos_since_boot() - sm.rcv_time("controlsState")) / 1e9; + + // Handle controls timeout + if (controls_frame < started_frame) { + // car is started, but controlsState hasn't been seen at all + a = {tr("openpilot Unavailable"), tr("Waiting for controls to start"), + "controlsWaiting", cereal::ControlsState::AlertSize::MID, + cereal::ControlsState::AlertStatus::NORMAL}; + } else if (controls_missing > CONTROLS_TIMEOUT && !Hardware::PC()) { + // car is started, but controls is lagging or died + if (cs.getEnabled() && (controls_missing - CONTROLS_TIMEOUT) < 10) { + a = {tr("TAKE CONTROL IMMEDIATELY"), tr("Controls Unresponsive"), + "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, + cereal::ControlsState::AlertStatus::CRITICAL}; + } else { + a = {tr("Controls Unresponsive"), tr("Reboot Device"), + "controlsUnresponsivePermanent", cereal::ControlsState::AlertSize::MID, + cereal::ControlsState::AlertStatus::NORMAL}; + } + } + } + return a; +} + +void OnroadAlerts::paintEvent(QPaintEvent *event) { + if (alert.size == cereal::ControlsState::AlertSize::NONE) { + return; + } + static std::map alert_heights = { + {cereal::ControlsState::AlertSize::SMALL, 271}, + {cereal::ControlsState::AlertSize::MID, 420}, + {cereal::ControlsState::AlertSize::FULL, height()}, + }; + int h = alert_heights[alert.size]; + + int margin = 40; + int radius = 30; + if (alert.size == cereal::ControlsState::AlertSize::FULL) { + margin = 0; + radius = 0; + } + QRect r = QRect(0 + margin, height() - h + margin, width() - margin*2, h - margin*2); + + QPainter p(this); + + // draw background + gradient + p.setPen(Qt::NoPen); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.setBrush(QBrush(alert_colors[alert.status])); + p.drawRoundedRect(r, radius, radius); + + QLinearGradient g(0, r.y(), 0, r.bottom()); + g.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.05)); + g.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0.35)); + + p.setCompositionMode(QPainter::CompositionMode_DestinationOver); + p.setBrush(QBrush(g)); + p.drawRoundedRect(r, radius, radius); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + // text + const QPoint c = r.center(); + p.setPen(QColor(0xff, 0xff, 0xff)); + p.setRenderHint(QPainter::TextAntialiasing); + if (alert.size == cereal::ControlsState::AlertSize::SMALL) { + p.setFont(InterFont(74, QFont::DemiBold)); + p.drawText(r, Qt::AlignCenter, alert.text1); + } else if (alert.size == cereal::ControlsState::AlertSize::MID) { + p.setFont(InterFont(88, QFont::Bold)); + p.drawText(QRect(0, c.y() - 125, width(), 150), Qt::AlignHCenter | Qt::AlignTop, alert.text1); + p.setFont(InterFont(66)); + p.drawText(QRect(0, c.y() + 21, width(), 90), Qt::AlignHCenter, alert.text2); + } else if (alert.size == cereal::ControlsState::AlertSize::FULL) { + bool l = alert.text1.length() > 15; + p.setFont(InterFont(l ? 132 : 177, QFont::Bold)); + p.drawText(QRect(0, r.y() + (l ? 240 : 270), width(), 600), Qt::AlignHCenter | Qt::TextWordWrap, alert.text1); + p.setFont(InterFont(88)); + p.drawText(QRect(0, r.height() - (l ? 361 : 420), width(), 300), Qt::AlignHCenter | Qt::TextWordWrap, alert.text2); + } +} diff --git a/selfdrive/ui/qt/onroad/alerts.h b/selfdrive/ui/qt/onroad/alerts.h new file mode 100644 index 0000000000..1f76ba305b --- /dev/null +++ b/selfdrive/ui/qt/onroad/alerts.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "selfdrive/ui/ui.h" + +class OnroadAlerts : public QWidget { + Q_OBJECT + +public: + OnroadAlerts(QWidget *parent = 0) : QWidget(parent) {} + void updateState(const UIState &s); + void clear(); + +protected: + struct Alert { + QString text1; + QString text2; + QString type; + cereal::ControlsState::AlertSize size; + cereal::ControlsState::AlertStatus status; + + bool equal(const Alert &other) const { + return text1 == other.text1 && text2 == other.text2 && type == other.type; + } + }; + + const QMap alert_colors = { + {cereal::ControlsState::AlertStatus::NORMAL, QColor(0x15, 0x15, 0x15, 0xf1)}, + {cereal::ControlsState::AlertStatus::USER_PROMPT, QColor(0xDA, 0x6F, 0x25, 0xf1)}, + {cereal::ControlsState::AlertStatus::CRITICAL, QColor(0xC9, 0x22, 0x31, 0xf1)}, + }; + + void paintEvent(QPaintEvent*) override; + OnroadAlerts::Alert getAlert(const SubMaster &sm, uint64_t started_frame); + + QColor bg; + Alert alert = {}; +}; diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc similarity index 66% rename from selfdrive/ui/qt/onroad.cc rename to selfdrive/ui/qt/onroad/annotated_camera.cc index 01750eaf2f..241bb6ed34 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -1,265 +1,13 @@ -#include "selfdrive/ui/qt/onroad.h" +#include "selfdrive/ui/qt/onroad/annotated_camera.h" + +#include #include #include -#include -#include -#include - -#include -#include #include "common/swaglog.h" -#include "common/timing.h" +#include "selfdrive/ui/qt/onroad/buttons.h" #include "selfdrive/ui/qt/util.h" -#ifdef ENABLE_MAPS -#include "selfdrive/ui/qt/maps/map_helpers.h" -#include "selfdrive/ui/qt/maps/map_panel.h" -#endif - -static void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity) { - p.setRenderHint(QPainter::Antialiasing); - p.setOpacity(1.0); // bg dictates opacity of ellipse - p.setPen(Qt::NoPen); - p.setBrush(bg); - p.drawEllipse(center, btn_size / 2, btn_size / 2); - p.setOpacity(opacity); - p.drawPixmap(center - QPoint(img.width() / 2, img.height() / 2), img); - p.setOpacity(1.0); -} - -OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setMargin(UI_BORDER_SIZE); - QStackedLayout *stacked_layout = new QStackedLayout; - stacked_layout->setStackingMode(QStackedLayout::StackAll); - main_layout->addLayout(stacked_layout); - - nvg = new AnnotatedCameraWidget(VISION_STREAM_ROAD, this); - - QWidget * split_wrapper = new QWidget; - split = new QHBoxLayout(split_wrapper); - split->setContentsMargins(0, 0, 0, 0); - split->setSpacing(0); - split->addWidget(nvg); - - if (getenv("DUAL_CAMERA_VIEW")) { - CameraWidget *arCam = new CameraWidget("camerad", VISION_STREAM_ROAD, true, this); - split->insertWidget(0, arCam); - } - - if (getenv("MAP_RENDER_VIEW")) { - CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this); - split->insertWidget(0, map_render); - } - - stacked_layout->addWidget(split_wrapper); - - alerts = new OnroadAlerts(this); - alerts->setAttribute(Qt::WA_TransparentForMouseEvents, true); - stacked_layout->addWidget(alerts); - - // setup stacking order - alerts->raise(); - - setAttribute(Qt::WA_OpaquePaintEvent); - QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); - QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition); - QObject::connect(uiState(), &UIState::primeChanged, this, &OnroadWindow::primeChanged); -} - -void OnroadWindow::updateState(const UIState &s) { - if (!s.scene.started) { - return; - } - - QColor bgColor = bg_colors[s.status]; - Alert alert = Alert::get(*(s.sm), s.scene.started_frame); - alerts->updateAlert(alert); - - if (s.scene.map_on_left) { - split->setDirection(QBoxLayout::LeftToRight); - } else { - split->setDirection(QBoxLayout::RightToLeft); - } - - nvg->updateState(s); - - if (bg != bgColor) { - // repaint border - bg = bgColor; - update(); - } -} - -void OnroadWindow::mousePressEvent(QMouseEvent* e) { -#ifdef ENABLE_MAPS - if (map != nullptr) { - // Switch between map and sidebar when using navigate on openpilot - bool sidebarVisible = geometry().x() > 0; - bool show_map = uiState()->scene.navigate_on_openpilot ? sidebarVisible : !sidebarVisible; - map->setVisible(show_map && !map->isVisible()); - } -#endif - // propagation event to parent(HomeWindow) - QWidget::mousePressEvent(e); -} - -void OnroadWindow::offroadTransition(bool offroad) { -#ifdef ENABLE_MAPS - if (!offroad) { - if (map == nullptr && (uiState()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) { - auto m = new MapPanel(get_mapbox_settings()); - map = m; - - QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindow::mapPanelRequested); - QObject::connect(nvg->map_settings_btn, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings); - nvg->map_settings_btn->setEnabled(true); - - m->setFixedWidth(topWidget(this)->width() / 2 - UI_BORDER_SIZE); - split->insertWidget(0, m); - - // hidden by default, made visible when navRoute is published - m->setVisible(false); - } - } -#endif - - alerts->updateAlert({}); -} - -void OnroadWindow::primeChanged(bool prime) { -#ifdef ENABLE_MAPS - if (map && (!prime && MAPBOX_TOKEN.isEmpty())) { - nvg->map_settings_btn->setEnabled(false); - nvg->map_settings_btn->setVisible(false); - map->deleteLater(); - map = nullptr; - } -#endif -} - -void OnroadWindow::paintEvent(QPaintEvent *event) { - QPainter p(this); - p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255)); -} - -// ***** onroad widgets ***** - -// OnroadAlerts -void OnroadAlerts::updateAlert(const Alert &a) { - if (!alert.equal(a)) { - alert = a; - update(); - } -} - -void OnroadAlerts::paintEvent(QPaintEvent *event) { - if (alert.size == cereal::ControlsState::AlertSize::NONE) { - return; - } - static std::map alert_heights = { - {cereal::ControlsState::AlertSize::SMALL, 271}, - {cereal::ControlsState::AlertSize::MID, 420}, - {cereal::ControlsState::AlertSize::FULL, height()}, - }; - int h = alert_heights[alert.size]; - - int margin = 40; - int radius = 30; - if (alert.size == cereal::ControlsState::AlertSize::FULL) { - margin = 0; - radius = 0; - } - QRect r = QRect(0 + margin, height() - h + margin, width() - margin*2, h - margin*2); - - QPainter p(this); - - // draw background + gradient - p.setPen(Qt::NoPen); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.setBrush(QBrush(alert_colors[alert.status])); - p.drawRoundedRect(r, radius, radius); - - QLinearGradient g(0, r.y(), 0, r.bottom()); - g.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.05)); - g.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0.35)); - - p.setCompositionMode(QPainter::CompositionMode_DestinationOver); - p.setBrush(QBrush(g)); - p.drawRoundedRect(r, radius, radius); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - - // text - const QPoint c = r.center(); - p.setPen(QColor(0xff, 0xff, 0xff)); - p.setRenderHint(QPainter::TextAntialiasing); - if (alert.size == cereal::ControlsState::AlertSize::SMALL) { - p.setFont(InterFont(74, QFont::DemiBold)); - p.drawText(r, Qt::AlignCenter, alert.text1); - } else if (alert.size == cereal::ControlsState::AlertSize::MID) { - p.setFont(InterFont(88, QFont::Bold)); - p.drawText(QRect(0, c.y() - 125, width(), 150), Qt::AlignHCenter | Qt::AlignTop, alert.text1); - p.setFont(InterFont(66)); - p.drawText(QRect(0, c.y() + 21, width(), 90), Qt::AlignHCenter, alert.text2); - } else if (alert.size == cereal::ControlsState::AlertSize::FULL) { - bool l = alert.text1.length() > 15; - p.setFont(InterFont(l ? 132 : 177, QFont::Bold)); - p.drawText(QRect(0, r.y() + (l ? 240 : 270), width(), 600), Qt::AlignHCenter | Qt::TextWordWrap, alert.text1); - p.setFont(InterFont(88)); - p.drawText(QRect(0, r.height() - (l ? 361 : 420), width(), 300), Qt::AlignHCenter | Qt::TextWordWrap, alert.text2); - } -} - -// ExperimentalButton -ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) { - setFixedSize(btn_size, btn_size); - - engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); - experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size, img_size}); - QObject::connect(this, &QPushButton::clicked, this, &ExperimentalButton::changeMode); -} - -void ExperimentalButton::changeMode() { - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); - bool can_change = hasLongitudinalControl(cp) && params.getBool("ExperimentalModeConfirmed"); - if (can_change) { - params.putBool("ExperimentalMode", !experimental_mode); - } -} - -void ExperimentalButton::updateState(const UIState &s) { - const auto cs = (*s.sm)["controlsState"].getControlsState(); - bool eng = cs.getEngageable() || cs.getEnabled(); - if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) { - engageable = eng; - experimental_mode = cs.getExperimentalMode(); - update(); - } -} - -void ExperimentalButton::paintEvent(QPaintEvent *event) { - QPainter p(this); - QPixmap img = experimental_mode ? experimental_img : engage_img; - drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, QColor(0, 0, 0, 166), (isDown() || !engageable) ? 0.6 : 1.0); -} - - -// MapSettingsButton -MapSettingsButton::MapSettingsButton(QWidget *parent) : QPushButton(parent) { - setFixedSize(btn_size, btn_size); - settings_img = loadPixmap("../assets/navigation/icon_directions_outlined.svg", {img_size, img_size}); - - // hidden by default, made visible if map is created (has prime or mapbox token) - setVisible(false); - setEnabled(false); -} - -void MapSettingsButton::paintEvent(QPaintEvent *event) { - QPainter p(this); - drawIcon(p, QPoint(btn_size / 2, btn_size / 2), settings_img, QColor(0, 0, 0, 166), isDown() ? 0.6 : 1.0); -} - // Window that shows camera view and variety of info drawn on top AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { @@ -487,8 +235,7 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { QLinearGradient bg(0, height(), 0, 0); if (sm["controlsState"].getControlsState().getExperimentalMode()) { // The first half of track_vertices are the points for the right side of the path - // and the indices match the positions of accel from uiPlan - const auto &acceleration = sm["uiPlan"].getUiPlan().getAccel(); + const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration().getX(); const int max_len = std::min(scene.track_vertices.length() / 2, acceleration.size()); for (int i = 0; i < max_len; ++i) { @@ -655,7 +402,7 @@ void AnnotatedCameraWidget::paintGL() { painter.setPen(Qt::NoPen); if (s->scene.world_objects_visible) { - update_model(s, model, sm["uiPlan"].getUiPlan()); + update_model(s, model); drawLaneLines(painter, s); if (s->scene.longitudinal_control && sm.rcv_frame("radarState") > s->scene.started_frame) { diff --git a/selfdrive/ui/qt/onroad/annotated_camera.h b/selfdrive/ui/qt/onroad/annotated_camera.h new file mode 100644 index 0000000000..0be4adfffa --- /dev/null +++ b/selfdrive/ui/qt/onroad/annotated_camera.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include "selfdrive/ui/qt/onroad/buttons.h" +#include "selfdrive/ui/qt/widgets/cameraview.h" + +class AnnotatedCameraWidget : public CameraWidget { + Q_OBJECT + +public: + explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0); + void updateState(const UIState &s); + + MapSettingsButton *map_settings_btn; + +private: + void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255); + + QVBoxLayout *main_layout; + ExperimentalButton *experimental_btn; + QPixmap dm_img; + float speed; + QString speedUnit; + float setSpeed; + float speedLimit; + bool is_cruise_set = false; + bool is_metric = false; + bool dmActive = false; + bool hideBottomIcons = false; + bool rightHandDM = false; + float dm_fade_state = 1.0; + bool has_us_speed_limit = false; + bool has_eu_speed_limit = false; + bool v_ego_cluster_seen = false; + int status = STATUS_DISENGAGED; + std::unique_ptr pm; + + int skip_frame_count = 0; + bool wide_cam_requested = false; + +protected: + void paintGL() override; + void initializeGL() override; + void showEvent(QShowEvent *event) override; + void updateFrameMat() override; + void drawLaneLines(QPainter &painter, const UIState *s); + void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd); + void drawHud(QPainter &p); + void drawDriverState(QPainter &painter, const UIState *s); + inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); } + inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); } + inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); } + + double prev_draw_t = 0; + FirstOrderFilter fps_filter; +}; diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc new file mode 100644 index 0000000000..75ec316174 --- /dev/null +++ b/selfdrive/ui/qt/onroad/buttons.cc @@ -0,0 +1,64 @@ +#include "selfdrive/ui/qt/onroad/buttons.h" + +#include + +#include "selfdrive/ui/qt/util.h" + +void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity) { + p.setRenderHint(QPainter::Antialiasing); + p.setOpacity(1.0); // bg dictates opacity of ellipse + p.setPen(Qt::NoPen); + p.setBrush(bg); + p.drawEllipse(center, btn_size / 2, btn_size / 2); + p.setOpacity(opacity); + p.drawPixmap(center - QPoint(img.width() / 2, img.height() / 2), img); + p.setOpacity(1.0); +} + +// ExperimentalButton +ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) { + setFixedSize(btn_size, btn_size); + + engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); + experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size, img_size}); + QObject::connect(this, &QPushButton::clicked, this, &ExperimentalButton::changeMode); +} + +void ExperimentalButton::changeMode() { + const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + bool can_change = hasLongitudinalControl(cp) && params.getBool("ExperimentalModeConfirmed"); + if (can_change) { + params.putBool("ExperimentalMode", !experimental_mode); + } +} + +void ExperimentalButton::updateState(const UIState &s) { + const auto cs = (*s.sm)["controlsState"].getControlsState(); + bool eng = cs.getEngageable() || cs.getEnabled(); + if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) { + engageable = eng; + experimental_mode = cs.getExperimentalMode(); + update(); + } +} + +void ExperimentalButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + QPixmap img = experimental_mode ? experimental_img : engage_img; + drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, QColor(0, 0, 0, 166), (isDown() || !engageable) ? 0.6 : 1.0); +} + +// MapSettingsButton +MapSettingsButton::MapSettingsButton(QWidget *parent) : QPushButton(parent) { + setFixedSize(btn_size, btn_size); + settings_img = loadPixmap("../assets/navigation/icon_directions_outlined.svg", {img_size, img_size}); + + // hidden by default, made visible if map is created (has prime or mapbox token) + setVisible(false); + setEnabled(false); +} + +void MapSettingsButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + drawIcon(p, QPoint(btn_size / 2, btn_size / 2), settings_img, QColor(0, 0, 0, 166), isDown() ? 0.6 : 1.0); +} diff --git a/selfdrive/ui/qt/onroad/buttons.h b/selfdrive/ui/qt/onroad/buttons.h new file mode 100644 index 0000000000..b0757795fb --- /dev/null +++ b/selfdrive/ui/qt/onroad/buttons.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "selfdrive/ui/ui.h" + +const int btn_size = 192; +const int img_size = (btn_size / 4) * 3; + +class ExperimentalButton : public QPushButton { + Q_OBJECT + +public: + explicit ExperimentalButton(QWidget *parent = 0); + void updateState(const UIState &s); + +private: + void paintEvent(QPaintEvent *event) override; + void changeMode(); + + Params params; + QPixmap engage_img; + QPixmap experimental_img; + bool experimental_mode; + bool engageable; +}; + + +class MapSettingsButton : public QPushButton { + Q_OBJECT + +public: + explicit MapSettingsButton(QWidget *parent = 0); + +private: + void paintEvent(QPaintEvent *event) override; + + QPixmap settings_img; +}; + +void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity); diff --git a/selfdrive/ui/qt/onroad/onroad_home.cc b/selfdrive/ui/qt/onroad/onroad_home.cc new file mode 100644 index 0000000000..66eb1812e6 --- /dev/null +++ b/selfdrive/ui/qt/onroad/onroad_home.cc @@ -0,0 +1,129 @@ +#include "selfdrive/ui/qt/onroad/onroad_home.h" + +#include +#include + +#ifdef ENABLE_MAPS +#include "selfdrive/ui/qt/maps/map_helpers.h" +#include "selfdrive/ui/qt/maps/map_panel.h" +#endif + +#include "selfdrive/ui/qt/util.h" + +OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setMargin(UI_BORDER_SIZE); + QStackedLayout *stacked_layout = new QStackedLayout; + stacked_layout->setStackingMode(QStackedLayout::StackAll); + main_layout->addLayout(stacked_layout); + + nvg = new AnnotatedCameraWidget(VISION_STREAM_ROAD, this); + + QWidget * split_wrapper = new QWidget; + split = new QHBoxLayout(split_wrapper); + split->setContentsMargins(0, 0, 0, 0); + split->setSpacing(0); + split->addWidget(nvg); + + if (getenv("DUAL_CAMERA_VIEW")) { + CameraWidget *arCam = new CameraWidget("camerad", VISION_STREAM_ROAD, true, this); + split->insertWidget(0, arCam); + } + + if (getenv("MAP_RENDER_VIEW")) { + CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this); + split->insertWidget(0, map_render); + } + + stacked_layout->addWidget(split_wrapper); + + alerts = new OnroadAlerts(this); + alerts->setAttribute(Qt::WA_TransparentForMouseEvents, true); + stacked_layout->addWidget(alerts); + + // setup stacking order + alerts->raise(); + + setAttribute(Qt::WA_OpaquePaintEvent); + QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); + QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition); + QObject::connect(uiState(), &UIState::primeChanged, this, &OnroadWindow::primeChanged); +} + +void OnroadWindow::updateState(const UIState &s) { + if (!s.scene.started) { + return; + } + + if (s.scene.map_on_left) { + split->setDirection(QBoxLayout::LeftToRight); + } else { + split->setDirection(QBoxLayout::RightToLeft); + } + + alerts->updateState(s); + nvg->updateState(s); + + QColor bgColor = bg_colors[s.status]; + if (bg != bgColor) { + // repaint border + bg = bgColor; + update(); + } +} + +void OnroadWindow::mousePressEvent(QMouseEvent* e) { +#ifdef ENABLE_MAPS + if (map != nullptr) { + bool sidebarVisible = geometry().x() > 0; + bool show_map = !sidebarVisible; + map->setVisible(show_map && !map->isVisible()); + } +#endif + // propagation event to parent(HomeWindow) + QWidget::mousePressEvent(e); +} + +void OnroadWindow::createMapWidget() { +#ifdef ENABLE_MAPS + auto m = new MapPanel(get_mapbox_settings()); + map = m; + QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindow::mapPanelRequested); + QObject::connect(nvg->map_settings_btn, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings); + nvg->map_settings_btn->setEnabled(true); + + m->setFixedWidth(topWidget(this)->width() / 2 - UI_BORDER_SIZE); + split->insertWidget(0, m); + // hidden by default, made visible when navRoute is published + m->setVisible(false); +#endif +} + +void OnroadWindow::offroadTransition(bool offroad) { +#ifdef ENABLE_MAPS + if (!offroad) { + if (map == nullptr && (uiState()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) { + createMapWidget(); + } + } +#endif + alerts->clear(); +} + +void OnroadWindow::primeChanged(bool prime) { +#ifdef ENABLE_MAPS + if (map && (!prime && MAPBOX_TOKEN.isEmpty())) { + nvg->map_settings_btn->setEnabled(false); + nvg->map_settings_btn->setVisible(false); + map->deleteLater(); + map = nullptr; + } else if (!map && (prime || !MAPBOX_TOKEN.isEmpty())) { + createMapWidget(); + } +#endif +} + +void OnroadWindow::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255)); +} diff --git a/selfdrive/ui/qt/onroad/onroad_home.h b/selfdrive/ui/qt/onroad/onroad_home.h new file mode 100644 index 0000000000..4976f56a67 --- /dev/null +++ b/selfdrive/ui/qt/onroad/onroad_home.h @@ -0,0 +1,31 @@ +#pragma once + +#include "selfdrive/ui/qt/onroad/alerts.h" +#include "selfdrive/ui/qt/onroad/annotated_camera.h" + +class OnroadWindow : public QWidget { + Q_OBJECT + +public: + OnroadWindow(QWidget* parent = 0); + bool isMapVisible() const { return map && map->isVisible(); } + void showMapPanel(bool show) { if (map) map->setVisible(show); } + +signals: + void mapPanelRequested(); + +private: + void createMapWidget(); + void paintEvent(QPaintEvent *event); + void mousePressEvent(QMouseEvent* e) override; + OnroadAlerts *alerts; + AnnotatedCameraWidget *nvg; + QColor bg = bg_colors[STATUS_DISENGAGED]; + QWidget *map = nullptr; + QHBoxLayout* split; + +private slots: + void offroadTransition(bool offroad); + void primeChanged(bool prime); + void updateState(const UIState &s); +}; diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index 4f2e4f48cb..aff9b015b3 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -20,7 +20,7 @@ #include "selfdrive/ui/qt/widgets/input.h" const std::string USER_AGENT = "AGNOSSetup-"; -const QString TEST_URL = "https://openpilot.comma.ai"; +const QString OPENPILOT_URL = "https://openpilot.comma.ai"; bool is_elf(char *fname) { FILE *fp = fopen(fname, "rb"); @@ -51,7 +51,7 @@ void Setup::download(QString url) { list = curl_slist_append(list, ("X-openpilot-serial: " + Hardware::get_serial()).c_str()); char tmpfile[] = "/tmp/installer_XXXXXX"; - FILE *fp = fdopen(mkstemp(tmpfile), "w"); + FILE *fp = fdopen(mkstemp(tmpfile), "wb"); curl_easy_setopt(curl, CURLOPT_URL, url.toStdString().c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL); @@ -201,20 +201,8 @@ QWidget * Setup::network_setup() { QPushButton *cont = new QPushButton(); cont->setObjectName("navBtn"); cont->setProperty("primary", true); - QObject::connect(cont, &QPushButton::clicked, [=]() { - auto w = currentWidget(); - QTimer::singleShot(0, [=]() { - setCurrentWidget(downloading_widget); - }); - QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); - if (!url.isEmpty()) { - QTimer::singleShot(1000, this, [=]() { - download(url); - }); - } else { - setCurrentWidget(w); - } - }); + cont->setEnabled(false); + QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage); blayout->addWidget(cont); // setup timer for testing internet connection @@ -229,11 +217,11 @@ QWidget * Setup::network_setup() { } repaint(); }); - request->sendRequest(TEST_URL); + request->sendRequest(OPENPILOT_URL); QTimer *timer = new QTimer(this); QObject::connect(timer, &QTimer::timeout, [=]() { if (!request->active() && cont->isVisible()) { - request->sendRequest(TEST_URL); + request->sendRequest(OPENPILOT_URL); } }); timer->start(1000); @@ -241,6 +229,106 @@ QWidget * Setup::network_setup() { return widget; } +QWidget * radio_button(QString title, QButtonGroup *group) { + QPushButton *btn = new QPushButton(title); + btn->setCheckable(true); + group->addButton(btn); + btn->setStyleSheet(R"( + QPushButton { + height: 230; + padding-left: 100px; + padding-right: 100px; + text-align: left; + font-size: 80px; + font-weight: 400; + border-radius: 10px; + background-color: #4F4F4F; + } + QPushButton:checked { + background-color: #465BEA; + } + )"); + + // checkmark icon + QPixmap pix(":/img_circled_check.svg"); + btn->setIcon(pix); + btn->setIconSize(QSize(0, 0)); + btn->setLayoutDirection(Qt::RightToLeft); + QObject::connect(btn, &QPushButton::toggled, [=](bool checked) { + btn->setIconSize(checked ? QSize(104, 104) : QSize(0, 0)); + }); + return btn; +} + +QWidget * Setup::software_selection() { + QWidget *widget = new QWidget(); + QVBoxLayout *main_layout = new QVBoxLayout(widget); + main_layout->setContentsMargins(55, 50, 55, 50); + main_layout->setSpacing(0); + + // title + QLabel *title = new QLabel(tr("Choose Software to Install")); + title->setStyleSheet("font-size: 90px; font-weight: 500;"); + main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); + + main_layout->addSpacing(50); + + // openpilot + custom radio buttons + QButtonGroup *group = new QButtonGroup(widget); + group->setExclusive(true); + + QWidget *openpilot = radio_button(tr("openpilot"), group); + main_layout->addWidget(openpilot); + + main_layout->addSpacing(30); + + QWidget *custom = radio_button(tr("Custom Software"), group); + main_layout->addWidget(custom); + + main_layout->addStretch(); + + // back + continue buttons + QHBoxLayout *blayout = new QHBoxLayout; + main_layout->addLayout(blayout); + blayout->setSpacing(50); + + QPushButton *back = new QPushButton(tr("Back")); + back->setObjectName("navBtn"); + QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); + blayout->addWidget(back); + + QPushButton *cont = new QPushButton(tr("Continue")); + cont->setObjectName("navBtn"); + cont->setEnabled(false); + cont->setProperty("primary", true); + blayout->addWidget(cont); + + QObject::connect(cont, &QPushButton::clicked, [=]() { + auto w = currentWidget(); + QTimer::singleShot(0, [=]() { + setCurrentWidget(downloading_widget); + }); + QString url = OPENPILOT_URL; + if (group->checkedButton() != openpilot) { + url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); + } + if (!url.isEmpty()) { + QTimer::singleShot(1000, this, [=]() { + download(url); + }); + } else { + setCurrentWidget(w); + } + }); + + connect(group, QOverload::of(&QButtonGroup::buttonClicked), [=](QAbstractButton *btn) { + btn->setChecked(true); + cont->setEnabled(true); + }); + + return widget; +} + QWidget * Setup::downloading() { QWidget *widget = new QWidget(); QVBoxLayout *main_layout = new QVBoxLayout(widget); @@ -326,6 +414,7 @@ Setup::Setup(QWidget *parent) : QStackedWidget(parent) { addWidget(getting_started()); addWidget(network_setup()); + addWidget(software_selection()); downloading_widget = downloading(); addWidget(downloading_widget); diff --git a/selfdrive/ui/qt/setup/setup.h b/selfdrive/ui/qt/setup/setup.h index 8c33acc380..ee5cc85483 100644 --- a/selfdrive/ui/qt/setup/setup.h +++ b/selfdrive/ui/qt/setup/setup.h @@ -17,6 +17,7 @@ private: QWidget *low_voltage(); QWidget *getting_started(); QWidget *network_setup(); + QWidget *software_selection(); QWidget *downloading(); QWidget *download_failed(QLabel *url, QLabel *body); diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index e952940056..75b966c9aa 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -55,7 +55,7 @@ void Sidebar::mouseReleaseEvent(QMouseEvent *event) { flag_pressed = settings_pressed = false; update(); } - if (home_btn.contains(event->pos())) { + if (onroad && home_btn.contains(event->pos())) { MessageBuilder msg; msg.initEvent().initUserFlag(); pm->send("userFlag", msg); diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index bc3c494fa7..0bf5f2865e 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -61,7 +61,7 @@ QString timeAgo(const QDateTime &date) { QString s; if (diff < 60) { - s = "now"; + s = QObject::tr("now"); } else if (diff < 60 * 60) { int minutes = diff / 60; s = QObject::tr("%n minute(s) ago", "", minutes); @@ -105,20 +105,21 @@ void initApp(int argc, char *argv[], bool disable_hidpi) { std::signal(SIGINT, sigTermHandler); std::signal(SIGTERM, sigTermHandler); - if (disable_hidpi) { + QString app_dir; #ifdef __APPLE__ - // Get the devicePixelRatio, and scale accordingly to maintain 1:1 rendering - QApplication tmp(argc, argv); - qputenv("QT_SCALE_FACTOR", QString::number(1.0 / tmp.devicePixelRatio() ).toLocal8Bit()); -#endif + // Get the devicePixelRatio, and scale accordingly to maintain 1:1 rendering + QApplication tmp(argc, argv); + app_dir = QCoreApplication::applicationDirPath(); + if (disable_hidpi) { + qputenv("QT_SCALE_FACTOR", QString::number(1.0 / tmp.devicePixelRatio()).toLocal8Bit()); } +#else + app_dir = QFileInfo(util::readlink("/proc/self/exe").c_str()).path(); +#endif 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); + QDir::setCurrent(app_dir); setQtSurfaceFormat(); } diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 7b1f2f1d24..914b04b654 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -41,6 +42,8 @@ const char frame_fragment_shader[] = "out vec4 colorOut;\n" "void main() {\n" " colorOut = texture(uTexture, vTexCoord);\n" + // gamma to improve worst case visibility when dark + " colorOut.rgb = pow(colorOut.rgb, vec3(1.0/1.28));\n" "}\n"; #else #ifdef __APPLE__ @@ -96,12 +99,13 @@ mat4 get_fit_view_transform(float widget_aspect_ratio, float frame_aspect_ratio) } // namespace CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget* parent) : - stream_name(stream_name), requested_stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { + stream_name(stream_name), active_stream_type(type), requested_stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); qRegisterMetaType>("availableStreams"); QObject::connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection); QObject::connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived, Qt::QueuedConnection); QObject::connect(this, &CameraWidget::vipcAvailableStreamsUpdated, this, &CameraWidget::availableStreamsUpdated, Qt::QueuedConnection); + QObject::connect(QApplication::instance(), &QCoreApplication::aboutToQuit, this, &CameraWidget::stopVipcThread); } CameraWidget::~CameraWidget() { diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index c97038cf43..85ec498735 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -22,7 +22,7 @@ #include #endif -#include "cereal/visionipc/visionipc_client.h" +#include "msgq/visionipc/visionipc_client.h" #include "system/camerad/cameras/camera_common.h" #include "selfdrive/ui/ui.h" diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index e4630c6590..aa304e0df6 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -223,10 +223,8 @@ public: button_group->addButton(button, i); } - QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonToggled), [=](int id, bool checked) { - if (checked) { - params.put(key, std::to_string(id)); - } + QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) { + params.put(key, std::to_string(id)); }); } @@ -236,6 +234,19 @@ public: } } + void setCheckedButton(int id) { + button_group->button(id)->setChecked(true); + } + + void refresh() { + int value = atoi(params.get(key).c_str()); + button_group->button(value)->setChecked(true); + } + + void showEvent(QShowEvent *event) override { + refresh(); + } + private: std::string key; Params params; diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc index 324d6cf6ae..2621612f67 100644 --- a/selfdrive/ui/qt/widgets/prime.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -235,7 +235,7 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) { mainLayout->addWidget(content); - primeUser->setVisible(uiState()->primeType()); + primeUser->setVisible(uiState()->hasPrime()); mainLayout->setCurrentIndex(1); setStyleSheet(R"( @@ -269,15 +269,16 @@ void SetupWidget::replyFinished(const QString &response, bool success) { } QJsonObject json = doc.object(); + bool is_paired = json["is_paired"].toBool(); PrimeType prime_type = static_cast(json["prime_type"].toInt()); - uiState()->setPrimeType(prime_type); + uiState()->setPrimeType(is_paired ? prime_type : PrimeType::UNPAIRED); - if (!json["is_paired"].toBool()) { + if (!is_paired) { mainLayout->setCurrentIndex(0); } else { popup->reject(); - primeUser->setVisible(prime_type); + primeUser->setVisible(uiState()->hasPrime()); mainLayout->setCurrentIndex(1); } } diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 74fd05ed7b..6b579fcc5d 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -75,10 +75,6 @@ void MainWindow::closeSettings() { if (uiState()->scene.started) { homeWindow->showSidebar(false); - // Map is always shown when using navigate on openpilot - if (uiState()->scene.navigate_on_openpilot) { - homeWindow->showMapPanel(true); - } } } diff --git a/selfdrive/ui/tests/body.py b/selfdrive/ui/tests/body.py index c34e717eaf..7e24c2beb7 100755 --- a/selfdrive/ui/tests/body.py +++ b/selfdrive/ui/tests/body.py @@ -8,7 +8,7 @@ if __name__ == "__main__": batt = 1. while True: msg = messaging.new_message('carParams') - msg.carParams.carName = "COMMA BODY" + msg.carParams.carName = "BODY" msg.carParams.notCar = True pm.send('carParams', msg) diff --git a/selfdrive/ui/tests/test_soundd.py b/selfdrive/ui/tests/test_soundd.py old mode 100755 new mode 100644 index 94ce26eb47..468bc92cca --- a/selfdrive/ui/tests/test_soundd.py +++ b/selfdrive/ui/tests/test_soundd.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python3 -import unittest - from cereal import car from cereal import messaging from cereal.messaging import SubMaster, PubMaster @@ -11,7 +8,7 @@ import time AudibleAlert = car.CarControl.HUDControl.AudibleAlert -class TestSoundd(unittest.TestCase): +class TestSoundd: def test_check_controls_timeout_alert(self): sm = SubMaster(['controlsState']) pm = PubMaster(['controlsState']) @@ -26,16 +23,13 @@ class TestSoundd(unittest.TestCase): sm.update(0) - self.assertFalse(check_controls_timeout_alert(sm)) + assert not check_controls_timeout_alert(sm) for _ in range(CONTROLS_TIMEOUT * 110): sm.update(0) time.sleep(0.01) - self.assertTrue(check_controls_timeout_alert(sm)) + assert check_controls_timeout_alert(sm) # TODO: add test with micd for checking that soundd actually outputs sounds - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py old mode 100755 new mode 100644 index 8e50695e70..0967152fa4 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python3 +import pytest import json import os import re -import unittest import shutil import tempfile import xml.etree.ElementTree as ET @@ -21,7 +20,7 @@ FORMAT_ARG = re.compile("%[0-9]+") @parameterized_class(("name", "file"), translation_files.items()) -class TestTranslations(unittest.TestCase): +class TestTranslations: name: str file: str @@ -32,8 +31,8 @@ class TestTranslations(unittest.TestCase): return f.read() def test_missing_translation_files(self): - self.assertTrue(os.path.exists(os.path.join(TRANSLATIONS_DIR, f"{self.file}.ts")), - f"{self.name} has no XML translation file, run selfdrive/ui/update_translations.py") + assert os.path.exists(os.path.join(TRANSLATIONS_DIR, f"{self.file}.ts")), \ + f"{self.name} has no XML translation file, run selfdrive/ui/update_translations.py" def test_translations_updated(self): with tempfile.TemporaryDirectory() as tmpdir: @@ -42,19 +41,19 @@ class TestTranslations(unittest.TestCase): cur_translations = self._read_translation_file(TRANSLATIONS_DIR, self.file) new_translations = self._read_translation_file(tmpdir, self.file) - self.assertEqual(cur_translations, new_translations, - f"{self.file} ({self.name}) XML translation file out of date. Run selfdrive/ui/update_translations.py to update the translation files") + assert cur_translations == new_translations, \ + f"{self.file} ({self.name}) XML translation file out of date. Run selfdrive/ui/update_translations.py to update the translation files" - @unittest.skip("Only test unfinished translations before going to release") + @pytest.mark.skip("Only test unfinished translations before going to release") def test_unfinished_translations(self): cur_translations = self._read_translation_file(TRANSLATIONS_DIR, self.file) - self.assertTrue(UNFINISHED_TRANSLATION_TAG not in cur_translations, - f"{self.file} ({self.name}) translation file has unfinished translations. Finish translations or mark them as completed in Qt Linguist") + assert UNFINISHED_TRANSLATION_TAG not in cur_translations, \ + f"{self.file} ({self.name}) translation file has unfinished translations. Finish translations or mark them as completed in Qt Linguist" def test_vanished_translations(self): cur_translations = self._read_translation_file(TRANSLATIONS_DIR, self.file) - self.assertTrue("" not in cur_translations, - f"{self.file} ({self.name}) translation file has obsolete translations. Run selfdrive/ui/update_translations.py --vanish to remove them") + assert "" not in cur_translations, \ + f"{self.file} ({self.name}) translation file has obsolete translations. Run selfdrive/ui/update_translations.py --vanish to remove them" def test_finished_translations(self): """ @@ -81,27 +80,27 @@ class TestTranslations(unittest.TestCase): numerusform = [t.text for t in translation.findall("numerusform")] for nf in numerusform: - self.assertIsNotNone(nf, f"Ensure all plural translation forms are completed: {source_text}") - self.assertIn("%n", nf, "Ensure numerus argument (%n) exists in translation.") - self.assertIsNone(FORMAT_ARG.search(nf), f"Plural translations must use %n, not %1, %2, etc.: {numerusform}") + assert nf is not None, f"Ensure all plural translation forms are completed: {source_text}" + assert "%n" in nf, "Ensure numerus argument (%n) exists in translation." + assert FORMAT_ARG.search(nf) is None, f"Plural translations must use %n, not %1, %2, etc.: {numerusform}" else: - self.assertIsNotNone(translation.text, f"Ensure translation is completed: {source_text}") + assert translation.text is not None, f"Ensure translation is completed: {source_text}" source_args = FORMAT_ARG.findall(source_text) translation_args = FORMAT_ARG.findall(translation.text) - self.assertEqual(sorted(source_args), sorted(translation_args), - f"Ensure format arguments are consistent: `{source_text}` vs. `{translation.text}`") + assert sorted(source_args) == sorted(translation_args), \ + f"Ensure format arguments are consistent: `{source_text}` vs. `{translation.text}`" def test_no_locations(self): for line in self._read_translation_file(TRANSLATIONS_DIR, self.file).splitlines(): - self.assertFalse(line.strip().startswith(LOCATION_TAG), - f"Line contains location tag: {line.strip()}, remove all line numbers.") + assert not line.strip().startswith(LOCATION_TAG), \ + f"Line contains location tag: {line.strip()}, remove all line numbers." def test_entities_error(self): cur_translations = self._read_translation_file(TRANSLATIONS_DIR, self.file) matches = re.findall(r'@(\w+);', cur_translations) - self.assertEqual(len(matches), 0, f"The string(s) {matches} were found with '@' instead of '&'") + assert len(matches) == 0, f"The string(s) {matches} were found with '@' instead of '&'" def test_bad_language(self): IGNORED_WORDS = {'pédale'} @@ -128,7 +127,3 @@ class TestTranslations(unittest.TestCase): words = set(translation_text.translate(str.maketrans('', '', string.punctuation + '%n')).lower().split()) bad_words_found = words & (banned_words - IGNORED_WORDS) assert not bad_words_found, f"Bad language found in {self.name}: '{translation_text}'. Bad word(s): {', '.join(bad_words_found)}" - - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/ui/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py index 7a2ac9a110..1d33c103a4 100644 --- a/selfdrive/ui/tests/test_ui/run.py +++ b/selfdrive/ui/tests/test_ui/run.py @@ -8,17 +8,15 @@ import numpy as np import os import pywinctl import time -import unittest -from parameterized import parameterized from cereal import messaging, car, log -from cereal.visionipc import VisionIpcServer, VisionStreamType +from msgq.visionipc import VisionIpcServer, VisionStreamType from cereal.messaging import SubMaster, PubMaster from openpilot.common.mock import mock_messages from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL -from openpilot.common.transformations.camera import tici_f_frame_size +from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.selfdrive.test.helpers import with_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state from openpilot.tools.webcam.camera import Camera @@ -69,15 +67,16 @@ def setup_onroad(click, pm: PubMaster): pm.send("pandaStates", dat) + d = DEVICE_CAMERAS[("tici", "ar0231")] server = VisionIpcServer("camerad") - server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, False, *tici_f_frame_size) - server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, *tici_f_frame_size) - server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, *tici_f_frame_size) + server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, False, d.fcam.width, d.fcam.height) + server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, d.dcam.width, d.dcam.height) + server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, d.fcam.width, d.fcam.height) server.start_listener() time.sleep(0.5) # give time for vipc server to start - IMG = Camera.bgr2nv12(np.random.randint(0, 255, (*tici_f_frame_size,3), dtype=np.uint8)) + IMG = Camera.bgr2nv12(np.random.randint(0, 255, (d.fcam.width, d.fcam.height, 3), dtype=np.uint8)) IMG_BYTES = IMG.flatten().tobytes() cams = ('roadCameraState', 'wideRoadCameraState') @@ -121,16 +120,11 @@ TEST_OUTPUT_DIR = TEST_DIR / "report" SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots" -class TestUI(unittest.TestCase): - @classmethod - def setUpClass(cls): +class TestUI: + def __init__(self): os.environ["SCALE"] = "1" sys.modules["mouseinfo"] = False - @classmethod - def tearDownClass(cls): - del sys.modules["mouseinfo"] - def setup(self): self.sm = SubMaster(["uiDebug"]) self.pm = PubMaster(["deviceState", "pandaStates", "controlsState", 'roadCameraState', 'wideRoadCameraState', 'liveLocationKalman']) @@ -146,8 +140,8 @@ class TestUI(unittest.TestCase): def screenshot(self): import pyautogui im = pyautogui.screenshot(region=(self.ui.left, self.ui.top, self.ui.width, self.ui.height)) - self.assertEqual(im.width, 2160) - self.assertEqual(im.height, 1080) + assert im.width == 2160 + assert im.height == 1080 img = np.array(im) im.close() return img @@ -157,7 +151,6 @@ class TestUI(unittest.TestCase): pyautogui.click(self.ui.left + x, self.ui.top + y, *args, **kwargs) time.sleep(UI_DELAY) # give enough time for the UI to react - @parameterized.expand(CASES.items()) @with_processes(["ui"]) def test_ui(self, name, setup_case): self.setup() @@ -187,7 +180,10 @@ def create_screenshots(): shutil.rmtree(TEST_OUTPUT_DIR) SCREENSHOTS_DIR.mkdir(parents=True) - unittest.main(exit=False) + + t = TestUI() + for name, setup in CASES.items(): + t.test_ui(name, setup) if __name__ == "__main__": print("creating test screenshots") diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index eb6a580c01..d8146723a4 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -293,6 +293,18 @@ Review مراجعة + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + اقرن جهازك بجهاز (connect.comma.ai) واحصل على عرضك من comma prime. + + + Pair Device + إقران الجهاز + + + PAIR + إقران +
    DriverViewWindow @@ -428,10 +440,6 @@ غير قادر على تحميل التحديثات %1 - - Invalid date and time settings, system won't start. Connect to internet to set time. - إعدادات التاريخ والتوقيت غير صحيحة، لن يبدأ النظام. اتصل بالإنترنت من أجل ضبط الوقت. - Taking camera snapshots. System won't start until finished. التقاط لقطات كاميرا. لن يبدأ النظام حتى تنتهي هذه العملية. @@ -480,6 +488,29 @@ تنبيه + + OnroadAlerts + + openpilot Unavailable + openpilot غير متوفر + + + Waiting for controls to start + في انتظار بدء عناصر التحكم + + + TAKE CONTROL IMMEDIATELY + تحكم على الفور + + + Controls Unresponsive + الضوابط غير مستجيبة + + + Reboot Device + إعادة التشغيل + + PairingPopup @@ -615,6 +646,10 @@ ft قدم + + now + الآن + Reset @@ -654,7 +689,7 @@ This may take up to a minute. System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - + تم تفعيل إعادة ضبط النظام. اضغط على تأكيد لمسح جميع المحتويات والإعدادات. اضغط على إلغاء لاستئناف التمهيد. @@ -762,6 +797,18 @@ This may take up to a minute. Select a language اختر لغة + + Choose Software to Install + اختر البرنامج للتثبيت + + + openpilot + openpilot + + + Custom Software + البرمجيات المخصصة + SetupWidget @@ -1005,7 +1052,7 @@ This may take up to a minute. TogglesPanel Enable openpilot - تمكين + تمكين openpilot Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. @@ -1091,10 +1138,6 @@ This may take up to a minute. Driving Personality شخصية القيادة - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - يوصى بالوضع القياسي. في الوضع الهجومي، سيتبع openpilot السيارات الرائدة بشكل أقرب، ويصبح أكثر هجومية في دواسات الوقود والمكابح. في وضعية الراحة يبقى openplot بعيداً لمسافة جيدة عن السيارة الرائدة. - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: يتم وضع openpilot بشكل قياسي في <b>وضعية الراحة</b>. يمكن الوضع التجريبي <b>ميزات المستوى ألفا</b> التي لا تكون جاهزة في وضع الراحة: @@ -1107,22 +1150,10 @@ This may take up to a minute. Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. دع نظام القيادة يتحكم بالوقود والمكابح. سيقوم openpilot بالقيادة كما لو أنه كائن بشري، بما في ذلك التوقف عند الإشارة الحمراء، وإشارات التوقف. وبما أن نمط القيادة يحدد سرعة القيادة، فإن السرعة المضبوطة تشكل الحد الأقصى فقط. هذه خاصية الجودة ألفا، فيجب توقع حدوث الأخطاء. - - Navigate on openpilot - التنقل على openpilot - - - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - عندما يكون هناك وجهة للتنقل، فإن openpilot سيقوم بإدخال معلومات الخريطة في هذا النموذج. وهذا يقدم سياقاً مفيداً ويسمح لـopenpilot بالبقاء يساراً أو يميناً بالشكل المناسب عند المنعطفات/المخارج. يبقى سلوك تغيير المسار مفعلاً عند السائق،. هذه هي خاصية الجودة ألفا، ولذلك يجب توقع الأخطاء لا سيما عند المخارج والمنعطفات هذه الأخطاء قد تشمل العبور غير المقصود لخطوط المسارات، والتأخر في الخروج، والقيادة نحو الحواجز الفاصلة في المناطق المثلثة بين الطريق الرئيسي والمخارج، وغير ذلك من الأخطاء المشابهة. - New Driving Visualization تصور القيادة الديد - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - تصور القيادة سينتقل إلى الكاميرا واسعة الزاوية المواجهة للطريق في السرعات المنخفضة من أجل إظهار بعض المنعطفات بشكل أفضل. سيتم أيضاً إظهار شعار الوضع التجريبي في الزاوية العلوية اليمنى. عند تحديد وجهة التنقل، واستخدام نظام القيادة لها كوضع مدخل، سيتحول مسار القيادة على الخريطة إلى اللون الأخضر. - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. الوضع التجريبي غير متوفر حالياً في هذه السيارة نظراً لاستخدام رصيد التحكم التكيفي بالسرعة من أجل التحكم الطولي. @@ -1139,6 +1170,22 @@ This may take up to a minute. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. تمكين التحكم الطولي من openpilot (ألفا) للسماح بالوضع التجريبي. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + يوصى بالمعيار. في الوضع العدواني، سيتبع الطيار المفتوح السيارات الرائدة بشكل أقرب ويكون أكثر عدوانية مع البنزين والفرامل. في الوضع المريح، سيبقى openpilot بعيدًا عن السيارات الرائدة. في السيارات المدعومة، يمكنك التنقل بين هذه الشخصيات باستخدام زر مسافة عجلة القيادة. + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + Updater diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 6d814b9625..010aa4d304 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -293,6 +293,18 @@ Review Überprüfen + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + Koppele dein Gerät mit Comma Connect (connect.comma.ai) und sichere dir dein Comma Prime Angebot. + + + Pair Device + + + + PAIR + + DriverViewWindow @@ -419,10 +431,6 @@ %1 - - Invalid date and time settings, system won't start. Connect to internet to set time. - - Taking camera snapshots. System won't start until finished. @@ -475,6 +483,29 @@ HINWEIS + + OnroadAlerts + + openpilot Unavailable + + + + Waiting for controls to start + + + + TAKE CONTROL IMMEDIATELY + + + + Controls Unresponsive + + + + Reboot Device + + + PairingPopup @@ -598,6 +629,10 @@ ft fuß + + now + + Reset @@ -744,6 +779,18 @@ This may take up to a minute. Select a language Sprache wählen + + Choose Software to Install + + + + openpilot + openpilot + + + Custom Software + + SetupWidget @@ -1093,26 +1140,10 @@ This may take up to a minute. On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - - End-to-End Longitudinal Control - - Navigate on openpilot - - - - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - - openpilot longitudinal control may come in a future update. @@ -1125,6 +1156,22 @@ This may take up to a minute. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + Updater diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index cd96c466e9..dde6adadd3 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -293,6 +293,18 @@ Disengage to Power Off Désengager pour éteindre + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + Associez votre appareil avec comma connect (connect.comma.ai) et profitez de l'offre comma prime. + + + Pair Device + + + + PAIR + + DriverViewWindow @@ -424,10 +436,6 @@ Impossible de télécharger les mises à jour %1 - - Invalid date and time settings, system won't start. Connect to internet to set time. - Paramètres de date et d'heure invalides, le système ne démarrera pas. Connectez l'appareil à Internet pour régler l'heure. - Taking camera snapshots. System won't start until finished. Capture de clichés photo. Le système ne démarrera pas tant qu'il n'est pas terminé. @@ -476,6 +484,29 @@ ALERTE + + OnroadAlerts + + openpilot Unavailable + + + + Waiting for controls to start + + + + TAKE CONTROL IMMEDIATELY + + + + Controls Unresponsive + + + + Reboot Device + + + PairingPopup @@ -599,6 +630,10 @@ ft ft + + now + + Reset @@ -746,6 +781,18 @@ Cela peut prendre jusqu'à une minute. Select a language Choisir une langue + + Choose Software to Install + + + + openpilot + openpilot + + + Custom Software + + SetupWidget @@ -1075,10 +1122,6 @@ Cela peut prendre jusqu'à une minute. Driving Personality Personnalité de conduite - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - Le mode standard est recommandé. En mode agressif, openpilot suivra de plus près les voitures de tête et sera plus agressif avec l'accélérateur et le frein. En mode détendu, openpilot restera plus éloigné des voitures de tête. - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: Par défaut, openpilot conduit en <b>mode détente</b>. Le mode expérimental permet d'activer des <b>fonctionnalités alpha</b> qui ne sont pas prêtes pour le mode détente. Les fonctionnalités expérimentales sont listées ci-dessous : @@ -1107,22 +1150,26 @@ Cela peut prendre jusqu'à une minute. End-to-End Longitudinal Control Contrôle longitudinal de bout en bout - - Navigate on openpilot - Navigation avec openpilot - - - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - Lorsque la navigation dispose d'une destination, openpilot entrera les informations de la carte dans le modèle. Cela fournit un contexte utile pour le modèle et permet à openpilot de se diriger à gauche ou à droite de manière appropriée aux bifurcations/sorties. Le comportement relatif au changement de voie reste inchangé et doit toujours être activé par le conducteur. Il s'agit d'une fonctionnalité alpha ; il faut s'attendre à des erreurs, en particulier aux abords des sorties et des bifurcations. Ces erreurs peuvent inclure des franchissements involontaires de passages piétons, des prises de sortie tardives, la conduite vers des zones de séparation de type zebras, etc. - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - La visualisation de la conduite passera sur la caméra grand angle dirigée vers la route à faible vitesse afin de mieux montrer certains virages. Le logo du mode expérimental s'affichera également dans le coin supérieur droit. Lorsqu'une destination de navigation est définie et que le modèle de conduite l'utilise comme entrée, la trajectoire de conduite sur la carte deviendra verte. - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. Activer le contrôle longitudinal d'openpilot (en alpha) pour autoriser le mode expérimental. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + La visualisation de la conduite passera sur la caméra grand angle dirigée vers la route à faible vitesse afin de mieux montrer certains virages. Le logo du mode expérimental s'affichera également dans le coin supérieur droit. + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + Updater diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index d07c7d525a..e0fb60620b 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -293,6 +293,18 @@ Review 確認 + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + デバイスを comma connect (connect.comma.ai)でペアリングし、comma primeの特典を申請してください。 + + + Pair Device + + + + PAIR + + DriverViewWindow @@ -418,10 +430,6 @@ %1 - - Invalid date and time settings, system won't start. Connect to internet to set time. - - Taking camera snapshots. System won't start until finished. @@ -474,6 +482,29 @@ 警告 + + OnroadAlerts + + openpilot Unavailable + + + + Waiting for controls to start + + + + TAKE CONTROL IMMEDIATELY + + + + Controls Unresponsive + + + + Reboot Device + + + PairingPopup @@ -594,6 +625,10 @@ ft フィート + + now + + Reset @@ -740,6 +775,18 @@ This may take up to a minute. Select a language 言語を選択 + + Choose Software to Install + + + + openpilot + openpilot + + + Custom Software + + SetupWidget @@ -1085,26 +1132,10 @@ This may take up to a minute. On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - - End-to-End Longitudinal Control - - Navigate on openpilot - - - - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - - openpilot longitudinal control may come in a future update. @@ -1117,6 +1148,22 @@ This may take up to a minute. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + Updater diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index a903978329..56fc5014ee 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -293,6 +293,18 @@ Review 다시보기 + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + 장치를 comma connect (connect.comma.ai)에서 페어링하고 comma prime 무료 이용권을 사용하세요. + + + Pair Device + 장치 동기화 + + + PAIR + 동기화 + DriverViewWindow @@ -419,10 +431,6 @@ 업데이트를 다운로드할 수 없습니다 %1 - - Invalid date and time settings, system won't start. Connect to internet to set time. - 날짜 및 시간 설정이 잘못되어 시스템이 시작되지 않습니다. 날짜와 시간을 동기화하려면 인터넷에 연결하세요. - Taking camera snapshots. System won't start until finished. 카메라 스냅샷 찍기가 완료될 때까지 시스템이 시작되지 않습니다. @@ -475,11 +483,34 @@ 알림 + + OnroadAlerts + + openpilot Unavailable + 오픈파일럿을 사용할수없습니다 + + + Waiting for controls to start + 프로세스가 준비중입니다 + + + TAKE CONTROL IMMEDIATELY + 핸들을 잡아주세요 + + + Controls Unresponsive + 프로세스가 응답하지않습니다 + + + Reboot Device + 장치를 재부팅하세요 + + PairingPopup Pair your device to your comma account - 장치를 comma 계정에 페어링합니다 + 장치를 comma 계정에 동기화합니다 Go to https://connect.comma.ai on your phone @@ -595,6 +626,10 @@ ft ft + + now + now + Reset @@ -634,7 +669,7 @@ This may take up to a minute. System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - + 시스템 재설정이 시작되었습니다. 모든 콘텐츠와 설정을 지우려면 확인을 누르시고 부팅을 재개하려면 취소를 누르세요. @@ -742,6 +777,18 @@ This may take up to a minute. Select a language 언어를 선택하세요 + + Choose Software to Install + 설치할 소프트웨어 선택 + + + openpilot + openpilot + + + Custom Software + 커스텀 소프트웨어 + SetupWidget @@ -762,7 +809,7 @@ This may take up to a minute. Sidebar CONNECT - 연결됨 + 커넥트 OFFLINE @@ -1091,18 +1138,10 @@ This may take up to a minute. Driving Personality 주행 모드 - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - 표준 모드를 권장합니다. 공격적 모드에서 openpilot은 앞 차량을 더 가까이 따라가며 적극적으로 가감속합니다. 편안한 모드에서 openpilot은 앞 차량을 더 멀리서 따라갑니다. - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. openpilot 가감속 제어 알파 버전은 비 릴리즈 브랜치에서 실험 모드와 함께 테스트할 수 있습니다. - - Navigate on openpilot - openpilot 내비게이트 - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. 실험 모드를 사용하려면 openpilot E2E 가감속 제어 (알파) 토글을 활성화하세요. @@ -1112,12 +1151,20 @@ This may take up to a minute. E2E 가감속 제어 - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - 내비게이션에 목적지가 설정되어 있으면 openpilot이 지도 정보를 주행 모델에 입력합니다. 이는 모델에 유용한 정보를 제공하고 openpilot이 진출입로 및 램프에서 적절하게 왼쪽 또는 오른쪽을 유지할 수 있도록 해 줍니다. 차선 변경 기능은 여전히 운전자의 조작에 의해 활성화됩니다. 이 기능은 알파 버전입니다. 특히 진출입로 및 분기점 주변에서 실수가 발생할 수 있으며 이러한 실수에는 의도하지 않은 차선 이탈, 늦은 진출, 도로 가장자리의 분리대 또는 경계석을 향해 운전하는 행동 등이 포함됩니다. + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + 표준 모드를 권장합니다. 공격적 모드의 openpilot은 선두 차량을 더 가까이 따라가고 가감속제어를 사용하여 더욱 공격적으로 움직입니다. 편안한 모드의 openpilot은 선두 차량으로부터 더 멀리 떨어져 있습니다. 지원되는 차량에서는 스티어링 휠 거리 버튼을 사용하여 이러한 특성을 순환할 수 있습니다. - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - 주행 시각화는 저속으로 주행 시 도로를 향한 광각 카메라로 자동 전환되어 일부 곡선 경로를 더 잘 보여줍니다. 실험 모드 로고는 우측 상단에 표시됩니다. 내비게이션 목적지가 설정되고 주행 모델에 입력되면 지도의 주행 경로가 녹색으로 바뀝니다. + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index fce5a8a8ff..feaa6e86a1 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -293,6 +293,18 @@ Review Revisar + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + Pareie seu dispositivo com comma connect (connect.comma.ai) e reivindique sua oferta de comma prime. + + + Pair Device + Parear Dispositivo + + + PAIR + PAREAR + DriverViewWindow @@ -420,10 +432,6 @@ Não é possível baixar atualizações %1 - - Invalid date and time settings, system won't start. Connect to internet to set time. - Configurações de data e hora inválidas, o sistema não será iniciado. Conecte-se à internet para definir o horário. - Taking camera snapshots. System won't start until finished. Tirando fotos da câmera. O sistema não será iniciado até terminar. @@ -476,6 +484,29 @@ ALERTA + + OnroadAlerts + + openpilot Unavailable + openpilot Indisponível + + + Waiting for controls to start + Aguardando controles para iniciar + + + TAKE CONTROL IMMEDIATELY + ASSUMA IMEDIATAMENTE + + + Controls Unresponsive + Controles Não Respondem + + + Reboot Device + Reinicie o Dispositivo + + PairingPopup @@ -599,6 +630,10 @@ ft pés + + now + agora + Reset @@ -638,7 +673,7 @@ Isso pode levar até um minuto. System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - Reinicialização do sistema acionada. Pressione confirmar para apagar todo o conteúdo e configurações. Pressione cancel para retomar a inicialização. + Reinicialização do sistema acionada. Pressione confirmar para apagar todo o conteúdo e configurações. Pressione cancel para retomar a inicialização. @@ -746,6 +781,18 @@ Isso pode levar até um minuto. Select a language Selecione o Idioma + + Choose Software to Install + Escolha o Software a ser Instalado + + + openpilot + openpilot + + + Custom Software + Software Customizado + SetupWidget @@ -1095,18 +1142,10 @@ Isso pode levar até um minuto. Driving Personality Temperamento de Direção - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - Neutro é o recomendado. No modo disputa o openpilot seguirá o carro da frente mais de perto e será mais agressivo com a aceleração e frenagem. No modo calmo o openpilot se manterá mais longe do carro da frente. - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. Uma versão embrionária do controle longitudinal openpilot pode ser testada em conjunto com o modo Experimental, em branches que não sejam de produção. - - Navigate on openpilot - Navegação no openpilot - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. Habilite o controle longitudinal (embrionário) openpilot para permitir o modo Experimental. @@ -1116,12 +1155,20 @@ Isso pode levar até um minuto. Controle Longitudinal de Ponta a Ponta - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - Quando a navegação tem um destino, o openpilot insere as informações do mapa no modelo. Isso fornece contexto útil para o modelo e permite que o openpilot mantenha a esquerda ou a direita apropriadamente em bifurcações/saídas. O comportamento de mudança de faixa permanece inalterado e ainda é ativado somente pelo motorista. Este é um recurso de qualidade embrionária; erros devem ser esperados, principalmente em torno de saídas e bifurcações. Esses erros podem incluir travessias não intencionais na faixa de rodagem, saída tardia, condução em direção a barreiras divisórias nas áreas de marcas de canalização, etc. + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + Neutro é o recomendado. No modo disputa o openpilot seguirá o carro da frente mais de perto e será mais agressivo com a aceleração e frenagem. No modo calmo o openpilot se manterá mais longe do carro da frente. Em carros compatíveis, você pode alternar esses temperamentos com o botão de distância do volante. - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - A visualização de condução fará a transição para a câmera grande angular voltada para a estrada em baixas velocidades para mostrar melhor algumas curvas. O logotipo do modo Experimental também será mostrado no canto superior direito. Quando um destino de navegação é definido e o modelo de condução o utiliza como entrada o caminho de condução no mapa fica verde. + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + A visualização de condução fará a transição para a câmera grande angular voltada para a estrada em baixas velocidades para mostrar melhor algumas curvas. O logotipo do modo Experimental também será mostrado no canto superior direito. + + + Always-On Driver Monitoring + Monitoramento do Motorista Sempre Ativo + + + Enable driver monitoring even when openpilot is not engaged. + Habilite o monitoramento do motorista mesmo quando o openpilot não estiver acionado. diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index f4d097b2a2..e594a6975f 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -68,23 +68,23 @@ Hidden Network - + เครือข่ายที่ซ่อนอยู่ CONNECT - เชื่อมต่อ + เชื่อมต่อ Enter SSID - ป้อนค่า SSID + ป้อนค่า SSID Enter password - ใส่รหัสผ่าน + ใส่รหัสผ่าน for "%1" - สำหรับ "%1" + สำหรับ "%1" @@ -293,6 +293,18 @@ Review ทบทวน + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + จับคู่อุปกรณ์ของคุณกับ comma connect (connect.comma.ai) และรับข้อเสนอ comma prime ของคุณ + + + Pair Device + จับคู่อุปกรณ์ + + + PAIR + จับคู่ + DriverViewWindow @@ -423,10 +435,6 @@ ไม่สามารถดาวน์โหลดอัพเดทได้ %1 - - Invalid date and time settings, system won't start. Connect to internet to set time. - วันที่และเวลาไม่ถูกต้อง ระบบจะไม่เริ่มทำงาน เชื่อต่ออินเตอร์เน็ตเพื่อตั้งเวลา - Taking camera snapshots. System won't start until finished. กล้องกำลังถ่ายภาพ ระบบจะไม่เริ่มทำงานจนกว่าจะเสร็จ @@ -475,6 +483,29 @@ การแจ้งเตือน + + OnroadAlerts + + openpilot Unavailable + openpilot ไม่สามารถใช้งานได้ + + + Waiting for controls to start + กำลังรอให้ controls เริ่มทำงาน + + + TAKE CONTROL IMMEDIATELY + เข้าควบคุมรถเดี๋ยวนี้ + + + Controls Unresponsive + Controls ไม่ตอบสนอง + + + Reboot Device + รีบูตอุปกรณ์ + + PairingPopup @@ -595,6 +626,10 @@ ft ฟุต + + now + ตอนนี้ + Reset @@ -634,7 +669,7 @@ This may take up to a minute. System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - + ระบบถูกรีเซ็ต กดยืนยันเพื่อลบข้อมูลและการตั้งค่าทั้งหมด กดยกเลิกเพื่อบูตต่อ @@ -742,6 +777,18 @@ This may take up to a minute. Select a language เลือกภาษา + + Choose Software to Install + เลือกซอฟต์แวร์ที่จะติดตั้ง + + + openpilot + openpilot + + + Custom Software + ซอฟต์แวร์ที่กำหนดเอง + SetupWidget @@ -1091,10 +1138,6 @@ This may take up to a minute. Driving Personality บุคลิกการขับขี่ - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - แนะนำให้ใช้แบบมาตรฐาน ในโหมดดุดัน openpilot จะตามรถคันหน้าใกล้ขึ้นและเร่งและเบรคแบบดุดันมากขึ้น ในโหมดผ่อนคลาย openpilot จะอยู่ห่างจากรถคันหน้ามากขึ้น - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. ระบบควบคุมการเร่ง/เบรคโดย openpilot เวอร์ชัน alpha สามารถทดสอบได้พร้อมกับโหมดการทดลอง บน branch ที่กำลังพัฒนา @@ -1103,22 +1146,26 @@ This may take up to a minute. End-to-End Longitudinal Control ควบคุมเร่ง/เบรคแบบ End-to-End - - Navigate on openpilot - การนำทางบน openpilot - - - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - เมื่อการนำทางมีจุดหมายปลายทาง openpilot จะป้อนข้อมูลแผนที่เข้าไปยังโมเดล ซึ่งจะเป็นบริบทที่มีประโยชน์สำหรับโมเดลและจะทำให้ openpilot สามารถรักษาเลนซ้ายหรือขวาได้อย่างเหมาะสมบริเวณทางแยกหรือทางออก พฤติกรรมการเปลี่ยนเลนยังคงเหมือนเดิมและยังคงต้องถูกเริ่มโดยคนขับ ความสามารถนี้ยังอยู่ในระดับ alpha ซึ่งอาจะเกิดความผิดพลาดได้โดยเฉพาะบริเวณทางแยกหรือทางออก ความผิดพลาดที่อาจเกิดขึ้นได้อาจรวมถึงการข้ามเส้นแบ่งเลนโดยไม่ตั้งใจ, การเข้าช่องทางออกช้ากว่าปกติ, การขับเข้าหาแบริเออร์ในเขตปลอดภัย, ฯลฯ - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - การแสดงภาพการขับขี่จะเปลี่ยนไปใช้กล้องมุมกว้างที่หันหน้าไปทางถนนเมื่ออยู่ในความเร็วต่ำ เพื่อแสดงภาพการเลี้ยวที่ดีขึ้น โลโก้โหมดการทดลองจะแสดงที่มุมบนขวาด้วย เมื่อเป้าหมายการนำทางถูกเลือกและโมเดลการขับขี่กำลังใช้เป็นอินพุต เส้นทางการขับขี่บนแผนที่จะเปลี่ยนเป็นสีเขียว - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. เปิดระบบควบคุมการเร่ง/เบรคโดย openpilot (alpha) เพื่อเปิดใช้งานโหมดทดลอง + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + แนะนำให้ใช้แบบมาตรฐาน ในโหมดดุดัน openpilot จะตามรถคันหน้าใกล้ขึ้นและเร่งและเบรคแบบดุดันมากขึ้น ในโหมดผ่อนคลาย openpilot จะอยู่ห่างจากรถคันหน้ามากขึ้น ในรถรุ่นที่รองรับคุณสามารถเปลี่ยนบุคลิกไปแบบต่าง ๆ โดยใช้ปุ่มปรับระยะห่างบนพวงมาลัย + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + การแสดงภาพการขับขี่จะเปลี่ยนไปใช้กล้องมุมกว้างที่หันหน้าไปทางถนนเมื่ออยู่ในความเร็วต่ำ เพื่อแสดงภาพการเลี้ยวที่ดีขึ้น โลโก้โหมดการทดลองจะแสดงที่มุมบนขวาด้วย + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + Updater diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index ec6f71f5b0..48615f1699 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -293,6 +293,18 @@ Review + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime aboneliğine göz atın. + + + Pair Device + + + + PAIR + + DriverViewWindow @@ -422,10 +434,6 @@ %1 - - Invalid date and time settings, system won't start. Connect to internet to set time. - - Taking camera snapshots. System won't start until finished. @@ -474,6 +482,29 @@ UYARI + + OnroadAlerts + + openpilot Unavailable + + + + Waiting for controls to start + + + + TAKE CONTROL IMMEDIATELY + + + + Controls Unresponsive + + + + Reboot Device + + + PairingPopup @@ -594,6 +625,10 @@ ft ft + + now + + Reset @@ -740,6 +775,18 @@ This may take up to a minute. Select a language Dil seçin + + Choose Software to Install + + + + openpilot + openpilot + + + Custom Software + + SetupWidget @@ -1069,10 +1116,6 @@ This may take up to a minute. On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: @@ -1085,22 +1128,10 @@ This may take up to a minute. Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - - Navigate on openpilot - - - - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - - New Driving Visualization - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. @@ -1117,6 +1148,22 @@ This may take up to a minute. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 91f55c6e94..32119ee10f 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -68,23 +68,23 @@ Hidden Network - + 隐藏的网络 CONNECT - CONNECT + 连线 Enter SSID - 输入SSID + 输入 SSID Enter password - 输入密码 + 输入密码 for "%1" - 网络名称:"%1" + 网络名称:"%1" @@ -293,6 +293,18 @@ Review 预览 + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + 将您的设备与comma connect (connect.comma.ai)配对并领取您的comma prime优惠。 + + + Pair Device + 配对设备 + + + PAIR + 配对 + DriverViewWindow @@ -419,10 +431,6 @@ 无法下载更新 %1 - - Invalid date and time settings, system won't start. Connect to internet to set time. - 日期和时间设置无效,系统无法启动。请连接至互联网以设置时间。 - Taking camera snapshots. System won't start until finished. 正在使用相机拍摄中。在完成之前,系统将无法启动。 @@ -475,6 +483,29 @@ 警报 + + OnroadAlerts + + openpilot Unavailable + 无法使用 openpilot + + + Waiting for controls to start + 等待控制服务啟動 + + + TAKE CONTROL IMMEDIATELY + 立即接管 + + + Controls Unresponsive + 控制服务无响应 + + + Reboot Device + 重启设备 + + PairingPopup @@ -595,6 +626,10 @@ ft ft + + now + 现在 + Reset @@ -634,7 +669,7 @@ This may take up to a minute. System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - + 系统重置已触发。按下“确认”以清除所有内容和设置,按下“取消”以继续启动。 @@ -742,6 +777,18 @@ This may take up to a minute. Select a language 选择语言 + + Choose Software to Install + 选择要安装的软件 + + + openpilot + openpilot + + + Custom Software + 定制软件 + SetupWidget @@ -1091,18 +1138,10 @@ This may take up to a minute. Driving Personality 驾驶风格 - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - 推荐使用标准模式。在积极模式中,openpilot 会更靠近前车并在加速和刹车方面更积极。在舒适模式中,openpilot 会与前车保持较远的距离。 - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. 在正式(release)版本以外的分支上,可以测试 openpilot 纵向控制的 Alpha 版本以及实验模式。 - - Navigate on openpilot - Navigate on openpilot - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. 启用 openpilot 纵向控制(alpha)开关以允许实验模式。 @@ -1112,12 +1151,20 @@ This may take up to a minute. 端到端纵向控制 - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - 当导航有目的地时,openpilot 将输入地图信息到模型中。这为模型提供了有用的背景信息,使 openpilot 能够在叉路/出口时适当地保持左侧或右侧行驶。车道变换行为保持不变,仍由驾驶员激活。这是一个 Alpha 版的功能;可能会出现错误,特别是在出口和分叉处。这些错误可能包括意外的车道越界、晚出口、朝着分隔栏驶向安全地带等。 + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + 推荐使用标准模式。在积极模式下,openpilot 会更靠近前方车辆,并在油门和刹车方面更加激进。在放松模式下,openpilot 会与前方车辆保持更远距离。在支持的车型上,你可以使用方向盘上的距离按钮来循环切换这些驾驶风格。 - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - 行驶画面将在低速时切换到道路朝向的广角摄像头,以更好地显示一些转弯。实验模式标志也将显示在右上角。当设置了导航目的地并且驾驶模型正在使用它作为输入时,地图上的驾驶路径将变为绿色。 + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 在低速时,驾驶可视化将转换为道路朝向的广角摄像头,以更好地展示某些转弯。测试模式标志也将显示在右上角。 + + + Always-On Driver Monitoring + 驾驶员监控常开 + + + Enable driver monitoring even when openpilot is not engaged. + 即使在openpilot未激活时也启用驾驶员监控。 diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index e8c8854d0e..9d1c16db9f 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -68,23 +68,23 @@ Hidden Network - + 隱藏的網路 CONNECT - 雲端服務 + 連線 Enter SSID - 輸入 SSID + 輸入 SSID Enter password - 輸入密碼 + 輸入密碼 for "%1" - 給 "%1" + 給 "%1" @@ -247,11 +247,11 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot 需要將設備固定在左右偏差 4° 以內,朝上偏差 5° 以內或朝下偏差 9° 以內。鏡頭在後台會持續自動校準,很少有需要重設的情況。 + openpilot 需要將裝置固定在左右偏差 4° 以內,朝上偏差 5° 以內或朝下偏差 9° 以內。鏡頭在後台會持續自動校準,很少有需要重置的情況。 Your device is pointed %1° %2 and %3° %4. - 你的設備目前朝%2 %1° 以及朝%4 %3° 。 + 你的裝置目前朝%2 %1° 以及朝%4 %3° 。 down @@ -293,6 +293,18 @@ Review 回顧 + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + 將您的裝置與 comma connect (connect.comma.ai) 配對並領取您的 comma 高級會員優惠。 + + + Pair Device + 配對裝置 + + + PAIR + 配對 + DriverViewWindow @@ -419,21 +431,17 @@ 無法下載更新 %1 - - Invalid date and time settings, system won't start. Connect to internet to set time. - 日期和時間設定無效,系統無法啟動。請連接至網際網路以設定時間。 - Taking camera snapshots. System won't start until finished. 正在使用相機拍攝中。在完成之前,系統將無法啟動。 An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - 一個給您設備的操作系統的更新正在後台下載中。當更新準備好安裝時,您將收到提示進行更新。 + 一個有關操作系統的更新正在後台下載中。當更新準備好安裝時,您將收到提示進行更新。 Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. - 設備註冊失敗。它將無法連接或上傳至 comma.ai 伺服器,並且無法獲得 comma.ai 的支援。如果這是一個官方設備,請訪問 https://comma.ai/support 。 + 裝置註冊失敗。它將無法連接或上傳至 comma.ai 伺服器,並且無法獲得 comma.ai 的支援。如果這是一個官方裝置,請訪問 https://comma.ai/support 。 NVMe drive not mounted. @@ -441,7 +449,7 @@ Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. - 檢測到不支援的 NVMe 固態硬碟。您的設備因為使用了不支援的 NVMe 固態硬碟可能會消耗更多電力並更易過熱。 + 檢測到不支援的 NVMe 固態硬碟。您的裝置因為使用了不支援的 NVMe 固態硬碟可能會消耗更多電力並更易過熱。 openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. @@ -453,11 +461,11 @@ openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - openpilot 偵測到設備的安裝位置發生變化。請確保設備完全安裝在支架上,並確保支架牢固地固定在擋風玻璃上。 + openpilot 偵測到裝置的安裝位置發生變化。請確保裝置完全安裝在支架上,並確保支架牢固地固定在擋風玻璃上。 Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - 設備溫度過高。系統正在冷卻中,等冷卻完畢後才會啟動。目前內部組件溫度:%1 + 裝置溫度過高。系統正在冷卻中,等冷卻完畢後才會啟動。目前內部組件溫度:%1 @@ -475,11 +483,34 @@ 提醒 + + OnroadAlerts + + openpilot Unavailable + 無法使用 openpilot + + + Waiting for controls to start + 等待操控服務開始 + + + TAKE CONTROL IMMEDIATELY + 立即接管 + + + Controls Unresponsive + 操控服務沒有反應 + + + Reboot Device + 請重新啟裝置 + + PairingPopup Pair your device to your comma account - 將設備與您的 comma 帳號配對 + 將裝置與您的 comma 帳號配對 Go to https://connect.comma.ai on your phone @@ -595,6 +626,10 @@ ft ft + + now + 現在 + Reset @@ -604,7 +639,7 @@ Are you sure you want to reset your device? - 您確定要重設你的設備嗎? + 您確定要重設你的裝置嗎? System Reset @@ -624,17 +659,17 @@ Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. - 無法掛載資料分割區。分割區可能已經毀損。請確認是否要刪除並重新設定。 + 無法掛載資料分割區。分割區可能已經毀損。請確認是否要刪除並重置。 Resetting device... This may take up to a minute. - 設備重設中… + 重置中… 這可能需要一分鐘的時間。 System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - + 系統重設已啟動。按下「確認」以清除所有內容和設定,或按下「取消」以繼續開機。 @@ -645,7 +680,7 @@ This may take up to a minute. Device - 設備 + 裝置 Network @@ -720,7 +755,7 @@ This may take up to a minute. Ensure the entered URL is valid, and the device’s internet connection is good. - 請確定您輸入的是有效的安裝網址,並且確定設備的網路連線狀態良好。 + 請確定您輸入的是有效的安裝網址,並且確定裝置的網路連線狀態良好。 Reboot device @@ -736,12 +771,24 @@ This may take up to a minute. Something went wrong. Reboot the device. - 發生了一些錯誤。請重新啟動您的設備。 + 發生了一些錯誤。請重新啟動您的裝置。 Select a language 選擇語言 + + Choose Software to Install + 選擇要安裝的軟體 + + + openpilot + openpilot + + + Custom Software + 自訂軟體 + SetupWidget @@ -751,11 +798,11 @@ This may take up to a minute. Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - 將您的設備與 comma connect (connect.comma.ai) 配對並領取您的 comma 高級會員優惠。 + 將您的裝置與 comma connect (connect.comma.ai) 配對並領取您的 comma 高級會員優惠。 Pair device - 配對設備 + 配對裝置 @@ -1091,18 +1138,10 @@ This may take up to a minute. Driving Personality 駕駛風格 - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - 推薦使用標準模式。在積極模式中,openpilot 會更靠近前車並在加速和剎車方面更積極。在舒適模式中,openpilot 會與前車保持較遠的距離。 - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. 在正式 (release) 版以外的分支上可以測試 openpilot 縱向控制的 Alpha 版本以及實驗模式。 - - Navigate on openpilot - Navigate on openpilot - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. 啟用 openpilot 縱向控制(alpha)切換以允許實驗模式。 @@ -1112,12 +1151,20 @@ This may take up to a minute. 端到端縱向控制 - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - 當導航有目的地時,openpilot 將把地圖資訊輸入模型中。這為模型提供了有用的背景資訊,使 openpilot 能夠在叉路/出口時適當地保持左側或右側行駛。車道變換行為保持不變,仍由駕駛員啟用。這是一個 Alpha 版的功能;可能會出現錯誤,特別是在出口和分叉處。這些錯誤可能包括意外的車道越界、晚出口、朝著分隔欄駛向分隔帶區域等。 + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + 建議使用標準模式。在積極模式下,openpilot 會更接近前車並更積極地使用油門和剎車。在輕鬆模式下,openpilot 會與前車保持較遠距離。對於支援的汽車,您可以使用方向盤上的距離按鈕來切換這些駕駛風格。 - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - 行駛畫面將在低速時切換至道路朝向的廣角鏡頭,以更好地顯示一些轉彎。實驗模式圖示也將顯示在右上角。當設定了導航目的地並且行駛模型正在將其作為輸入時,地圖上的行駛路徑將變為綠色。 + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 在低速時,駕駛可視化將切換至道路朝向的廣角攝影機,以更好地顯示某些彎道。在右上角還會顯示「實驗模式」的標誌。 + + + Always-On Driver Monitoring + 駕駛監控常開 + + + Enable driver monitoring even when openpilot is not engaged. + 即使在openpilot未激活時也啟用駕駛監控。 @@ -1128,7 +1175,7 @@ This may take up to a minute. An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. - 設備的操作系統需要更新。請將您的設備連接到 Wi-Fi 以獲得最快的更新體驗。下載大小約為 1GB。 + 需要進行作業系統更新。建議將您的裝置連接上 Wi-Fi 獲得更快的更新下載。下載大小約為 1GB。 Connect to Wi-Fi diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 9afd22f13a..06b8b13bc1 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -57,37 +57,30 @@ void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, con void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); - QPolygonF left_points, right_points; - left_points.reserve(max_idx + 1); - right_points.reserve(max_idx + 1); - + QPointF left, right; + pvd->clear(); for (int i = 0; i <= max_idx; i++) { // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera if (line_x[i] < 0) continue; - QPointF left, right; + bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off, &left); bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off, &right); if (l && r) { // For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts - if (!allow_invert && left_points.size() && left.y() > left_points.back().y()) { + if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) { continue; } - left_points.push_back(left); - right_points.push_front(right); + pvd->push_back(left); + pvd->push_front(right); } } - *pvd = left_points + right_points; } void update_model(UIState *s, - const cereal::ModelDataV2::Reader &model, - const cereal::UiPlan::Reader &plan) { + const cereal::ModelDataV2::Reader &model) { UIScene &scene = s->scene; - auto plan_position = plan.getPosition(); - if (plan_position.getX().size() < model.getPosition().getX().size()) { - plan_position = model.getPosition(); - } - float max_distance = std::clamp(*(plan_position.getX().end() - 1), + auto model_position = model.getPosition(); + float max_distance = std::clamp(*(model_position.getX().end() - 1), MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); // update lane lines @@ -113,8 +106,8 @@ void update_model(UIState *s, const float lead_d = lead_one.getDRel() * 2.; max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); } - max_idx = get_path_length_idx(plan_position, max_distance); - update_line_data(s, plan_position, 0.9, 1.22, &scene.track_vertices, max_idx, false); + max_idx = get_path_length_idx(model_position, max_distance); + update_line_data(s, model_position, 0.9, 1.22, &scene.track_vertices, max_idx, false); } void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd) { @@ -128,25 +121,27 @@ void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &drivers scene.driver_pose_coss[i] = cosf(scene.driver_pose_vals[i]*(1.0-dm_fade_state)); } + auto [sin_y, sin_x, sin_z] = scene.driver_pose_sins; + auto [cos_y, cos_x, cos_z] = scene.driver_pose_coss; + const mat3 r_xyz = (mat3){{ - scene.driver_pose_coss[1]*scene.driver_pose_coss[2], - scene.driver_pose_coss[1]*scene.driver_pose_sins[2], - -scene.driver_pose_sins[1], + cos_x * cos_z, + cos_x * sin_z, + -sin_x, - -scene.driver_pose_sins[0]*scene.driver_pose_sins[1]*scene.driver_pose_coss[2] - scene.driver_pose_coss[0]*scene.driver_pose_sins[2], - -scene.driver_pose_sins[0]*scene.driver_pose_sins[1]*scene.driver_pose_sins[2] + scene.driver_pose_coss[0]*scene.driver_pose_coss[2], - -scene.driver_pose_sins[0]*scene.driver_pose_coss[1], + -sin_y * sin_x * cos_z - cos_y * sin_z, + -sin_y * sin_x * sin_z + cos_y * cos_z, + -sin_y * cos_x, - scene.driver_pose_coss[0]*scene.driver_pose_sins[1]*scene.driver_pose_coss[2] - scene.driver_pose_sins[0]*scene.driver_pose_sins[2], - scene.driver_pose_coss[0]*scene.driver_pose_sins[1]*scene.driver_pose_sins[2] + scene.driver_pose_sins[0]*scene.driver_pose_coss[2], - scene.driver_pose_coss[0]*scene.driver_pose_coss[1], + cos_y * sin_x * cos_z - sin_y * sin_z, + cos_y * sin_x * sin_z + sin_y * cos_z, + cos_y * cos_x, }}; // transform vertices for (int kpi = 0; kpi < std::size(default_face_kpts_3d); kpi++) { - vec3 kpt_this = default_face_kpts_3d[kpi]; - kpt_this = matvecmul3(r_xyz, kpt_this); - scene.face_kpts_draw[kpi] = (vec3){{(float)kpt_this.v[0], (float)kpt_this.v[1], (float)(kpt_this.v[2] * (1.0-dm_fade_state) + 8 * dm_fade_state)}}; + vec3 kpt_this = matvecmul3(r_xyz, default_face_kpts_3d[kpi]); + scene.face_kpts_draw[kpi] = (vec3){{kpt_this.v[0], kpt_this.v[1], (float)(kpt_this.v[2] * (1.0-dm_fade_state) + 8 * dm_fade_state)}}; } } @@ -205,14 +200,15 @@ static void update_state(UIState *s) { auto cam_state = sm["wideRoadCameraState"].getWideRoadCameraState(); float scale = (cam_state.getSensor() == cereal::FrameData::ImageSensor::AR0231) ? 6.0f : 1.0f; scene.light_sensor = std::max(100.0f - scale * cam_state.getExposureValPercent(), 0.0f); + } else if (!sm.allAliveAndValid({"wideRoadCameraState"})) { + scene.light_sensor = -1; } scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; scene.world_objects_visible = scene.world_objects_visible || (scene.started && sm.rcv_frame("liveCalibration") > scene.started_frame && - sm.rcv_frame("modelV2") > scene.started_frame && - sm.rcv_frame("uiPlan") > scene.started_frame); + sm.rcv_frame("modelV2") > scene.started_frame); } void ui_update_params(UIState *s) { @@ -248,7 +244,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", - "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "uiPlan", + "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "clocks", }); Params params; @@ -320,7 +316,7 @@ void Device::resetInteractiveTimeout(int timeout) { void Device::updateBrightness(const UIState &s) { float clipped_brightness = offroad_brightness; - if (s.scene.started) { + if (s.scene.started && s.scene.light_sensor > 0) { clipped_brightness = s.scene.light_sensor; // CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 86cd70f028..9ef79b2eff 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -22,7 +21,6 @@ const int UI_HEADER_HEIGHT = 420; const int UI_FREQ = 20; // Hz const int BACKLIGHT_OFFROAD = 50; -typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert; const float MIN_DRAW_DISTANCE = 10.0; const float MAX_DRAW_DISTANCE = 100.0; @@ -47,59 +45,6 @@ constexpr vec3 default_face_kpts_3d[] = { {18.02, -49.14, 8.00}, {6.36, -51.20, 8.00}, {-5.98, -51.20, 8.00}, }; -struct Alert { - QString text1; - QString text2; - QString type; - cereal::ControlsState::AlertSize size; - cereal::ControlsState::AlertStatus status; - AudibleAlert sound; - - bool equal(const Alert &a2) { - return text1 == a2.text1 && text2 == a2.text2 && type == a2.type && sound == a2.sound; - } - - static Alert get(const SubMaster &sm, uint64_t started_frame) { - const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState(); - const uint64_t controls_frame = sm.rcv_frame("controlsState"); - - Alert alert = {}; - if (controls_frame >= started_frame) { // Don't get old alert. - alert = {cs.getAlertText1().cStr(), cs.getAlertText2().cStr(), - cs.getAlertType().cStr(), cs.getAlertSize(), - cs.getAlertStatus(), - cs.getAlertSound()}; - } - - if (!sm.updated("controlsState") && (sm.frame - started_frame) > 5 * UI_FREQ) { - const int CONTROLS_TIMEOUT = 5; - const int controls_missing = (nanos_since_boot() - sm.rcv_time("controlsState")) / 1e9; - - // Handle controls timeout - if (controls_frame < started_frame) { - // car is started, but controlsState hasn't been seen at all - alert = {"openpilot Unavailable", "Waiting for controls to start", - "controlsWaiting", cereal::ControlsState::AlertSize::MID, - cereal::ControlsState::AlertStatus::NORMAL, - AudibleAlert::NONE}; - } else if (controls_missing > CONTROLS_TIMEOUT && !Hardware::PC()) { - // car is started, but controls is lagging or died - if (cs.getEnabled() && (controls_missing - CONTROLS_TIMEOUT) < 10) { - alert = {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", - "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, - cereal::ControlsState::AlertStatus::CRITICAL, - AudibleAlert::WARNING_IMMEDIATE}; - } else { - alert = {"Controls Unresponsive", "Reboot Device", - "controlsUnresponsivePermanent", cereal::ControlsState::AlertSize::MID, - cereal::ControlsState::AlertStatus::NORMAL, - AudibleAlert::NONE}; - } - } - } - return alert; - } -}; typedef enum UIStatus { STATUS_DISENGAGED, @@ -108,7 +53,8 @@ typedef enum UIStatus { } UIStatus; enum PrimeType { - UNKNOWN = -1, + UNKNOWN = -2, + UNPAIRED = -1, NONE = 0, MAGENTA = 1, LITE = 2, @@ -123,11 +69,6 @@ const QColor bg_colors [] = { [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), }; -static std::map alert_colors = { - {cereal::ControlsState::AlertStatus::NORMAL, QColor(0x15, 0x15, 0x15, 0xf1)}, - {cereal::ControlsState::AlertStatus::USER_PROMPT, QColor(0xDA, 0x6F, 0x25, 0xf1)}, - {cereal::ControlsState::AlertStatus::CRITICAL, QColor(0xC9, 0x22, 0x31, 0xf1)}, -}; typedef struct UIScene { bool calibration_valid = false; @@ -154,9 +95,9 @@ typedef struct UIScene { float driver_pose_coss[3]; vec3 face_kpts_draw[std::size(default_face_kpts_3d)]; - bool navigate_on_openpilot = false; + cereal::LongitudinalPersonality personality; - float light_sensor; + float light_sensor = -1; bool started, ignition, is_metric, map_on_left, longitudinal_control; bool world_objects_visible = false; uint64_t started_frame; @@ -174,7 +115,7 @@ public: void setPrimeType(PrimeType type); inline PrimeType primeType() const { return prime_type; } - inline bool hasPrime() const { return prime_type != PrimeType::UNKNOWN && prime_type != PrimeType::NONE; } + inline bool hasPrime() const { return prime_type > PrimeType::NONE; } int fb_w = 0, fb_h = 0; @@ -243,8 +184,7 @@ Device *device(); void ui_update_params(UIState *s); int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height); void update_model(UIState *s, - const cereal::ModelDataV2::Reader &model, - const cereal::UiPlan::Reader &plan); + const cereal::ModelDataV2::Reader &model); void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd); void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py new file mode 100755 index 0000000000..660495b1de --- /dev/null +++ b/selfdrive/ui/ui.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +import os +import signal + +signal.signal(signal.SIGINT, signal.SIG_DFL) + +import cereal.messaging as messaging +from openpilot.system.hardware import HARDWARE + +from PyQt5.QtCore import Qt, QTimer +from PyQt5.QtWidgets import QLabel, QWidget, QVBoxLayout, QStackedLayout, QApplication +from openpilot.selfdrive.ui.qt.python_helpers import set_main_window + + +if __name__ == "__main__": + app = QApplication([]) + win = QWidget() + set_main_window(win) + + bg = QLabel("", alignment=Qt.AlignCenter) + + alert1 = QLabel() + alert2 = QLabel() + vlayout = QVBoxLayout() + vlayout.addWidget(alert1, alignment=Qt.AlignCenter) + vlayout.addWidget(alert2, alignment=Qt.AlignCenter) + + tmp = QWidget() + tmp.setLayout(vlayout) + + stack = QStackedLayout(win) + stack.addWidget(tmp) + stack.addWidget(bg) + stack.setStackingMode(QStackedLayout.StackAll) + + win.setObjectName("win") + win.setStyleSheet(""" + #win { + background-color: black; + } + QLabel { + color: white; + font-size: 40px; + } + """) + + sm = messaging.SubMaster(['deviceState', 'controlsState']) + + def update(): + sm.update(0) + + onroad = sm.all_checks(['deviceState']) and sm['deviceState'].started + if onroad: + cs = sm['controlsState'] + color = ("grey" if str(cs.state) in ("overriding", "preEnabled") else "green") if cs.enabled else "blue" + bg.setText("\U0001F44D" if cs.engageable else "\U0001F6D1") + bg.setStyleSheet(f"font-size: 100px; background-color: {color};") + bg.show() + + alert1.setText(cs.alertText1) + alert2.setText(cs.alertText2) + + if not sm.alive['controlsState']: + alert1.setText("waiting for controls...") + else: + bg.hide() + alert1.setText("") + alert2.setText("offroad") + + HARDWARE.set_screen_brightness(100 if onroad else 40) + os.system("echo 0 > /sys/class/backlight/panel0-backlight/bl_power") + + timer = QTimer() + timer.timeout.connect(update) + timer.start(50) + + app.exec_() diff --git a/selfdrive/boardd/__init__.py b/system/athena/__init__.py similarity index 100% rename from selfdrive/boardd/__init__.py rename to system/athena/__init__.py diff --git a/selfdrive/athena/athenad.py b/system/athena/athenad.py similarity index 97% rename from selfdrive/athena/athenad.py rename to system/athena/athenad.py index 9f901498b7..9eec7a931b 100755 --- a/selfdrive/athena/athenad.py +++ b/system/athena/athenad.py @@ -37,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_normalized_origin, get_short_branch, get_version +from openpilot.system.version import get_build_metadata from openpilot.system.hardware.hw import Paths @@ -319,11 +319,12 @@ def getMessage(service: str, timeout: int = 1000) -> dict: @dispatcher.add_method def getVersion() -> dict[str, str]: + build_metadata = get_build_metadata() return { - "version": get_version(), - "remote": get_normalized_origin(), - "branch": get_short_branch(), - "commit": get_commit(), + "version": build_metadata.openpilot.version, + "remote": build_metadata.openpilot.git_normalized_origin, + "branch": build_metadata.channel, + "commit": build_metadata.openpilot.git_commit, } @@ -466,6 +467,10 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local cookie="jwt=" + identity_token, enable_multithread=True) + # Set TOS to keep connection responsive while under load. + # DSCP of 36/HDD_LINUX_AC_VI with the minimum delay flag + ws.sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 0x90) + ssock, csock = socket.socketpair() local_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) local_sock.connect(('127.0.0.1', local_port)) @@ -656,10 +661,12 @@ def stat_handler(end_event: threading.Event) -> None: def ws_proxy_recv(ws: WebSocket, local_sock: socket.socket, ssock: socket.socket, end_event: threading.Event, global_end_event: threading.Event) -> None: while not (end_event.is_set() or global_end_event.is_set()): try: - data = ws.recv() - if isinstance(data, str): - data = data.encode("utf-8") - local_sock.sendall(data) + r = select.select((ws.sock,), (), (), 30) + if r[0]: + data = ws.recv() + if isinstance(data, str): + data = data.encode("utf-8") + local_sock.sendall(data) except WebSocketTimeoutException: pass except Exception: @@ -669,6 +676,7 @@ def ws_proxy_recv(ws: WebSocket, local_sock: socket.socket, ssock: socket.socket cloudlog.debug("athena.ws_proxy_recv closing sockets") ssock.close() local_sock.close() + ws.close() cloudlog.debug("athena.ws_proxy_recv done closing sockets") end_event.set() diff --git a/selfdrive/athena/manage_athenad.py b/system/athena/manage_athenad.py similarity index 57% rename from selfdrive/athena/manage_athenad.py rename to system/athena/manage_athenad.py index 486e426911..f5ab817206 100755 --- a/selfdrive/athena/manage_athenad.py +++ b/system/athena/manage_athenad.py @@ -4,10 +4,10 @@ import time from multiprocessing import Process from openpilot.common.params import Params -from openpilot.selfdrive.manager.process import launcher +from openpilot.system.manager.process import launcher from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE -from openpilot.system.version import get_version, get_normalized_origin, get_short_branch, get_commit, is_dirty +from openpilot.system.version import get_build_metadata ATHENA_MGR_PID_PARAM = "AthenadPid" @@ -15,18 +15,20 @@ ATHENA_MGR_PID_PARAM = "AthenadPid" def main(): params = Params() dongle_id = params.get("DongleId").decode('utf-8') + build_metadata = get_build_metadata() + cloudlog.bind_global(dongle_id=dongle_id, - version=get_version(), - origin=get_normalized_origin(), - branch=get_short_branch(), - commit=get_commit(), - dirty=is_dirty(), + version=build_metadata.openpilot.version, + origin=build_metadata.openpilot.git_normalized_origin, + branch=build_metadata.channel, + commit=build_metadata.openpilot.git_commit, + dirty=build_metadata.openpilot.is_dirty, device=HARDWARE.get_device_type()) try: while 1: cloudlog.info("starting athena daemon") - proc = Process(name='athenad', target=launcher, args=('selfdrive.athena.athenad', 'athenad')) + proc = Process(name='athenad', target=launcher, args=('system.athena.athenad', 'athenad')) proc.start() proc.join() cloudlog.event("athenad exited", exitcode=proc.exitcode) diff --git a/selfdrive/athena/registration.py b/system/athena/registration.py similarity index 96% rename from selfdrive/athena/registration.py rename to system/athena/registration.py index 6574d9ac20..97289e4199 100755 --- a/selfdrive/athena/registration.py +++ b/system/athena/registration.py @@ -4,7 +4,7 @@ import json import jwt from pathlib import Path -from datetime import datetime, timedelta +from datetime import datetime, timedelta, UTC from openpilot.common.api import api_get from openpilot.common.params import Params from openpilot.common.spinner import Spinner @@ -66,7 +66,7 @@ def register(show_spinner=False) -> str | None: start_time = time.monotonic() while True: try: - register_token = jwt.encode({'register': True, 'exp': datetime.utcnow() + timedelta(hours=1)}, private_key, algorithm='RS256') + register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)}, private_key, algorithm='RS256') cloudlog.info("getting pilotauth") resp = api_get("v2/pilotauth/", method='POST', timeout=15, imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token) diff --git a/selfdrive/boardd/tests/__init__.py b/system/athena/tests/__init__.py similarity index 100% rename from selfdrive/boardd/tests/__init__.py rename to system/athena/tests/__init__.py diff --git a/selfdrive/athena/tests/helpers.py b/system/athena/tests/helpers.py similarity index 92% rename from selfdrive/athena/tests/helpers.py rename to system/athena/tests/helpers.py index 3dd98f02c9..a0a9cccdc1 100644 --- a/selfdrive/athena/tests/helpers.py +++ b/system/athena/tests/helpers.py @@ -9,7 +9,7 @@ class MockResponse: self.status_code = status_code -class EchoSocket(): +class EchoSocket: def __init__(self, port): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.bind(('127.0.0.1', port)) @@ -34,7 +34,7 @@ class EchoSocket(): self.socket.close() -class MockApi(): +class MockApi: def __init__(self, dongle_id): pass @@ -42,7 +42,9 @@ class MockApi(): return "fake-token" -class MockWebsocket(): +class MockWebsocket: + sock = socket.socket() + def __init__(self, recv_queue, send_queue): self.recv_queue = recv_queue self.send_queue = send_queue @@ -56,6 +58,9 @@ class MockWebsocket(): def send(self, data, opcode): self.send_queue.put_nowait((data, opcode)) + def close(self): + pass + class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler): def do_PUT(self): diff --git a/selfdrive/athena/tests/test_athenad.py b/system/athena/tests/test_athenad.py old mode 100755 new mode 100644 similarity index 69% rename from selfdrive/athena/tests/test_athenad.py rename to system/athena/tests/test_athenad.py index 4850ab9a3f..48519a0ffd --- a/selfdrive/athena/tests/test_athenad.py +++ b/system/athena/tests/test_athenad.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python3 -from functools import partial, wraps +import pytest +from functools import wraps import json import multiprocessing import os @@ -8,12 +8,9 @@ import shutil import time import threading import queue -import unittest from dataclasses import asdict, replace from datetime import datetime, timedelta -from parameterized import parameterized -from unittest import mock from websocket import ABNF from websocket._exceptions import WebSocketConnectionClosedException @@ -21,10 +18,10 @@ from cereal import messaging 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 HTTPRequestHandler, MockWebsocket, MockApi, EchoSocket -from openpilot.selfdrive.test.helpers import with_http_server +from openpilot.system.athena import athenad +from openpilot.system.athena.athenad import MAX_RETRY_COUNT, dispatcher +from openpilot.system.athena.tests.helpers import HTTPRequestHandler, MockWebsocket, MockApi, EchoSocket +from openpilot.selfdrive.test.helpers import http_server_context from openpilot.system.hardware.hw import Paths @@ -37,10 +34,6 @@ def seed_athena_server(host, port): except requests.exceptions.ConnectionError: time.sleep(0.1) - -with_mock_athena = partial(with_http_server, handler=HTTPRequestHandler, setup=seed_athena_server) - - def with_upload_handler(func): @wraps(func) def wrapper(*args, **kwargs): @@ -54,15 +47,23 @@ def with_upload_handler(func): thread.join() return wrapper +@pytest.fixture +def mock_create_connection(mocker): + return mocker.patch('openpilot.system.athena.athenad.create_connection') -class TestAthenadMethods(unittest.TestCase): +@pytest.fixture +def host(): + with http_server_context(handler=HTTPRequestHandler, setup=seed_athena_server) as (host, port): + yield f"http://{host}:{port}" + +class TestAthenadMethods: @classmethod - def setUpClass(cls): + def setup_class(cls): cls.SOCKET_PORT = 45454 athenad.Api = MockApi athenad.LOCAL_PORT_WHITELIST = {cls.SOCKET_PORT} - def setUp(self): + def setup_method(self): self.default_params = { "DongleId": "0000000000000000", "GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private", # noqa: E501 @@ -109,8 +110,8 @@ class TestAthenadMethods(unittest.TestCase): def test_echo(self): assert dispatcher["echo"]("bob") == "bob" - def test_getMessage(self): - with self.assertRaises(TimeoutError) as _: + def test_get_message(self): + with pytest.raises(TimeoutError) as _: dispatcher["getMessage"]("controlsState") end_event = multiprocessing.Event() @@ -133,7 +134,7 @@ class TestAthenadMethods(unittest.TestCase): end_event.set() p.join() - def test_listDataDirectory(self): + def test_list_data_directory(self): route = '2021-03-29--13-32-47' segments = [0, 1, 2, 3, 11] @@ -143,69 +144,66 @@ class TestAthenadMethods(unittest.TestCase): self._create_file(file) resp = dispatcher["listDataDirectory"]() - self.assertTrue(resp, 'list empty!') - self.assertCountEqual(resp, files) + assert resp, 'list empty!' + assert len(resp) == len(files) resp = dispatcher["listDataDirectory"](f'{route}--123') - self.assertCountEqual(resp, []) + assert len(resp) == 0 prefix = f'{route}' - expected = filter(lambda f: f.startswith(prefix), files) + expected = list(filter(lambda f: f.startswith(prefix), files)) resp = dispatcher["listDataDirectory"](prefix) - self.assertTrue(resp, 'list empty!') - self.assertCountEqual(resp, expected) + assert resp, 'list empty!' + assert len(resp) == len(expected) prefix = f'{route}--1' - expected = filter(lambda f: f.startswith(prefix), files) + expected = list(filter(lambda f: f.startswith(prefix), files)) resp = dispatcher["listDataDirectory"](prefix) - self.assertTrue(resp, 'list empty!') - self.assertCountEqual(resp, expected) + assert resp, 'list empty!' + assert len(resp) == len(expected) prefix = f'{route}--1/' - expected = filter(lambda f: f.startswith(prefix), files) + expected = list(filter(lambda f: f.startswith(prefix), files)) resp = dispatcher["listDataDirectory"](prefix) - self.assertTrue(resp, 'list empty!') - self.assertCountEqual(resp, expected) + assert resp, 'list empty!' + assert len(resp) == len(expected) prefix = f'{route}--1/q' - expected = filter(lambda f: f.startswith(prefix), files) + expected = list(filter(lambda f: f.startswith(prefix), files)) resp = dispatcher["listDataDirectory"](prefix) - self.assertTrue(resp, 'list empty!') - self.assertCountEqual(resp, expected) + assert resp, 'list empty!' + assert len(resp) == len(expected) def test_strip_bz2_extension(self): fn = self._create_file('qlog.bz2') if fn.endswith('.bz2'): - self.assertEqual(athenad.strip_bz2_extension(fn), fn[:-4]) + assert athenad.strip_bz2_extension(fn) == fn[:-4] - @parameterized.expand([(True,), (False,)]) - @with_mock_athena - def test_do_upload(self, compress, host): + @pytest.mark.parametrize("compress", [True, False]) + def test_do_upload(self, host, compress): # random bytes to ensure rather large object post-compression fn = self._create_file('qlog', data=os.urandom(10000 * 1024)) upload_fn = fn + ('.bz2' if compress else '') item = athenad.UploadItem(path=upload_fn, url="http://localhost:1238", headers={}, created_at=int(time.time()*1000), id='') - with self.assertRaises(requests.exceptions.ConnectionError): + with pytest.raises(requests.exceptions.ConnectionError): athenad._do_upload(item) item = athenad.UploadItem(path=upload_fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='') resp = athenad._do_upload(item) - self.assertEqual(resp.status_code, 201) + assert resp.status_code == 201 - @with_mock_athena - def test_uploadFileToUrl(self, host): + def test_upload_file_to_url(self, host): fn = self._create_file('qlog.bz2') resp = dispatcher["uploadFileToUrl"]("qlog.bz2", f"{host}/qlog.bz2", {}) - self.assertEqual(resp['enqueued'], 1) - self.assertNotIn('failed', resp) - self.assertLessEqual({"path": fn, "url": f"{host}/qlog.bz2", "headers": {}}.items(), resp['items'][0].items()) - self.assertIsNotNone(resp['items'][0].get('id')) - self.assertEqual(athenad.upload_queue.qsize(), 1) + assert resp['enqueued'] == 1 + assert 'failed' not in resp + assert {"path": fn, "url": f"{host}/qlog.bz2", "headers": {}}.items() <= resp['items'][0].items() + assert resp['items'][0].get('id') is not None + assert athenad.upload_queue.qsize() == 1 - @with_mock_athena - def test_uploadFileToUrl_duplicate(self, host): + def test_upload_file_to_url_duplicate(self, host): self._create_file('qlog.bz2') url1 = f"{host}/qlog.bz2?sig=sig1" @@ -214,14 +212,12 @@ class TestAthenadMethods(unittest.TestCase): # Upload same file again, but with different signature url2 = f"{host}/qlog.bz2?sig=sig2" resp = dispatcher["uploadFileToUrl"]("qlog.bz2", url2, {}) - self.assertEqual(resp, {'enqueued': 0, 'items': []}) + assert resp == {'enqueued': 0, 'items': []} - @with_mock_athena - def test_uploadFileToUrl_does_not_exist(self, host): + def test_upload_file_to_url_does_not_exist(self, host): not_exists_resp = dispatcher["uploadFileToUrl"]("does_not_exist.bz2", "http://localhost:1238", {}) - self.assertEqual(not_exists_resp, {'enqueued': 0, 'items': [], 'failed': ['does_not_exist.bz2']}) + assert 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') @@ -233,13 +229,12 @@ class TestAthenadMethods(unittest.TestCase): # TODO: verify that upload actually succeeded # TODO: also check that end_event and metered network raises AbortTransferException - self.assertEqual(athenad.upload_queue.qsize(), 0) + assert athenad.upload_queue.qsize() == 0 - @parameterized.expand([(500, True), (412, False)]) - @with_mock_athena - @mock.patch('requests.put') + @pytest.mark.parametrize("status,retry", [(500,True), (412,False)]) @with_upload_handler - def test_upload_handler_retry(self, status, retry, mock_put, host): + def test_upload_handler_retry(self, mocker, host, status, retry): + mock_put = mocker.patch('requests.put') 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) @@ -248,10 +243,10 @@ class TestAthenadMethods(unittest.TestCase): self._wait_for_upload() time.sleep(0.1) - self.assertEqual(athenad.upload_queue.qsize(), 1 if retry else 0) + assert athenad.upload_queue.qsize() == (1 if retry else 0) if retry: - self.assertEqual(athenad.upload_queue.get().retry_count, 1) + assert athenad.upload_queue.get().retry_count == 1 @with_upload_handler def test_upload_handler_timeout(self): @@ -265,33 +260,33 @@ class TestAthenadMethods(unittest.TestCase): time.sleep(0.1) # Check that upload with retry count exceeded is not put back - self.assertEqual(athenad.upload_queue.qsize(), 0) + assert athenad.upload_queue.qsize() == 0 athenad.upload_queue.put_nowait(item) self._wait_for_upload() time.sleep(0.1) # 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) + assert athenad.upload_queue.qsize() == 1 + assert athenad.upload_queue.get().retry_count == 1 @with_upload_handler - def test_cancelUpload(self): + def test_cancel_upload(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) athenad.upload_queue.put_nowait(item) dispatcher["cancelUpload"](item.id) - self.assertIn(item.id, athenad.cancelled_uploads) + assert item.id in athenad.cancelled_uploads self._wait_for_upload() time.sleep(0.1) - self.assertEqual(athenad.upload_queue.qsize(), 0) - self.assertEqual(len(athenad.cancelled_uploads), 0) + assert athenad.upload_queue.qsize() == 0 + assert len(athenad.cancelled_uploads) == 0 @with_upload_handler - def test_cancelExpiry(self): + def test_cancel_expiry(self): t_future = datetime.now() - timedelta(days=40) ts = int(t_future.strftime("%s")) * 1000 @@ -303,15 +298,14 @@ class TestAthenadMethods(unittest.TestCase): self._wait_for_upload() time.sleep(0.1) - self.assertEqual(athenad.upload_queue.qsize(), 0) + assert athenad.upload_queue.qsize() == 0 - def test_listUploadQueueEmpty(self): + def test_list_upload_queue_empty(self): items = dispatcher["listUploadQueue"]() - self.assertEqual(len(items), 0) + assert len(items) == 0 - @with_http_server @with_upload_handler - def test_listUploadQueueCurrent(self, host: str): + def test_list_upload_queue_current(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) @@ -319,22 +313,22 @@ class TestAthenadMethods(unittest.TestCase): self._wait_for_upload() items = dispatcher["listUploadQueue"]() - self.assertEqual(len(items), 1) - self.assertTrue(items[0]['current']) + assert len(items) == 1 + assert items[0]['current'] - def test_listUploadQueue(self): + def test_list_upload_queue(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) athenad.upload_queue.put_nowait(item) items = dispatcher["listUploadQueue"]() - self.assertEqual(len(items), 1) - self.assertDictEqual(items[0], asdict(item)) - self.assertFalse(items[0]['current']) + assert len(items) == 1 + assert items[0] == asdict(item) + assert not items[0]['current'] athenad.cancelled_uploads.add(item.id) items = dispatcher["listUploadQueue"]() - self.assertEqual(len(items), 0) + assert len(items) == 0 def test_upload_queue_persistence(self): item1 = athenad.UploadItem(path="_", url="_", headers={}, created_at=int(time.time()), id='id1') @@ -353,11 +347,10 @@ class TestAthenadMethods(unittest.TestCase): athenad.upload_queue.queue.clear() athenad.UploadQueueCache.initialize(athenad.upload_queue) - self.assertEqual(athenad.upload_queue.qsize(), 1) - self.assertDictEqual(asdict(athenad.upload_queue.queue[-1]), asdict(item1)) + assert athenad.upload_queue.qsize() == 1 + assert asdict(athenad.upload_queue.queue[-1]) == asdict(item1) - @mock.patch('openpilot.selfdrive.athena.athenad.create_connection') - def test_startLocalProxy(self, mock_create_connection): + def test_start_local_proxy(self, mock_create_connection): end_event = threading.Event() ws_recv = queue.Queue() @@ -380,21 +373,21 @@ class TestAthenadMethods(unittest.TestCase): ws_recv.put_nowait(WebSocketConnectionClosedException()) socket_thread.join() - def test_getSshAuthorizedKeys(self): + def test_get_ssh_authorized_keys(self): keys = dispatcher["getSshAuthorizedKeys"]() - self.assertEqual(keys, self.default_params["GithubSshKeys"].decode('utf-8')) + assert keys == self.default_params["GithubSshKeys"].decode('utf-8') - def test_getGithubUsername(self): + def test_get_github_username(self): keys = dispatcher["getGithubUsername"]() - self.assertEqual(keys, self.default_params["GithubUsername"].decode('utf-8')) + assert keys == self.default_params["GithubUsername"].decode('utf-8') - def test_getVersion(self): + def test_get_version(self): resp = dispatcher["getVersion"]() keys = ["version", "remote", "branch", "commit"] - self.assertEqual(list(resp.keys()), keys) + assert list(resp.keys()) == keys for k in keys: - self.assertIsInstance(resp[k], str, f"{k} is not a string") - self.assertTrue(len(resp[k]) > 0, f"{k} has no value") + assert isinstance(resp[k], str), f"{k} is not a string" + assert len(resp[k]) > 0, f"{k} has no value" def test_jsonrpc_handler(self): end_event = threading.Event() @@ -405,15 +398,15 @@ class TestAthenadMethods(unittest.TestCase): # with params athenad.recv_queue.put_nowait(json.dumps({"method": "echo", "params": ["hello"], "jsonrpc": "2.0", "id": 0})) resp = athenad.send_queue.get(timeout=3) - self.assertDictEqual(json.loads(resp), {'result': 'hello', 'id': 0, 'jsonrpc': '2.0'}) + assert json.loads(resp) == {'result': 'hello', 'id': 0, 'jsonrpc': '2.0'} # without params athenad.recv_queue.put_nowait(json.dumps({"method": "getNetworkType", "jsonrpc": "2.0", "id": 0})) resp = athenad.send_queue.get(timeout=3) - self.assertDictEqual(json.loads(resp), {'result': 1, 'id': 0, 'jsonrpc': '2.0'}) + assert json.loads(resp) == {'result': 1, 'id': 0, 'jsonrpc': '2.0'} # log forwarding athenad.recv_queue.put_nowait(json.dumps({'result': {'success': 1}, 'id': 0, 'jsonrpc': '2.0'})) resp = athenad.log_recv_queue.get(timeout=3) - self.assertDictEqual(json.loads(resp), {'result': {'success': 1}, 'id': 0, 'jsonrpc': '2.0'}) + assert json.loads(resp) == {'result': {'success': 1}, 'id': 0, 'jsonrpc': '2.0'} finally: end_event.set() thread.join() @@ -427,8 +420,4 @@ class TestAthenadMethods(unittest.TestCase): # ensure the list is all logs except most recent sl = athenad.get_logs_to_send_sorted() - self.assertListEqual(sl, fl[:-1]) - - -if __name__ == '__main__': - unittest.main() + assert sl == fl[:-1] diff --git a/selfdrive/athena/tests/test_athenad_ping.py b/system/athena/tests/test_athenad_ping.py old mode 100755 new mode 100644 similarity index 65% rename from selfdrive/athena/tests/test_athenad_ping.py rename to system/athena/tests/test_athenad_ping.py index f56fcac8b5..73fe7783af --- a/selfdrive/athena/tests/test_athenad_ping.py +++ b/system/athena/tests/test_athenad_ping.py @@ -1,15 +1,13 @@ -#!/usr/bin/env python3 +import pytest import subprocess import threading import time -import unittest from typing import cast -from unittest import mock from openpilot.common.params import Params from openpilot.common.timeout import Timeout -from openpilot.selfdrive.athena import athenad -from openpilot.selfdrive.manager.helpers import write_onroad_params +from openpilot.system.athena import athenad +from openpilot.system.manager.helpers import write_onroad_params from openpilot.system.hardware import TICI TIMEOUT_TOLERANCE = 20 # seconds @@ -22,7 +20,7 @@ def wifi_radio(on: bool) -> None: subprocess.run(["nmcli", "radio", "wifi", "on" if on else "off"], check=True) -class TestAthenadPing(unittest.TestCase): +class TestAthenadPing: params: Params dongle_id: str @@ -39,10 +37,10 @@ class TestAthenadPing(unittest.TestCase): return self._get_ping_time() is not None @classmethod - def tearDownClass(cls) -> None: + def teardown_class(cls) -> None: wifi_radio(True) - def setUp(self) -> None: + def setup_method(self) -> None: self.params = Params() self.dongle_id = self.params.get("DongleId", encoding="utf-8") @@ -52,21 +50,23 @@ class TestAthenadPing(unittest.TestCase): self.exit_event = threading.Event() self.athenad = threading.Thread(target=athenad.main, args=(self.exit_event,)) - def tearDown(self) -> None: + def teardown_method(self) -> None: if self.athenad.is_alive(): self.exit_event.set() self.athenad.join() - @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: + def assertTimeout(self, reconnect_time: float, subtests, mocker) -> None: self.athenad.start() + mock_create_connection = mocker.patch('openpilot.system.athena.athenad.create_connection', + new_callable=lambda: mocker.MagicMock(wraps=athenad.create_connection)) + time.sleep(1) mock_create_connection.assert_called_once() mock_create_connection.reset_mock() # check normal behaviour, server pings on connection - with self.subTest("Wi-Fi: receives ping"), Timeout(70, "no ping received"): + with subtests.test("Wi-Fi: receives ping"), Timeout(70, "no ping received"): while not self._received_ping(): time.sleep(0.1) print("ping received") @@ -74,7 +74,7 @@ class TestAthenadPing(unittest.TestCase): mock_create_connection.assert_not_called() # websocket should attempt reconnect after short time - with self.subTest("LTE: attempt reconnect"): + with subtests.test("LTE: attempt reconnect"): wifi_radio(False) print("waiting for reconnect attempt") start_time = time.monotonic() @@ -86,21 +86,17 @@ class TestAthenadPing(unittest.TestCase): self._clear_ping_time() # check ping received after reconnect - with self.subTest("LTE: receives ping"), Timeout(70, "no ping received"): + with subtests.test("LTE: receives ping"), Timeout(70, "no ping received"): while not self._received_ping(): time.sleep(0.1) print("ping received") - @unittest.skipIf(not TICI, "only run on desk") - def test_offroad(self) -> None: + @pytest.mark.skipif(not TICI, reason="only run on desk") + def test_offroad(self, subtests, mocker) -> None: write_onroad_params(False, self.params) - self.assertTimeout(60 + TIMEOUT_TOLERANCE) # based using TCP keepalive settings + self.assertTimeout(60 + TIMEOUT_TOLERANCE, subtests, mocker) # based using TCP keepalive settings - @unittest.skipIf(not TICI, "only run on desk") - def test_onroad(self) -> None: + @pytest.mark.skipif(not TICI, reason="only run on desk") + def test_onroad(self, subtests, mocker) -> None: write_onroad_params(True, self.params) - self.assertTimeout(21 + TIMEOUT_TOLERANCE) - - -if __name__ == "__main__": - unittest.main() + self.assertTimeout(21 + TIMEOUT_TOLERANCE, subtests, mocker) diff --git a/system/athena/tests/test_registration.py b/system/athena/tests/test_registration.py new file mode 100644 index 0000000000..4f663fbc0a --- /dev/null +++ b/system/athena/tests/test_registration.py @@ -0,0 +1,74 @@ +import json +from Crypto.PublicKey import RSA +from pathlib import Path + +from openpilot.common.params import Params +from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_ID +from openpilot.system.athena.tests.helpers import MockResponse +from openpilot.system.hardware.hw import Paths + + +class TestRegistration: + + def setup_method(self): + # clear params and setup key paths + self.params = Params() + self.params.clear_all() + + persist_dir = Path(Paths.persist_root()) / "comma" + persist_dir.mkdir(parents=True, exist_ok=True) + + self.priv_key = persist_dir / "id_rsa" + self.pub_key = persist_dir / "id_rsa.pub" + + def _generate_keys(self): + self.pub_key.touch() + k = RSA.generate(2048) + with open(self.priv_key, "wb") as f: + f.write(k.export_key()) + with open(self.pub_key, "wb") as f: + f.write(k.publickey().export_key()) + + def test_valid_cache(self, mocker): + # if all params are written, return the cached dongle id + self.params.put("IMEI", "imei") + self.params.put("HardwareSerial", "serial") + self._generate_keys() + + m = mocker.patch("openpilot.system.athena.registration.api_get", autospec=True) + dongle = "DONGLE_ID_123" + self.params.put("DongleId", dongle) + assert register() == dongle + assert not m.called + + def test_no_keys(self, mocker): + # missing pubkey + m = mocker.patch("openpilot.system.athena.registration.api_get", autospec=True) + dongle = register() + assert m.call_count == 0 + assert dongle == UNREGISTERED_DONGLE_ID + assert self.params.get("DongleId", encoding='utf-8') == dongle + + def test_missing_cache(self, mocker): + # keys exist but no dongle id + self._generate_keys() + m = mocker.patch("openpilot.system.athena.registration.api_get", autospec=True) + dongle = "DONGLE_ID_123" + m.return_value = MockResponse(json.dumps({'dongle_id': dongle}), 200) + assert register() == dongle + assert m.call_count == 1 + + # call again, shouldn't hit the API this time + assert register() == dongle + assert m.call_count == 1 + assert self.params.get("DongleId", encoding='utf-8') == dongle + + def test_unregistered(self, mocker): + # keys exist, but unregistered + self._generate_keys() + m = mocker.patch("openpilot.system.athena.registration.api_get", autospec=True) + m.return_value = MockResponse(None, 402) + dongle = register() + assert m.call_count == 1 + assert dongle == UNREGISTERED_DONGLE_ID + assert self.params.get("DongleId", encoding='utf-8') == dongle diff --git a/system/camerad/SConscript b/system/camerad/SConscript index 8f19e7ee19..511664c275 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ -Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc') +Import('env', 'arch', 'messaging', 'common', 'gpucommon', 'visionipc') -libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', cereal, messaging, 'zmq', 'capnp', 'kj', visionipc, gpucommon, 'atomic'] +libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', messaging, visionipc, gpucommon, 'atomic'] camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc', 'sensors/ar0231.cc', 'sensors/ox03c10.cc', 'sensors/os04c10.cc']) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 91c6d44f84..9d82284d9f 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -8,7 +8,6 @@ #include "common/clutil.h" #include "common/swaglog.h" -#include "common/util.h" #include "third_party/linux/include/msm_media_info.h" #include "system/camerad/cameras/camera_qcom2.h" @@ -18,36 +17,38 @@ ExitHandler do_exit; -class Debayer { +class ImgProc { public: - Debayer(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s, int buf_width, int uv_offset) { + ImgProc(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s, int buf_width, int uv_offset) { char args[4096]; const SensorInfo *ci = s->ci.get(); snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " + "-cl-fast-relaxed-math -cl-denorms-are-zero -Isensors " "-DFRAME_WIDTH=%d -DFRAME_HEIGHT=%d -DFRAME_STRIDE=%d -DFRAME_OFFSET=%d " "-DRGB_WIDTH=%d -DRGB_HEIGHT=%d -DYUV_STRIDE=%d -DUV_OFFSET=%d " - "-DIS_OX=%d -DCAM_NUM=%d%s", - ci->frame_width, ci->frame_height, ci->frame_stride, ci->frame_offset, + "-DSENSOR_ID=%hu -DHDR_OFFSET=%d -DVIGNETTING=%d ", + ci->frame_width, ci->frame_height, ci->hdr_offset > 0 ? ci->frame_stride * 2 : ci->frame_stride, ci->frame_offset, b->rgb_width, b->rgb_height, buf_width, uv_offset, - ci->image_sensor == cereal::FrameData::ImageSensor::OX03C10, s->camera_num, s->camera_num==1 ? " -DVIGNETTING" : ""); - const char *cl_file = "cameras/real_debayer.cl"; - cl_program prg_debayer = cl_program_from_file(context, device_id, cl_file, args); - krnl_ = CL_CHECK_ERR(clCreateKernel(prg_debayer, "debayer10", &err)); - CL_CHECK(clReleaseProgram(prg_debayer)); + static_cast(ci->image_sensor), ci->hdr_offset, s->camera_num == 1); + const char *cl_file = "cameras/process_raw.cl"; + cl_program prg_imgproc = cl_program_from_file(context, device_id, cl_file, args); + krnl_ = CL_CHECK_ERR(clCreateKernel(prg_imgproc, "process_raw", &err)); + CL_CHECK(clReleaseProgram(prg_imgproc)); + } - void queue(cl_command_queue q, cl_mem cam_buf_cl, cl_mem buf_cl, int width, int height, cl_event *debayer_event) { + void queue(cl_command_queue q, cl_mem cam_buf_cl, cl_mem buf_cl, int width, int height, cl_event *imgproc_event, int expo_time) { CL_CHECK(clSetKernelArg(krnl_, 0, sizeof(cl_mem), &cam_buf_cl)); CL_CHECK(clSetKernelArg(krnl_, 1, sizeof(cl_mem), &buf_cl)); + CL_CHECK(clSetKernelArg(krnl_, 2, sizeof(cl_int), &expo_time)); const size_t globalWorkSize[] = {size_t(width / 2), size_t(height / 2)}; - const int debayer_local_worksize = 16; - const size_t localWorkSize[] = {debayer_local_worksize, debayer_local_worksize}; - CL_CHECK(clEnqueueNDRangeKernel(q, krnl_, 2, NULL, globalWorkSize, localWorkSize, 0, 0, debayer_event)); + const int imgproc_local_worksize = 16; + const size_t localWorkSize[] = {imgproc_local_worksize, imgproc_local_worksize}; + CL_CHECK(clEnqueueNDRangeKernel(q, krnl_, 2, NULL, globalWorkSize, localWorkSize, 0, 0, imgproc_event)); } - ~Debayer() { + ~ImgProc() { CL_CHECK(clReleaseKernel(krnl_)); } @@ -73,18 +74,22 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, LOGD("allocated %d CL buffers", frame_buf_count); rgb_width = ci->frame_width; - rgb_height = ci->frame_height; + rgb_height = ci->hdr_offset > 0 ? (ci->frame_height - ci->hdr_offset) / 2 : ci->frame_height; int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, rgb_width); int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, rgb_height); assert(nv12_width == VENUS_UV_STRIDE(COLOR_FMT_NV12, rgb_width)); assert(nv12_height/2 == VENUS_UV_SCANLINES(COLOR_FMT_NV12, rgb_height)); - size_t nv12_size = 2346 * nv12_width; // comes from v4l2_format.fmt.pix_mp.plane_fmt[0].sizeimage size_t nv12_uv_offset = nv12_width * nv12_height; + + // the encoder HW tells us the size it wants after setting it up. + // TODO: VENUS_BUFFER_SIZE should give the size, but it's too small. dependent on encoder settings? + size_t nv12_size = (rgb_width >= 2688 ? 2900 : 2346)*nv12_width; + vipc_server->create_buffers_with_sizes(stream_type, YUV_BUFFER_COUNT, false, rgb_width, rgb_height, nv12_size, nv12_width, nv12_uv_offset); LOGD("created %d YUV vipc buffers with size %dx%d", YUV_BUFFER_COUNT, nv12_width, nv12_height); - debayer = new Debayer(device_id, context, this, s, nv12_width, nv12_uv_offset); + imgproc = new ImgProc(device_id, context, this, s, nv12_width, nv12_uv_offset); const cl_queue_properties props[] = {0}; //CL_QUEUE_PRIORITY_KHR, CL_QUEUE_PRIORITY_HIGH_KHR, 0}; q = CL_CHECK_ERR(clCreateCommandQueueWithProperties(context, device_id, props, &err)); @@ -94,7 +99,7 @@ CameraBuf::~CameraBuf() { for (int i = 0; i < frame_buf_count; i++) { camera_bufs[i].free(); } - if (debayer) delete debayer; + if (imgproc) delete imgproc; if (q) CL_CHECK(clReleaseCommandQueue(q)); } @@ -112,7 +117,7 @@ bool CameraBuf::acquire() { double start_time = millis_since_boot(); cl_event event; - debayer->queue(q, camera_bufs[cur_buf_idx].buf_cl, cur_yuv_buf->buf_cl, rgb_width, rgb_height, &event); + imgproc->queue(q, camera_bufs[cur_buf_idx].buf_cl, cur_yuv_buf->buf_cl, rgb_width, rgb_height, &event, cur_frame_data.integ_lines); clWaitForEvents(1, &event); CL_CHECK(clReleaseEvent(event)); cur_frame_data.processing_time = (millis_since_boot() - start_time) / 1000.0; @@ -252,14 +257,14 @@ static void publish_thumbnail(PubMaster *pm, const CameraBuf *b) { pm->send("thumbnail", msg); } -float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip) { +float set_exposure_target(const CameraBuf *b, Rect ae_xywh, int x_skip, int y_skip) { int lum_med; uint32_t lum_binning[256] = {0}; const uint8_t *pix_ptr = b->cur_yuv_buf->y; unsigned int lum_total = 0; - for (int y = y_start; y < y_end; y += y_skip) { - for (int x = x_start; x < x_end; x += x_skip) { + for (int y = ae_xywh.y; y < ae_xywh.y + ae_xywh.h; y += y_skip) { + for (int x = ae_xywh.x; x < ae_xywh.x + ae_xywh.w; x += x_skip) { uint8_t lum = pix_ptr[(y * b->rgb_width) + x]; lum_binning[lum]++; lum_total += 1; diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index f98691ef00..555362ab8b 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -5,8 +5,9 @@ #include #include "cereal/messaging/messaging.h" -#include "cereal/visionipc/visionipc_server.h" +#include "msgq/visionipc/visionipc_server.h" #include "common/queue.h" +#include "common/util.h" const int YUV_BUFFER_COUNT = 20; @@ -44,12 +45,12 @@ typedef struct FrameMetadata { struct MultiCameraState; class CameraState; -class Debayer; +class ImgProc; class CameraBuf { private: VisionIpcServer *vipc_server; - Debayer *debayer = nullptr; + ImgProc *imgproc = nullptr; VisionStreamType stream_type; int cur_buf_idx; SafeQueue safe_queue; @@ -75,7 +76,7 @@ typedef void (*process_thread_cb)(MultiCameraState *s, CameraState *c, int cnt); void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &frame_data, CameraState *c); kj::Array get_raw_frame_image(const CameraBuf *b); -float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip); +float set_exposure_target(const CameraBuf *b, Rect ae_xywh, int x_skip, int y_skip); std::thread start_process_thread(MultiCameraState *cameras, CameraState *cs, process_thread_cb callback); void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx); diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 3191b821ac..096e288cc2 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -31,8 +31,7 @@ int CameraState::clear_req_queue() { req_mgr_flush_request.session_hdl = session_handle; req_mgr_flush_request.link_hdl = link_handle; req_mgr_flush_request.flush_type = CAM_REQ_MGR_FLUSH_TYPE_ALL; - int ret; - ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_FLUSH_REQ, &req_mgr_flush_request, sizeof(req_mgr_flush_request)); + int ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_FLUSH_REQ, &req_mgr_flush_request, sizeof(req_mgr_flush_request)); // LOGD("flushed all req: %d", ret); return ret; } @@ -396,6 +395,35 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) { // ******************* camera ******************* +void CameraState::set_exposure_rect() { + // set areas for each camera, shouldn't be changed + std::vector> ae_targets = { + // (Rect, F) + std::make_pair((Rect){96, 250, 1734, 524}, 567.0), // wide + std::make_pair((Rect){96, 160, 1734, 986}, 2648.0), // road + std::make_pair((Rect){96, 242, 1736, 906}, 567.0) // driver + }; + int h_ref = 1208; + /* + exposure target intrinics is + [ + [F, 0, 0.5*ae_xywh[2]] + [0, F, 0.5*H-ae_xywh[1]] + [0, 0, 1] + ] + */ + auto ae_target = ae_targets[camera_num]; + Rect xywh_ref = ae_target.first; + float fl_ref = ae_target.second; + + ae_xywh = (Rect){ + std::max(0, buf.rgb_width / 2 - (int)(fl_pix / fl_ref * xywh_ref.w / 2)), + std::max(0, buf.rgb_height / 2 - (int)(fl_pix / fl_ref * (h_ref / 2 - xywh_ref.y))), + std::min((int)(fl_pix / fl_ref * xywh_ref.w), buf.rgb_width / 2 + (int)(fl_pix / fl_ref * xywh_ref.w / 2)), + std::min((int)(fl_pix / fl_ref * xywh_ref.h), buf.rgb_height / 2 + (int)(fl_pix / fl_ref * (h_ref / 2 - xywh_ref.y))) + }; +} + void CameraState::sensor_set_parameters() { target_grey_fraction = 0.3; @@ -421,7 +449,7 @@ void CameraState::camera_map_bufs(MultiCameraState *s) { enqueue_req_multi(1, FRAME_BUF_COUNT, 0); } -void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type) { +void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type, float focal_len) { if (!enabled) return; LOGD("camera init %d", camera_num); @@ -430,6 +458,9 @@ void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, cl_devic buf.init(device_id, ctx, this, v, FRAME_BUF_COUNT, yuv_type); camera_map_bufs(s); + + fl_pix = focal_len / ci->pixel_size_mm; + set_exposure_rect(); } void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num_, bool enabled_) { @@ -438,7 +469,6 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num enabled = enabled_; if (!enabled) return; - int ret; sensor_fd = open_v4l_by_name_and_index("cam-sensor-driver", camera_num); assert(sensor_fd >= 0); LOGD("opened sensor for %d", camera_num); @@ -469,7 +499,7 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num // create session struct cam_req_mgr_session_info session_info = {}; - ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_CREATE_SESSION, &session_info, sizeof(session_info)); + int ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_CREATE_SESSION, &session_info, sizeof(session_info)); LOGD("get session: %d 0x%X", ret, session_info.session_hdl); session_handle = session_info.session_hdl; @@ -614,16 +644,14 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num } void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { - s->driver_cam.camera_init(s, v, device_id, ctx, VISION_STREAM_DRIVER); - s->road_cam.camera_init(s, v, device_id, ctx, VISION_STREAM_ROAD); - s->wide_road_cam.camera_init(s, v, device_id, ctx, VISION_STREAM_WIDE_ROAD); + s->driver_cam.camera_init(s, v, device_id, ctx, VISION_STREAM_DRIVER, DRIVER_FL_MM); + s->road_cam.camera_init(s, v, device_id, ctx, VISION_STREAM_ROAD, ROAD_FL_MM); + s->wide_road_cam.camera_init(s, v, device_id, ctx, VISION_STREAM_WIDE_ROAD, WIDE_FL_MM); s->pm = new PubMaster({"roadCameraState", "driverCameraState", "wideRoadCameraState", "thumbnail"}); } void cameras_open(MultiCameraState *s) { - int ret; - LOG("-- Opening devices"); // video0 is req_mgr, the target of many ioctls s->video0_fd = HANDLE_EINTR(open("/dev/v4l/by-path/platform-soc:qcom_cam-req-mgr-video-index0", O_RDWR | O_NONBLOCK)); @@ -647,7 +675,7 @@ void cameras_open(MultiCameraState *s) { query_cap_cmd.handle_type = 1; query_cap_cmd.caps_handle = (uint64_t)&isp_query_cap_cmd; query_cap_cmd.size = sizeof(isp_query_cap_cmd); - ret = do_cam_control(s->isp_fd, CAM_QUERY_CAP, &query_cap_cmd, sizeof(query_cap_cmd)); + int ret = do_cam_control(s->isp_fd, CAM_QUERY_CAP, &query_cap_cmd, sizeof(query_cap_cmd)); assert(ret == 0); LOGD("using MMU handle: %x", isp_query_cap_cmd.device_iommu.non_secure); LOGD("using MMU handle: %x", isp_query_cap_cmd.cdm_iommu.non_secure); @@ -671,15 +699,13 @@ void cameras_open(MultiCameraState *s) { } void CameraState::camera_close() { - int ret; - // stop devices LOG("-- Stop devices %d", camera_num); if (enabled) { // ret = device_control(sensor_fd, CAM_STOP_DEV, session_handle, sensor_dev_handle); // LOGD("stop sensor: %d", ret); - ret = device_control(multi_cam_state->isp_fd, CAM_STOP_DEV, session_handle, isp_dev_handle); + int ret = device_control(multi_cam_state->isp_fd, CAM_STOP_DEV, session_handle, isp_dev_handle); LOGD("stop isp: %d", ret); ret = device_control(csiphy_fd, CAM_STOP_DEV, session_handle, csiphy_dev_handle); LOGD("stop csiphy: %d", ret); @@ -714,7 +740,7 @@ void CameraState::camera_close() { LOGD("released buffers"); } - ret = device_control(sensor_fd, CAM_RELEASE_DEV, session_handle, sensor_dev_handle); + int ret = device_control(sensor_fd, CAM_RELEASE_DEV, session_handle, sensor_dev_handle); LOGD("release sensor: %d", ret); // destroyed session @@ -902,7 +928,7 @@ void CameraState::set_camera_exposure(float grey_frac) { } static void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { - c->set_camera_exposure(set_exposure_target(&c->buf, 96, 1832, 2, 242, 1148, 4)); + c->set_camera_exposure(set_exposure_target(&c->buf, c->ae_xywh, 2, 4)); MessageBuilder msg; auto framed = msg.initEvent().initDriverCameraState(); @@ -927,9 +953,8 @@ void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { c->ci->processRegisters(c, framed); s->pm->send(c == &s->road_cam ? "roadCameraState" : "wideRoadCameraState", msg); - const auto [x, y, w, h] = (c == &s->wide_road_cam) ? std::tuple(96, 250, 1734, 524) : std::tuple(96, 160, 1734, 986); const int skip = 2; - c->set_camera_exposure(set_exposure_target(b, x, x + w, skip, y, y + h, skip)); + c->set_camera_exposure(set_exposure_target(b, c->ae_xywh, skip, skip)); } void cameras_run(MultiCameraState *s) { diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 47ca578b99..0b15c9c3f0 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -11,6 +11,10 @@ #define FRAME_BUF_COUNT 4 +#define ROAD_FL_MM 8.0f +#define WIDE_FL_MM 1.71f +#define DRIVER_FL_MM 1.71f + class CameraState { public: MultiCameraState *multi_cam_state; @@ -30,6 +34,7 @@ public: int new_exp_g; int new_exp_t; + Rect ae_xywh; float measured_grey_fraction; float target_grey_fraction; @@ -37,6 +42,7 @@ public: unique_fd csiphy_fd; int camera_num; + float fl_pix; void handle_camera_event(void *evdat); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); @@ -45,9 +51,10 @@ public: void sensors_start(); void camera_open(MultiCameraState *multi_cam_state, int camera_num, bool enabled); + void set_exposure_rect(); void sensor_set_parameters(); void camera_map_bufs(MultiCameraState *s); - void camera_init(MultiCameraState *s, VisionIpcServer *v, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type); + void camera_init(MultiCameraState *s, VisionIpcServer *v, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type, float focal_len); void camera_close(); int32_t session_handle; diff --git a/system/camerad/cameras/camera_util.cc b/system/camerad/cameras/camera_util.cc index 4fe38997ce..7bd23fd9fe 100644 --- a/system/camerad/cameras/camera_util.cc +++ b/system/camerad/cameras/camera_util.cc @@ -86,11 +86,10 @@ void *alloc_w_mmu_hdl(int video0_fd, int len, uint32_t *handle, int align, int f } void release(int video0_fd, uint32_t handle) { - int ret; struct cam_mem_mgr_release_cmd mem_mgr_release_cmd = {0}; mem_mgr_release_cmd.buf_handle = handle; - ret = do_cam_control(video0_fd, CAM_REQ_MGR_RELEASE_BUF, &mem_mgr_release_cmd, sizeof(mem_mgr_release_cmd)); + int ret = do_cam_control(video0_fd, CAM_REQ_MGR_RELEASE_BUF, &mem_mgr_release_cmd, sizeof(mem_mgr_release_cmd)); assert(ret == 0); } diff --git a/system/camerad/cameras/process_raw.cl b/system/camerad/cameras/process_raw.cl new file mode 100644 index 0000000000..6f6612fab0 --- /dev/null +++ b/system/camerad/cameras/process_raw.cl @@ -0,0 +1,238 @@ +#include "ar0231_cl.h" +#include "ox03c10_cl.h" +#include "os04c10_cl.h" + +#define UV_WIDTH RGB_WIDTH / 2 +#define UV_HEIGHT RGB_HEIGHT / 2 + +#define RGB_TO_Y(r, g, b) ((((mul24(b, 13) + mul24(g, 65) + mul24(r, 33)) + 64) >> 7) + 16) +#define RGB_TO_U(r, g, b) ((mul24(b, 56) - mul24(g, 37) - mul24(r, 19) + 0x8080) >> 8) +#define RGB_TO_V(r, g, b) ((mul24(r, 56) - mul24(g, 47) - mul24(b, 9) + 0x8080) >> 8) +#define AVERAGE(x, y, z, w) ((convert_ushort(x) + convert_ushort(y) + convert_ushort(z) + convert_ushort(w) + 1) >> 1) + +#if defined(BGGR) + #define ROW_READ_ORDER (int[]){3, 2, 1, 0} + #define RGB_WRITE_ORDER (int[]){2, 3, 0, 1} +#else + #define ROW_READ_ORDER (int[]){0, 1, 2, 3} + #define RGB_WRITE_ORDER (int[]){0, 1, 2, 3} +#endif + +float get_vignetting_s(float r) { + if (r < 62500) { + return (1.0f + 0.0000008f*r); + } else if (r < 490000) { + return (0.9625f + 0.0000014f*r); + } else if (r < 1102500) { + return (1.26434f + 0.0000000000016f*r*r); + } else { + return (0.53503625f + 0.0000000000022f*r*r); + } +} + +int4 parse_12bit(uchar8 pvs) { + // lower bits scambled? + return (int4)(((int)pvs.s0<<4) + (pvs.s1>>4), + ((int)pvs.s2<<4) + (pvs.s4&0xF), + ((int)pvs.s3<<4) + (pvs.s4>>4), + ((int)pvs.s5<<4) + (pvs.s7&0xF)); +} + +int4 parse_10bit(uchar8 pvs, uchar ext, bool aligned) { + if (aligned) { + return (int4)(((int)pvs.s0 << 2) + (pvs.s1 & 0b00000011), + ((int)pvs.s2 << 2) + ((pvs.s6 & 0b11000000) / 64), + ((int)pvs.s3 << 2) + ((pvs.s6 & 0b00110000) / 16), + ((int)pvs.s4 << 2) + ((pvs.s6 & 0b00001100) / 4)); + } else { + return (int4)(((int)pvs.s0 << 2) + ((pvs.s3 & 0b00110000) / 16), + ((int)pvs.s1 << 2) + ((pvs.s3 & 0b00001100) / 4), + ((int)pvs.s2 << 2) + ((pvs.s3 & 0b00000011)), + ((int)pvs.s4 << 2) + ((ext & 0b11000000) / 64)); + } +} + +float get_k(float a, float b, float c, float d) { + return 2.0 - (fabs(a - b) + fabs(c - d)); +} + +__kernel void process_raw(const __global uchar * in, __global uchar * out, int expo_time) +{ + const int gid_x = get_global_id(0); + const int gid_y = get_global_id(1); + + // estimate vignetting + #if VIGNETTING + int gx = (gid_x*2 - RGB_WIDTH/2); + int gy = (gid_y*2 - RGB_HEIGHT/2); + const float vignette_factor = get_vignetting_s((gx*gx + gy*gy) / VIGNETTE_RSZ); + #else + const float vignette_factor = 1.0; + #endif + + const int row_before_offset = (gid_y == 0) ? 2 : 0; + const int row_after_offset = (gid_y == (RGB_HEIGHT/2 - 1)) ? 1 : 3; + + float3 rgb_tmp; + uchar3 rgb_out[4]; // output is 2x2 window + + // read offset + int start_idx; + #if BIT_DEPTH == 10 + bool aligned10; + if (gid_x % 2 == 0) { + aligned10 = true; + start_idx = (2 * gid_y - 1) * FRAME_STRIDE + (5 * gid_x / 2 - 2) + (FRAME_STRIDE * FRAME_OFFSET); + } else { + aligned10 = false; + start_idx = (2 * gid_y - 1) * FRAME_STRIDE + (5 * (gid_x - 1) / 2 + 1) + (FRAME_STRIDE * FRAME_OFFSET); + } + #else + start_idx = (2 * gid_y - 1) * FRAME_STRIDE + (3 * gid_x - 2) + (FRAME_STRIDE * FRAME_OFFSET); + #endif + + // read in 4 rows, 8 uchars each + uchar8 dat[4]; + // row_before + dat[0] = vload8(0, in + start_idx + FRAME_STRIDE*row_before_offset); + // row_0 + if (gid_x == 0 && gid_y == 0) { + // this wasn't a problem due to extra rows + dat[1] = vload8(0, in + start_idx + FRAME_STRIDE*1 + 2); + dat[1] = (uchar8)(0, 0, dat[1].s0, dat[1].s1, dat[1].s2, dat[1].s3, dat[1].s4, dat[1].s5); + } else { + dat[1] = vload8(0, in + start_idx + FRAME_STRIDE*1); + } + // row_1 + dat[2] = vload8(0, in + start_idx + FRAME_STRIDE*2); + // row_after + dat[3] = vload8(0, in + start_idx + FRAME_STRIDE*row_after_offset); + // need extra bit for 10-bit, 4 rows, 1 uchar each + #if BIT_DEPTH == 10 + uchar extra_dat[4]; + if (!aligned10) { + extra_dat[0] = in[start_idx + FRAME_STRIDE*row_before_offset + 8]; + extra_dat[1] = in[start_idx + FRAME_STRIDE*1 + 8]; + extra_dat[2] = in[start_idx + FRAME_STRIDE*2 + 8]; + extra_dat[3] = in[start_idx + FRAME_STRIDE*row_after_offset + 8]; + } + #endif + + // read odd rows for staggered second exposure + #if HDR_OFFSET > 0 + uchar8 short_dat[4]; + short_dat[0] = vload8(0, in + start_idx + FRAME_STRIDE*(row_before_offset+HDR_OFFSET/2) + FRAME_STRIDE/2); + short_dat[1] = vload8(0, in + start_idx + FRAME_STRIDE*(1+HDR_OFFSET/2) + FRAME_STRIDE/2); + short_dat[2] = vload8(0, in + start_idx + FRAME_STRIDE*(2+HDR_OFFSET/2) + FRAME_STRIDE/2); + short_dat[3] = vload8(0, in + start_idx + FRAME_STRIDE*(row_after_offset+HDR_OFFSET/2) + FRAME_STRIDE/2); + #if BIT_DEPTH == 10 + uchar short_extra_dat[4]; + if (!aligned10) { + short_extra_dat[0] = in[start_idx + FRAME_STRIDE*(row_before_offset+HDR_OFFSET/2) + FRAME_STRIDE/2 + 8]; + short_extra_dat[1] = in[start_idx + FRAME_STRIDE*(1+HDR_OFFSET/2) + FRAME_STRIDE/2 + 8]; + short_extra_dat[2] = in[start_idx + FRAME_STRIDE*(2+HDR_OFFSET/2) + FRAME_STRIDE/2 + 8]; + short_extra_dat[3] = in[start_idx + FRAME_STRIDE*(row_after_offset+HDR_OFFSET/2) + FRAME_STRIDE/2 + 8]; + } + #endif + #endif + + // parse into floats 0.0-1.0 + float4 v_rows[4]; + #if BIT_DEPTH == 10 + // for now it's always HDR + int4 parsed = parse_10bit(dat[0], extra_dat[0], aligned10); + int4 short_parsed = parse_10bit(short_dat[0], short_extra_dat[0], aligned10); + v_rows[ROW_READ_ORDER[0]] = normalize_pv_hdr(parsed, short_parsed, vignette_factor, expo_time); + parsed = parse_10bit(dat[1], extra_dat[1], aligned10); + short_parsed = parse_10bit(short_dat[1], short_extra_dat[1], aligned10); + v_rows[ROW_READ_ORDER[1]] = normalize_pv_hdr(parsed, short_parsed, vignette_factor, expo_time); + parsed = parse_10bit(dat[2], extra_dat[2], aligned10); + short_parsed = parse_10bit(short_dat[2], short_extra_dat[2], aligned10); + v_rows[ROW_READ_ORDER[2]] = normalize_pv_hdr(parsed, short_parsed, vignette_factor, expo_time); + parsed = parse_10bit(dat[3], extra_dat[3], aligned10); + short_parsed = parse_10bit(short_dat[3], short_extra_dat[3], aligned10); + v_rows[ROW_READ_ORDER[3]] = normalize_pv_hdr(parsed, short_parsed, vignette_factor, expo_time); + #else + // no HDR here + int4 parsed = parse_12bit(dat[0]); + v_rows[ROW_READ_ORDER[0]] = normalize_pv(parsed, vignette_factor); + parsed = parse_12bit(dat[1]); + v_rows[ROW_READ_ORDER[1]] = normalize_pv(parsed, vignette_factor); + parsed = parse_12bit(dat[2]); + v_rows[ROW_READ_ORDER[2]] = normalize_pv(parsed, vignette_factor); + parsed = parse_12bit(dat[3]); + v_rows[ROW_READ_ORDER[3]] = normalize_pv(parsed, vignette_factor); + #endif + + // mirror padding + if (gid_x == 0) { + v_rows[0].s0 = v_rows[0].s2; + v_rows[1].s0 = v_rows[1].s2; + v_rows[2].s0 = v_rows[2].s2; + v_rows[3].s0 = v_rows[3].s2; + } else if (gid_x == RGB_WIDTH/2 - 1) { + v_rows[0].s3 = v_rows[0].s1; + v_rows[1].s3 = v_rows[1].s1; + v_rows[2].s3 = v_rows[2].s1; + v_rows[3].s3 = v_rows[3].s1; + } + + // debayering + // a simplified version of https://opensignalprocessingjournal.com/contents/volumes/V6/TOSIGPJ-6-1/TOSIGPJ-6-1.pdf + const float k01 = get_k(v_rows[0].s0, v_rows[1].s1, v_rows[0].s2, v_rows[1].s1); + const float k02 = get_k(v_rows[0].s2, v_rows[1].s1, v_rows[2].s2, v_rows[1].s1); + const float k03 = get_k(v_rows[2].s0, v_rows[1].s1, v_rows[2].s2, v_rows[1].s1); + const float k04 = get_k(v_rows[0].s0, v_rows[1].s1, v_rows[2].s0, v_rows[1].s1); + rgb_tmp.x = (k02*v_rows[1].s2+k04*v_rows[1].s0)/(k02+k04); // R_G1 + rgb_tmp.y = v_rows[1].s1; // G1(R) + rgb_tmp.z = (k01*v_rows[0].s1+k03*v_rows[2].s1)/(k01+k03); // B_G1 + rgb_out[RGB_WRITE_ORDER[0]] = convert_uchar3_sat(apply_gamma(color_correct(clamp(rgb_tmp, 0.0, 1.0)), expo_time) * 255.0); + + const float k11 = get_k(v_rows[0].s1, v_rows[2].s1, v_rows[0].s3, v_rows[2].s3); + const float k12 = get_k(v_rows[0].s2, v_rows[1].s1, v_rows[1].s3, v_rows[2].s2); + const float k13 = get_k(v_rows[0].s1, v_rows[0].s3, v_rows[2].s1, v_rows[2].s3); + const float k14 = get_k(v_rows[0].s2, v_rows[1].s3, v_rows[2].s2, v_rows[1].s1); + rgb_tmp.x = v_rows[1].s2; // R + rgb_tmp.y = (k11*(v_rows[0].s2+v_rows[2].s2)*0.5+k13*(v_rows[1].s3+v_rows[1].s1)*0.5)/(k11+k13); // G_R + rgb_tmp.z = (k12*(v_rows[0].s3+v_rows[2].s1)*0.5+k14*(v_rows[0].s1+v_rows[2].s3)*0.5)/(k12+k14); // B_R + rgb_out[RGB_WRITE_ORDER[1]] = convert_uchar3_sat(apply_gamma(color_correct(clamp(rgb_tmp, 0.0, 1.0)), expo_time) * 255.0); + + const float k21 = get_k(v_rows[1].s0, v_rows[3].s0, v_rows[1].s2, v_rows[3].s2); + const float k22 = get_k(v_rows[1].s1, v_rows[2].s0, v_rows[2].s2, v_rows[3].s1); + const float k23 = get_k(v_rows[1].s0, v_rows[1].s2, v_rows[3].s0, v_rows[3].s2); + const float k24 = get_k(v_rows[1].s1, v_rows[2].s2, v_rows[3].s1, v_rows[2].s0); + rgb_tmp.x = (k22*(v_rows[1].s2+v_rows[3].s0)*0.5+k24*(v_rows[1].s0+v_rows[3].s2)*0.5)/(k22+k24); // R_B + rgb_tmp.y = (k21*(v_rows[1].s1+v_rows[3].s1)*0.5+k23*(v_rows[2].s2+v_rows[2].s0)*0.5)/(k21+k23); // G_B + rgb_tmp.z = v_rows[2].s1; // B + rgb_out[RGB_WRITE_ORDER[2]] = convert_uchar3_sat(apply_gamma(color_correct(clamp(rgb_tmp, 0.0, 1.0)), expo_time) * 255.0); + + const float k31 = get_k(v_rows[1].s1, v_rows[2].s2, v_rows[1].s3, v_rows[2].s2); + const float k32 = get_k(v_rows[1].s3, v_rows[2].s2, v_rows[3].s3, v_rows[2].s2); + const float k33 = get_k(v_rows[3].s1, v_rows[2].s2, v_rows[3].s3, v_rows[2].s2); + const float k34 = get_k(v_rows[1].s1, v_rows[2].s2, v_rows[3].s1, v_rows[2].s2); + rgb_tmp.x = (k31*v_rows[1].s2+k33*v_rows[3].s2)/(k31+k33); // R_G2 + rgb_tmp.y = v_rows[2].s2; // G2(B) + rgb_tmp.z = (k32*v_rows[2].s3+k34*v_rows[2].s1)/(k32+k34); // B_G2 + rgb_out[RGB_WRITE_ORDER[3]] = convert_uchar3_sat(apply_gamma(color_correct(clamp(rgb_tmp, 0.0, 1.0)), expo_time) * 255.0); + + // rgb2yuv(nv12) + uchar2 yy = (uchar2)( + RGB_TO_Y(rgb_out[0].s0, rgb_out[0].s1, rgb_out[0].s2), + RGB_TO_Y(rgb_out[1].s0, rgb_out[1].s1, rgb_out[1].s2) + ); + vstore2(yy, 0, out + mad24(gid_y * 2, YUV_STRIDE, gid_x * 2)); + yy = (uchar2)( + RGB_TO_Y(rgb_out[2].s0, rgb_out[2].s1, rgb_out[2].s2), + RGB_TO_Y(rgb_out[3].s0, rgb_out[3].s1, rgb_out[3].s2) + ); + vstore2(yy, 0, out + mad24(gid_y * 2 + 1, YUV_STRIDE, gid_x * 2)); + + const short ar = AVERAGE(rgb_out[0].s0, rgb_out[1].s0, rgb_out[2].s0, rgb_out[3].s0); + const short ag = AVERAGE(rgb_out[0].s1, rgb_out[1].s1, rgb_out[2].s1, rgb_out[3].s1); + const short ab = AVERAGE(rgb_out[0].s2, rgb_out[1].s2, rgb_out[2].s2, rgb_out[3].s2); + uchar2 uv = (uchar2)( + RGB_TO_U(ar, ag, ab), + RGB_TO_V(ar, ag, ab) + ); + vstore2(uv, 0, out + UV_OFFSET + mad24(gid_y, YUV_STRIDE, gid_x * 2)); +} diff --git a/system/camerad/main.cc b/system/camerad/main.cc index 19de21c9bb..b86b4f57ff 100644 --- a/system/camerad/main.cc +++ b/system/camerad/main.cc @@ -12,8 +12,7 @@ int main(int argc, char *argv[]) { return 0; } - int ret; - ret = util::set_realtime_priority(53); + int ret = util::set_realtime_priority(53); assert(ret == 0); ret = util::set_core_affinity({6}); assert(ret == 0 || Params().getBool("IsOffroad")); // failure ok while offroad due to offlining cores diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index 1ca4b3f1ad..9b688389c4 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -79,15 +79,16 @@ float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_r AR0231::AR0231() { image_sensor = cereal::FrameData::ImageSensor::AR0231; + pixel_size_mm = 0.003; data_word = true; - frame_width = FRAME_WIDTH; - frame_height = FRAME_HEIGHT; - frame_stride = FRAME_STRIDE; + frame_width = 1928; + frame_height = 1208; + frame_stride = (frame_width * 12 / 8) + 4; extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT; registers_offset = 0; frame_offset = AR0231_REGISTERS_HEIGHT; - stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT; + stats_offset = AR0231_REGISTERS_HEIGHT + frame_height; start_reg_array.assign(std::begin(start_reg_array_ar0231), std::end(start_reg_array_ar0231)); init_reg_array.assign(std::begin(init_array_ar0231), std::end(init_array_ar0231)); diff --git a/system/camerad/sensors/ar0231_cl.h b/system/camerad/sensors/ar0231_cl.h new file mode 100644 index 0000000000..8e96bbce5b --- /dev/null +++ b/system/camerad/sensors/ar0231_cl.h @@ -0,0 +1,33 @@ +#if SENSOR_ID == 1 + +#define BIT_DEPTH 12 +#define PV_MAX 4096 +#define BLACK_LVL 168 +#define VIGNETTE_RSZ 1.0f + +float4 normalize_pv(int4 parsed, float vignette_factor) { + float4 pv = (convert_float4(parsed) - BLACK_LVL) / (PV_MAX - BLACK_LVL); + return clamp(pv*vignette_factor, 0.0, 1.0); +} + +float3 color_correct(float3 rgb) { + float3 corrected = rgb.x * (float3)(1.82717181, -0.31231438, 0.07307673); + corrected += rgb.y * (float3)(-0.5743977, 1.36858544, -0.53183455); + corrected += rgb.z * (float3)(-0.25277411, -0.05627105, 1.45875782); + return corrected; +} + +float3 apply_gamma(float3 rgb, int expo_time) { + // tone mapping params + const float gamma_k = 0.75; + const float gamma_b = 0.125; + const float mp = 0.01; // ideally midpoint should be adaptive + const float rk = 9 - 100*mp; + + // poly approximation for s curve + return (rgb > mp) ? + ((rk * (rgb-mp) * (1-(gamma_k*mp+gamma_b)) * (1+1/(rk*(1-mp))) / (1+rk*(rgb-mp))) + gamma_k*mp + gamma_b) : + ((rk * (rgb-mp) * (gamma_k*mp+gamma_b) * (1+1/(rk*mp)) / (1-rk*(rgb-mp))) + gamma_k*mp + gamma_b); +} + +#endif \ No newline at end of file diff --git a/system/camerad/sensors/os04c10.cc b/system/camerad/sensors/os04c10.cc index 449e06be83..97a317407a 100644 --- a/system/camerad/sensors/os04c10.cc +++ b/system/camerad/sensors/os04c10.cc @@ -10,30 +10,23 @@ const float sensor_analog_gains_OS04C10[] = { 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5}; const uint32_t os04c10_analog_gains_reg[] = { - 0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 0x160, 0x170, 0x180, 0x190, 0x1B0, - 0x1D0, 0x1F0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x2E0, 0x300, - 0x320, 0x360, 0x3A0, 0x3E0, 0x400, 0x440, 0x480, 0x4C0, 0x500, 0x540, 0x580, - 0x5C0, 0x600, 0x640, 0x680, 0x700, 0x780, 0x800, 0x880, 0x900, 0x980, 0xA00, - 0xA80, 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, 0xD80, 0xE00, 0xE80, 0xF00, 0xF80}; - -const uint32_t VS_TIME_MIN_OS04C10 = 1; -//const uint32_t VS_TIME_MAX_OS04C10 = 34; // vs < 35 + 0x080, 0x088, 0x090, 0x098, 0x0A0, 0x0A8, 0x0B0, 0x0B8, 0x0C0, 0x0C8, 0x0D8, + 0x0E8, 0x0F8, 0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 0x160, 0x170, 0x180, + 0x190, 0x1B0, 0x1D0, 0x1F0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, + 0x2E0, 0x300, 0x320, 0x340, 0x380, 0x3C0, 0x400, 0x440, 0x480, 0x4C0, 0x500, + 0x540, 0x580, 0x5C0, 0x600, 0x640, 0x680, 0x6C0, 0x700, 0x740, 0x780, 0x7C0}; } // namespace OS04C10::OS04C10() { image_sensor = cereal::FrameData::ImageSensor::OS04C10; + pixel_size_mm = 0.002; data_word = false; - frame_width = 1920; - frame_height = 1080; - frame_stride = (1920*10/8); - - /* - frame_width = 0xa80; - frame_height = 0x5f0; - frame_stride = 0xd20; - */ + hdr_offset = 64 * 2 + 8; // stagger + frame_width = 2688; + frame_height = 1520 * 2 + hdr_offset; + frame_stride = (frame_width * 10 / 8); // no alignment extra_height = 0; frame_offset = 0; @@ -46,13 +39,13 @@ OS04C10::OS04C10() { frame_data_type = 0x2b; mclk_frequency = 24000000; // Hz - dc_gain_factor = 7.32; + dc_gain_factor = 1; dc_gain_min_weight = 1; // always on is fine dc_gain_max_weight = 1; dc_gain_on_grey = 0.9; dc_gain_off_grey = 1.0; - exposure_time_min = 2; // 1x - exposure_time_max = 2016; + exposure_time_min = 2; + exposure_time_max = 2400; analog_gain_min_idx = 0x0; analog_gain_rec_idx = 0x0; // 1x analog_gain_max_idx = 0x36; @@ -62,30 +55,19 @@ OS04C10::OS04C10() { for (int i = 0; i <= analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_OS04C10[i]; } - min_ev = (exposure_time_min + VS_TIME_MIN_OS04C10) * sensor_analog_gains[analog_gain_min_idx]; + min_ev = (exposure_time_min) * sensor_analog_gains[analog_gain_min_idx]; max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; target_grey_factor = 0.01; } std::vector OS04C10::getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const { - // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD - uint32_t hcg_time = exposure_time; - //uint32_t lcg_time = hcg_time; - //uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (exposure_time_max + VS_TIME_MAX_OS04C10) / 3), exposure_time_max + VS_TIME_MAX_OS04C10); - //uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OS04C10), VS_TIME_MAX_OS04C10); - + uint32_t long_time = exposure_time; uint32_t real_gain = os04c10_analog_gains_reg[new_exp_g]; - hcg_time = 100; - real_gain = 0x320; - return { - {0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF}, - //{0x3581, lcg_time>>8}, {0x3582, lcg_time&0xFF}, - //{0x3541, spd_time>>8}, {0x3542, spd_time&0xFF}, - //{0x35c2, vs_time&0xFF}, - + {0x3501, long_time>>8}, {0x3502, long_time&0xFF}, {0x3508, real_gain>>8}, {0x3509, real_gain&0xFF}, + {0x350c, real_gain>>8}, {0x350d, real_gain&0xFF}, }; } @@ -100,6 +82,6 @@ float OS04C10::getExposureScore(float desired_ev, int exp_t, int exp_g_idx, floa score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; score += ((1 - analog_gain_cost_delta) + analog_gain_cost_delta * (exp_g_idx - analog_gain_min_idx) / (analog_gain_max_idx - analog_gain_min_idx)) * - std::abs(exp_g_idx - gain_idx) * 5.0; + std::abs(exp_g_idx - gain_idx) * 3.0; return score; } diff --git a/system/camerad/sensors/os04c10_cl.h b/system/camerad/sensors/os04c10_cl.h new file mode 100644 index 0000000000..61775dcdc8 --- /dev/null +++ b/system/camerad/sensors/os04c10_cl.h @@ -0,0 +1,55 @@ +#if SENSOR_ID == 3 + +#define BGGR + +#define BIT_DEPTH 10 +#define PV_MAX10 1023 +#define PV_MAX16 65536 // gamma curve is calibrated to 16bit +#define BLACK_LVL 64 +#define VIGNETTE_RSZ 2.2545f + +float combine_dual_pvs(float lv, float sv, int expo_time) { + float svc = fmax(sv * expo_time, (float)(64 * (PV_MAX10 - BLACK_LVL))); + float svd = sv * fmin(expo_time, 8.0) / 8; + + if (expo_time > 64) { + if (lv < PV_MAX10 - BLACK_LVL) { + return lv / (PV_MAX16 - BLACK_LVL); + } else { + return (svc / 64) / (PV_MAX16 - BLACK_LVL); + } + } else { + if (lv > 32) { + return (lv * 64 / fmax(expo_time, 8.0)) / (PV_MAX16 - BLACK_LVL); + } else { + return svd / (PV_MAX16 - BLACK_LVL); + } + } +} + +float4 normalize_pv_hdr(int4 parsed, int4 short_parsed, float vignette_factor, int expo_time) { + float4 pl = convert_float4(parsed - BLACK_LVL); + float4 ps = convert_float4(short_parsed - BLACK_LVL); + float4 pv; + pv.s0 = combine_dual_pvs(pl.s0, ps.s0, expo_time); + pv.s1 = combine_dual_pvs(pl.s1, ps.s1, expo_time); + pv.s2 = combine_dual_pvs(pl.s2, ps.s2, expo_time); + pv.s3 = combine_dual_pvs(pl.s3, ps.s3, expo_time); + return clamp(pv*vignette_factor, 0.0, 1.0); +} + +float3 color_correct(float3 rgb) { + float3 corrected = rgb.x * (float3)(1.55361989, -0.268894615, -0.000593219); + corrected += rgb.y * (float3)(-0.421217301, 1.51883144, -0.69760146); + corrected += rgb.z * (float3)(-0.132402589, -0.249936825, 1.69819468); + return corrected; +} + +float3 apply_gamma(float3 rgb, int expo_time) { + float s = log2((float)expo_time); + if (s < 6) {s = fmin(12.0 - s, 9.0);} + // log function adaptive to number of bits + return clamp(log(1 + rgb*(PV_MAX16 - BLACK_LVL)) * (0.48*s*s - 12.92*s + 115.0) - (1.08*s*s - 29.2*s + 260.0), 0.0, 255.0) / 255.0; +} + +#endif diff --git a/system/camerad/sensors/os04c10_registers.h b/system/camerad/sensors/os04c10_registers.h index ad91a02950..91eb48b24f 100644 --- a/system/camerad/sensors/os04c10_registers.h +++ b/system/camerad/sensors/os04c10_registers.h @@ -4,69 +4,59 @@ const struct i2c_random_wr_payload start_reg_array_os04c10[] = {{0x100, 1}}; const struct i2c_random_wr_payload stop_reg_array_os04c10[] = {{0x100, 0}}; const struct i2c_random_wr_payload init_array_os04c10[] = { - // OS04C10_AA_00_02_17_wAO_1920x1080_MIPI728Mbps_Linear12bit_20FPS_4Lane_MCLK24MHz + // DP_2688X1520_NEWSTG_MIPI0776Mbps_30FPS_10BIT_FOURLANE {0x0103, 0x01}, + + // PLL {0x0301, 0x84}, {0x0303, 0x01}, - {0x0305, 0x5b}, + {0x0305, 0x61}, {0x0306, 0x01}, {0x0307, 0x17}, {0x0323, 0x04}, {0x0324, 0x01}, - {0x0325, 0x62}, + {0x0325, 0x7a}, + {0x3012, 0x06}, {0x3013, 0x02}, {0x3016, 0x72}, {0x3021, 0x03}, {0x3106, 0x21}, {0x3107, 0xa1}, - {0x3500, 0x00}, - {0x3501, 0x00}, - {0x3502, 0x40}, - {0x3503, 0x88}, - {0x3508, 0x07}, - {0x3509, 0xc0}, - {0x350a, 0x04}, - {0x350b, 0x00}, - {0x350c, 0x07}, - {0x350d, 0xc0}, - {0x350e, 0x04}, - {0x350f, 0x00}, - {0x3510, 0x00}, - {0x3511, 0x00}, - {0x3512, 0x20}, + + // ? {0x3624, 0x00}, {0x3625, 0x4c}, - {0x3660, 0x00}, + {0x3660, 0x04}, {0x3666, 0xa5}, {0x3667, 0xa5}, - {0x366a, 0x64}, + {0x366a, 0x54}, {0x3673, 0x0d}, {0x3672, 0x0d}, {0x3671, 0x0d}, {0x3670, 0x0d}, - {0x3685, 0x00}, + {0x3685, 0x0a}, {0x3694, 0x0d}, {0x3693, 0x0d}, {0x3692, 0x0d}, {0x3691, 0x0d}, {0x3696, 0x4c}, {0x3697, 0x4c}, - {0x3698, 0x40}, + {0x3698, 0x00}, {0x3699, 0x80}, - {0x369a, 0x18}, + {0x369a, 0x80}, {0x369b, 0x1f}, - {0x369c, 0x14}, + {0x369c, 0x1f}, {0x369d, 0x80}, {0x369e, 0x40}, {0x369f, 0x21}, {0x36a0, 0x12}, - {0x36a1, 0x5d}, + {0x36a1, 0xdd}, {0x36a2, 0x66}, {0x370a, 0x00}, - {0x370e, 0x0c}, + {0x370e, 0x00}, {0x3710, 0x00}, - {0x3713, 0x00}, + {0x3713, 0x04}, {0x3725, 0x02}, {0x372a, 0x03}, {0x3738, 0xce}, @@ -75,10 +65,10 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x374c, 0x00}, {0x374e, 0x00}, {0x3756, 0x00}, - {0x3757, 0x0e}, + {0x3757, 0x00}, {0x3767, 0x00}, {0x3771, 0x00}, - {0x377b, 0x20}, + {0x377b, 0x28}, {0x377c, 0x00}, {0x377d, 0x0c}, {0x3781, 0x03}, @@ -91,26 +81,29 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x37ba, 0x03}, {0x37bb, 0x00}, {0x37bc, 0x04}, - {0x37be, 0x08}, + {0x37be, 0x26}, {0x37c4, 0x11}, {0x37c5, 0x80}, {0x37c6, 0x14}, - {0x37c7, 0x08}, + {0x37c7, 0xa8}, {0x37da, 0x11}, {0x381f, 0x08}, - {0x3829, 0x03}, + // {0x3829, 0x03}, + // {0x3832, 0x00}, {0x3881, 0x00}, {0x3888, 0x04}, {0x388b, 0x00}, {0x3c80, 0x10}, {0x3c86, 0x00}, - {0x3c8c, 0x20}, + // {0x3c8c, 0x20}, {0x3c9f, 0x01}, {0x3d85, 0x1b}, {0x3d8c, 0x71}, {0x3d8d, 0xe2}, {0x3f00, 0x0b}, {0x3f06, 0x04}, + + // BLC {0x400a, 0x01}, {0x400b, 0x50}, {0x400e, 0x08}, @@ -128,18 +121,19 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x40b7, 0x00}, {0x40b8, 0x00}, {0x40b9, 0x00}, - {0x40ba, 0x00}, + {0x40ba, 0x01}, + {0x4301, 0x00}, {0x4303, 0x00}, {0x4502, 0x04}, {0x4503, 0x00}, {0x4504, 0x06}, {0x4506, 0x00}, - {0x4507, 0x64}, + {0x4507, 0x57}, {0x4803, 0x00}, {0x480c, 0x32}, - {0x480e, 0x00}, - {0x4813, 0x00}, + {0x480e, 0x04}, + {0x4813, 0xe4}, {0x4819, 0x70}, {0x481f, 0x30}, {0x4823, 0x3f}, @@ -156,27 +150,33 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x4d0b, 0x01}, {0x4e00, 0x2a}, {0x4e0d, 0x00}, - {0x5001, 0x09}, + + // ISP + {0x5001, 0x00}, {0x5004, 0x00}, {0x5080, 0x04}, - {0x5036, 0x00}, + {0x5036, 0x80}, {0x5180, 0x70}, {0x5181, 0x10}, + + // DPC {0x520a, 0x03}, {0x520b, 0x06}, {0x520c, 0x0c}, + {0x580b, 0x0f}, {0x580d, 0x00}, {0x580f, 0x00}, {0x5820, 0x00}, {0x5821, 0x00}, + {0x301c, 0xf8}, {0x301e, 0xb4}, - {0x301f, 0xd0}, + {0x301f, 0xf0}, {0x3022, 0x01}, {0x3109, 0xe7}, {0x3600, 0x00}, - {0x3610, 0x65}, + {0x3610, 0x75}, {0x3611, 0x85}, {0x3613, 0x3a}, {0x3615, 0x60}, @@ -186,10 +186,10 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x3661, 0x04}, {0x3664, 0x70}, {0x3665, 0x00}, - {0x3681, 0xa6}, - {0x3682, 0x53}, - {0x3683, 0x2a}, - {0x3684, 0x15}, + {0x3681, 0x80}, + {0x3682, 0x40}, + {0x3683, 0x21}, + {0x3684, 0x12}, {0x3700, 0x2a}, {0x3701, 0x12}, {0x3703, 0x28}, @@ -198,7 +198,7 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x3709, 0x4a}, {0x370b, 0xa2}, {0x370c, 0x01}, - {0x370f, 0x04}, + {0x370f, 0x00}, {0x3714, 0x24}, {0x3716, 0x04}, {0x3719, 0x11}, @@ -233,66 +233,103 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x37c0, 0x11}, {0x37c2, 0x04}, {0x37cd, 0x19}, - {0x37e0, 0x08}, - {0x37e6, 0x04}, + // {0x37e0, 0x08}, + // {0x37e6, 0x04}, {0x37e5, 0x02}, - {0x37e1, 0x0c}, - {0x3737, 0x04}, + // {0x37e1, 0x0c}, + // {0x3737, 0x04}, {0x37d8, 0x02}, - {0x37e2, 0x10}, + // {0x37e2, 0x10}, {0x3739, 0x10}, {0x3662, 0x10}, - {0x37e4, 0x20}, - {0x37e3, 0x08}, + // {0x37e4, 0x20}, + // {0x37e3, 0x08}, {0x37d9, 0x08}, {0x4040, 0x00}, {0x4041, 0x07}, {0x4008, 0x02}, {0x4009, 0x0d}, - {0x3800, 0x01}, - {0x3801, 0x80}, - {0x3802, 0x00}, - {0x3803, 0xdc}, - {0x3804, 0x09}, - {0x3805, 0x0f}, - {0x3806, 0x05}, - {0x3807, 0x23}, - {0x3808, 0x07}, - {0x3809, 0x80}, - {0x380a, 0x04}, - {0x380b, 0x38}, - {0x380c, 0x04}, - {0x380d, 0x2e}, - {0x380e, 0x12}, - {0x380f, 0x70}, + + // FSIN + {0x3002, 0x22}, + {0x3663, 0x22}, + {0x368a, 0x04}, + {0x3822, 0x44}, + {0x3823, 0x00}, + {0x3829, 0x03}, + {0x3832, 0xf8}, + {0x382c, 0x00}, + {0x3844, 0x06}, + {0x3843, 0x00}, + {0x382a, 0x00}, + {0x382b, 0x0c}, + + // 2704x1536 -> 2688x1520 out + {0x3800, 0x00}, {0x3801, 0x00}, + {0x3802, 0x00}, {0x3803, 0x00}, + {0x3804, 0x0a}, {0x3805, 0x8f}, + {0x3806, 0x05}, {0x3807, 0xff}, + {0x3808, 0x0a}, {0x3809, 0x80}, + {0x380a, 0x05}, {0x380b, 0xf0}, {0x3811, 0x08}, {0x3813, 0x08}, {0x3814, 0x01}, {0x3815, 0x01}, {0x3816, 0x01}, {0x3817, 0x01}, - {0x3820, 0xB0}, - {0x3821, 0x00}, - {0x3880, 0x25}, + + {0x380c, 0x04}, {0x380d, 0x2e}, // HTS + {0x380e, 0x09}, {0x380f, 0xdb}, // VTS + + {0x3820, 0xb0}, + {0x3821, 0x04}, + {0x3880, 0x00}, {0x3882, 0x20}, {0x3c91, 0x0b}, {0x3c94, 0x45}, - {0x3cad, 0x00}, - {0x3cae, 0x00}, + // {0x3cad, 0x00}, + // {0x3cae, 0x00}, {0x4000, 0xf3}, {0x4001, 0x60}, {0x4003, 0x40}, {0x4300, 0xff}, {0x4302, 0x0f}, - {0x4305, 0x83}, + {0x4305, 0x93}, {0x4505, 0x84}, - {0x4809, 0x1e}, + {0x4809, 0x0e}, {0x480a, 0x04}, - {0x4837, 0x15}, + {0x4837, 0x14}, {0x4c00, 0x08}, {0x4c01, 0x08}, {0x4c04, 0x00}, {0x4c05, 0x00}, {0x5000, 0xf9}, - {0x3c8c, 0x10}, + // {0x0100, 0x01}, + // {0x320d, 0x00}, + // {0x3208, 0xa0}, + // {0x3822, 0x14}, + + // initialize exposure + {0x3503, 0x88}, + + // long + {0x3500, 0x00}, {0x3501, 0x00}, {0x3502, 0x10}, + {0x3508, 0x00}, {0x3509, 0x80}, + {0x350a, 0x04}, {0x350b, 0x00}, + + // short + {0x3510, 0x00}, {0x3511, 0x00}, {0x3512, 0x40}, + {0x350c, 0x00}, {0x350d, 0x80}, + {0x350e, 0x04}, {0x350f, 0x00}, + + // wb + // b + {0x5100, 0x06}, {0x5101, 0x7e}, + {0x5140, 0x06}, {0x5141, 0x7e}, + // g + {0x5102, 0x04}, {0x5103, 0x00}, + {0x5142, 0x04}, {0x5143, 0x00}, + // r + {0x5104, 0x08}, {0x5105, 0xd6}, + {0x5144, 0x08}, {0x5145, 0xd6}, }; diff --git a/system/camerad/sensors/ox03c10.cc b/system/camerad/sensors/ox03c10.cc index 1f0609820b..94efa0ea24 100644 --- a/system/camerad/sensors/ox03c10.cc +++ b/system/camerad/sensors/ox03c10.cc @@ -23,10 +23,11 @@ const uint32_t VS_TIME_MAX_OX03C10 = 34; // vs < 35 OX03C10::OX03C10() { image_sensor = cereal::FrameData::ImageSensor::OX03C10; + pixel_size_mm = 0.003; data_word = false; - frame_width = FRAME_WIDTH; - frame_height = FRAME_HEIGHT; - frame_stride = FRAME_STRIDE; // (0xa80*12//8) + frame_width = 1928; + frame_height = 1208; + frame_stride = (frame_width * 12 / 8) + 4; extra_height = 16; // top 2 + bot 14 frame_offset = 2; diff --git a/system/camerad/cameras/real_debayer.cl b/system/camerad/sensors/ox03c10_cl.h similarity index 88% rename from system/camerad/cameras/real_debayer.cl rename to system/camerad/sensors/ox03c10_cl.h index e15a873d6d..21441902fc 100644 --- a/system/camerad/cameras/real_debayer.cl +++ b/system/camerad/sensors/ox03c10_cl.h @@ -1,197 +1,43 @@ -#define UV_WIDTH RGB_WIDTH / 2 -#define UV_HEIGHT RGB_HEIGHT / 2 +#if SENSOR_ID == 2 -#define RGB_TO_Y(r, g, b) ((((mul24(b, 13) + mul24(g, 65) + mul24(r, 33)) + 64) >> 7) + 16) -#define RGB_TO_U(r, g, b) ((mul24(b, 56) - mul24(g, 37) - mul24(r, 19) + 0x8080) >> 8) -#define RGB_TO_V(r, g, b) ((mul24(r, 56) - mul24(g, 47) - mul24(b, 9) + 0x8080) >> 8) -#define AVERAGE(x, y, z, w) ((convert_ushort(x) + convert_ushort(y) + convert_ushort(z) + convert_ushort(w) + 1) >> 1) - -float3 color_correct(float3 rgb) { - // color correction - #if IS_OX - float3 x = rgb.x * (float3)(1.5664815 , -0.29808738, -0.03973474); - x += rgb.y * (float3)(-0.48672447, 1.41914433, -0.40295248); - x += rgb.z * (float3)(-0.07975703, -0.12105695, 1.44268722); - #else - float3 x = rgb.x * (float3)(1.82717181, -0.31231438, 0.07307673); - x += rgb.y * (float3)(-0.5743977, 1.36858544, -0.53183455); - x += rgb.z * (float3)(-0.25277411, -0.05627105, 1.45875782); - #endif - - #if IS_OX - return -0.507089*exp(-12.54124638*x)+0.9655*powr(x,0.5)-0.472597*x+0.507089; - #else - // tone mapping params - const float gamma_k = 0.75; - const float gamma_b = 0.125; - const float mp = 0.01; // ideally midpoint should be adaptive - const float rk = 9 - 100*mp; - - // poly approximation for s curve - return (x > mp) ? - ((rk * (x-mp) * (1-(gamma_k*mp+gamma_b)) * (1+1/(rk*(1-mp))) / (1+rk*(x-mp))) + gamma_k*mp + gamma_b) : - ((rk * (x-mp) * (gamma_k*mp+gamma_b) * (1+1/(rk*mp)) / (1-rk*(x-mp))) + gamma_k*mp + gamma_b); - #endif -} - -float get_vignetting_s(float r) { - if (r < 62500) { - return (1.0f + 0.0000008f*r); - } else if (r < 490000) { - return (0.9625f + 0.0000014f*r); - } else if (r < 1102500) { - return (1.26434f + 0.0000000000016f*r*r); - } else { - return (0.53503625f + 0.0000000000022f*r*r); - } -} +#define BIT_DEPTH 12 +#define BLACK_LVL 64 +#define VIGNETTE_RSZ 1.0f constant float ox03c10_lut[] = { - 0.0000e+00, 5.9488e-08, 1.1898e-07, 1.7846e-07, 2.3795e-07, 2.9744e-07, 3.5693e-07, 4.1642e-07, 4.7591e-07, 5.3539e-07, 5.9488e-07, 6.5437e-07, 7.1386e-07, 7.7335e-07, 8.3284e-07, 8.9232e-07, 9.5181e-07, 1.0113e-06, 1.0708e-06, 1.1303e-06, 1.1898e-06, 1.2493e-06, 1.3087e-06, 1.3682e-06, 1.4277e-06, 1.4872e-06, 1.5467e-06, 1.6062e-06, 1.6657e-06, 1.7252e-06, 1.7846e-06, 1.8441e-06, 1.9036e-06, 1.9631e-06, 2.0226e-06, 2.0821e-06, 2.1416e-06, 2.2011e-06, 2.2606e-06, 2.3200e-06, 2.3795e-06, 2.4390e-06, 2.4985e-06, 2.5580e-06, 2.6175e-06, 2.6770e-06, 2.7365e-06, 2.7959e-06, 2.8554e-06, 2.9149e-06, 2.9744e-06, 3.0339e-06, 3.0934e-06, 3.1529e-06, 3.2124e-06, 3.2719e-06, 3.3313e-06, 3.3908e-06, 3.4503e-06, 3.5098e-06, 3.5693e-06, 3.6288e-06, 3.6883e-06, 3.7478e-06, 3.8072e-06, 3.8667e-06, 3.9262e-06, 3.9857e-06, 4.0452e-06, 4.1047e-06, 4.1642e-06, 4.2237e-06, 4.2832e-06, 4.3426e-06, 4.4021e-06, 4.4616e-06, 4.5211e-06, 4.5806e-06, 4.6401e-06, 4.6996e-06, 4.7591e-06, 4.8185e-06, 4.8780e-06, 4.9375e-06, 4.9970e-06, 5.0565e-06, 5.1160e-06, 5.1755e-06, 5.2350e-06, 5.2945e-06, 5.3539e-06, 5.4134e-06, 5.4729e-06, 5.5324e-06, 5.5919e-06, 5.6514e-06, 5.7109e-06, 5.7704e-06, 5.8298e-06, 5.8893e-06, 5.9488e-06, 6.0083e-06, 6.0678e-06, 6.1273e-06, 6.1868e-06, 6.2463e-06, 6.3058e-06, 6.3652e-06, 6.4247e-06, 6.4842e-06, 6.5437e-06, 6.6032e-06, 6.6627e-06, 6.7222e-06, 6.7817e-06, 6.8411e-06, 6.9006e-06, 6.9601e-06, 7.0196e-06, 7.0791e-06, 7.1386e-06, 7.1981e-06, 7.2576e-06, 7.3171e-06, 7.3765e-06, 7.4360e-06, 7.4955e-06, 7.5550e-06, 7.6145e-06, 7.6740e-06, 7.7335e-06, 7.7930e-06, 7.8524e-06, 7.9119e-06, 7.9714e-06, 8.0309e-06, 8.0904e-06, 8.1499e-06, 8.2094e-06, 8.2689e-06, 8.3284e-06, 8.3878e-06, 8.4473e-06, 8.5068e-06, 8.5663e-06, 8.6258e-06, 8.6853e-06, 8.7448e-06, 8.8043e-06, 8.8637e-06, 8.9232e-06, 8.9827e-06, 9.0422e-06, 9.1017e-06, 9.1612e-06, 9.2207e-06, 9.2802e-06, 9.3397e-06, 9.3991e-06, 9.4586e-06, 9.5181e-06, 9.5776e-06, 9.6371e-06, 9.6966e-06, 9.7561e-06, 9.8156e-06, 9.8750e-06, 9.9345e-06, 9.9940e-06, 1.0054e-05, 1.0113e-05, 1.0172e-05, 1.0232e-05, 1.0291e-05, 1.0351e-05, 1.0410e-05, 1.0470e-05, 1.0529e-05, 1.0589e-05, 1.0648e-05, 1.0708e-05, 1.0767e-05, 1.0827e-05, 1.0886e-05, 1.0946e-05, 1.1005e-05, 1.1065e-05, 1.1124e-05, 1.1184e-05, 1.1243e-05, 1.1303e-05, 1.1362e-05, 1.1422e-05, 1.1481e-05, 1.1541e-05, 1.1600e-05, 1.1660e-05, 1.1719e-05, 1.1779e-05, 1.1838e-05, 1.1898e-05, 1.1957e-05, 1.2017e-05, 1.2076e-05, 1.2136e-05, 1.2195e-05, 1.2255e-05, 1.2314e-05, 1.2374e-05, 1.2433e-05, 1.2493e-05, 1.2552e-05, 1.2612e-05, 1.2671e-05, 1.2730e-05, 1.2790e-05, 1.2849e-05, 1.2909e-05, 1.2968e-05, 1.3028e-05, 1.3087e-05, 1.3147e-05, 1.3206e-05, 1.3266e-05, 1.3325e-05, 1.3385e-05, 1.3444e-05, 1.3504e-05, 1.3563e-05, 1.3623e-05, 1.3682e-05, 1.3742e-05, 1.3801e-05, 1.3861e-05, 1.3920e-05, 1.3980e-05, 1.4039e-05, 1.4099e-05, 1.4158e-05, 1.4218e-05, 1.4277e-05, 1.4337e-05, 1.4396e-05, 1.4456e-05, 1.4515e-05, 1.4575e-05, 1.4634e-05, 1.4694e-05, 1.4753e-05, 1.4813e-05, 1.4872e-05, 1.4932e-05, 1.4991e-05, 1.5051e-05, 1.5110e-05, 1.5169e-05, - 1.5229e-05, 1.5288e-05, 1.5348e-05, 1.5407e-05, 1.5467e-05, 1.5526e-05, 1.5586e-05, 1.5645e-05, 1.5705e-05, 1.5764e-05, 1.5824e-05, 1.5883e-05, 1.5943e-05, 1.6002e-05, 1.6062e-05, 1.6121e-05, 1.6181e-05, 1.6240e-05, 1.6300e-05, 1.6359e-05, 1.6419e-05, 1.6478e-05, 1.6538e-05, 1.6597e-05, 1.6657e-05, 1.6716e-05, 1.6776e-05, 1.6835e-05, 1.6895e-05, 1.6954e-05, 1.7014e-05, 1.7073e-05, 1.7133e-05, 1.7192e-05, 1.7252e-05, 1.7311e-05, 1.7371e-05, 1.7430e-05, 1.7490e-05, 1.7549e-05, 1.7609e-05, 1.7668e-05, 1.7727e-05, 1.7787e-05, 1.7846e-05, 1.7906e-05, 1.7965e-05, 1.8025e-05, 1.8084e-05, 1.8144e-05, 1.8203e-05, 1.8263e-05, 1.8322e-05, 1.8382e-05, 1.8441e-05, 1.8501e-05, 1.8560e-05, 1.8620e-05, 1.8679e-05, 1.8739e-05, 1.8798e-05, 1.8858e-05, 1.8917e-05, 1.8977e-05, 1.9036e-05, 1.9096e-05, 1.9155e-05, 1.9215e-05, 1.9274e-05, 1.9334e-05, 1.9393e-05, 1.9453e-05, 1.9512e-05, 1.9572e-05, 1.9631e-05, 1.9691e-05, 1.9750e-05, 1.9810e-05, 1.9869e-05, 1.9929e-05, 1.9988e-05, 2.0048e-05, 2.0107e-05, 2.0167e-05, 2.0226e-05, 2.0285e-05, 2.0345e-05, 2.0404e-05, 2.0464e-05, 2.0523e-05, 2.0583e-05, 2.0642e-05, 2.0702e-05, 2.0761e-05, 2.0821e-05, 2.0880e-05, 2.0940e-05, 2.0999e-05, 2.1059e-05, 2.1118e-05, 2.1178e-05, 2.1237e-05, 2.1297e-05, 2.1356e-05, 2.1416e-05, 2.1475e-05, 2.1535e-05, 2.1594e-05, 2.1654e-05, 2.1713e-05, 2.1773e-05, 2.1832e-05, 2.1892e-05, 2.1951e-05, 2.2011e-05, 2.2070e-05, 2.2130e-05, 2.2189e-05, 2.2249e-05, 2.2308e-05, 2.2368e-05, 2.2427e-05, 2.2487e-05, 2.2546e-05, 2.2606e-05, 2.2665e-05, 2.2725e-05, 2.2784e-05, 2.2843e-05, 2.2903e-05, 2.2962e-05, 2.3022e-05, 2.3081e-05, 2.3141e-05, 2.3200e-05, 2.3260e-05, 2.3319e-05, 2.3379e-05, 2.3438e-05, 2.3498e-05, 2.3557e-05, 2.3617e-05, 2.3676e-05, 2.3736e-05, 2.3795e-05, 2.3855e-05, 2.3914e-05, 2.3974e-05, 2.4033e-05, 2.4093e-05, 2.4152e-05, 2.4212e-05, 2.4271e-05, 2.4331e-05, 2.4390e-05, 2.4450e-05, 2.4509e-05, 2.4569e-05, 2.4628e-05, 2.4688e-05, 2.4747e-05, 2.4807e-05, 2.4866e-05, 2.4926e-05, 2.4985e-05, 2.5045e-05, 2.5104e-05, 2.5164e-05, 2.5223e-05, 2.5282e-05, 2.5342e-05, 2.5401e-05, 2.5461e-05, 2.5520e-05, 2.5580e-05, 2.5639e-05, 2.5699e-05, 2.5758e-05, 2.5818e-05, 2.5877e-05, 2.5937e-05, 2.5996e-05, 2.6056e-05, 2.6115e-05, 2.6175e-05, 2.6234e-05, 2.6294e-05, 2.6353e-05, 2.6413e-05, 2.6472e-05, 2.6532e-05, 2.6591e-05, 2.6651e-05, 2.6710e-05, 2.6770e-05, 2.6829e-05, 2.6889e-05, 2.6948e-05, 2.7008e-05, 2.7067e-05, 2.7127e-05, 2.7186e-05, 2.7246e-05, 2.7305e-05, 2.7365e-05, 2.7424e-05, 2.7484e-05, 2.7543e-05, 2.7603e-05, 2.7662e-05, 2.7722e-05, 2.7781e-05, 2.7840e-05, 2.7900e-05, 2.7959e-05, 2.8019e-05, 2.8078e-05, 2.8138e-05, 2.8197e-05, 2.8257e-05, 2.8316e-05, 2.8376e-05, 2.8435e-05, 2.8495e-05, 2.8554e-05, 2.8614e-05, 2.8673e-05, 2.8733e-05, 2.8792e-05, 2.8852e-05, 2.8911e-05, 2.8971e-05, 2.9030e-05, 2.9090e-05, 2.9149e-05, 2.9209e-05, 2.9268e-05, 2.9328e-05, 2.9387e-05, 2.9447e-05, 2.9506e-05, 2.9566e-05, 2.9625e-05, 2.9685e-05, 2.9744e-05, 2.9804e-05, 2.9863e-05, 2.9923e-05, 2.9982e-05, 3.0042e-05, 3.0101e-05, 3.0161e-05, 3.0220e-05, 3.0280e-05, 3.0339e-05, 3.0398e-05, - 3.0458e-05, 3.0577e-05, 3.0697e-05, 3.0816e-05, 3.0936e-05, 3.1055e-05, 3.1175e-05, 3.1294e-05, 3.1414e-05, 3.1533e-05, 3.1652e-05, 3.1772e-05, 3.1891e-05, 3.2011e-05, 3.2130e-05, 3.2250e-05, 3.2369e-05, 3.2489e-05, 3.2608e-05, 3.2727e-05, 3.2847e-05, 3.2966e-05, 3.3086e-05, 3.3205e-05, 3.3325e-05, 3.3444e-05, 3.3563e-05, 3.3683e-05, 3.3802e-05, 3.3922e-05, 3.4041e-05, 3.4161e-05, 3.4280e-05, 3.4400e-05, 3.4519e-05, 3.4638e-05, 3.4758e-05, 3.4877e-05, 3.4997e-05, 3.5116e-05, 3.5236e-05, 3.5355e-05, 3.5475e-05, 3.5594e-05, 3.5713e-05, 3.5833e-05, 3.5952e-05, 3.6072e-05, 3.6191e-05, 3.6311e-05, 3.6430e-05, 3.6550e-05, 3.6669e-05, 3.6788e-05, 3.6908e-05, 3.7027e-05, 3.7147e-05, 3.7266e-05, 3.7386e-05, 3.7505e-05, 3.7625e-05, 3.7744e-05, 3.7863e-05, 3.7983e-05, 3.8102e-05, 3.8222e-05, 3.8341e-05, 3.8461e-05, 3.8580e-05, 3.8700e-05, 3.8819e-05, 3.8938e-05, 3.9058e-05, 3.9177e-05, 3.9297e-05, 3.9416e-05, 3.9536e-05, 3.9655e-05, 3.9775e-05, 3.9894e-05, 4.0013e-05, 4.0133e-05, 4.0252e-05, 4.0372e-05, 4.0491e-05, 4.0611e-05, 4.0730e-05, 4.0850e-05, 4.0969e-05, 4.1088e-05, 4.1208e-05, 4.1327e-05, 4.1447e-05, 4.1566e-05, 4.1686e-05, 4.1805e-05, 4.1925e-05, 4.2044e-05, 4.2163e-05, 4.2283e-05, 4.2402e-05, 4.2522e-05, 4.2641e-05, 4.2761e-05, 4.2880e-05, 4.2999e-05, 4.3119e-05, 4.3238e-05, 4.3358e-05, 4.3477e-05, 4.3597e-05, 4.3716e-05, 4.3836e-05, 4.3955e-05, 4.4074e-05, 4.4194e-05, 4.4313e-05, 4.4433e-05, 4.4552e-05, 4.4672e-05, 4.4791e-05, 4.4911e-05, 4.5030e-05, 4.5149e-05, 4.5269e-05, 4.5388e-05, 4.5508e-05, 4.5627e-05, 4.5747e-05, 4.5866e-05, 4.5986e-05, 4.6105e-05, 4.6224e-05, 4.6344e-05, 4.6463e-05, 4.6583e-05, 4.6702e-05, 4.6822e-05, 4.6941e-05, 4.7061e-05, 4.7180e-05, 4.7299e-05, 4.7419e-05, 4.7538e-05, 4.7658e-05, 4.7777e-05, 4.7897e-05, 4.8016e-05, 4.8136e-05, 4.8255e-05, 4.8374e-05, 4.8494e-05, 4.8613e-05, 4.8733e-05, 4.8852e-05, 4.8972e-05, 4.9091e-05, 4.9211e-05, 4.9330e-05, 4.9449e-05, 4.9569e-05, 4.9688e-05, 4.9808e-05, 4.9927e-05, 5.0047e-05, 5.0166e-05, 5.0286e-05, 5.0405e-05, 5.0524e-05, 5.0644e-05, 5.0763e-05, 5.0883e-05, 5.1002e-05, 5.1122e-05, 5.1241e-05, 5.1361e-05, 5.1480e-05, 5.1599e-05, 5.1719e-05, 5.1838e-05, 5.1958e-05, 5.2077e-05, 5.2197e-05, 5.2316e-05, 5.2435e-05, 5.2555e-05, 5.2674e-05, 5.2794e-05, 5.2913e-05, 5.3033e-05, 5.3152e-05, 5.3272e-05, 5.3391e-05, 5.3510e-05, 5.3630e-05, 5.3749e-05, 5.3869e-05, 5.3988e-05, 5.4108e-05, 5.4227e-05, 5.4347e-05, 5.4466e-05, 5.4585e-05, 5.4705e-05, 5.4824e-05, 5.4944e-05, 5.5063e-05, 5.5183e-05, 5.5302e-05, 5.5422e-05, 5.5541e-05, 5.5660e-05, 5.5780e-05, 5.5899e-05, 5.6019e-05, 5.6138e-05, 5.6258e-05, 5.6377e-05, 5.6497e-05, 5.6616e-05, 5.6735e-05, 5.6855e-05, 5.6974e-05, 5.7094e-05, 5.7213e-05, 5.7333e-05, 5.7452e-05, 5.7572e-05, 5.7691e-05, 5.7810e-05, 5.7930e-05, 5.8049e-05, 5.8169e-05, 5.8288e-05, 5.8408e-05, 5.8527e-05, 5.8647e-05, 5.8766e-05, 5.8885e-05, 5.9005e-05, 5.9124e-05, 5.9244e-05, 5.9363e-05, 5.9483e-05, 5.9602e-05, 5.9722e-05, 5.9841e-05, 5.9960e-05, 6.0080e-05, 6.0199e-05, 6.0319e-05, 6.0438e-05, 6.0558e-05, 6.0677e-05, 6.0797e-05, 6.0916e-05, - 6.1154e-05, 6.1392e-05, 6.1631e-05, 6.1869e-05, 6.2107e-05, 6.2345e-05, 6.2583e-05, 6.2821e-05, 6.3060e-05, 6.3298e-05, 6.3536e-05, 6.3774e-05, 6.4012e-05, 6.4251e-05, 6.4489e-05, 6.4727e-05, 6.4965e-05, 6.5203e-05, 6.5441e-05, 6.5680e-05, 6.5918e-05, 6.6156e-05, 6.6394e-05, 6.6632e-05, 6.6871e-05, 6.7109e-05, 6.7347e-05, 6.7585e-05, 6.7823e-05, 6.8062e-05, 6.8300e-05, 6.8538e-05, 6.8776e-05, 6.9014e-05, 6.9252e-05, 6.9491e-05, 6.9729e-05, 6.9967e-05, 7.0205e-05, 7.0443e-05, 7.0682e-05, 7.0920e-05, 7.1158e-05, 7.1396e-05, 7.1634e-05, 7.1872e-05, 7.2111e-05, 7.2349e-05, 7.2587e-05, 7.2825e-05, 7.3063e-05, 7.3302e-05, 7.3540e-05, 7.3778e-05, 7.4016e-05, 7.4254e-05, 7.4493e-05, 7.4731e-05, 7.4969e-05, 7.5207e-05, 7.5445e-05, 7.5683e-05, 7.5922e-05, 7.6160e-05, 7.6398e-05, 7.6636e-05, 7.6874e-05, 7.7113e-05, 7.7351e-05, 7.7589e-05, 7.7827e-05, 7.8065e-05, 7.8304e-05, 7.8542e-05, 7.8780e-05, 7.9018e-05, 7.9256e-05, 7.9494e-05, 7.9733e-05, 7.9971e-05, 8.0209e-05, 8.0447e-05, 8.0685e-05, 8.0924e-05, 8.1162e-05, 8.1400e-05, 8.1638e-05, 8.1876e-05, 8.2114e-05, 8.2353e-05, 8.2591e-05, 8.2829e-05, 8.3067e-05, 8.3305e-05, 8.3544e-05, 8.3782e-05, 8.4020e-05, 8.4258e-05, 8.4496e-05, 8.4735e-05, 8.4973e-05, 8.5211e-05, 8.5449e-05, 8.5687e-05, 8.5925e-05, 8.6164e-05, 8.6402e-05, 8.6640e-05, 8.6878e-05, 8.7116e-05, 8.7355e-05, 8.7593e-05, 8.7831e-05, 8.8069e-05, 8.8307e-05, 8.8545e-05, 8.8784e-05, 8.9022e-05, 8.9260e-05, 8.9498e-05, 8.9736e-05, 8.9975e-05, 9.0213e-05, 9.0451e-05, 9.0689e-05, 9.0927e-05, 9.1166e-05, 9.1404e-05, 9.1642e-05, 9.1880e-05, 9.2118e-05, 9.2356e-05, 9.2595e-05, 9.2833e-05, 9.3071e-05, 9.3309e-05, 9.3547e-05, 9.3786e-05, 9.4024e-05, 9.4262e-05, 9.4500e-05, 9.4738e-05, 9.4977e-05, 9.5215e-05, 9.5453e-05, 9.5691e-05, 9.5929e-05, 9.6167e-05, 9.6406e-05, 9.6644e-05, 9.6882e-05, 9.7120e-05, 9.7358e-05, 9.7597e-05, 9.7835e-05, 9.8073e-05, 9.8311e-05, 9.8549e-05, 9.8787e-05, 9.9026e-05, 9.9264e-05, 9.9502e-05, 9.9740e-05, 9.9978e-05, 1.0022e-04, 1.0045e-04, 1.0069e-04, 1.0093e-04, 1.0117e-04, 1.0141e-04, 1.0165e-04, 1.0188e-04, 1.0212e-04, 1.0236e-04, 1.0260e-04, 1.0284e-04, 1.0307e-04, 1.0331e-04, 1.0355e-04, 1.0379e-04, 1.0403e-04, 1.0427e-04, 1.0450e-04, 1.0474e-04, 1.0498e-04, 1.0522e-04, 1.0546e-04, 1.0569e-04, 1.0593e-04, 1.0617e-04, 1.0641e-04, 1.0665e-04, 1.0689e-04, 1.0712e-04, 1.0736e-04, 1.0760e-04, 1.0784e-04, 1.0808e-04, 1.0831e-04, 1.0855e-04, 1.0879e-04, 1.0903e-04, 1.0927e-04, 1.0951e-04, 1.0974e-04, 1.0998e-04, 1.1022e-04, 1.1046e-04, 1.1070e-04, 1.1093e-04, 1.1117e-04, 1.1141e-04, 1.1165e-04, 1.1189e-04, 1.1213e-04, 1.1236e-04, 1.1260e-04, 1.1284e-04, 1.1308e-04, 1.1332e-04, 1.1355e-04, 1.1379e-04, 1.1403e-04, 1.1427e-04, 1.1451e-04, 1.1475e-04, 1.1498e-04, 1.1522e-04, 1.1546e-04, 1.1570e-04, 1.1594e-04, 1.1618e-04, 1.1641e-04, 1.1665e-04, 1.1689e-04, 1.1713e-04, 1.1737e-04, 1.1760e-04, 1.1784e-04, 1.1808e-04, 1.1832e-04, 1.1856e-04, 1.1880e-04, 1.1903e-04, 1.1927e-04, 1.1951e-04, 1.1975e-04, 1.1999e-04, 1.2022e-04, 1.2046e-04, 1.2070e-04, 1.2094e-04, 1.2118e-04, 1.2142e-04, 1.2165e-04, 1.2189e-04, - 1.2213e-04, 1.2237e-04, 1.2261e-04, 1.2284e-04, 1.2308e-04, 1.2332e-04, 1.2356e-04, 1.2380e-04, 1.2404e-04, 1.2427e-04, 1.2451e-04, 1.2475e-04, 1.2499e-04, 1.2523e-04, 1.2546e-04, 1.2570e-04, 1.2594e-04, 1.2618e-04, 1.2642e-04, 1.2666e-04, 1.2689e-04, 1.2713e-04, 1.2737e-04, 1.2761e-04, 1.2785e-04, 1.2808e-04, 1.2832e-04, 1.2856e-04, 1.2880e-04, 1.2904e-04, 1.2928e-04, 1.2951e-04, 1.2975e-04, 1.2999e-04, 1.3023e-04, 1.3047e-04, 1.3070e-04, 1.3094e-04, 1.3118e-04, 1.3142e-04, 1.3166e-04, 1.3190e-04, 1.3213e-04, 1.3237e-04, 1.3261e-04, 1.3285e-04, 1.3309e-04, 1.3332e-04, 1.3356e-04, 1.3380e-04, 1.3404e-04, 1.3428e-04, 1.3452e-04, 1.3475e-04, 1.3499e-04, 1.3523e-04, 1.3547e-04, 1.3571e-04, 1.3594e-04, 1.3618e-04, 1.3642e-04, 1.3666e-04, 1.3690e-04, 1.3714e-04, 1.3737e-04, 1.3761e-04, 1.3785e-04, 1.3809e-04, 1.3833e-04, 1.3856e-04, 1.3880e-04, 1.3904e-04, 1.3928e-04, 1.3952e-04, 1.3976e-04, 1.3999e-04, 1.4023e-04, 1.4047e-04, 1.4071e-04, 1.4095e-04, 1.4118e-04, 1.4142e-04, 1.4166e-04, 1.4190e-04, 1.4214e-04, 1.4238e-04, 1.4261e-04, 1.4285e-04, 1.4309e-04, 1.4333e-04, 1.4357e-04, 1.4380e-04, 1.4404e-04, 1.4428e-04, 1.4452e-04, 1.4476e-04, 1.4500e-04, 1.4523e-04, 1.4547e-04, 1.4571e-04, 1.4595e-04, 1.4619e-04, 1.4642e-04, 1.4666e-04, 1.4690e-04, 1.4714e-04, 1.4738e-04, 1.4762e-04, 1.4785e-04, 1.4809e-04, 1.4833e-04, 1.4857e-04, 1.4881e-04, 1.4904e-04, 1.4928e-04, 1.4952e-04, 1.4976e-04, 1.5000e-04, 1.5024e-04, 1.5047e-04, 1.5071e-04, 1.5095e-04, 1.5119e-04, 1.5143e-04, 1.5166e-04, 1.5190e-04, 1.5214e-04, 1.5238e-04, 1.5262e-04, 1.5286e-04, 1.5309e-04, 1.5333e-04, 1.5357e-04, 1.5381e-04, 1.5405e-04, 1.5428e-04, 1.5452e-04, 1.5476e-04, 1.5500e-04, 1.5524e-04, 1.5548e-04, 1.5571e-04, 1.5595e-04, 1.5619e-04, 1.5643e-04, 1.5667e-04, 1.5690e-04, 1.5714e-04, 1.5738e-04, 1.5762e-04, 1.5786e-04, 1.5810e-04, 1.5833e-04, 1.5857e-04, 1.5881e-04, 1.5905e-04, 1.5929e-04, 1.5952e-04, 1.5976e-04, 1.6000e-04, 1.6024e-04, 1.6048e-04, 1.6072e-04, 1.6095e-04, 1.6119e-04, 1.6143e-04, 1.6167e-04, 1.6191e-04, 1.6214e-04, 1.6238e-04, 1.6262e-04, 1.6286e-04, 1.6310e-04, 1.6334e-04, 1.6357e-04, 1.6381e-04, 1.6405e-04, 1.6429e-04, 1.6453e-04, 1.6476e-04, 1.6500e-04, 1.6524e-04, 1.6548e-04, 1.6572e-04, 1.6596e-04, 1.6619e-04, 1.6643e-04, 1.6667e-04, 1.6691e-04, 1.6715e-04, 1.6738e-04, 1.6762e-04, 1.6786e-04, 1.6810e-04, 1.6834e-04, 1.6858e-04, 1.6881e-04, 1.6905e-04, 1.6929e-04, 1.6953e-04, 1.6977e-04, 1.7001e-04, 1.7024e-04, 1.7048e-04, 1.7072e-04, 1.7096e-04, 1.7120e-04, 1.7143e-04, 1.7167e-04, 1.7191e-04, 1.7215e-04, 1.7239e-04, 1.7263e-04, 1.7286e-04, 1.7310e-04, 1.7334e-04, 1.7358e-04, 1.7382e-04, 1.7405e-04, 1.7429e-04, 1.7453e-04, 1.7477e-04, 1.7501e-04, 1.7525e-04, 1.7548e-04, 1.7572e-04, 1.7596e-04, 1.7620e-04, 1.7644e-04, 1.7667e-04, 1.7691e-04, 1.7715e-04, 1.7739e-04, 1.7763e-04, 1.7787e-04, 1.7810e-04, 1.7834e-04, 1.7858e-04, 1.7882e-04, 1.7906e-04, 1.7929e-04, 1.7953e-04, 1.7977e-04, 1.8001e-04, 1.8025e-04, 1.8049e-04, 1.8072e-04, 1.8096e-04, 1.8120e-04, 1.8144e-04, 1.8168e-04, 1.8191e-04, 1.8215e-04, 1.8239e-04, 1.8263e-04, 1.8287e-04, - 1.8311e-04, 1.8334e-04, 1.8358e-04, 1.8382e-04, 1.8406e-04, 1.8430e-04, 1.8453e-04, 1.8477e-04, 1.8501e-04, 1.8525e-04, 1.8549e-04, 1.8573e-04, 1.8596e-04, 1.8620e-04, 1.8644e-04, 1.8668e-04, 1.8692e-04, 1.8715e-04, 1.8739e-04, 1.8763e-04, 1.8787e-04, 1.8811e-04, 1.8835e-04, 1.8858e-04, 1.8882e-04, 1.8906e-04, 1.8930e-04, 1.8954e-04, 1.8977e-04, 1.9001e-04, 1.9025e-04, 1.9049e-04, 1.9073e-04, 1.9097e-04, 1.9120e-04, 1.9144e-04, 1.9168e-04, 1.9192e-04, 1.9216e-04, 1.9239e-04, 1.9263e-04, 1.9287e-04, 1.9311e-04, 1.9335e-04, 1.9359e-04, 1.9382e-04, 1.9406e-04, 1.9430e-04, 1.9454e-04, 1.9478e-04, 1.9501e-04, 1.9525e-04, 1.9549e-04, 1.9573e-04, 1.9597e-04, 1.9621e-04, 1.9644e-04, 1.9668e-04, 1.9692e-04, 1.9716e-04, 1.9740e-04, 1.9763e-04, 1.9787e-04, 1.9811e-04, 1.9835e-04, 1.9859e-04, 1.9883e-04, 1.9906e-04, 1.9930e-04, 1.9954e-04, 1.9978e-04, 2.0002e-04, 2.0025e-04, 2.0049e-04, 2.0073e-04, 2.0097e-04, 2.0121e-04, 2.0145e-04, 2.0168e-04, 2.0192e-04, 2.0216e-04, 2.0240e-04, 2.0264e-04, 2.0287e-04, 2.0311e-04, 2.0335e-04, 2.0359e-04, 2.0383e-04, 2.0407e-04, 2.0430e-04, 2.0454e-04, 2.0478e-04, 2.0502e-04, 2.0526e-04, 2.0549e-04, 2.0573e-04, 2.0597e-04, 2.0621e-04, 2.0645e-04, 2.0669e-04, 2.0692e-04, 2.0716e-04, 2.0740e-04, 2.0764e-04, 2.0788e-04, 2.0811e-04, 2.0835e-04, 2.0859e-04, 2.0883e-04, 2.0907e-04, 2.0931e-04, 2.0954e-04, 2.0978e-04, 2.1002e-04, 2.1026e-04, 2.1050e-04, 2.1073e-04, 2.1097e-04, 2.1121e-04, 2.1145e-04, 2.1169e-04, 2.1193e-04, 2.1216e-04, 2.1240e-04, 2.1264e-04, 2.1288e-04, 2.1312e-04, 2.1335e-04, 2.1359e-04, 2.1383e-04, 2.1407e-04, 2.1431e-04, 2.1455e-04, 2.1478e-04, 2.1502e-04, 2.1526e-04, 2.1550e-04, 2.1574e-04, 2.1597e-04, 2.1621e-04, 2.1645e-04, 2.1669e-04, 2.1693e-04, 2.1717e-04, 2.1740e-04, 2.1764e-04, 2.1788e-04, 2.1812e-04, 2.1836e-04, 2.1859e-04, 2.1883e-04, 2.1907e-04, 2.1931e-04, 2.1955e-04, 2.1979e-04, 2.2002e-04, 2.2026e-04, 2.2050e-04, 2.2074e-04, 2.2098e-04, 2.2121e-04, 2.2145e-04, 2.2169e-04, 2.2193e-04, 2.2217e-04, 2.2241e-04, 2.2264e-04, 2.2288e-04, 2.2312e-04, 2.2336e-04, 2.2360e-04, 2.2383e-04, 2.2407e-04, 2.2431e-04, 2.2455e-04, 2.2479e-04, 2.2503e-04, 2.2526e-04, 2.2550e-04, 2.2574e-04, 2.2598e-04, 2.2622e-04, 2.2646e-04, 2.2669e-04, 2.2693e-04, 2.2717e-04, 2.2741e-04, 2.2765e-04, 2.2788e-04, 2.2812e-04, 2.2836e-04, 2.2860e-04, 2.2884e-04, 2.2908e-04, 2.2931e-04, 2.2955e-04, 2.2979e-04, 2.3003e-04, 2.3027e-04, 2.3050e-04, 2.3074e-04, 2.3098e-04, 2.3122e-04, 2.3146e-04, 2.3170e-04, 2.3193e-04, 2.3217e-04, 2.3241e-04, 2.3265e-04, 2.3289e-04, 2.3312e-04, 2.3336e-04, 2.3360e-04, 2.3384e-04, 2.3408e-04, 2.3432e-04, 2.3455e-04, 2.3479e-04, 2.3503e-04, 2.3527e-04, 2.3551e-04, 2.3574e-04, 2.3598e-04, 2.3622e-04, 2.3646e-04, 2.3670e-04, 2.3694e-04, 2.3717e-04, 2.3741e-04, 2.3765e-04, 2.3789e-04, 2.3813e-04, 2.3836e-04, 2.3860e-04, 2.3884e-04, 2.3908e-04, 2.3932e-04, 2.3956e-04, 2.3979e-04, 2.4003e-04, 2.4027e-04, 2.4051e-04, 2.4075e-04, 2.4098e-04, 2.4122e-04, 2.4146e-04, 2.4170e-04, 2.4194e-04, 2.4218e-04, 2.4241e-04, 2.4265e-04, 2.4289e-04, 2.4313e-04, 2.4337e-04, 2.4360e-04, 2.4384e-04, - 2.4480e-04, 2.4575e-04, 2.4670e-04, 2.4766e-04, 2.4861e-04, 2.4956e-04, 2.5052e-04, 2.5147e-04, 2.5242e-04, 2.5337e-04, 2.5433e-04, 2.5528e-04, 2.5623e-04, 2.5719e-04, 2.5814e-04, 2.5909e-04, 2.6005e-04, 2.6100e-04, 2.6195e-04, 2.6291e-04, 2.6386e-04, 2.6481e-04, 2.6577e-04, 2.6672e-04, 2.6767e-04, 2.6863e-04, 2.6958e-04, 2.7053e-04, 2.7149e-04, 2.7244e-04, 2.7339e-04, 2.7435e-04, 2.7530e-04, 2.7625e-04, 2.7720e-04, 2.7816e-04, 2.7911e-04, 2.8006e-04, 2.8102e-04, 2.8197e-04, 2.8292e-04, 2.8388e-04, 2.8483e-04, 2.8578e-04, 2.8674e-04, 2.8769e-04, 2.8864e-04, 2.8960e-04, 2.9055e-04, 2.9150e-04, 2.9246e-04, 2.9341e-04, 2.9436e-04, 2.9532e-04, 2.9627e-04, 2.9722e-04, 2.9818e-04, 2.9913e-04, 3.0008e-04, 3.0104e-04, 3.0199e-04, 3.0294e-04, 3.0389e-04, 3.0485e-04, 3.0580e-04, 3.0675e-04, 3.0771e-04, 3.0866e-04, 3.0961e-04, 3.1057e-04, 3.1152e-04, 3.1247e-04, 3.1343e-04, 3.1438e-04, 3.1533e-04, 3.1629e-04, 3.1724e-04, 3.1819e-04, 3.1915e-04, 3.2010e-04, 3.2105e-04, 3.2201e-04, 3.2296e-04, 3.2391e-04, 3.2487e-04, 3.2582e-04, 3.2677e-04, 3.2772e-04, 3.2868e-04, 3.2963e-04, 3.3058e-04, 3.3154e-04, 3.3249e-04, 3.3344e-04, 3.3440e-04, 3.3535e-04, 3.3630e-04, 3.3726e-04, 3.3821e-04, 3.3916e-04, 3.4012e-04, 3.4107e-04, 3.4202e-04, 3.4298e-04, 3.4393e-04, 3.4488e-04, 3.4584e-04, 3.4679e-04, 3.4774e-04, 3.4870e-04, 3.4965e-04, 3.5060e-04, 3.5156e-04, 3.5251e-04, 3.5346e-04, 3.5441e-04, 3.5537e-04, 3.5632e-04, 3.5727e-04, 3.5823e-04, 3.5918e-04, 3.6013e-04, 3.6109e-04, 3.6204e-04, 3.6299e-04, 3.6395e-04, 3.6490e-04, 3.6585e-04, 3.6681e-04, 3.6776e-04, 3.6871e-04, 3.6967e-04, 3.7062e-04, 3.7157e-04, 3.7253e-04, 3.7348e-04, 3.7443e-04, 3.7539e-04, 3.7634e-04, 3.7729e-04, 3.7825e-04, 3.7920e-04, 3.8015e-04, 3.8110e-04, 3.8206e-04, 3.8301e-04, 3.8396e-04, 3.8492e-04, 3.8587e-04, 3.8682e-04, 3.8778e-04, 3.8873e-04, 3.8968e-04, 3.9064e-04, 3.9159e-04, 3.9254e-04, 3.9350e-04, 3.9445e-04, 3.9540e-04, 3.9636e-04, 3.9731e-04, 3.9826e-04, 3.9922e-04, 4.0017e-04, 4.0112e-04, 4.0208e-04, 4.0303e-04, 4.0398e-04, 4.0493e-04, 4.0589e-04, 4.0684e-04, 4.0779e-04, 4.0875e-04, 4.0970e-04, 4.1065e-04, 4.1161e-04, 4.1256e-04, 4.1351e-04, 4.1447e-04, 4.1542e-04, 4.1637e-04, 4.1733e-04, 4.1828e-04, 4.1923e-04, 4.2019e-04, 4.2114e-04, 4.2209e-04, 4.2305e-04, 4.2400e-04, 4.2495e-04, 4.2591e-04, 4.2686e-04, 4.2781e-04, 4.2877e-04, 4.2972e-04, 4.3067e-04, 4.3162e-04, 4.3258e-04, 4.3353e-04, 4.3448e-04, 4.3544e-04, 4.3639e-04, 4.3734e-04, 4.3830e-04, 4.3925e-04, 4.4020e-04, 4.4116e-04, 4.4211e-04, 4.4306e-04, 4.4402e-04, 4.4497e-04, 4.4592e-04, 4.4688e-04, 4.4783e-04, 4.4878e-04, 4.4974e-04, 4.5069e-04, 4.5164e-04, 4.5260e-04, 4.5355e-04, 4.5450e-04, 4.5545e-04, 4.5641e-04, 4.5736e-04, 4.5831e-04, 4.5927e-04, 4.6022e-04, 4.6117e-04, 4.6213e-04, 4.6308e-04, 4.6403e-04, 4.6499e-04, 4.6594e-04, 4.6689e-04, 4.6785e-04, 4.6880e-04, 4.6975e-04, 4.7071e-04, 4.7166e-04, 4.7261e-04, 4.7357e-04, 4.7452e-04, 4.7547e-04, 4.7643e-04, 4.7738e-04, 4.7833e-04, 4.7929e-04, 4.8024e-04, 4.8119e-04, 4.8214e-04, 4.8310e-04, 4.8405e-04, 4.8500e-04, 4.8596e-04, 4.8691e-04, 4.8786e-04, - 4.8977e-04, 4.9168e-04, 4.9358e-04, 4.9549e-04, 4.9740e-04, 4.9931e-04, 5.0121e-04, 5.0312e-04, 5.0503e-04, 5.0693e-04, 5.0884e-04, 5.1075e-04, 5.1265e-04, 5.1456e-04, 5.1647e-04, 5.1837e-04, 5.2028e-04, 5.2219e-04, 5.2409e-04, 5.2600e-04, 5.2791e-04, 5.2982e-04, 5.3172e-04, 5.3363e-04, 5.3554e-04, 5.3744e-04, 5.3935e-04, 5.4126e-04, 5.4316e-04, 5.4507e-04, 5.4698e-04, 5.4888e-04, 5.5079e-04, 5.5270e-04, 5.5460e-04, 5.5651e-04, 5.5842e-04, 5.6033e-04, 5.6223e-04, 5.6414e-04, 5.6605e-04, 5.6795e-04, 5.6986e-04, 5.7177e-04, 5.7367e-04, 5.7558e-04, 5.7749e-04, 5.7939e-04, 5.8130e-04, 5.8321e-04, 5.8512e-04, 5.8702e-04, 5.8893e-04, 5.9084e-04, 5.9274e-04, 5.9465e-04, 5.9656e-04, 5.9846e-04, 6.0037e-04, 6.0228e-04, 6.0418e-04, 6.0609e-04, 6.0800e-04, 6.0990e-04, 6.1181e-04, 6.1372e-04, 6.1563e-04, 6.1753e-04, 6.1944e-04, 6.2135e-04, 6.2325e-04, 6.2516e-04, 6.2707e-04, 6.2897e-04, 6.3088e-04, 6.3279e-04, 6.3469e-04, 6.3660e-04, 6.3851e-04, 6.4041e-04, 6.4232e-04, 6.4423e-04, 6.4614e-04, 6.4804e-04, 6.4995e-04, 6.5186e-04, 6.5376e-04, 6.5567e-04, 6.5758e-04, 6.5948e-04, 6.6139e-04, 6.6330e-04, 6.6520e-04, 6.6711e-04, 6.6902e-04, 6.7092e-04, 6.7283e-04, 6.7474e-04, 6.7665e-04, 6.7855e-04, 6.8046e-04, 6.8237e-04, 6.8427e-04, 6.8618e-04, 6.8809e-04, 6.8999e-04, 6.9190e-04, 6.9381e-04, 6.9571e-04, 6.9762e-04, 6.9953e-04, 7.0143e-04, 7.0334e-04, 7.0525e-04, 7.0716e-04, 7.0906e-04, 7.1097e-04, 7.1288e-04, 7.1478e-04, 7.1669e-04, 7.1860e-04, 7.2050e-04, 7.2241e-04, 7.2432e-04, 7.2622e-04, 7.2813e-04, 7.3004e-04, 7.3195e-04, 7.3385e-04, 7.3576e-04, 7.3767e-04, 7.3957e-04, 7.4148e-04, 7.4339e-04, 7.4529e-04, 7.4720e-04, 7.4911e-04, 7.5101e-04, 7.5292e-04, 7.5483e-04, 7.5673e-04, 7.5864e-04, 7.6055e-04, 7.6246e-04, 7.6436e-04, 7.6627e-04, 7.6818e-04, 7.7008e-04, 7.7199e-04, 7.7390e-04, 7.7580e-04, 7.7771e-04, 7.7962e-04, 7.8152e-04, 7.8343e-04, 7.8534e-04, 7.8724e-04, 7.8915e-04, 7.9106e-04, 7.9297e-04, 7.9487e-04, 7.9678e-04, 7.9869e-04, 8.0059e-04, 8.0250e-04, 8.0441e-04, 8.0631e-04, 8.0822e-04, 8.1013e-04, 8.1203e-04, 8.1394e-04, 8.1585e-04, 8.1775e-04, 8.1966e-04, 8.2157e-04, 8.2348e-04, 8.2538e-04, 8.2729e-04, 8.2920e-04, 8.3110e-04, 8.3301e-04, 8.3492e-04, 8.3682e-04, 8.3873e-04, 8.4064e-04, 8.4254e-04, 8.4445e-04, 8.4636e-04, 8.4826e-04, 8.5017e-04, 8.5208e-04, 8.5399e-04, 8.5589e-04, 8.5780e-04, 8.5971e-04, 8.6161e-04, 8.6352e-04, 8.6543e-04, 8.6733e-04, 8.6924e-04, 8.7115e-04, 8.7305e-04, 8.7496e-04, 8.7687e-04, 8.7878e-04, 8.8068e-04, 8.8259e-04, 8.8450e-04, 8.8640e-04, 8.8831e-04, 8.9022e-04, 8.9212e-04, 8.9403e-04, 8.9594e-04, 8.9784e-04, 8.9975e-04, 9.0166e-04, 9.0356e-04, 9.0547e-04, 9.0738e-04, 9.0929e-04, 9.1119e-04, 9.1310e-04, 9.1501e-04, 9.1691e-04, 9.1882e-04, 9.2073e-04, 9.2263e-04, 9.2454e-04, 9.2645e-04, 9.2835e-04, 9.3026e-04, 9.3217e-04, 9.3407e-04, 9.3598e-04, 9.3789e-04, 9.3980e-04, 9.4170e-04, 9.4361e-04, 9.4552e-04, 9.4742e-04, 9.4933e-04, 9.5124e-04, 9.5314e-04, 9.5505e-04, 9.5696e-04, 9.5886e-04, 9.6077e-04, 9.6268e-04, 9.6458e-04, 9.6649e-04, 9.6840e-04, 9.7031e-04, 9.7221e-04, 9.7412e-04, 9.7603e-04, - 9.7984e-04, 9.8365e-04, 9.8747e-04, 9.9128e-04, 9.9510e-04, 9.9891e-04, 1.0027e-03, 1.0065e-03, 1.0104e-03, 1.0142e-03, 1.0180e-03, 1.0218e-03, 1.0256e-03, 1.0294e-03, 1.0332e-03, 1.0371e-03, 1.0409e-03, 1.0447e-03, 1.0485e-03, 1.0523e-03, 1.0561e-03, 1.0599e-03, 1.0638e-03, 1.0676e-03, 1.0714e-03, 1.0752e-03, 1.0790e-03, 1.0828e-03, 1.0866e-03, 1.0905e-03, 1.0943e-03, 1.0981e-03, 1.1019e-03, 1.1057e-03, 1.1095e-03, 1.1133e-03, 1.1172e-03, 1.1210e-03, 1.1248e-03, 1.1286e-03, 1.1324e-03, 1.1362e-03, 1.1400e-03, 1.1439e-03, 1.1477e-03, 1.1515e-03, 1.1553e-03, 1.1591e-03, 1.1629e-03, 1.1667e-03, 1.1706e-03, 1.1744e-03, 1.1782e-03, 1.1820e-03, 1.1858e-03, 1.1896e-03, 1.1934e-03, 1.1973e-03, 1.2011e-03, 1.2049e-03, 1.2087e-03, 1.2125e-03, 1.2163e-03, 1.2201e-03, 1.2240e-03, 1.2278e-03, 1.2316e-03, 1.2354e-03, 1.2392e-03, 1.2430e-03, 1.2468e-03, 1.2507e-03, 1.2545e-03, 1.2583e-03, 1.2621e-03, 1.2659e-03, 1.2697e-03, 1.2735e-03, 1.2774e-03, 1.2812e-03, 1.2850e-03, 1.2888e-03, 1.2926e-03, 1.2964e-03, 1.3002e-03, 1.3040e-03, 1.3079e-03, 1.3117e-03, 1.3155e-03, 1.3193e-03, 1.3231e-03, 1.3269e-03, 1.3307e-03, 1.3346e-03, 1.3384e-03, 1.3422e-03, 1.3460e-03, 1.3498e-03, 1.3536e-03, 1.3574e-03, 1.3613e-03, 1.3651e-03, 1.3689e-03, 1.3727e-03, 1.3765e-03, 1.3803e-03, 1.3841e-03, 1.3880e-03, 1.3918e-03, 1.3956e-03, 1.3994e-03, 1.4032e-03, 1.4070e-03, 1.4108e-03, 1.4147e-03, 1.4185e-03, 1.4223e-03, 1.4261e-03, 1.4299e-03, 1.4337e-03, 1.4375e-03, 1.4414e-03, 1.4452e-03, 1.4490e-03, 1.4528e-03, 1.4566e-03, 1.4604e-03, 1.4642e-03, 1.4681e-03, 1.4719e-03, 1.4757e-03, 1.4795e-03, 1.4833e-03, 1.4871e-03, 1.4909e-03, 1.4948e-03, 1.4986e-03, 1.5024e-03, 1.5062e-03, 1.5100e-03, 1.5138e-03, 1.5176e-03, 1.5215e-03, 1.5253e-03, 1.5291e-03, 1.5329e-03, 1.5367e-03, 1.5405e-03, 1.5443e-03, 1.5482e-03, 1.5520e-03, 1.5558e-03, 1.5596e-03, 1.5634e-03, 1.5672e-03, 1.5710e-03, 1.5749e-03, 1.5787e-03, 1.5825e-03, 1.5863e-03, 1.5901e-03, 1.5939e-03, 1.5977e-03, 1.6016e-03, 1.6054e-03, 1.6092e-03, 1.6130e-03, 1.6168e-03, 1.6206e-03, 1.6244e-03, 1.6283e-03, 1.6321e-03, 1.6359e-03, 1.6397e-03, 1.6435e-03, 1.6473e-03, 1.6511e-03, 1.6550e-03, 1.6588e-03, 1.6626e-03, 1.6664e-03, 1.6702e-03, 1.6740e-03, 1.6778e-03, 1.6817e-03, 1.6855e-03, 1.6893e-03, 1.6931e-03, 1.6969e-03, 1.7007e-03, 1.7045e-03, 1.7084e-03, 1.7122e-03, 1.7160e-03, 1.7198e-03, 1.7236e-03, 1.7274e-03, 1.7312e-03, 1.7351e-03, 1.7389e-03, 1.7427e-03, 1.7465e-03, 1.7503e-03, 1.7541e-03, 1.7579e-03, 1.7618e-03, 1.7656e-03, 1.7694e-03, 1.7732e-03, 1.7770e-03, 1.7808e-03, 1.7846e-03, 1.7885e-03, 1.7923e-03, 1.7961e-03, 1.7999e-03, 1.8037e-03, 1.8075e-03, 1.8113e-03, 1.8152e-03, 1.8190e-03, 1.8228e-03, 1.8266e-03, 1.8304e-03, 1.8342e-03, 1.8380e-03, 1.8419e-03, 1.8457e-03, 1.8495e-03, 1.8533e-03, 1.8571e-03, 1.8609e-03, 1.8647e-03, 1.8686e-03, 1.8724e-03, 1.8762e-03, 1.8800e-03, 1.8838e-03, 1.8876e-03, 1.8914e-03, 1.8953e-03, 1.8991e-03, 1.9029e-03, 1.9067e-03, 1.9105e-03, 1.9143e-03, 1.9181e-03, 1.9220e-03, 1.9258e-03, 1.9296e-03, 1.9334e-03, 1.9372e-03, 1.9410e-03, 1.9448e-03, 1.9487e-03, 1.9525e-03, - 1.9601e-03, 1.9677e-03, 1.9754e-03, 1.9830e-03, 1.9906e-03, 1.9982e-03, 2.0059e-03, 2.0135e-03, 2.0211e-03, 2.0288e-03, 2.0364e-03, 2.0440e-03, 2.0516e-03, 2.0593e-03, 2.0669e-03, 2.0745e-03, 2.0822e-03, 2.0898e-03, 2.0974e-03, 2.1050e-03, 2.1127e-03, 2.1203e-03, 2.1279e-03, 2.1356e-03, 2.1432e-03, 2.1508e-03, 2.1585e-03, 2.1661e-03, 2.1737e-03, 2.1813e-03, 2.1890e-03, 2.1966e-03, 2.2042e-03, 2.2119e-03, 2.2195e-03, 2.2271e-03, 2.2347e-03, 2.2424e-03, 2.2500e-03, 2.2576e-03, 2.2653e-03, 2.2729e-03, 2.2805e-03, 2.2881e-03, 2.2958e-03, 2.3034e-03, 2.3110e-03, 2.3187e-03, 2.3263e-03, 2.3339e-03, 2.3415e-03, 2.3492e-03, 2.3568e-03, 2.3644e-03, 2.3721e-03, 2.3797e-03, 2.3873e-03, 2.3949e-03, 2.4026e-03, 2.4102e-03, 2.4178e-03, 2.4255e-03, 2.4331e-03, 2.4407e-03, 2.4483e-03, 2.4560e-03, 2.4636e-03, 2.4712e-03, 2.4789e-03, 2.4865e-03, 2.4941e-03, 2.5018e-03, 2.5094e-03, 2.5170e-03, 2.5246e-03, 2.5323e-03, 2.5399e-03, 2.5475e-03, 2.5552e-03, 2.5628e-03, 2.5704e-03, 2.5780e-03, 2.5857e-03, 2.5933e-03, 2.6009e-03, 2.6086e-03, 2.6162e-03, 2.6238e-03, 2.6314e-03, 2.6391e-03, 2.6467e-03, 2.6543e-03, 2.6620e-03, 2.6696e-03, 2.6772e-03, 2.6848e-03, 2.6925e-03, 2.7001e-03, 2.7077e-03, 2.7154e-03, 2.7230e-03, 2.7306e-03, 2.7382e-03, 2.7459e-03, 2.7535e-03, 2.7611e-03, 2.7688e-03, 2.7764e-03, 2.7840e-03, 2.7917e-03, 2.7993e-03, 2.8069e-03, 2.8145e-03, 2.8222e-03, 2.8298e-03, 2.8374e-03, 2.8451e-03, 2.8527e-03, 2.8603e-03, 2.8679e-03, 2.8756e-03, 2.8832e-03, 2.8908e-03, 2.8985e-03, 2.9061e-03, 2.9137e-03, 2.9213e-03, 2.9290e-03, 2.9366e-03, 2.9442e-03, 2.9519e-03, 2.9595e-03, 2.9671e-03, 2.9747e-03, 2.9824e-03, 2.9900e-03, 2.9976e-03, 3.0053e-03, 3.0129e-03, 3.0205e-03, 3.0281e-03, 3.0358e-03, 3.0434e-03, 3.0510e-03, 3.0587e-03, 3.0663e-03, 3.0739e-03, 3.0816e-03, 3.0892e-03, 3.0968e-03, 3.1044e-03, 3.1121e-03, 3.1197e-03, 3.1273e-03, 3.1350e-03, 3.1426e-03, 3.1502e-03, 3.1578e-03, 3.1655e-03, 3.1731e-03, 3.1807e-03, 3.1884e-03, 3.1960e-03, 3.2036e-03, 3.2112e-03, 3.2189e-03, 3.2265e-03, 3.2341e-03, 3.2418e-03, 3.2494e-03, 3.2570e-03, 3.2646e-03, 3.2723e-03, 3.2799e-03, 3.2875e-03, 3.2952e-03, 3.3028e-03, 3.3104e-03, 3.3180e-03, 3.3257e-03, 3.3333e-03, 3.3409e-03, 3.3486e-03, 3.3562e-03, 3.3638e-03, 3.3715e-03, 3.3791e-03, 3.3867e-03, 3.3943e-03, 3.4020e-03, 3.4096e-03, 3.4172e-03, 3.4249e-03, 3.4325e-03, 3.4401e-03, 3.4477e-03, 3.4554e-03, 3.4630e-03, 3.4706e-03, 3.4783e-03, 3.4859e-03, 3.4935e-03, 3.5011e-03, 3.5088e-03, 3.5164e-03, 3.5240e-03, 3.5317e-03, 3.5393e-03, 3.5469e-03, 3.5545e-03, 3.5622e-03, 3.5698e-03, 3.5774e-03, 3.5851e-03, 3.5927e-03, 3.6003e-03, 3.6079e-03, 3.6156e-03, 3.6232e-03, 3.6308e-03, 3.6385e-03, 3.6461e-03, 3.6537e-03, 3.6613e-03, 3.6690e-03, 3.6766e-03, 3.6842e-03, 3.6919e-03, 3.6995e-03, 3.7071e-03, 3.7148e-03, 3.7224e-03, 3.7300e-03, 3.7376e-03, 3.7453e-03, 3.7529e-03, 3.7605e-03, 3.7682e-03, 3.7758e-03, 3.7834e-03, 3.7910e-03, 3.7987e-03, 3.8063e-03, 3.8139e-03, 3.8216e-03, 3.8292e-03, 3.8368e-03, 3.8444e-03, 3.8521e-03, 3.8597e-03, 3.8673e-03, 3.8750e-03, 3.8826e-03, 3.8902e-03, 3.8978e-03, 3.9055e-03, - 3.9207e-03, 3.9360e-03, 3.9513e-03, 3.9665e-03, 3.9818e-03, 3.9970e-03, 4.0123e-03, 4.0275e-03, 4.0428e-03, 4.0581e-03, 4.0733e-03, 4.0886e-03, 4.1038e-03, 4.1191e-03, 4.1343e-03, 4.1496e-03, 4.1649e-03, 4.1801e-03, 4.1954e-03, 4.2106e-03, 4.2259e-03, 4.2412e-03, 4.2564e-03, 4.2717e-03, 4.2869e-03, 4.3022e-03, 4.3174e-03, 4.3327e-03, 4.3480e-03, 4.3632e-03, 4.3785e-03, 4.3937e-03, 4.4090e-03, 4.4243e-03, 4.4395e-03, 4.4548e-03, 4.4700e-03, 4.4853e-03, 4.5005e-03, 4.5158e-03, 4.5311e-03, 4.5463e-03, 4.5616e-03, 4.5768e-03, 4.5921e-03, 4.6074e-03, 4.6226e-03, 4.6379e-03, 4.6531e-03, 4.6684e-03, 4.6836e-03, 4.6989e-03, 4.7142e-03, 4.7294e-03, 4.7447e-03, 4.7599e-03, 4.7752e-03, 4.7905e-03, 4.8057e-03, 4.8210e-03, 4.8362e-03, 4.8515e-03, 4.8667e-03, 4.8820e-03, 4.8973e-03, 4.9125e-03, 4.9278e-03, 4.9430e-03, 4.9583e-03, 4.9736e-03, 4.9888e-03, 5.0041e-03, 5.0193e-03, 5.0346e-03, 5.0498e-03, 5.0651e-03, 5.0804e-03, 5.0956e-03, 5.1109e-03, 5.1261e-03, 5.1414e-03, 5.1567e-03, 5.1719e-03, 5.1872e-03, 5.2024e-03, 5.2177e-03, 5.2329e-03, 5.2482e-03, 5.2635e-03, 5.2787e-03, 5.2940e-03, 5.3092e-03, 5.3245e-03, 5.3398e-03, 5.3550e-03, 5.3703e-03, 5.3855e-03, 5.4008e-03, 5.4160e-03, 5.4313e-03, 5.4466e-03, 5.4618e-03, 5.4771e-03, 5.4923e-03, 5.5076e-03, 5.5229e-03, 5.5381e-03, 5.5534e-03, 5.5686e-03, 5.5839e-03, 5.5991e-03, 5.6144e-03, 5.6297e-03, 5.6449e-03, 5.6602e-03, 5.6754e-03, 5.6907e-03, 5.7060e-03, 5.7212e-03, 5.7365e-03, 5.7517e-03, 5.7670e-03, 5.7822e-03, 5.7975e-03, 5.8128e-03, 5.8280e-03, 5.8433e-03, 5.8585e-03, 5.8738e-03, 5.8891e-03, 5.9043e-03, 5.9196e-03, 5.9348e-03, 5.9501e-03, 5.9653e-03, 5.9806e-03, 5.9959e-03, 6.0111e-03, 6.0264e-03, 6.0416e-03, 6.0569e-03, 6.0722e-03, 6.0874e-03, 6.1027e-03, 6.1179e-03, 6.1332e-03, 6.1484e-03, 6.1637e-03, 6.1790e-03, 6.1942e-03, 6.2095e-03, 6.2247e-03, 6.2400e-03, 6.2553e-03, 6.2705e-03, 6.2858e-03, 6.3010e-03, 6.3163e-03, 6.3315e-03, 6.3468e-03, 6.3621e-03, 6.3773e-03, 6.3926e-03, 6.4078e-03, 6.4231e-03, 6.4384e-03, 6.4536e-03, 6.4689e-03, 6.4841e-03, 6.4994e-03, 6.5146e-03, 6.5299e-03, 6.5452e-03, 6.5604e-03, 6.5757e-03, 6.5909e-03, 6.6062e-03, 6.6215e-03, 6.6367e-03, 6.6520e-03, 6.6672e-03, 6.6825e-03, 6.6977e-03, 6.7130e-03, 6.7283e-03, 6.7435e-03, 6.7588e-03, 6.7740e-03, 6.7893e-03, 6.8046e-03, 6.8198e-03, 6.8351e-03, 6.8503e-03, 6.8656e-03, 6.8808e-03, 6.8961e-03, 6.9114e-03, 6.9266e-03, 6.9419e-03, 6.9571e-03, 6.9724e-03, 6.9877e-03, 7.0029e-03, 7.0182e-03, 7.0334e-03, 7.0487e-03, 7.0639e-03, 7.0792e-03, 7.0945e-03, 7.1097e-03, 7.1250e-03, 7.1402e-03, 7.1555e-03, 7.1708e-03, 7.1860e-03, 7.2013e-03, 7.2165e-03, 7.2318e-03, 7.2470e-03, 7.2623e-03, 7.2776e-03, 7.2928e-03, 7.3081e-03, 7.3233e-03, 7.3386e-03, 7.3539e-03, 7.3691e-03, 7.3844e-03, 7.3996e-03, 7.4149e-03, 7.4301e-03, 7.4454e-03, 7.4607e-03, 7.4759e-03, 7.4912e-03, 7.5064e-03, 7.5217e-03, 7.5370e-03, 7.5522e-03, 7.5675e-03, 7.5827e-03, 7.5980e-03, 7.6132e-03, 7.6285e-03, 7.6438e-03, 7.6590e-03, 7.6743e-03, 7.6895e-03, 7.7048e-03, 7.7201e-03, 7.7353e-03, 7.7506e-03, 7.7658e-03, 7.7811e-03, 7.7963e-03, 7.8116e-03, - 7.8421e-03, 7.8726e-03, 7.9032e-03, 7.9337e-03, 7.9642e-03, 7.9947e-03, 8.0252e-03, 8.0557e-03, 8.0863e-03, 8.1168e-03, 8.1473e-03, 8.1778e-03, 8.2083e-03, 8.2388e-03, 8.2694e-03, 8.2999e-03, 8.3304e-03, 8.3609e-03, 8.3914e-03, 8.4219e-03, 8.4525e-03, 8.4830e-03, 8.5135e-03, 8.5440e-03, 8.5745e-03, 8.6051e-03, 8.6356e-03, 8.6661e-03, 8.6966e-03, 8.7271e-03, 8.7576e-03, 8.7882e-03, 8.8187e-03, 8.8492e-03, 8.8797e-03, 8.9102e-03, 8.9407e-03, 8.9713e-03, 9.0018e-03, 9.0323e-03, 9.0628e-03, 9.0933e-03, 9.1238e-03, 9.1544e-03, 9.1849e-03, 9.2154e-03, 9.2459e-03, 9.2764e-03, 9.3069e-03, 9.3375e-03, 9.3680e-03, 9.3985e-03, 9.4290e-03, 9.4595e-03, 9.4900e-03, 9.5206e-03, 9.5511e-03, 9.5816e-03, 9.6121e-03, 9.6426e-03, 9.6731e-03, 9.7037e-03, 9.7342e-03, 9.7647e-03, 9.7952e-03, 9.8257e-03, 9.8563e-03, 9.8868e-03, 9.9173e-03, 9.9478e-03, 9.9783e-03, 1.0009e-02, 1.0039e-02, 1.0070e-02, 1.0100e-02, 1.0131e-02, 1.0161e-02, 1.0192e-02, 1.0222e-02, 1.0253e-02, 1.0283e-02, 1.0314e-02, 1.0345e-02, 1.0375e-02, 1.0406e-02, 1.0436e-02, 1.0467e-02, 1.0497e-02, 1.0528e-02, 1.0558e-02, 1.0589e-02, 1.0619e-02, 1.0650e-02, 1.0680e-02, 1.0711e-02, 1.0741e-02, 1.0772e-02, 1.0802e-02, 1.0833e-02, 1.0863e-02, 1.0894e-02, 1.0924e-02, 1.0955e-02, 1.0985e-02, 1.1016e-02, 1.1046e-02, 1.1077e-02, 1.1107e-02, 1.1138e-02, 1.1168e-02, 1.1199e-02, 1.1230e-02, 1.1260e-02, 1.1291e-02, 1.1321e-02, 1.1352e-02, 1.1382e-02, 1.1413e-02, 1.1443e-02, 1.1474e-02, 1.1504e-02, 1.1535e-02, 1.1565e-02, 1.1596e-02, 1.1626e-02, 1.1657e-02, 1.1687e-02, 1.1718e-02, 1.1748e-02, 1.1779e-02, 1.1809e-02, 1.1840e-02, 1.1870e-02, 1.1901e-02, 1.1931e-02, 1.1962e-02, 1.1992e-02, 1.2023e-02, 1.2053e-02, 1.2084e-02, 1.2115e-02, 1.2145e-02, 1.2176e-02, 1.2206e-02, 1.2237e-02, 1.2267e-02, 1.2298e-02, 1.2328e-02, 1.2359e-02, 1.2389e-02, 1.2420e-02, 1.2450e-02, 1.2481e-02, 1.2511e-02, 1.2542e-02, 1.2572e-02, 1.2603e-02, 1.2633e-02, 1.2664e-02, 1.2694e-02, 1.2725e-02, 1.2755e-02, 1.2786e-02, 1.2816e-02, 1.2847e-02, 1.2877e-02, 1.2908e-02, 1.2938e-02, 1.2969e-02, 1.3000e-02, 1.3030e-02, 1.3061e-02, 1.3091e-02, 1.3122e-02, 1.3152e-02, 1.3183e-02, 1.3213e-02, 1.3244e-02, 1.3274e-02, 1.3305e-02, 1.3335e-02, 1.3366e-02, 1.3396e-02, 1.3427e-02, 1.3457e-02, 1.3488e-02, 1.3518e-02, 1.3549e-02, 1.3579e-02, 1.3610e-02, 1.3640e-02, 1.3671e-02, 1.3701e-02, 1.3732e-02, 1.3762e-02, 1.3793e-02, 1.3823e-02, 1.3854e-02, 1.3885e-02, 1.3915e-02, 1.3946e-02, 1.3976e-02, 1.4007e-02, 1.4037e-02, 1.4068e-02, 1.4098e-02, 1.4129e-02, 1.4159e-02, 1.4190e-02, 1.4220e-02, 1.4251e-02, 1.4281e-02, 1.4312e-02, 1.4342e-02, 1.4373e-02, 1.4403e-02, 1.4434e-02, 1.4464e-02, 1.4495e-02, 1.4525e-02, 1.4556e-02, 1.4586e-02, 1.4617e-02, 1.4647e-02, 1.4678e-02, 1.4708e-02, 1.4739e-02, 1.4770e-02, 1.4800e-02, 1.4831e-02, 1.4861e-02, 1.4892e-02, 1.4922e-02, 1.4953e-02, 1.4983e-02, 1.5014e-02, 1.5044e-02, 1.5075e-02, 1.5105e-02, 1.5136e-02, 1.5166e-02, 1.5197e-02, 1.5227e-02, 1.5258e-02, 1.5288e-02, 1.5319e-02, 1.5349e-02, 1.5380e-02, 1.5410e-02, 1.5441e-02, 1.5471e-02, 1.5502e-02, 1.5532e-02, 1.5563e-02, 1.5593e-02, 1.5624e-02, - 1.5746e-02, 1.5868e-02, 1.5990e-02, 1.6112e-02, 1.6234e-02, 1.6356e-02, 1.6478e-02, 1.6601e-02, 1.6723e-02, 1.6845e-02, 1.6967e-02, 1.7089e-02, 1.7211e-02, 1.7333e-02, 1.7455e-02, 1.7577e-02, 1.7699e-02, 1.7821e-02, 1.7943e-02, 1.8065e-02, 1.8187e-02, 1.8310e-02, 1.8432e-02, 1.8554e-02, 1.8676e-02, 1.8798e-02, 1.8920e-02, 1.9042e-02, 1.9164e-02, 1.9286e-02, 1.9408e-02, 1.9530e-02, 1.9652e-02, 1.9774e-02, 1.9896e-02, 2.0018e-02, 2.0141e-02, 2.0263e-02, 2.0385e-02, 2.0507e-02, 2.0629e-02, 2.0751e-02, 2.0873e-02, 2.0995e-02, 2.1117e-02, 2.1239e-02, 2.1361e-02, 2.1483e-02, 2.1605e-02, 2.1727e-02, 2.1850e-02, 2.1972e-02, 2.2094e-02, 2.2216e-02, 2.2338e-02, 2.2460e-02, 2.2582e-02, 2.2704e-02, 2.2826e-02, 2.2948e-02, 2.3070e-02, 2.3192e-02, 2.3314e-02, 2.3436e-02, 2.3558e-02, 2.3681e-02, 2.3803e-02, 2.3925e-02, 2.4047e-02, 2.4169e-02, 2.4291e-02, 2.4413e-02, 2.4535e-02, 2.4657e-02, 2.4779e-02, 2.4901e-02, 2.5023e-02, 2.5145e-02, 2.5267e-02, 2.5390e-02, 2.5512e-02, 2.5634e-02, 2.5756e-02, 2.5878e-02, 2.6000e-02, 2.6122e-02, 2.6244e-02, 2.6366e-02, 2.6488e-02, 2.6610e-02, 2.6732e-02, 2.6854e-02, 2.6976e-02, 2.7099e-02, 2.7221e-02, 2.7343e-02, 2.7465e-02, 2.7587e-02, 2.7709e-02, 2.7831e-02, 2.7953e-02, 2.8075e-02, 2.8197e-02, 2.8319e-02, 2.8441e-02, 2.8563e-02, 2.8685e-02, 2.8807e-02, 2.8930e-02, 2.9052e-02, 2.9174e-02, 2.9296e-02, 2.9418e-02, 2.9540e-02, 2.9662e-02, 2.9784e-02, 2.9906e-02, 3.0028e-02, 3.0150e-02, 3.0272e-02, 3.0394e-02, 3.0516e-02, 3.0639e-02, 3.0761e-02, 3.0883e-02, 3.1005e-02, 3.1127e-02, 3.1249e-02, 3.1493e-02, 3.1737e-02, 3.1981e-02, 3.2225e-02, 3.2470e-02, 3.2714e-02, 3.2958e-02, 3.3202e-02, 3.3446e-02, 3.3690e-02, 3.3934e-02, 3.4179e-02, 3.4423e-02, 3.4667e-02, 3.4911e-02, 3.5155e-02, 3.5399e-02, 3.5643e-02, 3.5888e-02, 3.6132e-02, 3.6376e-02, 3.6620e-02, 3.6864e-02, 3.7108e-02, 3.7352e-02, 3.7596e-02, 3.7841e-02, 3.8085e-02, 3.8329e-02, 3.8573e-02, 3.8817e-02, 3.9061e-02, 3.9305e-02, 3.9550e-02, 3.9794e-02, 4.0038e-02, 4.0282e-02, 4.0526e-02, 4.0770e-02, 4.1014e-02, 4.1259e-02, 4.1503e-02, 4.1747e-02, 4.1991e-02, 4.2235e-02, 4.2479e-02, 4.2723e-02, 4.2968e-02, 4.3212e-02, 4.3456e-02, 4.3700e-02, 4.3944e-02, 4.4188e-02, 4.4432e-02, 4.4677e-02, 4.4921e-02, 4.5165e-02, 4.5409e-02, 4.5653e-02, 4.5897e-02, 4.6141e-02, 4.6386e-02, 4.6630e-02, 4.6874e-02, 4.7118e-02, 4.7362e-02, 4.7606e-02, 4.7850e-02, 4.8095e-02, 4.8339e-02, 4.8583e-02, 4.8827e-02, 4.9071e-02, 4.9315e-02, 4.9559e-02, 4.9803e-02, 5.0048e-02, 5.0292e-02, 5.0536e-02, 5.0780e-02, 5.1024e-02, 5.1268e-02, 5.1512e-02, 5.1757e-02, 5.2001e-02, 5.2245e-02, 5.2489e-02, 5.2733e-02, 5.2977e-02, 5.3221e-02, 5.3466e-02, 5.3710e-02, 5.3954e-02, 5.4198e-02, 5.4442e-02, 5.4686e-02, 5.4930e-02, 5.5175e-02, 5.5419e-02, 5.5663e-02, 5.5907e-02, 5.6151e-02, 5.6395e-02, 5.6639e-02, 5.6884e-02, 5.7128e-02, 5.7372e-02, 5.7616e-02, 5.7860e-02, 5.8104e-02, 5.8348e-02, 5.8593e-02, 5.8837e-02, 5.9081e-02, 5.9325e-02, 5.9569e-02, 5.9813e-02, 6.0057e-02, 6.0301e-02, 6.0546e-02, 6.0790e-02, 6.1034e-02, 6.1278e-02, 6.1522e-02, 6.1766e-02, 6.2010e-02, 6.2255e-02, 6.2499e-02, - 6.2743e-02, 6.2987e-02, 6.3231e-02, 6.3475e-02, 6.3719e-02, 6.3964e-02, 6.4208e-02, 6.4452e-02, 6.4696e-02, 6.4940e-02, 6.5184e-02, 6.5428e-02, 6.5673e-02, 6.5917e-02, 6.6161e-02, 6.6405e-02, 6.6649e-02, 6.6893e-02, 6.7137e-02, 6.7382e-02, 6.7626e-02, 6.7870e-02, 6.8114e-02, 6.8358e-02, 6.8602e-02, 6.8846e-02, 6.9091e-02, 6.9335e-02, 6.9579e-02, 6.9823e-02, 7.0067e-02, 7.0311e-02, 7.0555e-02, 7.0799e-02, 7.1044e-02, 7.1288e-02, 7.1532e-02, 7.1776e-02, 7.2020e-02, 7.2264e-02, 7.2508e-02, 7.2753e-02, 7.2997e-02, 7.3241e-02, 7.3485e-02, 7.3729e-02, 7.3973e-02, 7.4217e-02, 7.4462e-02, 7.4706e-02, 7.4950e-02, 7.5194e-02, 7.5438e-02, 7.5682e-02, 7.5926e-02, 7.6171e-02, 7.6415e-02, 7.6659e-02, 7.6903e-02, 7.7147e-02, 7.7391e-02, 7.7635e-02, 7.7880e-02, 7.8124e-02, 7.8368e-02, 7.8612e-02, 7.8856e-02, 7.9100e-02, 7.9344e-02, 7.9589e-02, 7.9833e-02, 8.0077e-02, 8.0321e-02, 8.0565e-02, 8.0809e-02, 8.1053e-02, 8.1298e-02, 8.1542e-02, 8.1786e-02, 8.2030e-02, 8.2274e-02, 8.2518e-02, 8.2762e-02, 8.3006e-02, 8.3251e-02, 8.3495e-02, 8.3739e-02, 8.3983e-02, 8.4227e-02, 8.4471e-02, 8.4715e-02, 8.4960e-02, 8.5204e-02, 8.5448e-02, 8.5692e-02, 8.5936e-02, 8.6180e-02, 8.6424e-02, 8.6669e-02, 8.6913e-02, 8.7157e-02, 8.7401e-02, 8.7645e-02, 8.7889e-02, 8.8133e-02, 8.8378e-02, 8.8622e-02, 8.8866e-02, 8.9110e-02, 8.9354e-02, 8.9598e-02, 8.9842e-02, 9.0087e-02, 9.0331e-02, 9.0575e-02, 9.0819e-02, 9.1063e-02, 9.1307e-02, 9.1551e-02, 9.1796e-02, 9.2040e-02, 9.2284e-02, 9.2528e-02, 9.2772e-02, 9.3016e-02, 9.3260e-02, 9.3504e-02, 9.3749e-02, 9.4237e-02, 9.4725e-02, 9.5213e-02, 9.5702e-02, 9.6190e-02, 9.6678e-02, 9.7167e-02, 9.7655e-02, 9.8143e-02, 9.8631e-02, 9.9120e-02, 9.9608e-02, 1.0010e-01, 1.0058e-01, 1.0107e-01, 1.0156e-01, 1.0205e-01, 1.0254e-01, 1.0303e-01, 1.0351e-01, 1.0400e-01, 1.0449e-01, 1.0498e-01, 1.0547e-01, 1.0596e-01, 1.0644e-01, 1.0693e-01, 1.0742e-01, 1.0791e-01, 1.0840e-01, 1.0889e-01, 1.0937e-01, 1.0986e-01, 1.1035e-01, 1.1084e-01, 1.1133e-01, 1.1182e-01, 1.1230e-01, 1.1279e-01, 1.1328e-01, 1.1377e-01, 1.1426e-01, 1.1474e-01, 1.1523e-01, 1.1572e-01, 1.1621e-01, 1.1670e-01, 1.1719e-01, 1.1767e-01, 1.1816e-01, 1.1865e-01, 1.1914e-01, 1.1963e-01, 1.2012e-01, 1.2060e-01, 1.2109e-01, 1.2158e-01, 1.2207e-01, 1.2256e-01, 1.2305e-01, 1.2353e-01, 1.2402e-01, 1.2451e-01, 1.2500e-01, 1.2549e-01, 1.2598e-01, 1.2646e-01, 1.2695e-01, 1.2744e-01, 1.2793e-01, 1.2842e-01, 1.2890e-01, 1.2939e-01, 1.2988e-01, 1.3037e-01, 1.3086e-01, 1.3135e-01, 1.3183e-01, 1.3232e-01, 1.3281e-01, 1.3330e-01, 1.3379e-01, 1.3428e-01, 1.3476e-01, 1.3525e-01, 1.3574e-01, 1.3623e-01, 1.3672e-01, 1.3721e-01, 1.3769e-01, 1.3818e-01, 1.3867e-01, 1.3916e-01, 1.3965e-01, 1.4014e-01, 1.4062e-01, 1.4111e-01, 1.4160e-01, 1.4209e-01, 1.4258e-01, 1.4306e-01, 1.4355e-01, 1.4404e-01, 1.4453e-01, 1.4502e-01, 1.4551e-01, 1.4599e-01, 1.4648e-01, 1.4697e-01, 1.4746e-01, 1.4795e-01, 1.4844e-01, 1.4892e-01, 1.4941e-01, 1.4990e-01, 1.5039e-01, 1.5088e-01, 1.5137e-01, 1.5185e-01, 1.5234e-01, 1.5283e-01, 1.5332e-01, 1.5381e-01, 1.5430e-01, 1.5478e-01, 1.5527e-01, 1.5576e-01, 1.5625e-01, - 1.5674e-01, 1.5723e-01, 1.5771e-01, 1.5820e-01, 1.5869e-01, 1.5918e-01, 1.5967e-01, 1.6015e-01, 1.6064e-01, 1.6113e-01, 1.6162e-01, 1.6211e-01, 1.6260e-01, 1.6308e-01, 1.6357e-01, 1.6406e-01, 1.6455e-01, 1.6504e-01, 1.6553e-01, 1.6601e-01, 1.6650e-01, 1.6699e-01, 1.6748e-01, 1.6797e-01, 1.6846e-01, 1.6894e-01, 1.6943e-01, 1.6992e-01, 1.7041e-01, 1.7090e-01, 1.7139e-01, 1.7187e-01, 1.7236e-01, 1.7285e-01, 1.7334e-01, 1.7383e-01, 1.7431e-01, 1.7480e-01, 1.7529e-01, 1.7578e-01, 1.7627e-01, 1.7676e-01, 1.7724e-01, 1.7773e-01, 1.7822e-01, 1.7871e-01, 1.7920e-01, 1.7969e-01, 1.8017e-01, 1.8066e-01, 1.8115e-01, 1.8164e-01, 1.8213e-01, 1.8262e-01, 1.8310e-01, 1.8359e-01, 1.8408e-01, 1.8457e-01, 1.8506e-01, 1.8555e-01, 1.8603e-01, 1.8652e-01, 1.8701e-01, 1.8750e-01, 1.8848e-01, 1.8945e-01, 1.9043e-01, 1.9140e-01, 1.9238e-01, 1.9336e-01, 1.9433e-01, 1.9531e-01, 1.9629e-01, 1.9726e-01, 1.9824e-01, 1.9922e-01, 2.0019e-01, 2.0117e-01, 2.0215e-01, 2.0312e-01, 2.0410e-01, 2.0508e-01, 2.0605e-01, 2.0703e-01, 2.0801e-01, 2.0898e-01, 2.0996e-01, 2.1094e-01, 2.1191e-01, 2.1289e-01, 2.1387e-01, 2.1484e-01, 2.1582e-01, 2.1680e-01, 2.1777e-01, 2.1875e-01, 2.1972e-01, 2.2070e-01, 2.2168e-01, 2.2265e-01, 2.2363e-01, 2.2461e-01, 2.2558e-01, 2.2656e-01, 2.2754e-01, 2.2851e-01, 2.2949e-01, 2.3047e-01, 2.3144e-01, 2.3242e-01, 2.3340e-01, 2.3437e-01, 2.3535e-01, 2.3633e-01, 2.3730e-01, 2.3828e-01, 2.3926e-01, 2.4023e-01, 2.4121e-01, 2.4219e-01, 2.4316e-01, 2.4414e-01, 2.4512e-01, 2.4609e-01, 2.4707e-01, 2.4805e-01, 2.4902e-01, 2.5000e-01, 2.5097e-01, 2.5195e-01, 2.5293e-01, 2.5390e-01, 2.5488e-01, 2.5586e-01, 2.5683e-01, 2.5781e-01, 2.5879e-01, 2.5976e-01, 2.6074e-01, 2.6172e-01, 2.6269e-01, 2.6367e-01, 2.6465e-01, 2.6562e-01, 2.6660e-01, 2.6758e-01, 2.6855e-01, 2.6953e-01, 2.7051e-01, 2.7148e-01, 2.7246e-01, 2.7344e-01, 2.7441e-01, 2.7539e-01, 2.7637e-01, 2.7734e-01, 2.7832e-01, 2.7930e-01, 2.8027e-01, 2.8125e-01, 2.8222e-01, 2.8320e-01, 2.8418e-01, 2.8515e-01, 2.8613e-01, 2.8711e-01, 2.8808e-01, 2.8906e-01, 2.9004e-01, 2.9101e-01, 2.9199e-01, 2.9297e-01, 2.9394e-01, 2.9492e-01, 2.9590e-01, 2.9687e-01, 2.9785e-01, 2.9883e-01, 2.9980e-01, 3.0078e-01, 3.0176e-01, 3.0273e-01, 3.0371e-01, 3.0469e-01, 3.0566e-01, 3.0664e-01, 3.0762e-01, 3.0859e-01, 3.0957e-01, 3.1055e-01, 3.1152e-01, 3.1250e-01, 3.1347e-01, 3.1445e-01, 3.1543e-01, 3.1640e-01, 3.1738e-01, 3.1836e-01, 3.1933e-01, 3.2031e-01, 3.2129e-01, 3.2226e-01, 3.2324e-01, 3.2422e-01, 3.2519e-01, 3.2617e-01, 3.2715e-01, 3.2812e-01, 3.2910e-01, 3.3008e-01, 3.3105e-01, 3.3203e-01, 3.3301e-01, 3.3398e-01, 3.3496e-01, 3.3594e-01, 3.3691e-01, 3.3789e-01, 3.3887e-01, 3.3984e-01, 3.4082e-01, 3.4180e-01, 3.4277e-01, 3.4375e-01, 3.4472e-01, 3.4570e-01, 3.4668e-01, 3.4765e-01, 3.4863e-01, 3.4961e-01, 3.5058e-01, 3.5156e-01, 3.5254e-01, 3.5351e-01, 3.5449e-01, 3.5547e-01, 3.5644e-01, 3.5742e-01, 3.5840e-01, 3.5937e-01, 3.6035e-01, 3.6133e-01, 3.6230e-01, 3.6328e-01, 3.6426e-01, 3.6523e-01, 3.6621e-01, 3.6719e-01, 3.6816e-01, 3.6914e-01, 3.7012e-01, 3.7109e-01, 3.7207e-01, 3.7305e-01, 3.7402e-01, 3.7500e-01, - 3.7695e-01, 3.7890e-01, 3.8086e-01, 3.8281e-01, 3.8476e-01, 3.8672e-01, 3.8867e-01, 3.9062e-01, 3.9258e-01, 3.9453e-01, 3.9648e-01, 3.9844e-01, 4.0039e-01, 4.0234e-01, 4.0430e-01, 4.0625e-01, 4.0820e-01, 4.1015e-01, 4.1211e-01, 4.1406e-01, 4.1601e-01, 4.1797e-01, 4.1992e-01, 4.2187e-01, 4.2383e-01, 4.2578e-01, 4.2773e-01, 4.2969e-01, 4.3164e-01, 4.3359e-01, 4.3555e-01, 4.3750e-01, 4.3945e-01, 4.4140e-01, 4.4336e-01, 4.4531e-01, 4.4726e-01, 4.4922e-01, 4.5117e-01, 4.5312e-01, 4.5508e-01, 4.5703e-01, 4.5898e-01, 4.6094e-01, 4.6289e-01, 4.6484e-01, 4.6680e-01, 4.6875e-01, 4.7070e-01, 4.7265e-01, 4.7461e-01, 4.7656e-01, 4.7851e-01, 4.8047e-01, 4.8242e-01, 4.8437e-01, 4.8633e-01, 4.8828e-01, 4.9023e-01, 4.9219e-01, 4.9414e-01, 4.9609e-01, 4.9805e-01, 5.0000e-01, 5.0195e-01, 5.0390e-01, 5.0586e-01, 5.0781e-01, 5.0976e-01, 5.1172e-01, 5.1367e-01, 5.1562e-01, 5.1758e-01, 5.1953e-01, 5.2148e-01, 5.2344e-01, 5.2539e-01, 5.2734e-01, 5.2930e-01, 5.3125e-01, 5.3320e-01, 5.3515e-01, 5.3711e-01, 5.3906e-01, 5.4101e-01, 5.4297e-01, 5.4492e-01, 5.4687e-01, 5.4883e-01, 5.5078e-01, 5.5273e-01, 5.5469e-01, 5.5664e-01, 5.5859e-01, 5.6055e-01, 5.6250e-01, 5.6445e-01, 5.6640e-01, 5.6836e-01, 5.7031e-01, 5.7226e-01, 5.7422e-01, 5.7617e-01, 5.7812e-01, 5.8008e-01, 5.8203e-01, 5.8398e-01, 5.8594e-01, 5.8789e-01, 5.8984e-01, 5.9180e-01, 5.9375e-01, 5.9570e-01, 5.9765e-01, 5.9961e-01, 6.0156e-01, 6.0351e-01, 6.0547e-01, 6.0742e-01, 6.0937e-01, 6.1133e-01, 6.1328e-01, 6.1523e-01, 6.1719e-01, 6.1914e-01, 6.2109e-01, 6.2305e-01, 6.2500e-01, 6.2695e-01, 6.2890e-01, 6.3086e-01, 6.3281e-01, 6.3476e-01, 6.3672e-01, 6.3867e-01, 6.4062e-01, 6.4258e-01, 6.4453e-01, 6.4648e-01, 6.4844e-01, 6.5039e-01, 6.5234e-01, 6.5430e-01, 6.5625e-01, 6.5820e-01, 6.6015e-01, 6.6211e-01, 6.6406e-01, 6.6601e-01, 6.6797e-01, 6.6992e-01, 6.7187e-01, 6.7383e-01, 6.7578e-01, 6.7773e-01, 6.7969e-01, 6.8164e-01, 6.8359e-01, 6.8554e-01, 6.8750e-01, 6.8945e-01, 6.9140e-01, 6.9336e-01, 6.9531e-01, 6.9726e-01, 6.9922e-01, 7.0117e-01, 7.0312e-01, 7.0508e-01, 7.0703e-01, 7.0898e-01, 7.1094e-01, 7.1289e-01, 7.1484e-01, 7.1679e-01, 7.1875e-01, 7.2070e-01, 7.2265e-01, 7.2461e-01, 7.2656e-01, 7.2851e-01, 7.3047e-01, 7.3242e-01, 7.3437e-01, 7.3633e-01, 7.3828e-01, 7.4023e-01, 7.4219e-01, 7.4414e-01, 7.4609e-01, 7.4804e-01, 7.5000e-01, 7.5390e-01, 7.5781e-01, 7.6172e-01, 7.6562e-01, 7.6953e-01, 7.7344e-01, 7.7734e-01, 7.8125e-01, 7.8515e-01, 7.8906e-01, 7.9297e-01, 7.9687e-01, 8.0078e-01, 8.0469e-01, 8.0859e-01, 8.1250e-01, 8.1640e-01, 8.2031e-01, 8.2422e-01, 8.2812e-01, 8.3203e-01, 8.3594e-01, 8.3984e-01, 8.4375e-01, 8.4765e-01, 8.5156e-01, 8.5547e-01, 8.5937e-01, 8.6328e-01, 8.6719e-01, 8.7109e-01, 8.7500e-01, 8.7890e-01, 8.8281e-01, 8.8672e-01, 8.9062e-01, 8.9453e-01, 8.9844e-01, 9.0234e-01, 9.0625e-01, 9.1015e-01, 9.1406e-01, 9.1797e-01, 9.2187e-01, 9.2578e-01, 9.2969e-01, 9.3359e-01, 9.3750e-01, 9.4140e-01, 9.4531e-01, 9.4922e-01, 9.5312e-01, 9.5703e-01, 9.6094e-01, 9.6484e-01, 9.6875e-01, 9.7265e-01, 9.7656e-01, 9.8047e-01, 9.8437e-01, 9.8828e-01, 9.9219e-01, 9.9609e-01, 1.0000e+00 + 0.0000e+00, 5.9488e-08, 1.1898e-07, 1.7846e-07, 2.3795e-07, 2.9744e-07, 3.5693e-07, 4.1642e-07, 4.7591e-07, 5.3539e-07, 5.9488e-07, 6.5437e-07, 7.1386e-07, 7.7335e-07, 8.3284e-07, 8.9232e-07, 9.5181e-07, 1.0113e-06, 1.0708e-06, 1.1303e-06, 1.1898e-06, 1.2493e-06, 1.3087e-06, 1.3682e-06, 1.4277e-06, 1.4872e-06, 1.5467e-06, 1.6062e-06, 1.6657e-06, 1.7252e-06, 1.7846e-06, 1.8441e-06, 1.9036e-06, 1.9631e-06, 2.0226e-06, 2.0821e-06, 2.1416e-06, 2.2011e-06, 2.2606e-06, 2.3200e-06, 2.3795e-06, 2.4390e-06, 2.4985e-06, 2.5580e-06, 2.6175e-06, 2.6770e-06, 2.7365e-06, 2.7959e-06, 2.8554e-06, 2.9149e-06, 2.9744e-06, 3.0339e-06, 3.0934e-06, 3.1529e-06, 3.2124e-06, 3.2719e-06, 3.3313e-06, 3.3908e-06, 3.4503e-06, 3.5098e-06, 3.5693e-06, 3.6288e-06, 3.6883e-06, 3.7478e-06, 3.8072e-06, 3.8667e-06, 3.9262e-06, 3.9857e-06, 4.0452e-06, 4.1047e-06, 4.1642e-06, 4.2237e-06, 4.2832e-06, 4.3426e-06, 4.4021e-06, 4.4616e-06, 4.5211e-06, 4.5806e-06, 4.6401e-06, 4.6996e-06, 4.7591e-06, 4.8185e-06, 4.8780e-06, 4.9375e-06, 4.9970e-06, 5.0565e-06, 5.1160e-06, 5.1755e-06, 5.2350e-06, 5.2945e-06, 5.3539e-06, 5.4134e-06, 5.4729e-06, 5.5324e-06, 5.5919e-06, 5.6514e-06, 5.7109e-06, 5.7704e-06, 5.8298e-06, 5.8893e-06, 5.9488e-06, 6.0083e-06, 6.0678e-06, 6.1273e-06, 6.1868e-06, 6.2463e-06, 6.3058e-06, 6.3652e-06, 6.4247e-06, 6.4842e-06, 6.5437e-06, 6.6032e-06, 6.6627e-06, 6.7222e-06, 6.7817e-06, 6.8411e-06, 6.9006e-06, 6.9601e-06, 7.0196e-06, 7.0791e-06, 7.1386e-06, 7.1981e-06, 7.2576e-06, 7.3171e-06, 7.3765e-06, 7.4360e-06, 7.4955e-06, 7.5550e-06, 7.6145e-06, 7.6740e-06, 7.7335e-06, 7.7930e-06, 7.8524e-06, 7.9119e-06, 7.9714e-06, 8.0309e-06, 8.0904e-06, 8.1499e-06, 8.2094e-06, 8.2689e-06, 8.3284e-06, 8.3878e-06, 8.4473e-06, 8.5068e-06, 8.5663e-06, 8.6258e-06, 8.6853e-06, 8.7448e-06, 8.8043e-06, 8.8637e-06, 8.9232e-06, 8.9827e-06, 9.0422e-06, 9.1017e-06, 9.1612e-06, 9.2207e-06, 9.2802e-06, 9.3397e-06, 9.3991e-06, 9.4586e-06, 9.5181e-06, 9.5776e-06, 9.6371e-06, 9.6966e-06, 9.7561e-06, 9.8156e-06, 9.8750e-06, 9.9345e-06, 9.9940e-06, 1.0054e-05, 1.0113e-05, 1.0172e-05, 1.0232e-05, 1.0291e-05, 1.0351e-05, 1.0410e-05, 1.0470e-05, 1.0529e-05, 1.0589e-05, 1.0648e-05, 1.0708e-05, 1.0767e-05, 1.0827e-05, 1.0886e-05, 1.0946e-05, 1.1005e-05, 1.1065e-05, 1.1124e-05, 1.1184e-05, 1.1243e-05, 1.1303e-05, 1.1362e-05, 1.1422e-05, 1.1481e-05, 1.1541e-05, 1.1600e-05, 1.1660e-05, 1.1719e-05, 1.1779e-05, 1.1838e-05, 1.1898e-05, 1.1957e-05, 1.2017e-05, 1.2076e-05, 1.2136e-05, 1.2195e-05, 1.2255e-05, 1.2314e-05, 1.2374e-05, 1.2433e-05, 1.2493e-05, 1.2552e-05, 1.2612e-05, 1.2671e-05, 1.2730e-05, 1.2790e-05, 1.2849e-05, 1.2909e-05, 1.2968e-05, 1.3028e-05, 1.3087e-05, 1.3147e-05, 1.3206e-05, 1.3266e-05, 1.3325e-05, 1.3385e-05, 1.3444e-05, 1.3504e-05, 1.3563e-05, 1.3623e-05, 1.3682e-05, 1.3742e-05, 1.3801e-05, 1.3861e-05, 1.3920e-05, 1.3980e-05, 1.4039e-05, 1.4099e-05, 1.4158e-05, 1.4218e-05, 1.4277e-05, 1.4337e-05, 1.4396e-05, 1.4456e-05, 1.4515e-05, 1.4575e-05, 1.4634e-05, 1.4694e-05, 1.4753e-05, 1.4813e-05, 1.4872e-05, 1.4932e-05, 1.4991e-05, 1.5051e-05, 1.5110e-05, 1.5169e-05, // NOLINT + 1.5229e-05, 1.5288e-05, 1.5348e-05, 1.5407e-05, 1.5467e-05, 1.5526e-05, 1.5586e-05, 1.5645e-05, 1.5705e-05, 1.5764e-05, 1.5824e-05, 1.5883e-05, 1.5943e-05, 1.6002e-05, 1.6062e-05, 1.6121e-05, 1.6181e-05, 1.6240e-05, 1.6300e-05, 1.6359e-05, 1.6419e-05, 1.6478e-05, 1.6538e-05, 1.6597e-05, 1.6657e-05, 1.6716e-05, 1.6776e-05, 1.6835e-05, 1.6895e-05, 1.6954e-05, 1.7014e-05, 1.7073e-05, 1.7133e-05, 1.7192e-05, 1.7252e-05, 1.7311e-05, 1.7371e-05, 1.7430e-05, 1.7490e-05, 1.7549e-05, 1.7609e-05, 1.7668e-05, 1.7727e-05, 1.7787e-05, 1.7846e-05, 1.7906e-05, 1.7965e-05, 1.8025e-05, 1.8084e-05, 1.8144e-05, 1.8203e-05, 1.8263e-05, 1.8322e-05, 1.8382e-05, 1.8441e-05, 1.8501e-05, 1.8560e-05, 1.8620e-05, 1.8679e-05, 1.8739e-05, 1.8798e-05, 1.8858e-05, 1.8917e-05, 1.8977e-05, 1.9036e-05, 1.9096e-05, 1.9155e-05, 1.9215e-05, 1.9274e-05, 1.9334e-05, 1.9393e-05, 1.9453e-05, 1.9512e-05, 1.9572e-05, 1.9631e-05, 1.9691e-05, 1.9750e-05, 1.9810e-05, 1.9869e-05, 1.9929e-05, 1.9988e-05, 2.0048e-05, 2.0107e-05, 2.0167e-05, 2.0226e-05, 2.0285e-05, 2.0345e-05, 2.0404e-05, 2.0464e-05, 2.0523e-05, 2.0583e-05, 2.0642e-05, 2.0702e-05, 2.0761e-05, 2.0821e-05, 2.0880e-05, 2.0940e-05, 2.0999e-05, 2.1059e-05, 2.1118e-05, 2.1178e-05, 2.1237e-05, 2.1297e-05, 2.1356e-05, 2.1416e-05, 2.1475e-05, 2.1535e-05, 2.1594e-05, 2.1654e-05, 2.1713e-05, 2.1773e-05, 2.1832e-05, 2.1892e-05, 2.1951e-05, 2.2011e-05, 2.2070e-05, 2.2130e-05, 2.2189e-05, 2.2249e-05, 2.2308e-05, 2.2368e-05, 2.2427e-05, 2.2487e-05, 2.2546e-05, 2.2606e-05, 2.2665e-05, 2.2725e-05, 2.2784e-05, 2.2843e-05, 2.2903e-05, 2.2962e-05, 2.3022e-05, 2.3081e-05, 2.3141e-05, 2.3200e-05, 2.3260e-05, 2.3319e-05, 2.3379e-05, 2.3438e-05, 2.3498e-05, 2.3557e-05, 2.3617e-05, 2.3676e-05, 2.3736e-05, 2.3795e-05, 2.3855e-05, 2.3914e-05, 2.3974e-05, 2.4033e-05, 2.4093e-05, 2.4152e-05, 2.4212e-05, 2.4271e-05, 2.4331e-05, 2.4390e-05, 2.4450e-05, 2.4509e-05, 2.4569e-05, 2.4628e-05, 2.4688e-05, 2.4747e-05, 2.4807e-05, 2.4866e-05, 2.4926e-05, 2.4985e-05, 2.5045e-05, 2.5104e-05, 2.5164e-05, 2.5223e-05, 2.5282e-05, 2.5342e-05, 2.5401e-05, 2.5461e-05, 2.5520e-05, 2.5580e-05, 2.5639e-05, 2.5699e-05, 2.5758e-05, 2.5818e-05, 2.5877e-05, 2.5937e-05, 2.5996e-05, 2.6056e-05, 2.6115e-05, 2.6175e-05, 2.6234e-05, 2.6294e-05, 2.6353e-05, 2.6413e-05, 2.6472e-05, 2.6532e-05, 2.6591e-05, 2.6651e-05, 2.6710e-05, 2.6770e-05, 2.6829e-05, 2.6889e-05, 2.6948e-05, 2.7008e-05, 2.7067e-05, 2.7127e-05, 2.7186e-05, 2.7246e-05, 2.7305e-05, 2.7365e-05, 2.7424e-05, 2.7484e-05, 2.7543e-05, 2.7603e-05, 2.7662e-05, 2.7722e-05, 2.7781e-05, 2.7840e-05, 2.7900e-05, 2.7959e-05, 2.8019e-05, 2.8078e-05, 2.8138e-05, 2.8197e-05, 2.8257e-05, 2.8316e-05, 2.8376e-05, 2.8435e-05, 2.8495e-05, 2.8554e-05, 2.8614e-05, 2.8673e-05, 2.8733e-05, 2.8792e-05, 2.8852e-05, 2.8911e-05, 2.8971e-05, 2.9030e-05, 2.9090e-05, 2.9149e-05, 2.9209e-05, 2.9268e-05, 2.9328e-05, 2.9387e-05, 2.9447e-05, 2.9506e-05, 2.9566e-05, 2.9625e-05, 2.9685e-05, 2.9744e-05, 2.9804e-05, 2.9863e-05, 2.9923e-05, 2.9982e-05, 3.0042e-05, 3.0101e-05, 3.0161e-05, 3.0220e-05, 3.0280e-05, 3.0339e-05, 3.0398e-05, // NOLINT + 3.0458e-05, 3.0577e-05, 3.0697e-05, 3.0816e-05, 3.0936e-05, 3.1055e-05, 3.1175e-05, 3.1294e-05, 3.1414e-05, 3.1533e-05, 3.1652e-05, 3.1772e-05, 3.1891e-05, 3.2011e-05, 3.2130e-05, 3.2250e-05, 3.2369e-05, 3.2489e-05, 3.2608e-05, 3.2727e-05, 3.2847e-05, 3.2966e-05, 3.3086e-05, 3.3205e-05, 3.3325e-05, 3.3444e-05, 3.3563e-05, 3.3683e-05, 3.3802e-05, 3.3922e-05, 3.4041e-05, 3.4161e-05, 3.4280e-05, 3.4400e-05, 3.4519e-05, 3.4638e-05, 3.4758e-05, 3.4877e-05, 3.4997e-05, 3.5116e-05, 3.5236e-05, 3.5355e-05, 3.5475e-05, 3.5594e-05, 3.5713e-05, 3.5833e-05, 3.5952e-05, 3.6072e-05, 3.6191e-05, 3.6311e-05, 3.6430e-05, 3.6550e-05, 3.6669e-05, 3.6788e-05, 3.6908e-05, 3.7027e-05, 3.7147e-05, 3.7266e-05, 3.7386e-05, 3.7505e-05, 3.7625e-05, 3.7744e-05, 3.7863e-05, 3.7983e-05, 3.8102e-05, 3.8222e-05, 3.8341e-05, 3.8461e-05, 3.8580e-05, 3.8700e-05, 3.8819e-05, 3.8938e-05, 3.9058e-05, 3.9177e-05, 3.9297e-05, 3.9416e-05, 3.9536e-05, 3.9655e-05, 3.9775e-05, 3.9894e-05, 4.0013e-05, 4.0133e-05, 4.0252e-05, 4.0372e-05, 4.0491e-05, 4.0611e-05, 4.0730e-05, 4.0850e-05, 4.0969e-05, 4.1088e-05, 4.1208e-05, 4.1327e-05, 4.1447e-05, 4.1566e-05, 4.1686e-05, 4.1805e-05, 4.1925e-05, 4.2044e-05, 4.2163e-05, 4.2283e-05, 4.2402e-05, 4.2522e-05, 4.2641e-05, 4.2761e-05, 4.2880e-05, 4.2999e-05, 4.3119e-05, 4.3238e-05, 4.3358e-05, 4.3477e-05, 4.3597e-05, 4.3716e-05, 4.3836e-05, 4.3955e-05, 4.4074e-05, 4.4194e-05, 4.4313e-05, 4.4433e-05, 4.4552e-05, 4.4672e-05, 4.4791e-05, 4.4911e-05, 4.5030e-05, 4.5149e-05, 4.5269e-05, 4.5388e-05, 4.5508e-05, 4.5627e-05, 4.5747e-05, 4.5866e-05, 4.5986e-05, 4.6105e-05, 4.6224e-05, 4.6344e-05, 4.6463e-05, 4.6583e-05, 4.6702e-05, 4.6822e-05, 4.6941e-05, 4.7061e-05, 4.7180e-05, 4.7299e-05, 4.7419e-05, 4.7538e-05, 4.7658e-05, 4.7777e-05, 4.7897e-05, 4.8016e-05, 4.8136e-05, 4.8255e-05, 4.8374e-05, 4.8494e-05, 4.8613e-05, 4.8733e-05, 4.8852e-05, 4.8972e-05, 4.9091e-05, 4.9211e-05, 4.9330e-05, 4.9449e-05, 4.9569e-05, 4.9688e-05, 4.9808e-05, 4.9927e-05, 5.0047e-05, 5.0166e-05, 5.0286e-05, 5.0405e-05, 5.0524e-05, 5.0644e-05, 5.0763e-05, 5.0883e-05, 5.1002e-05, 5.1122e-05, 5.1241e-05, 5.1361e-05, 5.1480e-05, 5.1599e-05, 5.1719e-05, 5.1838e-05, 5.1958e-05, 5.2077e-05, 5.2197e-05, 5.2316e-05, 5.2435e-05, 5.2555e-05, 5.2674e-05, 5.2794e-05, 5.2913e-05, 5.3033e-05, 5.3152e-05, 5.3272e-05, 5.3391e-05, 5.3510e-05, 5.3630e-05, 5.3749e-05, 5.3869e-05, 5.3988e-05, 5.4108e-05, 5.4227e-05, 5.4347e-05, 5.4466e-05, 5.4585e-05, 5.4705e-05, 5.4824e-05, 5.4944e-05, 5.5063e-05, 5.5183e-05, 5.5302e-05, 5.5422e-05, 5.5541e-05, 5.5660e-05, 5.5780e-05, 5.5899e-05, 5.6019e-05, 5.6138e-05, 5.6258e-05, 5.6377e-05, 5.6497e-05, 5.6616e-05, 5.6735e-05, 5.6855e-05, 5.6974e-05, 5.7094e-05, 5.7213e-05, 5.7333e-05, 5.7452e-05, 5.7572e-05, 5.7691e-05, 5.7810e-05, 5.7930e-05, 5.8049e-05, 5.8169e-05, 5.8288e-05, 5.8408e-05, 5.8527e-05, 5.8647e-05, 5.8766e-05, 5.8885e-05, 5.9005e-05, 5.9124e-05, 5.9244e-05, 5.9363e-05, 5.9483e-05, 5.9602e-05, 5.9722e-05, 5.9841e-05, 5.9960e-05, 6.0080e-05, 6.0199e-05, 6.0319e-05, 6.0438e-05, 6.0558e-05, 6.0677e-05, 6.0797e-05, 6.0916e-05, // NOLINT + 6.1154e-05, 6.1392e-05, 6.1631e-05, 6.1869e-05, 6.2107e-05, 6.2345e-05, 6.2583e-05, 6.2821e-05, 6.3060e-05, 6.3298e-05, 6.3536e-05, 6.3774e-05, 6.4012e-05, 6.4251e-05, 6.4489e-05, 6.4727e-05, 6.4965e-05, 6.5203e-05, 6.5441e-05, 6.5680e-05, 6.5918e-05, 6.6156e-05, 6.6394e-05, 6.6632e-05, 6.6871e-05, 6.7109e-05, 6.7347e-05, 6.7585e-05, 6.7823e-05, 6.8062e-05, 6.8300e-05, 6.8538e-05, 6.8776e-05, 6.9014e-05, 6.9252e-05, 6.9491e-05, 6.9729e-05, 6.9967e-05, 7.0205e-05, 7.0443e-05, 7.0682e-05, 7.0920e-05, 7.1158e-05, 7.1396e-05, 7.1634e-05, 7.1872e-05, 7.2111e-05, 7.2349e-05, 7.2587e-05, 7.2825e-05, 7.3063e-05, 7.3302e-05, 7.3540e-05, 7.3778e-05, 7.4016e-05, 7.4254e-05, 7.4493e-05, 7.4731e-05, 7.4969e-05, 7.5207e-05, 7.5445e-05, 7.5683e-05, 7.5922e-05, 7.6160e-05, 7.6398e-05, 7.6636e-05, 7.6874e-05, 7.7113e-05, 7.7351e-05, 7.7589e-05, 7.7827e-05, 7.8065e-05, 7.8304e-05, 7.8542e-05, 7.8780e-05, 7.9018e-05, 7.9256e-05, 7.9494e-05, 7.9733e-05, 7.9971e-05, 8.0209e-05, 8.0447e-05, 8.0685e-05, 8.0924e-05, 8.1162e-05, 8.1400e-05, 8.1638e-05, 8.1876e-05, 8.2114e-05, 8.2353e-05, 8.2591e-05, 8.2829e-05, 8.3067e-05, 8.3305e-05, 8.3544e-05, 8.3782e-05, 8.4020e-05, 8.4258e-05, 8.4496e-05, 8.4735e-05, 8.4973e-05, 8.5211e-05, 8.5449e-05, 8.5687e-05, 8.5925e-05, 8.6164e-05, 8.6402e-05, 8.6640e-05, 8.6878e-05, 8.7116e-05, 8.7355e-05, 8.7593e-05, 8.7831e-05, 8.8069e-05, 8.8307e-05, 8.8545e-05, 8.8784e-05, 8.9022e-05, 8.9260e-05, 8.9498e-05, 8.9736e-05, 8.9975e-05, 9.0213e-05, 9.0451e-05, 9.0689e-05, 9.0927e-05, 9.1166e-05, 9.1404e-05, 9.1642e-05, 9.1880e-05, 9.2118e-05, 9.2356e-05, 9.2595e-05, 9.2833e-05, 9.3071e-05, 9.3309e-05, 9.3547e-05, 9.3786e-05, 9.4024e-05, 9.4262e-05, 9.4500e-05, 9.4738e-05, 9.4977e-05, 9.5215e-05, 9.5453e-05, 9.5691e-05, 9.5929e-05, 9.6167e-05, 9.6406e-05, 9.6644e-05, 9.6882e-05, 9.7120e-05, 9.7358e-05, 9.7597e-05, 9.7835e-05, 9.8073e-05, 9.8311e-05, 9.8549e-05, 9.8787e-05, 9.9026e-05, 9.9264e-05, 9.9502e-05, 9.9740e-05, 9.9978e-05, 1.0022e-04, 1.0045e-04, 1.0069e-04, 1.0093e-04, 1.0117e-04, 1.0141e-04, 1.0165e-04, 1.0188e-04, 1.0212e-04, 1.0236e-04, 1.0260e-04, 1.0284e-04, 1.0307e-04, 1.0331e-04, 1.0355e-04, 1.0379e-04, 1.0403e-04, 1.0427e-04, 1.0450e-04, 1.0474e-04, 1.0498e-04, 1.0522e-04, 1.0546e-04, 1.0569e-04, 1.0593e-04, 1.0617e-04, 1.0641e-04, 1.0665e-04, 1.0689e-04, 1.0712e-04, 1.0736e-04, 1.0760e-04, 1.0784e-04, 1.0808e-04, 1.0831e-04, 1.0855e-04, 1.0879e-04, 1.0903e-04, 1.0927e-04, 1.0951e-04, 1.0974e-04, 1.0998e-04, 1.1022e-04, 1.1046e-04, 1.1070e-04, 1.1093e-04, 1.1117e-04, 1.1141e-04, 1.1165e-04, 1.1189e-04, 1.1213e-04, 1.1236e-04, 1.1260e-04, 1.1284e-04, 1.1308e-04, 1.1332e-04, 1.1355e-04, 1.1379e-04, 1.1403e-04, 1.1427e-04, 1.1451e-04, 1.1475e-04, 1.1498e-04, 1.1522e-04, 1.1546e-04, 1.1570e-04, 1.1594e-04, 1.1618e-04, 1.1641e-04, 1.1665e-04, 1.1689e-04, 1.1713e-04, 1.1737e-04, 1.1760e-04, 1.1784e-04, 1.1808e-04, 1.1832e-04, 1.1856e-04, 1.1880e-04, 1.1903e-04, 1.1927e-04, 1.1951e-04, 1.1975e-04, 1.1999e-04, 1.2022e-04, 1.2046e-04, 1.2070e-04, 1.2094e-04, 1.2118e-04, 1.2142e-04, 1.2165e-04, 1.2189e-04, // NOLINT + 1.2213e-04, 1.2237e-04, 1.2261e-04, 1.2284e-04, 1.2308e-04, 1.2332e-04, 1.2356e-04, 1.2380e-04, 1.2404e-04, 1.2427e-04, 1.2451e-04, 1.2475e-04, 1.2499e-04, 1.2523e-04, 1.2546e-04, 1.2570e-04, 1.2594e-04, 1.2618e-04, 1.2642e-04, 1.2666e-04, 1.2689e-04, 1.2713e-04, 1.2737e-04, 1.2761e-04, 1.2785e-04, 1.2808e-04, 1.2832e-04, 1.2856e-04, 1.2880e-04, 1.2904e-04, 1.2928e-04, 1.2951e-04, 1.2975e-04, 1.2999e-04, 1.3023e-04, 1.3047e-04, 1.3070e-04, 1.3094e-04, 1.3118e-04, 1.3142e-04, 1.3166e-04, 1.3190e-04, 1.3213e-04, 1.3237e-04, 1.3261e-04, 1.3285e-04, 1.3309e-04, 1.3332e-04, 1.3356e-04, 1.3380e-04, 1.3404e-04, 1.3428e-04, 1.3452e-04, 1.3475e-04, 1.3499e-04, 1.3523e-04, 1.3547e-04, 1.3571e-04, 1.3594e-04, 1.3618e-04, 1.3642e-04, 1.3666e-04, 1.3690e-04, 1.3714e-04, 1.3737e-04, 1.3761e-04, 1.3785e-04, 1.3809e-04, 1.3833e-04, 1.3856e-04, 1.3880e-04, 1.3904e-04, 1.3928e-04, 1.3952e-04, 1.3976e-04, 1.3999e-04, 1.4023e-04, 1.4047e-04, 1.4071e-04, 1.4095e-04, 1.4118e-04, 1.4142e-04, 1.4166e-04, 1.4190e-04, 1.4214e-04, 1.4238e-04, 1.4261e-04, 1.4285e-04, 1.4309e-04, 1.4333e-04, 1.4357e-04, 1.4380e-04, 1.4404e-04, 1.4428e-04, 1.4452e-04, 1.4476e-04, 1.4500e-04, 1.4523e-04, 1.4547e-04, 1.4571e-04, 1.4595e-04, 1.4619e-04, 1.4642e-04, 1.4666e-04, 1.4690e-04, 1.4714e-04, 1.4738e-04, 1.4762e-04, 1.4785e-04, 1.4809e-04, 1.4833e-04, 1.4857e-04, 1.4881e-04, 1.4904e-04, 1.4928e-04, 1.4952e-04, 1.4976e-04, 1.5000e-04, 1.5024e-04, 1.5047e-04, 1.5071e-04, 1.5095e-04, 1.5119e-04, 1.5143e-04, 1.5166e-04, 1.5190e-04, 1.5214e-04, 1.5238e-04, 1.5262e-04, 1.5286e-04, 1.5309e-04, 1.5333e-04, 1.5357e-04, 1.5381e-04, 1.5405e-04, 1.5428e-04, 1.5452e-04, 1.5476e-04, 1.5500e-04, 1.5524e-04, 1.5548e-04, 1.5571e-04, 1.5595e-04, 1.5619e-04, 1.5643e-04, 1.5667e-04, 1.5690e-04, 1.5714e-04, 1.5738e-04, 1.5762e-04, 1.5786e-04, 1.5810e-04, 1.5833e-04, 1.5857e-04, 1.5881e-04, 1.5905e-04, 1.5929e-04, 1.5952e-04, 1.5976e-04, 1.6000e-04, 1.6024e-04, 1.6048e-04, 1.6072e-04, 1.6095e-04, 1.6119e-04, 1.6143e-04, 1.6167e-04, 1.6191e-04, 1.6214e-04, 1.6238e-04, 1.6262e-04, 1.6286e-04, 1.6310e-04, 1.6334e-04, 1.6357e-04, 1.6381e-04, 1.6405e-04, 1.6429e-04, 1.6453e-04, 1.6476e-04, 1.6500e-04, 1.6524e-04, 1.6548e-04, 1.6572e-04, 1.6596e-04, 1.6619e-04, 1.6643e-04, 1.6667e-04, 1.6691e-04, 1.6715e-04, 1.6738e-04, 1.6762e-04, 1.6786e-04, 1.6810e-04, 1.6834e-04, 1.6858e-04, 1.6881e-04, 1.6905e-04, 1.6929e-04, 1.6953e-04, 1.6977e-04, 1.7001e-04, 1.7024e-04, 1.7048e-04, 1.7072e-04, 1.7096e-04, 1.7120e-04, 1.7143e-04, 1.7167e-04, 1.7191e-04, 1.7215e-04, 1.7239e-04, 1.7263e-04, 1.7286e-04, 1.7310e-04, 1.7334e-04, 1.7358e-04, 1.7382e-04, 1.7405e-04, 1.7429e-04, 1.7453e-04, 1.7477e-04, 1.7501e-04, 1.7525e-04, 1.7548e-04, 1.7572e-04, 1.7596e-04, 1.7620e-04, 1.7644e-04, 1.7667e-04, 1.7691e-04, 1.7715e-04, 1.7739e-04, 1.7763e-04, 1.7787e-04, 1.7810e-04, 1.7834e-04, 1.7858e-04, 1.7882e-04, 1.7906e-04, 1.7929e-04, 1.7953e-04, 1.7977e-04, 1.8001e-04, 1.8025e-04, 1.8049e-04, 1.8072e-04, 1.8096e-04, 1.8120e-04, 1.8144e-04, 1.8168e-04, 1.8191e-04, 1.8215e-04, 1.8239e-04, 1.8263e-04, 1.8287e-04, // NOLINT + 1.8311e-04, 1.8334e-04, 1.8358e-04, 1.8382e-04, 1.8406e-04, 1.8430e-04, 1.8453e-04, 1.8477e-04, 1.8501e-04, 1.8525e-04, 1.8549e-04, 1.8573e-04, 1.8596e-04, 1.8620e-04, 1.8644e-04, 1.8668e-04, 1.8692e-04, 1.8715e-04, 1.8739e-04, 1.8763e-04, 1.8787e-04, 1.8811e-04, 1.8835e-04, 1.8858e-04, 1.8882e-04, 1.8906e-04, 1.8930e-04, 1.8954e-04, 1.8977e-04, 1.9001e-04, 1.9025e-04, 1.9049e-04, 1.9073e-04, 1.9097e-04, 1.9120e-04, 1.9144e-04, 1.9168e-04, 1.9192e-04, 1.9216e-04, 1.9239e-04, 1.9263e-04, 1.9287e-04, 1.9311e-04, 1.9335e-04, 1.9359e-04, 1.9382e-04, 1.9406e-04, 1.9430e-04, 1.9454e-04, 1.9478e-04, 1.9501e-04, 1.9525e-04, 1.9549e-04, 1.9573e-04, 1.9597e-04, 1.9621e-04, 1.9644e-04, 1.9668e-04, 1.9692e-04, 1.9716e-04, 1.9740e-04, 1.9763e-04, 1.9787e-04, 1.9811e-04, 1.9835e-04, 1.9859e-04, 1.9883e-04, 1.9906e-04, 1.9930e-04, 1.9954e-04, 1.9978e-04, 2.0002e-04, 2.0025e-04, 2.0049e-04, 2.0073e-04, 2.0097e-04, 2.0121e-04, 2.0145e-04, 2.0168e-04, 2.0192e-04, 2.0216e-04, 2.0240e-04, 2.0264e-04, 2.0287e-04, 2.0311e-04, 2.0335e-04, 2.0359e-04, 2.0383e-04, 2.0407e-04, 2.0430e-04, 2.0454e-04, 2.0478e-04, 2.0502e-04, 2.0526e-04, 2.0549e-04, 2.0573e-04, 2.0597e-04, 2.0621e-04, 2.0645e-04, 2.0669e-04, 2.0692e-04, 2.0716e-04, 2.0740e-04, 2.0764e-04, 2.0788e-04, 2.0811e-04, 2.0835e-04, 2.0859e-04, 2.0883e-04, 2.0907e-04, 2.0931e-04, 2.0954e-04, 2.0978e-04, 2.1002e-04, 2.1026e-04, 2.1050e-04, 2.1073e-04, 2.1097e-04, 2.1121e-04, 2.1145e-04, 2.1169e-04, 2.1193e-04, 2.1216e-04, 2.1240e-04, 2.1264e-04, 2.1288e-04, 2.1312e-04, 2.1335e-04, 2.1359e-04, 2.1383e-04, 2.1407e-04, 2.1431e-04, 2.1455e-04, 2.1478e-04, 2.1502e-04, 2.1526e-04, 2.1550e-04, 2.1574e-04, 2.1597e-04, 2.1621e-04, 2.1645e-04, 2.1669e-04, 2.1693e-04, 2.1717e-04, 2.1740e-04, 2.1764e-04, 2.1788e-04, 2.1812e-04, 2.1836e-04, 2.1859e-04, 2.1883e-04, 2.1907e-04, 2.1931e-04, 2.1955e-04, 2.1979e-04, 2.2002e-04, 2.2026e-04, 2.2050e-04, 2.2074e-04, 2.2098e-04, 2.2121e-04, 2.2145e-04, 2.2169e-04, 2.2193e-04, 2.2217e-04, 2.2241e-04, 2.2264e-04, 2.2288e-04, 2.2312e-04, 2.2336e-04, 2.2360e-04, 2.2383e-04, 2.2407e-04, 2.2431e-04, 2.2455e-04, 2.2479e-04, 2.2503e-04, 2.2526e-04, 2.2550e-04, 2.2574e-04, 2.2598e-04, 2.2622e-04, 2.2646e-04, 2.2669e-04, 2.2693e-04, 2.2717e-04, 2.2741e-04, 2.2765e-04, 2.2788e-04, 2.2812e-04, 2.2836e-04, 2.2860e-04, 2.2884e-04, 2.2908e-04, 2.2931e-04, 2.2955e-04, 2.2979e-04, 2.3003e-04, 2.3027e-04, 2.3050e-04, 2.3074e-04, 2.3098e-04, 2.3122e-04, 2.3146e-04, 2.3170e-04, 2.3193e-04, 2.3217e-04, 2.3241e-04, 2.3265e-04, 2.3289e-04, 2.3312e-04, 2.3336e-04, 2.3360e-04, 2.3384e-04, 2.3408e-04, 2.3432e-04, 2.3455e-04, 2.3479e-04, 2.3503e-04, 2.3527e-04, 2.3551e-04, 2.3574e-04, 2.3598e-04, 2.3622e-04, 2.3646e-04, 2.3670e-04, 2.3694e-04, 2.3717e-04, 2.3741e-04, 2.3765e-04, 2.3789e-04, 2.3813e-04, 2.3836e-04, 2.3860e-04, 2.3884e-04, 2.3908e-04, 2.3932e-04, 2.3956e-04, 2.3979e-04, 2.4003e-04, 2.4027e-04, 2.4051e-04, 2.4075e-04, 2.4098e-04, 2.4122e-04, 2.4146e-04, 2.4170e-04, 2.4194e-04, 2.4218e-04, 2.4241e-04, 2.4265e-04, 2.4289e-04, 2.4313e-04, 2.4337e-04, 2.4360e-04, 2.4384e-04, // NOLINT + 2.4480e-04, 2.4575e-04, 2.4670e-04, 2.4766e-04, 2.4861e-04, 2.4956e-04, 2.5052e-04, 2.5147e-04, 2.5242e-04, 2.5337e-04, 2.5433e-04, 2.5528e-04, 2.5623e-04, 2.5719e-04, 2.5814e-04, 2.5909e-04, 2.6005e-04, 2.6100e-04, 2.6195e-04, 2.6291e-04, 2.6386e-04, 2.6481e-04, 2.6577e-04, 2.6672e-04, 2.6767e-04, 2.6863e-04, 2.6958e-04, 2.7053e-04, 2.7149e-04, 2.7244e-04, 2.7339e-04, 2.7435e-04, 2.7530e-04, 2.7625e-04, 2.7720e-04, 2.7816e-04, 2.7911e-04, 2.8006e-04, 2.8102e-04, 2.8197e-04, 2.8292e-04, 2.8388e-04, 2.8483e-04, 2.8578e-04, 2.8674e-04, 2.8769e-04, 2.8864e-04, 2.8960e-04, 2.9055e-04, 2.9150e-04, 2.9246e-04, 2.9341e-04, 2.9436e-04, 2.9532e-04, 2.9627e-04, 2.9722e-04, 2.9818e-04, 2.9913e-04, 3.0008e-04, 3.0104e-04, 3.0199e-04, 3.0294e-04, 3.0389e-04, 3.0485e-04, 3.0580e-04, 3.0675e-04, 3.0771e-04, 3.0866e-04, 3.0961e-04, 3.1057e-04, 3.1152e-04, 3.1247e-04, 3.1343e-04, 3.1438e-04, 3.1533e-04, 3.1629e-04, 3.1724e-04, 3.1819e-04, 3.1915e-04, 3.2010e-04, 3.2105e-04, 3.2201e-04, 3.2296e-04, 3.2391e-04, 3.2487e-04, 3.2582e-04, 3.2677e-04, 3.2772e-04, 3.2868e-04, 3.2963e-04, 3.3058e-04, 3.3154e-04, 3.3249e-04, 3.3344e-04, 3.3440e-04, 3.3535e-04, 3.3630e-04, 3.3726e-04, 3.3821e-04, 3.3916e-04, 3.4012e-04, 3.4107e-04, 3.4202e-04, 3.4298e-04, 3.4393e-04, 3.4488e-04, 3.4584e-04, 3.4679e-04, 3.4774e-04, 3.4870e-04, 3.4965e-04, 3.5060e-04, 3.5156e-04, 3.5251e-04, 3.5346e-04, 3.5441e-04, 3.5537e-04, 3.5632e-04, 3.5727e-04, 3.5823e-04, 3.5918e-04, 3.6013e-04, 3.6109e-04, 3.6204e-04, 3.6299e-04, 3.6395e-04, 3.6490e-04, 3.6585e-04, 3.6681e-04, 3.6776e-04, 3.6871e-04, 3.6967e-04, 3.7062e-04, 3.7157e-04, 3.7253e-04, 3.7348e-04, 3.7443e-04, 3.7539e-04, 3.7634e-04, 3.7729e-04, 3.7825e-04, 3.7920e-04, 3.8015e-04, 3.8110e-04, 3.8206e-04, 3.8301e-04, 3.8396e-04, 3.8492e-04, 3.8587e-04, 3.8682e-04, 3.8778e-04, 3.8873e-04, 3.8968e-04, 3.9064e-04, 3.9159e-04, 3.9254e-04, 3.9350e-04, 3.9445e-04, 3.9540e-04, 3.9636e-04, 3.9731e-04, 3.9826e-04, 3.9922e-04, 4.0017e-04, 4.0112e-04, 4.0208e-04, 4.0303e-04, 4.0398e-04, 4.0493e-04, 4.0589e-04, 4.0684e-04, 4.0779e-04, 4.0875e-04, 4.0970e-04, 4.1065e-04, 4.1161e-04, 4.1256e-04, 4.1351e-04, 4.1447e-04, 4.1542e-04, 4.1637e-04, 4.1733e-04, 4.1828e-04, 4.1923e-04, 4.2019e-04, 4.2114e-04, 4.2209e-04, 4.2305e-04, 4.2400e-04, 4.2495e-04, 4.2591e-04, 4.2686e-04, 4.2781e-04, 4.2877e-04, 4.2972e-04, 4.3067e-04, 4.3162e-04, 4.3258e-04, 4.3353e-04, 4.3448e-04, 4.3544e-04, 4.3639e-04, 4.3734e-04, 4.3830e-04, 4.3925e-04, 4.4020e-04, 4.4116e-04, 4.4211e-04, 4.4306e-04, 4.4402e-04, 4.4497e-04, 4.4592e-04, 4.4688e-04, 4.4783e-04, 4.4878e-04, 4.4974e-04, 4.5069e-04, 4.5164e-04, 4.5260e-04, 4.5355e-04, 4.5450e-04, 4.5545e-04, 4.5641e-04, 4.5736e-04, 4.5831e-04, 4.5927e-04, 4.6022e-04, 4.6117e-04, 4.6213e-04, 4.6308e-04, 4.6403e-04, 4.6499e-04, 4.6594e-04, 4.6689e-04, 4.6785e-04, 4.6880e-04, 4.6975e-04, 4.7071e-04, 4.7166e-04, 4.7261e-04, 4.7357e-04, 4.7452e-04, 4.7547e-04, 4.7643e-04, 4.7738e-04, 4.7833e-04, 4.7929e-04, 4.8024e-04, 4.8119e-04, 4.8214e-04, 4.8310e-04, 4.8405e-04, 4.8500e-04, 4.8596e-04, 4.8691e-04, 4.8786e-04, // NOLINT + 4.8977e-04, 4.9168e-04, 4.9358e-04, 4.9549e-04, 4.9740e-04, 4.9931e-04, 5.0121e-04, 5.0312e-04, 5.0503e-04, 5.0693e-04, 5.0884e-04, 5.1075e-04, 5.1265e-04, 5.1456e-04, 5.1647e-04, 5.1837e-04, 5.2028e-04, 5.2219e-04, 5.2409e-04, 5.2600e-04, 5.2791e-04, 5.2982e-04, 5.3172e-04, 5.3363e-04, 5.3554e-04, 5.3744e-04, 5.3935e-04, 5.4126e-04, 5.4316e-04, 5.4507e-04, 5.4698e-04, 5.4888e-04, 5.5079e-04, 5.5270e-04, 5.5460e-04, 5.5651e-04, 5.5842e-04, 5.6033e-04, 5.6223e-04, 5.6414e-04, 5.6605e-04, 5.6795e-04, 5.6986e-04, 5.7177e-04, 5.7367e-04, 5.7558e-04, 5.7749e-04, 5.7939e-04, 5.8130e-04, 5.8321e-04, 5.8512e-04, 5.8702e-04, 5.8893e-04, 5.9084e-04, 5.9274e-04, 5.9465e-04, 5.9656e-04, 5.9846e-04, 6.0037e-04, 6.0228e-04, 6.0418e-04, 6.0609e-04, 6.0800e-04, 6.0990e-04, 6.1181e-04, 6.1372e-04, 6.1563e-04, 6.1753e-04, 6.1944e-04, 6.2135e-04, 6.2325e-04, 6.2516e-04, 6.2707e-04, 6.2897e-04, 6.3088e-04, 6.3279e-04, 6.3469e-04, 6.3660e-04, 6.3851e-04, 6.4041e-04, 6.4232e-04, 6.4423e-04, 6.4614e-04, 6.4804e-04, 6.4995e-04, 6.5186e-04, 6.5376e-04, 6.5567e-04, 6.5758e-04, 6.5948e-04, 6.6139e-04, 6.6330e-04, 6.6520e-04, 6.6711e-04, 6.6902e-04, 6.7092e-04, 6.7283e-04, 6.7474e-04, 6.7665e-04, 6.7855e-04, 6.8046e-04, 6.8237e-04, 6.8427e-04, 6.8618e-04, 6.8809e-04, 6.8999e-04, 6.9190e-04, 6.9381e-04, 6.9571e-04, 6.9762e-04, 6.9953e-04, 7.0143e-04, 7.0334e-04, 7.0525e-04, 7.0716e-04, 7.0906e-04, 7.1097e-04, 7.1288e-04, 7.1478e-04, 7.1669e-04, 7.1860e-04, 7.2050e-04, 7.2241e-04, 7.2432e-04, 7.2622e-04, 7.2813e-04, 7.3004e-04, 7.3195e-04, 7.3385e-04, 7.3576e-04, 7.3767e-04, 7.3957e-04, 7.4148e-04, 7.4339e-04, 7.4529e-04, 7.4720e-04, 7.4911e-04, 7.5101e-04, 7.5292e-04, 7.5483e-04, 7.5673e-04, 7.5864e-04, 7.6055e-04, 7.6246e-04, 7.6436e-04, 7.6627e-04, 7.6818e-04, 7.7008e-04, 7.7199e-04, 7.7390e-04, 7.7580e-04, 7.7771e-04, 7.7962e-04, 7.8152e-04, 7.8343e-04, 7.8534e-04, 7.8724e-04, 7.8915e-04, 7.9106e-04, 7.9297e-04, 7.9487e-04, 7.9678e-04, 7.9869e-04, 8.0059e-04, 8.0250e-04, 8.0441e-04, 8.0631e-04, 8.0822e-04, 8.1013e-04, 8.1203e-04, 8.1394e-04, 8.1585e-04, 8.1775e-04, 8.1966e-04, 8.2157e-04, 8.2348e-04, 8.2538e-04, 8.2729e-04, 8.2920e-04, 8.3110e-04, 8.3301e-04, 8.3492e-04, 8.3682e-04, 8.3873e-04, 8.4064e-04, 8.4254e-04, 8.4445e-04, 8.4636e-04, 8.4826e-04, 8.5017e-04, 8.5208e-04, 8.5399e-04, 8.5589e-04, 8.5780e-04, 8.5971e-04, 8.6161e-04, 8.6352e-04, 8.6543e-04, 8.6733e-04, 8.6924e-04, 8.7115e-04, 8.7305e-04, 8.7496e-04, 8.7687e-04, 8.7878e-04, 8.8068e-04, 8.8259e-04, 8.8450e-04, 8.8640e-04, 8.8831e-04, 8.9022e-04, 8.9212e-04, 8.9403e-04, 8.9594e-04, 8.9784e-04, 8.9975e-04, 9.0166e-04, 9.0356e-04, 9.0547e-04, 9.0738e-04, 9.0929e-04, 9.1119e-04, 9.1310e-04, 9.1501e-04, 9.1691e-04, 9.1882e-04, 9.2073e-04, 9.2263e-04, 9.2454e-04, 9.2645e-04, 9.2835e-04, 9.3026e-04, 9.3217e-04, 9.3407e-04, 9.3598e-04, 9.3789e-04, 9.3980e-04, 9.4170e-04, 9.4361e-04, 9.4552e-04, 9.4742e-04, 9.4933e-04, 9.5124e-04, 9.5314e-04, 9.5505e-04, 9.5696e-04, 9.5886e-04, 9.6077e-04, 9.6268e-04, 9.6458e-04, 9.6649e-04, 9.6840e-04, 9.7031e-04, 9.7221e-04, 9.7412e-04, 9.7603e-04, // NOLINT + 9.7984e-04, 9.8365e-04, 9.8747e-04, 9.9128e-04, 9.9510e-04, 9.9891e-04, 1.0027e-03, 1.0065e-03, 1.0104e-03, 1.0142e-03, 1.0180e-03, 1.0218e-03, 1.0256e-03, 1.0294e-03, 1.0332e-03, 1.0371e-03, 1.0409e-03, 1.0447e-03, 1.0485e-03, 1.0523e-03, 1.0561e-03, 1.0599e-03, 1.0638e-03, 1.0676e-03, 1.0714e-03, 1.0752e-03, 1.0790e-03, 1.0828e-03, 1.0866e-03, 1.0905e-03, 1.0943e-03, 1.0981e-03, 1.1019e-03, 1.1057e-03, 1.1095e-03, 1.1133e-03, 1.1172e-03, 1.1210e-03, 1.1248e-03, 1.1286e-03, 1.1324e-03, 1.1362e-03, 1.1400e-03, 1.1439e-03, 1.1477e-03, 1.1515e-03, 1.1553e-03, 1.1591e-03, 1.1629e-03, 1.1667e-03, 1.1706e-03, 1.1744e-03, 1.1782e-03, 1.1820e-03, 1.1858e-03, 1.1896e-03, 1.1934e-03, 1.1973e-03, 1.2011e-03, 1.2049e-03, 1.2087e-03, 1.2125e-03, 1.2163e-03, 1.2201e-03, 1.2240e-03, 1.2278e-03, 1.2316e-03, 1.2354e-03, 1.2392e-03, 1.2430e-03, 1.2468e-03, 1.2507e-03, 1.2545e-03, 1.2583e-03, 1.2621e-03, 1.2659e-03, 1.2697e-03, 1.2735e-03, 1.2774e-03, 1.2812e-03, 1.2850e-03, 1.2888e-03, 1.2926e-03, 1.2964e-03, 1.3002e-03, 1.3040e-03, 1.3079e-03, 1.3117e-03, 1.3155e-03, 1.3193e-03, 1.3231e-03, 1.3269e-03, 1.3307e-03, 1.3346e-03, 1.3384e-03, 1.3422e-03, 1.3460e-03, 1.3498e-03, 1.3536e-03, 1.3574e-03, 1.3613e-03, 1.3651e-03, 1.3689e-03, 1.3727e-03, 1.3765e-03, 1.3803e-03, 1.3841e-03, 1.3880e-03, 1.3918e-03, 1.3956e-03, 1.3994e-03, 1.4032e-03, 1.4070e-03, 1.4108e-03, 1.4147e-03, 1.4185e-03, 1.4223e-03, 1.4261e-03, 1.4299e-03, 1.4337e-03, 1.4375e-03, 1.4414e-03, 1.4452e-03, 1.4490e-03, 1.4528e-03, 1.4566e-03, 1.4604e-03, 1.4642e-03, 1.4681e-03, 1.4719e-03, 1.4757e-03, 1.4795e-03, 1.4833e-03, 1.4871e-03, 1.4909e-03, 1.4948e-03, 1.4986e-03, 1.5024e-03, 1.5062e-03, 1.5100e-03, 1.5138e-03, 1.5176e-03, 1.5215e-03, 1.5253e-03, 1.5291e-03, 1.5329e-03, 1.5367e-03, 1.5405e-03, 1.5443e-03, 1.5482e-03, 1.5520e-03, 1.5558e-03, 1.5596e-03, 1.5634e-03, 1.5672e-03, 1.5710e-03, 1.5749e-03, 1.5787e-03, 1.5825e-03, 1.5863e-03, 1.5901e-03, 1.5939e-03, 1.5977e-03, 1.6016e-03, 1.6054e-03, 1.6092e-03, 1.6130e-03, 1.6168e-03, 1.6206e-03, 1.6244e-03, 1.6283e-03, 1.6321e-03, 1.6359e-03, 1.6397e-03, 1.6435e-03, 1.6473e-03, 1.6511e-03, 1.6550e-03, 1.6588e-03, 1.6626e-03, 1.6664e-03, 1.6702e-03, 1.6740e-03, 1.6778e-03, 1.6817e-03, 1.6855e-03, 1.6893e-03, 1.6931e-03, 1.6969e-03, 1.7007e-03, 1.7045e-03, 1.7084e-03, 1.7122e-03, 1.7160e-03, 1.7198e-03, 1.7236e-03, 1.7274e-03, 1.7312e-03, 1.7351e-03, 1.7389e-03, 1.7427e-03, 1.7465e-03, 1.7503e-03, 1.7541e-03, 1.7579e-03, 1.7618e-03, 1.7656e-03, 1.7694e-03, 1.7732e-03, 1.7770e-03, 1.7808e-03, 1.7846e-03, 1.7885e-03, 1.7923e-03, 1.7961e-03, 1.7999e-03, 1.8037e-03, 1.8075e-03, 1.8113e-03, 1.8152e-03, 1.8190e-03, 1.8228e-03, 1.8266e-03, 1.8304e-03, 1.8342e-03, 1.8380e-03, 1.8419e-03, 1.8457e-03, 1.8495e-03, 1.8533e-03, 1.8571e-03, 1.8609e-03, 1.8647e-03, 1.8686e-03, 1.8724e-03, 1.8762e-03, 1.8800e-03, 1.8838e-03, 1.8876e-03, 1.8914e-03, 1.8953e-03, 1.8991e-03, 1.9029e-03, 1.9067e-03, 1.9105e-03, 1.9143e-03, 1.9181e-03, 1.9220e-03, 1.9258e-03, 1.9296e-03, 1.9334e-03, 1.9372e-03, 1.9410e-03, 1.9448e-03, 1.9487e-03, 1.9525e-03, // NOLINT + 1.9601e-03, 1.9677e-03, 1.9754e-03, 1.9830e-03, 1.9906e-03, 1.9982e-03, 2.0059e-03, 2.0135e-03, 2.0211e-03, 2.0288e-03, 2.0364e-03, 2.0440e-03, 2.0516e-03, 2.0593e-03, 2.0669e-03, 2.0745e-03, 2.0822e-03, 2.0898e-03, 2.0974e-03, 2.1050e-03, 2.1127e-03, 2.1203e-03, 2.1279e-03, 2.1356e-03, 2.1432e-03, 2.1508e-03, 2.1585e-03, 2.1661e-03, 2.1737e-03, 2.1813e-03, 2.1890e-03, 2.1966e-03, 2.2042e-03, 2.2119e-03, 2.2195e-03, 2.2271e-03, 2.2347e-03, 2.2424e-03, 2.2500e-03, 2.2576e-03, 2.2653e-03, 2.2729e-03, 2.2805e-03, 2.2881e-03, 2.2958e-03, 2.3034e-03, 2.3110e-03, 2.3187e-03, 2.3263e-03, 2.3339e-03, 2.3415e-03, 2.3492e-03, 2.3568e-03, 2.3644e-03, 2.3721e-03, 2.3797e-03, 2.3873e-03, 2.3949e-03, 2.4026e-03, 2.4102e-03, 2.4178e-03, 2.4255e-03, 2.4331e-03, 2.4407e-03, 2.4483e-03, 2.4560e-03, 2.4636e-03, 2.4712e-03, 2.4789e-03, 2.4865e-03, 2.4941e-03, 2.5018e-03, 2.5094e-03, 2.5170e-03, 2.5246e-03, 2.5323e-03, 2.5399e-03, 2.5475e-03, 2.5552e-03, 2.5628e-03, 2.5704e-03, 2.5780e-03, 2.5857e-03, 2.5933e-03, 2.6009e-03, 2.6086e-03, 2.6162e-03, 2.6238e-03, 2.6314e-03, 2.6391e-03, 2.6467e-03, 2.6543e-03, 2.6620e-03, 2.6696e-03, 2.6772e-03, 2.6848e-03, 2.6925e-03, 2.7001e-03, 2.7077e-03, 2.7154e-03, 2.7230e-03, 2.7306e-03, 2.7382e-03, 2.7459e-03, 2.7535e-03, 2.7611e-03, 2.7688e-03, 2.7764e-03, 2.7840e-03, 2.7917e-03, 2.7993e-03, 2.8069e-03, 2.8145e-03, 2.8222e-03, 2.8298e-03, 2.8374e-03, 2.8451e-03, 2.8527e-03, 2.8603e-03, 2.8679e-03, 2.8756e-03, 2.8832e-03, 2.8908e-03, 2.8985e-03, 2.9061e-03, 2.9137e-03, 2.9213e-03, 2.9290e-03, 2.9366e-03, 2.9442e-03, 2.9519e-03, 2.9595e-03, 2.9671e-03, 2.9747e-03, 2.9824e-03, 2.9900e-03, 2.9976e-03, 3.0053e-03, 3.0129e-03, 3.0205e-03, 3.0281e-03, 3.0358e-03, 3.0434e-03, 3.0510e-03, 3.0587e-03, 3.0663e-03, 3.0739e-03, 3.0816e-03, 3.0892e-03, 3.0968e-03, 3.1044e-03, 3.1121e-03, 3.1197e-03, 3.1273e-03, 3.1350e-03, 3.1426e-03, 3.1502e-03, 3.1578e-03, 3.1655e-03, 3.1731e-03, 3.1807e-03, 3.1884e-03, 3.1960e-03, 3.2036e-03, 3.2112e-03, 3.2189e-03, 3.2265e-03, 3.2341e-03, 3.2418e-03, 3.2494e-03, 3.2570e-03, 3.2646e-03, 3.2723e-03, 3.2799e-03, 3.2875e-03, 3.2952e-03, 3.3028e-03, 3.3104e-03, 3.3180e-03, 3.3257e-03, 3.3333e-03, 3.3409e-03, 3.3486e-03, 3.3562e-03, 3.3638e-03, 3.3715e-03, 3.3791e-03, 3.3867e-03, 3.3943e-03, 3.4020e-03, 3.4096e-03, 3.4172e-03, 3.4249e-03, 3.4325e-03, 3.4401e-03, 3.4477e-03, 3.4554e-03, 3.4630e-03, 3.4706e-03, 3.4783e-03, 3.4859e-03, 3.4935e-03, 3.5011e-03, 3.5088e-03, 3.5164e-03, 3.5240e-03, 3.5317e-03, 3.5393e-03, 3.5469e-03, 3.5545e-03, 3.5622e-03, 3.5698e-03, 3.5774e-03, 3.5851e-03, 3.5927e-03, 3.6003e-03, 3.6079e-03, 3.6156e-03, 3.6232e-03, 3.6308e-03, 3.6385e-03, 3.6461e-03, 3.6537e-03, 3.6613e-03, 3.6690e-03, 3.6766e-03, 3.6842e-03, 3.6919e-03, 3.6995e-03, 3.7071e-03, 3.7148e-03, 3.7224e-03, 3.7300e-03, 3.7376e-03, 3.7453e-03, 3.7529e-03, 3.7605e-03, 3.7682e-03, 3.7758e-03, 3.7834e-03, 3.7910e-03, 3.7987e-03, 3.8063e-03, 3.8139e-03, 3.8216e-03, 3.8292e-03, 3.8368e-03, 3.8444e-03, 3.8521e-03, 3.8597e-03, 3.8673e-03, 3.8750e-03, 3.8826e-03, 3.8902e-03, 3.8978e-03, 3.9055e-03, // NOLINT + 3.9207e-03, 3.9360e-03, 3.9513e-03, 3.9665e-03, 3.9818e-03, 3.9970e-03, 4.0123e-03, 4.0275e-03, 4.0428e-03, 4.0581e-03, 4.0733e-03, 4.0886e-03, 4.1038e-03, 4.1191e-03, 4.1343e-03, 4.1496e-03, 4.1649e-03, 4.1801e-03, 4.1954e-03, 4.2106e-03, 4.2259e-03, 4.2412e-03, 4.2564e-03, 4.2717e-03, 4.2869e-03, 4.3022e-03, 4.3174e-03, 4.3327e-03, 4.3480e-03, 4.3632e-03, 4.3785e-03, 4.3937e-03, 4.4090e-03, 4.4243e-03, 4.4395e-03, 4.4548e-03, 4.4700e-03, 4.4853e-03, 4.5005e-03, 4.5158e-03, 4.5311e-03, 4.5463e-03, 4.5616e-03, 4.5768e-03, 4.5921e-03, 4.6074e-03, 4.6226e-03, 4.6379e-03, 4.6531e-03, 4.6684e-03, 4.6836e-03, 4.6989e-03, 4.7142e-03, 4.7294e-03, 4.7447e-03, 4.7599e-03, 4.7752e-03, 4.7905e-03, 4.8057e-03, 4.8210e-03, 4.8362e-03, 4.8515e-03, 4.8667e-03, 4.8820e-03, 4.8973e-03, 4.9125e-03, 4.9278e-03, 4.9430e-03, 4.9583e-03, 4.9736e-03, 4.9888e-03, 5.0041e-03, 5.0193e-03, 5.0346e-03, 5.0498e-03, 5.0651e-03, 5.0804e-03, 5.0956e-03, 5.1109e-03, 5.1261e-03, 5.1414e-03, 5.1567e-03, 5.1719e-03, 5.1872e-03, 5.2024e-03, 5.2177e-03, 5.2329e-03, 5.2482e-03, 5.2635e-03, 5.2787e-03, 5.2940e-03, 5.3092e-03, 5.3245e-03, 5.3398e-03, 5.3550e-03, 5.3703e-03, 5.3855e-03, 5.4008e-03, 5.4160e-03, 5.4313e-03, 5.4466e-03, 5.4618e-03, 5.4771e-03, 5.4923e-03, 5.5076e-03, 5.5229e-03, 5.5381e-03, 5.5534e-03, 5.5686e-03, 5.5839e-03, 5.5991e-03, 5.6144e-03, 5.6297e-03, 5.6449e-03, 5.6602e-03, 5.6754e-03, 5.6907e-03, 5.7060e-03, 5.7212e-03, 5.7365e-03, 5.7517e-03, 5.7670e-03, 5.7822e-03, 5.7975e-03, 5.8128e-03, 5.8280e-03, 5.8433e-03, 5.8585e-03, 5.8738e-03, 5.8891e-03, 5.9043e-03, 5.9196e-03, 5.9348e-03, 5.9501e-03, 5.9653e-03, 5.9806e-03, 5.9959e-03, 6.0111e-03, 6.0264e-03, 6.0416e-03, 6.0569e-03, 6.0722e-03, 6.0874e-03, 6.1027e-03, 6.1179e-03, 6.1332e-03, 6.1484e-03, 6.1637e-03, 6.1790e-03, 6.1942e-03, 6.2095e-03, 6.2247e-03, 6.2400e-03, 6.2553e-03, 6.2705e-03, 6.2858e-03, 6.3010e-03, 6.3163e-03, 6.3315e-03, 6.3468e-03, 6.3621e-03, 6.3773e-03, 6.3926e-03, 6.4078e-03, 6.4231e-03, 6.4384e-03, 6.4536e-03, 6.4689e-03, 6.4841e-03, 6.4994e-03, 6.5146e-03, 6.5299e-03, 6.5452e-03, 6.5604e-03, 6.5757e-03, 6.5909e-03, 6.6062e-03, 6.6215e-03, 6.6367e-03, 6.6520e-03, 6.6672e-03, 6.6825e-03, 6.6977e-03, 6.7130e-03, 6.7283e-03, 6.7435e-03, 6.7588e-03, 6.7740e-03, 6.7893e-03, 6.8046e-03, 6.8198e-03, 6.8351e-03, 6.8503e-03, 6.8656e-03, 6.8808e-03, 6.8961e-03, 6.9114e-03, 6.9266e-03, 6.9419e-03, 6.9571e-03, 6.9724e-03, 6.9877e-03, 7.0029e-03, 7.0182e-03, 7.0334e-03, 7.0487e-03, 7.0639e-03, 7.0792e-03, 7.0945e-03, 7.1097e-03, 7.1250e-03, 7.1402e-03, 7.1555e-03, 7.1708e-03, 7.1860e-03, 7.2013e-03, 7.2165e-03, 7.2318e-03, 7.2470e-03, 7.2623e-03, 7.2776e-03, 7.2928e-03, 7.3081e-03, 7.3233e-03, 7.3386e-03, 7.3539e-03, 7.3691e-03, 7.3844e-03, 7.3996e-03, 7.4149e-03, 7.4301e-03, 7.4454e-03, 7.4607e-03, 7.4759e-03, 7.4912e-03, 7.5064e-03, 7.5217e-03, 7.5370e-03, 7.5522e-03, 7.5675e-03, 7.5827e-03, 7.5980e-03, 7.6132e-03, 7.6285e-03, 7.6438e-03, 7.6590e-03, 7.6743e-03, 7.6895e-03, 7.7048e-03, 7.7201e-03, 7.7353e-03, 7.7506e-03, 7.7658e-03, 7.7811e-03, 7.7963e-03, 7.8116e-03, // NOLINT + 7.8421e-03, 7.8726e-03, 7.9032e-03, 7.9337e-03, 7.9642e-03, 7.9947e-03, 8.0252e-03, 8.0557e-03, 8.0863e-03, 8.1168e-03, 8.1473e-03, 8.1778e-03, 8.2083e-03, 8.2388e-03, 8.2694e-03, 8.2999e-03, 8.3304e-03, 8.3609e-03, 8.3914e-03, 8.4219e-03, 8.4525e-03, 8.4830e-03, 8.5135e-03, 8.5440e-03, 8.5745e-03, 8.6051e-03, 8.6356e-03, 8.6661e-03, 8.6966e-03, 8.7271e-03, 8.7576e-03, 8.7882e-03, 8.8187e-03, 8.8492e-03, 8.8797e-03, 8.9102e-03, 8.9407e-03, 8.9713e-03, 9.0018e-03, 9.0323e-03, 9.0628e-03, 9.0933e-03, 9.1238e-03, 9.1544e-03, 9.1849e-03, 9.2154e-03, 9.2459e-03, 9.2764e-03, 9.3069e-03, 9.3375e-03, 9.3680e-03, 9.3985e-03, 9.4290e-03, 9.4595e-03, 9.4900e-03, 9.5206e-03, 9.5511e-03, 9.5816e-03, 9.6121e-03, 9.6426e-03, 9.6731e-03, 9.7037e-03, 9.7342e-03, 9.7647e-03, 9.7952e-03, 9.8257e-03, 9.8563e-03, 9.8868e-03, 9.9173e-03, 9.9478e-03, 9.9783e-03, 1.0009e-02, 1.0039e-02, 1.0070e-02, 1.0100e-02, 1.0131e-02, 1.0161e-02, 1.0192e-02, 1.0222e-02, 1.0253e-02, 1.0283e-02, 1.0314e-02, 1.0345e-02, 1.0375e-02, 1.0406e-02, 1.0436e-02, 1.0467e-02, 1.0497e-02, 1.0528e-02, 1.0558e-02, 1.0589e-02, 1.0619e-02, 1.0650e-02, 1.0680e-02, 1.0711e-02, 1.0741e-02, 1.0772e-02, 1.0802e-02, 1.0833e-02, 1.0863e-02, 1.0894e-02, 1.0924e-02, 1.0955e-02, 1.0985e-02, 1.1016e-02, 1.1046e-02, 1.1077e-02, 1.1107e-02, 1.1138e-02, 1.1168e-02, 1.1199e-02, 1.1230e-02, 1.1260e-02, 1.1291e-02, 1.1321e-02, 1.1352e-02, 1.1382e-02, 1.1413e-02, 1.1443e-02, 1.1474e-02, 1.1504e-02, 1.1535e-02, 1.1565e-02, 1.1596e-02, 1.1626e-02, 1.1657e-02, 1.1687e-02, 1.1718e-02, 1.1748e-02, 1.1779e-02, 1.1809e-02, 1.1840e-02, 1.1870e-02, 1.1901e-02, 1.1931e-02, 1.1962e-02, 1.1992e-02, 1.2023e-02, 1.2053e-02, 1.2084e-02, 1.2115e-02, 1.2145e-02, 1.2176e-02, 1.2206e-02, 1.2237e-02, 1.2267e-02, 1.2298e-02, 1.2328e-02, 1.2359e-02, 1.2389e-02, 1.2420e-02, 1.2450e-02, 1.2481e-02, 1.2511e-02, 1.2542e-02, 1.2572e-02, 1.2603e-02, 1.2633e-02, 1.2664e-02, 1.2694e-02, 1.2725e-02, 1.2755e-02, 1.2786e-02, 1.2816e-02, 1.2847e-02, 1.2877e-02, 1.2908e-02, 1.2938e-02, 1.2969e-02, 1.3000e-02, 1.3030e-02, 1.3061e-02, 1.3091e-02, 1.3122e-02, 1.3152e-02, 1.3183e-02, 1.3213e-02, 1.3244e-02, 1.3274e-02, 1.3305e-02, 1.3335e-02, 1.3366e-02, 1.3396e-02, 1.3427e-02, 1.3457e-02, 1.3488e-02, 1.3518e-02, 1.3549e-02, 1.3579e-02, 1.3610e-02, 1.3640e-02, 1.3671e-02, 1.3701e-02, 1.3732e-02, 1.3762e-02, 1.3793e-02, 1.3823e-02, 1.3854e-02, 1.3885e-02, 1.3915e-02, 1.3946e-02, 1.3976e-02, 1.4007e-02, 1.4037e-02, 1.4068e-02, 1.4098e-02, 1.4129e-02, 1.4159e-02, 1.4190e-02, 1.4220e-02, 1.4251e-02, 1.4281e-02, 1.4312e-02, 1.4342e-02, 1.4373e-02, 1.4403e-02, 1.4434e-02, 1.4464e-02, 1.4495e-02, 1.4525e-02, 1.4556e-02, 1.4586e-02, 1.4617e-02, 1.4647e-02, 1.4678e-02, 1.4708e-02, 1.4739e-02, 1.4770e-02, 1.4800e-02, 1.4831e-02, 1.4861e-02, 1.4892e-02, 1.4922e-02, 1.4953e-02, 1.4983e-02, 1.5014e-02, 1.5044e-02, 1.5075e-02, 1.5105e-02, 1.5136e-02, 1.5166e-02, 1.5197e-02, 1.5227e-02, 1.5258e-02, 1.5288e-02, 1.5319e-02, 1.5349e-02, 1.5380e-02, 1.5410e-02, 1.5441e-02, 1.5471e-02, 1.5502e-02, 1.5532e-02, 1.5563e-02, 1.5593e-02, 1.5624e-02, // NOLINT + 1.5746e-02, 1.5868e-02, 1.5990e-02, 1.6112e-02, 1.6234e-02, 1.6356e-02, 1.6478e-02, 1.6601e-02, 1.6723e-02, 1.6845e-02, 1.6967e-02, 1.7089e-02, 1.7211e-02, 1.7333e-02, 1.7455e-02, 1.7577e-02, 1.7699e-02, 1.7821e-02, 1.7943e-02, 1.8065e-02, 1.8187e-02, 1.8310e-02, 1.8432e-02, 1.8554e-02, 1.8676e-02, 1.8798e-02, 1.8920e-02, 1.9042e-02, 1.9164e-02, 1.9286e-02, 1.9408e-02, 1.9530e-02, 1.9652e-02, 1.9774e-02, 1.9896e-02, 2.0018e-02, 2.0141e-02, 2.0263e-02, 2.0385e-02, 2.0507e-02, 2.0629e-02, 2.0751e-02, 2.0873e-02, 2.0995e-02, 2.1117e-02, 2.1239e-02, 2.1361e-02, 2.1483e-02, 2.1605e-02, 2.1727e-02, 2.1850e-02, 2.1972e-02, 2.2094e-02, 2.2216e-02, 2.2338e-02, 2.2460e-02, 2.2582e-02, 2.2704e-02, 2.2826e-02, 2.2948e-02, 2.3070e-02, 2.3192e-02, 2.3314e-02, 2.3436e-02, 2.3558e-02, 2.3681e-02, 2.3803e-02, 2.3925e-02, 2.4047e-02, 2.4169e-02, 2.4291e-02, 2.4413e-02, 2.4535e-02, 2.4657e-02, 2.4779e-02, 2.4901e-02, 2.5023e-02, 2.5145e-02, 2.5267e-02, 2.5390e-02, 2.5512e-02, 2.5634e-02, 2.5756e-02, 2.5878e-02, 2.6000e-02, 2.6122e-02, 2.6244e-02, 2.6366e-02, 2.6488e-02, 2.6610e-02, 2.6732e-02, 2.6854e-02, 2.6976e-02, 2.7099e-02, 2.7221e-02, 2.7343e-02, 2.7465e-02, 2.7587e-02, 2.7709e-02, 2.7831e-02, 2.7953e-02, 2.8075e-02, 2.8197e-02, 2.8319e-02, 2.8441e-02, 2.8563e-02, 2.8685e-02, 2.8807e-02, 2.8930e-02, 2.9052e-02, 2.9174e-02, 2.9296e-02, 2.9418e-02, 2.9540e-02, 2.9662e-02, 2.9784e-02, 2.9906e-02, 3.0028e-02, 3.0150e-02, 3.0272e-02, 3.0394e-02, 3.0516e-02, 3.0639e-02, 3.0761e-02, 3.0883e-02, 3.1005e-02, 3.1127e-02, 3.1249e-02, 3.1493e-02, 3.1737e-02, 3.1981e-02, 3.2225e-02, 3.2470e-02, 3.2714e-02, 3.2958e-02, 3.3202e-02, 3.3446e-02, 3.3690e-02, 3.3934e-02, 3.4179e-02, 3.4423e-02, 3.4667e-02, 3.4911e-02, 3.5155e-02, 3.5399e-02, 3.5643e-02, 3.5888e-02, 3.6132e-02, 3.6376e-02, 3.6620e-02, 3.6864e-02, 3.7108e-02, 3.7352e-02, 3.7596e-02, 3.7841e-02, 3.8085e-02, 3.8329e-02, 3.8573e-02, 3.8817e-02, 3.9061e-02, 3.9305e-02, 3.9550e-02, 3.9794e-02, 4.0038e-02, 4.0282e-02, 4.0526e-02, 4.0770e-02, 4.1014e-02, 4.1259e-02, 4.1503e-02, 4.1747e-02, 4.1991e-02, 4.2235e-02, 4.2479e-02, 4.2723e-02, 4.2968e-02, 4.3212e-02, 4.3456e-02, 4.3700e-02, 4.3944e-02, 4.4188e-02, 4.4432e-02, 4.4677e-02, 4.4921e-02, 4.5165e-02, 4.5409e-02, 4.5653e-02, 4.5897e-02, 4.6141e-02, 4.6386e-02, 4.6630e-02, 4.6874e-02, 4.7118e-02, 4.7362e-02, 4.7606e-02, 4.7850e-02, 4.8095e-02, 4.8339e-02, 4.8583e-02, 4.8827e-02, 4.9071e-02, 4.9315e-02, 4.9559e-02, 4.9803e-02, 5.0048e-02, 5.0292e-02, 5.0536e-02, 5.0780e-02, 5.1024e-02, 5.1268e-02, 5.1512e-02, 5.1757e-02, 5.2001e-02, 5.2245e-02, 5.2489e-02, 5.2733e-02, 5.2977e-02, 5.3221e-02, 5.3466e-02, 5.3710e-02, 5.3954e-02, 5.4198e-02, 5.4442e-02, 5.4686e-02, 5.4930e-02, 5.5175e-02, 5.5419e-02, 5.5663e-02, 5.5907e-02, 5.6151e-02, 5.6395e-02, 5.6639e-02, 5.6884e-02, 5.7128e-02, 5.7372e-02, 5.7616e-02, 5.7860e-02, 5.8104e-02, 5.8348e-02, 5.8593e-02, 5.8837e-02, 5.9081e-02, 5.9325e-02, 5.9569e-02, 5.9813e-02, 6.0057e-02, 6.0301e-02, 6.0546e-02, 6.0790e-02, 6.1034e-02, 6.1278e-02, 6.1522e-02, 6.1766e-02, 6.2010e-02, 6.2255e-02, 6.2499e-02, // NOLINT + 6.2743e-02, 6.2987e-02, 6.3231e-02, 6.3475e-02, 6.3719e-02, 6.3964e-02, 6.4208e-02, 6.4452e-02, 6.4696e-02, 6.4940e-02, 6.5184e-02, 6.5428e-02, 6.5673e-02, 6.5917e-02, 6.6161e-02, 6.6405e-02, 6.6649e-02, 6.6893e-02, 6.7137e-02, 6.7382e-02, 6.7626e-02, 6.7870e-02, 6.8114e-02, 6.8358e-02, 6.8602e-02, 6.8846e-02, 6.9091e-02, 6.9335e-02, 6.9579e-02, 6.9823e-02, 7.0067e-02, 7.0311e-02, 7.0555e-02, 7.0799e-02, 7.1044e-02, 7.1288e-02, 7.1532e-02, 7.1776e-02, 7.2020e-02, 7.2264e-02, 7.2508e-02, 7.2753e-02, 7.2997e-02, 7.3241e-02, 7.3485e-02, 7.3729e-02, 7.3973e-02, 7.4217e-02, 7.4462e-02, 7.4706e-02, 7.4950e-02, 7.5194e-02, 7.5438e-02, 7.5682e-02, 7.5926e-02, 7.6171e-02, 7.6415e-02, 7.6659e-02, 7.6903e-02, 7.7147e-02, 7.7391e-02, 7.7635e-02, 7.7880e-02, 7.8124e-02, 7.8368e-02, 7.8612e-02, 7.8856e-02, 7.9100e-02, 7.9344e-02, 7.9589e-02, 7.9833e-02, 8.0077e-02, 8.0321e-02, 8.0565e-02, 8.0809e-02, 8.1053e-02, 8.1298e-02, 8.1542e-02, 8.1786e-02, 8.2030e-02, 8.2274e-02, 8.2518e-02, 8.2762e-02, 8.3006e-02, 8.3251e-02, 8.3495e-02, 8.3739e-02, 8.3983e-02, 8.4227e-02, 8.4471e-02, 8.4715e-02, 8.4960e-02, 8.5204e-02, 8.5448e-02, 8.5692e-02, 8.5936e-02, 8.6180e-02, 8.6424e-02, 8.6669e-02, 8.6913e-02, 8.7157e-02, 8.7401e-02, 8.7645e-02, 8.7889e-02, 8.8133e-02, 8.8378e-02, 8.8622e-02, 8.8866e-02, 8.9110e-02, 8.9354e-02, 8.9598e-02, 8.9842e-02, 9.0087e-02, 9.0331e-02, 9.0575e-02, 9.0819e-02, 9.1063e-02, 9.1307e-02, 9.1551e-02, 9.1796e-02, 9.2040e-02, 9.2284e-02, 9.2528e-02, 9.2772e-02, 9.3016e-02, 9.3260e-02, 9.3504e-02, 9.3749e-02, 9.4237e-02, 9.4725e-02, 9.5213e-02, 9.5702e-02, 9.6190e-02, 9.6678e-02, 9.7167e-02, 9.7655e-02, 9.8143e-02, 9.8631e-02, 9.9120e-02, 9.9608e-02, 1.0010e-01, 1.0058e-01, 1.0107e-01, 1.0156e-01, 1.0205e-01, 1.0254e-01, 1.0303e-01, 1.0351e-01, 1.0400e-01, 1.0449e-01, 1.0498e-01, 1.0547e-01, 1.0596e-01, 1.0644e-01, 1.0693e-01, 1.0742e-01, 1.0791e-01, 1.0840e-01, 1.0889e-01, 1.0937e-01, 1.0986e-01, 1.1035e-01, 1.1084e-01, 1.1133e-01, 1.1182e-01, 1.1230e-01, 1.1279e-01, 1.1328e-01, 1.1377e-01, 1.1426e-01, 1.1474e-01, 1.1523e-01, 1.1572e-01, 1.1621e-01, 1.1670e-01, 1.1719e-01, 1.1767e-01, 1.1816e-01, 1.1865e-01, 1.1914e-01, 1.1963e-01, 1.2012e-01, 1.2060e-01, 1.2109e-01, 1.2158e-01, 1.2207e-01, 1.2256e-01, 1.2305e-01, 1.2353e-01, 1.2402e-01, 1.2451e-01, 1.2500e-01, 1.2549e-01, 1.2598e-01, 1.2646e-01, 1.2695e-01, 1.2744e-01, 1.2793e-01, 1.2842e-01, 1.2890e-01, 1.2939e-01, 1.2988e-01, 1.3037e-01, 1.3086e-01, 1.3135e-01, 1.3183e-01, 1.3232e-01, 1.3281e-01, 1.3330e-01, 1.3379e-01, 1.3428e-01, 1.3476e-01, 1.3525e-01, 1.3574e-01, 1.3623e-01, 1.3672e-01, 1.3721e-01, 1.3769e-01, 1.3818e-01, 1.3867e-01, 1.3916e-01, 1.3965e-01, 1.4014e-01, 1.4062e-01, 1.4111e-01, 1.4160e-01, 1.4209e-01, 1.4258e-01, 1.4306e-01, 1.4355e-01, 1.4404e-01, 1.4453e-01, 1.4502e-01, 1.4551e-01, 1.4599e-01, 1.4648e-01, 1.4697e-01, 1.4746e-01, 1.4795e-01, 1.4844e-01, 1.4892e-01, 1.4941e-01, 1.4990e-01, 1.5039e-01, 1.5088e-01, 1.5137e-01, 1.5185e-01, 1.5234e-01, 1.5283e-01, 1.5332e-01, 1.5381e-01, 1.5430e-01, 1.5478e-01, 1.5527e-01, 1.5576e-01, 1.5625e-01, // NOLINT + 1.5674e-01, 1.5723e-01, 1.5771e-01, 1.5820e-01, 1.5869e-01, 1.5918e-01, 1.5967e-01, 1.6015e-01, 1.6064e-01, 1.6113e-01, 1.6162e-01, 1.6211e-01, 1.6260e-01, 1.6308e-01, 1.6357e-01, 1.6406e-01, 1.6455e-01, 1.6504e-01, 1.6553e-01, 1.6601e-01, 1.6650e-01, 1.6699e-01, 1.6748e-01, 1.6797e-01, 1.6846e-01, 1.6894e-01, 1.6943e-01, 1.6992e-01, 1.7041e-01, 1.7090e-01, 1.7139e-01, 1.7187e-01, 1.7236e-01, 1.7285e-01, 1.7334e-01, 1.7383e-01, 1.7431e-01, 1.7480e-01, 1.7529e-01, 1.7578e-01, 1.7627e-01, 1.7676e-01, 1.7724e-01, 1.7773e-01, 1.7822e-01, 1.7871e-01, 1.7920e-01, 1.7969e-01, 1.8017e-01, 1.8066e-01, 1.8115e-01, 1.8164e-01, 1.8213e-01, 1.8262e-01, 1.8310e-01, 1.8359e-01, 1.8408e-01, 1.8457e-01, 1.8506e-01, 1.8555e-01, 1.8603e-01, 1.8652e-01, 1.8701e-01, 1.8750e-01, 1.8848e-01, 1.8945e-01, 1.9043e-01, 1.9140e-01, 1.9238e-01, 1.9336e-01, 1.9433e-01, 1.9531e-01, 1.9629e-01, 1.9726e-01, 1.9824e-01, 1.9922e-01, 2.0019e-01, 2.0117e-01, 2.0215e-01, 2.0312e-01, 2.0410e-01, 2.0508e-01, 2.0605e-01, 2.0703e-01, 2.0801e-01, 2.0898e-01, 2.0996e-01, 2.1094e-01, 2.1191e-01, 2.1289e-01, 2.1387e-01, 2.1484e-01, 2.1582e-01, 2.1680e-01, 2.1777e-01, 2.1875e-01, 2.1972e-01, 2.2070e-01, 2.2168e-01, 2.2265e-01, 2.2363e-01, 2.2461e-01, 2.2558e-01, 2.2656e-01, 2.2754e-01, 2.2851e-01, 2.2949e-01, 2.3047e-01, 2.3144e-01, 2.3242e-01, 2.3340e-01, 2.3437e-01, 2.3535e-01, 2.3633e-01, 2.3730e-01, 2.3828e-01, 2.3926e-01, 2.4023e-01, 2.4121e-01, 2.4219e-01, 2.4316e-01, 2.4414e-01, 2.4512e-01, 2.4609e-01, 2.4707e-01, 2.4805e-01, 2.4902e-01, 2.5000e-01, 2.5097e-01, 2.5195e-01, 2.5293e-01, 2.5390e-01, 2.5488e-01, 2.5586e-01, 2.5683e-01, 2.5781e-01, 2.5879e-01, 2.5976e-01, 2.6074e-01, 2.6172e-01, 2.6269e-01, 2.6367e-01, 2.6465e-01, 2.6562e-01, 2.6660e-01, 2.6758e-01, 2.6855e-01, 2.6953e-01, 2.7051e-01, 2.7148e-01, 2.7246e-01, 2.7344e-01, 2.7441e-01, 2.7539e-01, 2.7637e-01, 2.7734e-01, 2.7832e-01, 2.7930e-01, 2.8027e-01, 2.8125e-01, 2.8222e-01, 2.8320e-01, 2.8418e-01, 2.8515e-01, 2.8613e-01, 2.8711e-01, 2.8808e-01, 2.8906e-01, 2.9004e-01, 2.9101e-01, 2.9199e-01, 2.9297e-01, 2.9394e-01, 2.9492e-01, 2.9590e-01, 2.9687e-01, 2.9785e-01, 2.9883e-01, 2.9980e-01, 3.0078e-01, 3.0176e-01, 3.0273e-01, 3.0371e-01, 3.0469e-01, 3.0566e-01, 3.0664e-01, 3.0762e-01, 3.0859e-01, 3.0957e-01, 3.1055e-01, 3.1152e-01, 3.1250e-01, 3.1347e-01, 3.1445e-01, 3.1543e-01, 3.1640e-01, 3.1738e-01, 3.1836e-01, 3.1933e-01, 3.2031e-01, 3.2129e-01, 3.2226e-01, 3.2324e-01, 3.2422e-01, 3.2519e-01, 3.2617e-01, 3.2715e-01, 3.2812e-01, 3.2910e-01, 3.3008e-01, 3.3105e-01, 3.3203e-01, 3.3301e-01, 3.3398e-01, 3.3496e-01, 3.3594e-01, 3.3691e-01, 3.3789e-01, 3.3887e-01, 3.3984e-01, 3.4082e-01, 3.4180e-01, 3.4277e-01, 3.4375e-01, 3.4472e-01, 3.4570e-01, 3.4668e-01, 3.4765e-01, 3.4863e-01, 3.4961e-01, 3.5058e-01, 3.5156e-01, 3.5254e-01, 3.5351e-01, 3.5449e-01, 3.5547e-01, 3.5644e-01, 3.5742e-01, 3.5840e-01, 3.5937e-01, 3.6035e-01, 3.6133e-01, 3.6230e-01, 3.6328e-01, 3.6426e-01, 3.6523e-01, 3.6621e-01, 3.6719e-01, 3.6816e-01, 3.6914e-01, 3.7012e-01, 3.7109e-01, 3.7207e-01, 3.7305e-01, 3.7402e-01, 3.7500e-01, // NOLINT + 3.7695e-01, 3.7890e-01, 3.8086e-01, 3.8281e-01, 3.8476e-01, 3.8672e-01, 3.8867e-01, 3.9062e-01, 3.9258e-01, 3.9453e-01, 3.9648e-01, 3.9844e-01, 4.0039e-01, 4.0234e-01, 4.0430e-01, 4.0625e-01, 4.0820e-01, 4.1015e-01, 4.1211e-01, 4.1406e-01, 4.1601e-01, 4.1797e-01, 4.1992e-01, 4.2187e-01, 4.2383e-01, 4.2578e-01, 4.2773e-01, 4.2969e-01, 4.3164e-01, 4.3359e-01, 4.3555e-01, 4.3750e-01, 4.3945e-01, 4.4140e-01, 4.4336e-01, 4.4531e-01, 4.4726e-01, 4.4922e-01, 4.5117e-01, 4.5312e-01, 4.5508e-01, 4.5703e-01, 4.5898e-01, 4.6094e-01, 4.6289e-01, 4.6484e-01, 4.6680e-01, 4.6875e-01, 4.7070e-01, 4.7265e-01, 4.7461e-01, 4.7656e-01, 4.7851e-01, 4.8047e-01, 4.8242e-01, 4.8437e-01, 4.8633e-01, 4.8828e-01, 4.9023e-01, 4.9219e-01, 4.9414e-01, 4.9609e-01, 4.9805e-01, 5.0000e-01, 5.0195e-01, 5.0390e-01, 5.0586e-01, 5.0781e-01, 5.0976e-01, 5.1172e-01, 5.1367e-01, 5.1562e-01, 5.1758e-01, 5.1953e-01, 5.2148e-01, 5.2344e-01, 5.2539e-01, 5.2734e-01, 5.2930e-01, 5.3125e-01, 5.3320e-01, 5.3515e-01, 5.3711e-01, 5.3906e-01, 5.4101e-01, 5.4297e-01, 5.4492e-01, 5.4687e-01, 5.4883e-01, 5.5078e-01, 5.5273e-01, 5.5469e-01, 5.5664e-01, 5.5859e-01, 5.6055e-01, 5.6250e-01, 5.6445e-01, 5.6640e-01, 5.6836e-01, 5.7031e-01, 5.7226e-01, 5.7422e-01, 5.7617e-01, 5.7812e-01, 5.8008e-01, 5.8203e-01, 5.8398e-01, 5.8594e-01, 5.8789e-01, 5.8984e-01, 5.9180e-01, 5.9375e-01, 5.9570e-01, 5.9765e-01, 5.9961e-01, 6.0156e-01, 6.0351e-01, 6.0547e-01, 6.0742e-01, 6.0937e-01, 6.1133e-01, 6.1328e-01, 6.1523e-01, 6.1719e-01, 6.1914e-01, 6.2109e-01, 6.2305e-01, 6.2500e-01, 6.2695e-01, 6.2890e-01, 6.3086e-01, 6.3281e-01, 6.3476e-01, 6.3672e-01, 6.3867e-01, 6.4062e-01, 6.4258e-01, 6.4453e-01, 6.4648e-01, 6.4844e-01, 6.5039e-01, 6.5234e-01, 6.5430e-01, 6.5625e-01, 6.5820e-01, 6.6015e-01, 6.6211e-01, 6.6406e-01, 6.6601e-01, 6.6797e-01, 6.6992e-01, 6.7187e-01, 6.7383e-01, 6.7578e-01, 6.7773e-01, 6.7969e-01, 6.8164e-01, 6.8359e-01, 6.8554e-01, 6.8750e-01, 6.8945e-01, 6.9140e-01, 6.9336e-01, 6.9531e-01, 6.9726e-01, 6.9922e-01, 7.0117e-01, 7.0312e-01, 7.0508e-01, 7.0703e-01, 7.0898e-01, 7.1094e-01, 7.1289e-01, 7.1484e-01, 7.1679e-01, 7.1875e-01, 7.2070e-01, 7.2265e-01, 7.2461e-01, 7.2656e-01, 7.2851e-01, 7.3047e-01, 7.3242e-01, 7.3437e-01, 7.3633e-01, 7.3828e-01, 7.4023e-01, 7.4219e-01, 7.4414e-01, 7.4609e-01, 7.4804e-01, 7.5000e-01, 7.5390e-01, 7.5781e-01, 7.6172e-01, 7.6562e-01, 7.6953e-01, 7.7344e-01, 7.7734e-01, 7.8125e-01, 7.8515e-01, 7.8906e-01, 7.9297e-01, 7.9687e-01, 8.0078e-01, 8.0469e-01, 8.0859e-01, 8.1250e-01, 8.1640e-01, 8.2031e-01, 8.2422e-01, 8.2812e-01, 8.3203e-01, 8.3594e-01, 8.3984e-01, 8.4375e-01, 8.4765e-01, 8.5156e-01, 8.5547e-01, 8.5937e-01, 8.6328e-01, 8.6719e-01, 8.7109e-01, 8.7500e-01, 8.7890e-01, 8.8281e-01, 8.8672e-01, 8.9062e-01, 8.9453e-01, 8.9844e-01, 9.0234e-01, 9.0625e-01, 9.1015e-01, 9.1406e-01, 9.1797e-01, 9.2187e-01, 9.2578e-01, 9.2969e-01, 9.3359e-01, 9.3750e-01, 9.4140e-01, 9.4531e-01, 9.4922e-01, 9.5312e-01, 9.5703e-01, 9.6094e-01, 9.6484e-01, 9.6875e-01, 9.7265e-01, 9.7656e-01, 9.8047e-01, 9.8437e-01, 9.8828e-01, 9.9219e-01, 9.9609e-01, 1.0000e+00 // NOLINT }; -float4 val4_from_12(uchar8 pvs, float gain) { - uint4 parsed = (uint4)(((uint)pvs.s0<<4) + (pvs.s1>>4), // is from the previous 10 bit - ((uint)pvs.s2<<4) + (pvs.s4&0xF), - ((uint)pvs.s3<<4) + (pvs.s4>>4), - ((uint)pvs.s5<<4) + (pvs.s7&0xF)); - #if IS_OX +float4 normalize_pv(int4 parsed, float vignette_factor) { // PWL - //float4 pv = (convert_float4(parsed) - 64.0) / (4096.0 - 64.0); float4 pv = {ox03c10_lut[parsed.s0], ox03c10_lut[parsed.s1], ox03c10_lut[parsed.s2], ox03c10_lut[parsed.s3]}; - - // it's a 24 bit signal, center in the middle 8 bits - return clamp(pv*gain*256.0, 0.0, 1.0); - #else // AR - // normalize and scale - float4 pv = (convert_float4(parsed) - 168.0) / (4096.0 - 168.0); - return clamp(pv*gain, 0.0, 1.0); - #endif - + return clamp(pv*vignette_factor*256.0, 0.0, 1.0); } -float get_k(float a, float b, float c, float d) { - return 2.0 - (fabs(a - b) + fabs(c - d)); +float3 color_correct(float3 rgb) { + float3 corrected = rgb.x * (float3)(1.5664815, -0.29808738, -0.03973474); + corrected += rgb.y * (float3)(-0.48672447, 1.41914433, -0.40295248); + corrected += rgb.z * (float3)(-0.07975703, -0.12105695, 1.44268722); + return corrected; } -__kernel void debayer10(const __global uchar * in, __global uchar * out) -{ - const int gid_x = get_global_id(0); - const int gid_y = get_global_id(1); - - const int y_top_mod = (gid_y == 0) ? 2: 0; - const int y_bot_mod = (gid_y == (RGB_HEIGHT/2 - 1)) ? 1: 3; - - float3 rgb; - uchar3 rgb_out[4]; - - int start = (2 * gid_y - 1) * FRAME_STRIDE + (3 * gid_x - 2) + (FRAME_STRIDE * FRAME_OFFSET); - - // read in 8x4 chars - uchar8 dat[4]; - dat[0] = vload8(0, in + start + FRAME_STRIDE*y_top_mod); - dat[1] = vload8(0, in + start + FRAME_STRIDE*1); - dat[2] = vload8(0, in + start + FRAME_STRIDE*2); - dat[3] = vload8(0, in + start + FRAME_STRIDE*y_bot_mod); - - // correct vignetting - #if VIGNETTING - int gx = (gid_x*2 - RGB_WIDTH/2); - int gy = (gid_y*2 - RGB_HEIGHT/2); - const float gain = get_vignetting_s(gx*gx + gy*gy); - #else - const float gain = 1.0; - #endif - - // process them to floats - float4 va = val4_from_12(dat[0], gain); - float4 vb = val4_from_12(dat[1], gain); - float4 vc = val4_from_12(dat[2], gain); - float4 vd = val4_from_12(dat[3], gain); - - if (gid_x == 0) { - va.s0 = va.s2; - vb.s0 = vb.s2; - vc.s0 = vc.s2; - vd.s0 = vd.s2; - } else if (gid_x == RGB_WIDTH/2 - 1) { - va.s3 = va.s1; - vb.s3 = vb.s1; - vc.s3 = vc.s1; - vd.s3 = vd.s1; - } - - // a simplified version of https://opensignalprocessingjournal.com/contents/volumes/V6/TOSIGPJ-6-1/TOSIGPJ-6-1.pdf - const float k01 = get_k(va.s0, vb.s1, va.s2, vb.s1); - const float k02 = get_k(va.s2, vb.s1, vc.s2, vb.s1); - const float k03 = get_k(vc.s0, vb.s1, vc.s2, vb.s1); - const float k04 = get_k(va.s0, vb.s1, vc.s0, vb.s1); - rgb.x = (k02*vb.s2+k04*vb.s0)/(k02+k04); // R_G1 - rgb.y = vb.s1; // G1(R) - rgb.z = (k01*va.s1+k03*vc.s1)/(k01+k03); // B_G1 - rgb_out[0] = convert_uchar3_sat(color_correct(clamp(rgb, 0.0, 1.0)) * 255.0); - - const float k11 = get_k(va.s1, vc.s1, va.s3, vc.s3); - const float k12 = get_k(va.s2, vb.s1, vb.s3, vc.s2); - const float k13 = get_k(va.s1, va.s3, vc.s1, vc.s3); - const float k14 = get_k(va.s2, vb.s3, vc.s2, vb.s1); - rgb.x = vb.s2; // R - rgb.y = (k11*(va.s2+vc.s2)*0.5+k13*(vb.s3+vb.s1)*0.5)/(k11+k13); // G_R - rgb.z = (k12*(va.s3+vc.s1)*0.5+k14*(va.s1+vc.s3)*0.5)/(k12+k14); // B_R - rgb_out[1] = convert_uchar3_sat(color_correct(clamp(rgb, 0.0, 1.0)) * 255.0); - - const float k21 = get_k(vb.s0, vd.s0, vb.s2, vd.s2); - const float k22 = get_k(vb.s1, vc.s0, vc.s2, vd.s1); - const float k23 = get_k(vb.s0, vb.s2, vd.s0, vd.s2); - const float k24 = get_k(vb.s1, vc.s2, vd.s1, vc.s0); - rgb.x = (k22*(vb.s2+vd.s0)*0.5+k24*(vb.s0+vd.s2)*0.5)/(k22+k24); // R_B - rgb.y = (k21*(vb.s1+vd.s1)*0.5+k23*(vc.s2+vc.s0)*0.5)/(k21+k23); // G_B - rgb.z = vc.s1; // B - rgb_out[2] = convert_uchar3_sat(color_correct(clamp(rgb, 0.0, 1.0)) * 255.0); - - const float k31 = get_k(vb.s1, vc.s2, vb.s3, vc.s2); - const float k32 = get_k(vb.s3, vc.s2, vd.s3, vc.s2); - const float k33 = get_k(vd.s1, vc.s2, vd.s3, vc.s2); - const float k34 = get_k(vb.s1, vc.s2, vd.s1, vc.s2); - rgb.x = (k31*vb.s2+k33*vd.s2)/(k31+k33); // R_G2 - rgb.y = vc.s2; // G2(B) - rgb.z = (k32*vc.s3+k34*vc.s1)/(k32+k34); // B_G2 - rgb_out[3] = convert_uchar3_sat(color_correct(clamp(rgb, 0.0, 1.0)) * 255.0); - - // write ys - uchar2 yy = (uchar2)( - RGB_TO_Y(rgb_out[0].s0, rgb_out[0].s1, rgb_out[0].s2), - RGB_TO_Y(rgb_out[1].s0, rgb_out[1].s1, rgb_out[1].s2) - ); - vstore2(yy, 0, out + mad24(gid_y * 2, YUV_STRIDE, gid_x * 2)); - yy = (uchar2)( - RGB_TO_Y(rgb_out[2].s0, rgb_out[2].s1, rgb_out[2].s2), - RGB_TO_Y(rgb_out[3].s0, rgb_out[3].s1, rgb_out[3].s2) - ); - vstore2(yy, 0, out + mad24(gid_y * 2 + 1, YUV_STRIDE, gid_x * 2)); - - // write uvs - const short ar = AVERAGE(rgb_out[0].s0, rgb_out[1].s0, rgb_out[2].s0, rgb_out[3].s0); - const short ag = AVERAGE(rgb_out[0].s1, rgb_out[1].s1, rgb_out[2].s1, rgb_out[3].s1); - const short ab = AVERAGE(rgb_out[0].s2, rgb_out[1].s2, rgb_out[2].s2, rgb_out[3].s2); - uchar2 uv = (uchar2)( - RGB_TO_U(ar, ag, ab), - RGB_TO_V(ar, ag, ab) - ); - vstore2(uv, 0, out + UV_OFFSET + mad24(gid_y, YUV_STRIDE, gid_x * 2)); +float3 apply_gamma(float3 rgb, int expo_time) { + return -0.507089*exp(-12.54124638*rgb) + 0.9655*powr(rgb, 0.5) - 0.472597*rgb + 0.507089; } + +#endif \ No newline at end of file diff --git a/system/camerad/sensors/sensor.h b/system/camerad/sensors/sensor.h index 4e2194d914..add514b117 100644 --- a/system/camerad/sensors/sensor.h +++ b/system/camerad/sensors/sensor.h @@ -12,10 +12,6 @@ #include "system/camerad/sensors/os04c10_registers.h" #define ANALOG_GAIN_MAX_CNT 55 -const size_t FRAME_WIDTH = 1928; -const size_t FRAME_HEIGHT = 1208; -const size_t FRAME_STRIDE = 2896; // for 12 bit output. 1928 * 12 / 8 + 4 (alignment) - class SensorInfo { public: @@ -26,12 +22,14 @@ public: virtual void processRegisters(CameraState *c, cereal::FrameData::Builder &framed) const {} cereal::FrameData::ImageSensor image_sensor = cereal::FrameData::ImageSensor::UNKNOWN; + float pixel_size_mm; uint32_t frame_width, frame_height; uint32_t frame_stride; uint32_t frame_offset = 0; uint32_t extra_height = 0; int registers_offset = -1; int stats_offset = -1; + int hdr_offset = -1; int exposure_time_min; int exposure_time_max; diff --git a/system/camerad/snapshot/snapshot.py b/system/camerad/snapshot/snapshot.py index 8c1b6084c7..4ba38c1df4 100755 --- a/system/camerad/snapshot/snapshot.py +++ b/system/camerad/snapshot/snapshot.py @@ -6,12 +6,12 @@ import numpy as np from PIL import Image import cereal.messaging as messaging -from cereal.visionipc import VisionIpcClient, VisionStreamType +from msgq.visionipc import VisionIpcClient, VisionStreamType from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL from openpilot.system.hardware import PC from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes VISION_STREAMS = { @@ -83,7 +83,7 @@ def snapshot(): front_camera_allowed = params.get_bool("RecordFront") params.put_bool("IsTakingSnapshot", True) set_offroad_alert("Offroad_IsTakingSnapshot", True) - time.sleep(2.0) # Give thermald time to read the param, or if just started give camerad time to start + time.sleep(2.0) # Give hardwared time to read the param, or if just started give camerad time to start # Check if camerad is already started try: diff --git a/system/camerad/test/test_ae_gray.cc b/system/camerad/test/test_ae_gray.cc index 06d784927a..be9a0cc59f 100644 --- a/system/camerad/test/test_ae_gray.cc +++ b/system/camerad/test/test_ae_gray.cc @@ -36,6 +36,7 @@ TEST_CASE("camera.test_set_exposure_target") { cb.cur_yuv_buf = &vb; cb.rgb_width = W; cb.rgb_height = H; + Rect rect = {0, 0, W-1, H-1}; printf("AE test patterns %dx%d\n", cb.rgb_width, cb.rgb_height); @@ -60,7 +61,7 @@ TEST_CASE("camera.test_set_exposure_target") { memset(&fb_y[h_0*W+h_1*W], l[2], h_2*W); memset(&fb_y[h_0*W+h_1*W+h_2*W], l[3], h_3*W); memset(&fb_y[h_0*W+h_1*W+h_2*W+h_3*W], l[4], h_4*W); - float ev = set_exposure_target((const CameraBuf*) &cb, 0, W-1, 1, 0, H-1, 1); + float ev = set_exposure_target((const CameraBuf*) &cb, rect, 1, 1); // printf("%d/%d/%d/%d/%d ev is %f\n", h_0, h_1, h_2, h_3, h_4, ev); // printf("%f\n", ev); diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py old mode 100755 new mode 100644 index 408115607e..ada9594895 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import pytest import time import numpy as np @@ -8,7 +7,7 @@ from collections import defaultdict import cereal.messaging as messaging from cereal import log from cereal.services import SERVICE_LIST -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes TEST_TIMESPAN = 30 LAG_FRAME_TOLERANCE = {log.FrameData.ImageSensor.ar0231: 0.5, # ARs use synced pulses for frame starts diff --git a/system/camerad/test/test_exposure.py b/system/camerad/test/test_exposure.py old mode 100755 new mode 100644 index 50467f9db4..dbe6f3d880 --- a/system/camerad/test/test_exposure.py +++ b/system/camerad/test/test_exposure.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 import time -import unittest import numpy as np from openpilot.selfdrive.test.helpers import with_processes, phone_only @@ -9,9 +7,9 @@ from openpilot.system.camerad.snapshot.snapshot import get_snapshots TEST_TIME = 45 REPEAT = 5 -class TestCamerad(unittest.TestCase): +class TestCamerad: @classmethod - def setUpClass(cls): + def setup_class(cls): pass def _numpy_rgb2gray(self, im): @@ -49,7 +47,4 @@ class TestCamerad(unittest.TestCase): passed += int(res) time.sleep(2) - self.assertGreaterEqual(passed, REPEAT) - -if __name__ == "__main__": - unittest.main() + assert passed >= REPEAT diff --git a/system/hardware/base.h b/system/hardware/base.h index dc2282a93a..ca24633a18 100644 --- a/system/hardware/base.h +++ b/system/hardware/base.h @@ -5,7 +5,7 @@ #include #include -#include "cereal/messaging/messaging.h" +#include "cereal/gen/cpp/log.capnp.h" // no-op base hw class class HardwareNone { diff --git a/selfdrive/thermald/fan_controller.py b/system/hardware/fan_controller.py similarity index 94% rename from selfdrive/thermald/fan_controller.py rename to system/hardware/fan_controller.py index 19c3292ce2..f32133f6bf 100755 --- a/selfdrive/thermald/fan_controller.py +++ b/system/hardware/fan_controller.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from abc import ABC, abstractmethod -from openpilot.common.realtime import DT_TRML +from openpilot.common.realtime import DT_HW from openpilot.common.numpy_fast import interp from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.controls.lib.pid import PIDController @@ -18,7 +18,7 @@ class TiciFanController(BaseFanController): cloudlog.info("Setting up TICI fan handler") self.last_ignition = False - self.controller = PIDController(k_p=0, k_i=4e-3, k_f=1, rate=(1 / DT_TRML)) + self.controller = PIDController(k_p=0, k_i=4e-3, k_f=1, rate=(1 / DT_HW)) def update(self, cur_temp: float, ignition: bool) -> int: self.controller.neg_limit = -(100 if ignition else 30) diff --git a/selfdrive/thermald/thermald.py b/system/hardware/hardwared.py similarity index 94% rename from selfdrive/thermald/thermald.py rename to system/hardware/hardwared.py index 93ebd3ab87..e3a4c81711 100755 --- a/selfdrive/thermald/thermald.py +++ b/system/hardware/hardwared.py @@ -15,14 +15,14 @@ from cereal.services import SERVICE_LIST from openpilot.common.dict_helpers import strip_deprecated_keys from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params -from openpilot.common.realtime import DT_TRML +from openpilot.common.realtime import DT_HW from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert from openpilot.system.hardware import HARDWARE, TICI, AGNOS from openpilot.system.loggerd.config import get_available_percent -from openpilot.selfdrive.statsd import statlog +from openpilot.system.statsd import statlog from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.thermald.power_monitoring import PowerMonitoring -from openpilot.selfdrive.thermald.fan_controller import TiciFanController +from openpilot.system.hardware.power_monitoring import PowerMonitoring +from openpilot.system.hardware.fan_controller import TiciFanController from openpilot.system.version import terms_version, training_version ThermalStatus = log.DeviceState.ThermalStatus @@ -106,7 +106,7 @@ def hw_state_thread(end_event, hw_queue): while not end_event.is_set(): # these are expensive calls. update every 10s - if (count % int(10. / DT_TRML)) == 0: + if (count % int(10. / DT_HW)) == 0: try: network_type = HARDWARE.get_network_type() modem_temps = HARDWARE.get_modem_temperatures() @@ -159,10 +159,10 @@ def hw_state_thread(end_event, hw_queue): cloudlog.exception("Error getting hardware state") count += 1 - time.sleep(DT_TRML) + time.sleep(DT_HW) -def thermald_thread(end_event, hw_queue) -> None: +def hardware_thread(end_event, hw_queue) -> None: pm = messaging.PubMaster(['deviceState']) sm = messaging.SubMaster(["peripheralState", "gpsLocationExternal", "controlsState", "pandaStates"], poll="pandaStates") @@ -190,8 +190,8 @@ def thermald_thread(end_event, hw_queue) -> None: modem_temps=[], ) - all_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML, initialized=False) - offroad_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML, initialized=False) + all_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_HW, initialized=False) + offroad_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_HW, initialized=False) should_start_prev = False in_car = False engaged_prev = False @@ -207,16 +207,10 @@ def thermald_thread(end_event, hw_queue) -> None: while not end_event.is_set(): sm.update(PANDA_STATES_TIMEOUT) - # Run at 2Hz - if sm.frame % round(SERVICE_LIST['pandaStates'].frequency * DT_TRML) != 0: - continue - pandaStates = sm['pandaStates'] peripheralState = sm['peripheralState'] peripheral_panda_present = peripheralState.pandaType != log.PandaState.PandaType.unknown - msg = read_thermal(thermal_config) - if sm.updated['pandaStates'] and len(pandaStates) > 0: # Set ignition based on any panda connected @@ -236,6 +230,14 @@ def thermald_thread(end_event, hw_queue) -> None: onroad_conditions["ignition"] = False cloudlog.error("panda timed out onroad") + # Run at 2Hz, plus rising edge of ignition + ign_edge = started_ts is None and onroad_conditions["ignition"] + if (sm.frame % round(SERVICE_LIST['pandaStates'].frequency * DT_HW) != 0) and not ign_edge: + continue + + msg = read_thermal(thermal_config) + msg.deviceState.deviceType = HARDWARE.get_device_type() + try: last_hw_state = hw_queue.get_nowait() except queue.Empty: @@ -347,7 +349,7 @@ def thermald_thread(end_event, hw_queue) -> None: try: with open('/dev/kmsg', 'w') as kmsg: - kmsg.write(f"<3>[thermald] engaged: {engaged}\n") + kmsg.write(f"<3>[hardware] engaged: {engaged}\n") except Exception: pass @@ -421,7 +423,7 @@ def thermald_thread(end_event, hw_queue) -> None: # report to server once every 10 minutes rising_edge_started = should_start and not should_start_prev - if rising_edge_started or (count % int(600. / DT_TRML)) == 0: + if rising_edge_started or (count % int(600. / DT_HW)) == 0: dat = { 'count': count, 'pandaStates': [strip_deprecated_keys(p.to_dict()) for p in pandaStates], @@ -450,7 +452,7 @@ def main(): threads = [ threading.Thread(target=hw_state_thread, args=(end_event, hw_queue)), - threading.Thread(target=thermald_thread, args=(end_event, hw_queue)), + threading.Thread(target=hardware_thread, args=(end_event, hw_queue)), ] for t in threads: diff --git a/selfdrive/thermald/power_monitoring.py b/system/hardware/power_monitoring.py similarity index 99% rename from selfdrive/thermald/power_monitoring.py rename to system/hardware/power_monitoring.py index 073589edb7..5a94625b48 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/system/hardware/power_monitoring.py @@ -4,7 +4,7 @@ import threading from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.statsd import statlog +from openpilot.system.statsd import statlog CAR_VOLTAGE_LOW_PASS_K = 0.011 # LPF gain for 45s tau (dt/tau / (dt/tau + 1)) diff --git a/selfdrive/manager/__init__.py b/system/hardware/tests/__init__.py similarity index 100% rename from selfdrive/manager/__init__.py rename to system/hardware/tests/__init__.py diff --git a/system/hardware/tests/test_fan_controller.py b/system/hardware/tests/test_fan_controller.py new file mode 100644 index 0000000000..002c1edfda --- /dev/null +++ b/system/hardware/tests/test_fan_controller.py @@ -0,0 +1,50 @@ +import pytest + +from openpilot.system.hardware.fan_controller import TiciFanController + +ALL_CONTROLLERS = [TiciFanController] + +def patched_controller(mocker, controller_class): + mocker.patch("os.system", new=mocker.Mock()) + return controller_class() + +class TestFanController: + def wind_up(self, controller, ignition=True): + for _ in range(1000): + controller.update(100, ignition) + + def wind_down(self, controller, ignition=False): + for _ in range(1000): + controller.update(10, ignition) + + @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS) + def test_hot_onroad(self, mocker, controller_class): + controller = patched_controller(mocker, controller_class) + self.wind_up(controller) + assert controller.update(100, True) >= 70 + + @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS) + def test_offroad_limits(self, mocker, controller_class): + controller = patched_controller(mocker, controller_class) + self.wind_up(controller) + assert controller.update(100, False) <= 30 + + @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS) + def test_no_fan_wear(self, mocker, controller_class): + controller = patched_controller(mocker, controller_class) + self.wind_down(controller) + assert controller.update(10, False) == 0 + + @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS) + def test_limited(self, mocker, controller_class): + controller = patched_controller(mocker, controller_class) + self.wind_up(controller, True) + assert controller.update(100, True) == 100 + + @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS) + def test_windup_speed(self, mocker, controller_class): + controller = patched_controller(mocker, controller_class) + self.wind_down(controller, True) + for _ in range(10): + controller.update(90, True) + assert controller.update(90, True) >= 60 diff --git a/system/hardware/tests/test_power_monitoring.py b/system/hardware/tests/test_power_monitoring.py new file mode 100644 index 0000000000..1dff6c6c5f --- /dev/null +++ b/system/hardware/tests/test_power_monitoring.py @@ -0,0 +1,199 @@ +import pytest + +from openpilot.common.params import Params +from openpilot.system.hardware.power_monitoring import PowerMonitoring, CAR_BATTERY_CAPACITY_uWh, \ + CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S + +# Create fake time +ssb = 0. +def mock_time_monotonic(): + global ssb + ssb += 1. + return ssb + +TEST_DURATION_S = 50 +GOOD_VOLTAGE = 12 * 1e3 +VOLTAGE_BELOW_PAUSE_CHARGING = (VBATT_PAUSE_CHARGING - 1) * 1e3 + +def pm_patch(mocker, name, value, constant=False): + if constant: + mocker.patch(f"openpilot.system.hardware.power_monitoring.{name}", value) + else: + mocker.patch(f"openpilot.system.hardware.power_monitoring.{name}", return_value=value) + + +@pytest.fixture(autouse=True) +def mock_time(mocker): + mocker.patch("time.monotonic", mock_time_monotonic) + + +class TestPowerMonitoring: + def setup_method(self): + self.params = Params() + + # Test to see that it doesn't do anything when pandaState is None + def test_panda_state_present(self): + pm = PowerMonitoring() + for _ in range(10): + pm.calculate(None, None) + assert pm.get_power_used() == 0 + assert pm.get_car_battery_capacity() == (CAR_BATTERY_CAPACITY_uWh / 10) + + # Test to see that it doesn't integrate offroad when ignition is True + def test_offroad_ignition(self): + pm = PowerMonitoring() + for _ in range(10): + pm.calculate(GOOD_VOLTAGE, True) + assert pm.get_power_used() == 0 + + # Test to see that it integrates with discharging battery + def test_offroad_integration_discharging(self, mocker): + POWER_DRAW = 4 + pm_patch(mocker, "HARDWARE.get_current_power_draw", POWER_DRAW) + pm = PowerMonitoring() + for _ in range(TEST_DURATION_S + 1): + pm.calculate(GOOD_VOLTAGE, False) + expected_power_usage = ((TEST_DURATION_S/3600) * POWER_DRAW * 1e6) + assert abs(pm.get_power_used() - expected_power_usage) < 10 + + # Test to check positive integration of car_battery_capacity + def test_car_battery_integration_onroad(self, mocker): + POWER_DRAW = 4 + pm_patch(mocker, "HARDWARE.get_current_power_draw", POWER_DRAW) + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = 0 + for _ in range(TEST_DURATION_S + 1): + pm.calculate(GOOD_VOLTAGE, True) + expected_capacity = ((TEST_DURATION_S/3600) * CAR_CHARGING_RATE_W * 1e6) + assert abs(pm.get_car_battery_capacity() - expected_capacity) < 10 + + # Test to check positive integration upper limit + def test_car_battery_integration_upper_limit(self, mocker): + POWER_DRAW = 4 + pm_patch(mocker, "HARDWARE.get_current_power_draw", POWER_DRAW) + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh - 1000 + for _ in range(TEST_DURATION_S + 1): + pm.calculate(GOOD_VOLTAGE, True) + estimated_capacity = CAR_BATTERY_CAPACITY_uWh + (CAR_CHARGING_RATE_W / 3600 * 1e6) + assert abs(pm.get_car_battery_capacity() - estimated_capacity) < 10 + + # Test to check negative integration of car_battery_capacity + def test_car_battery_integration_offroad(self, mocker): + POWER_DRAW = 4 + pm_patch(mocker, "HARDWARE.get_current_power_draw", POWER_DRAW) + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh + for _ in range(TEST_DURATION_S + 1): + pm.calculate(GOOD_VOLTAGE, False) + expected_capacity = CAR_BATTERY_CAPACITY_uWh - ((TEST_DURATION_S/3600) * POWER_DRAW * 1e6) + assert abs(pm.get_car_battery_capacity() - expected_capacity) < 10 + + # Test to check negative integration lower limit + def test_car_battery_integration_lower_limit(self, mocker): + POWER_DRAW = 4 + pm_patch(mocker, "HARDWARE.get_current_power_draw", POWER_DRAW) + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = 1000 + for _ in range(TEST_DURATION_S + 1): + pm.calculate(GOOD_VOLTAGE, False) + estimated_capacity = 0 - ((1/3600) * POWER_DRAW * 1e6) + assert abs(pm.get_car_battery_capacity() - estimated_capacity) < 10 + + # Test to check policy of stopping charging after MAX_TIME_OFFROAD_S + def test_max_time_offroad(self, mocker): + MOCKED_MAX_OFFROAD_TIME = 3600 + POWER_DRAW = 0 # To stop shutting down for other reasons + pm_patch(mocker, "MAX_TIME_OFFROAD_S", MOCKED_MAX_OFFROAD_TIME, constant=True) + pm_patch(mocker, "HARDWARE.get_current_power_draw", POWER_DRAW) + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh + start_time = ssb + ignition = False + while ssb <= start_time + MOCKED_MAX_OFFROAD_TIME: + pm.calculate(GOOD_VOLTAGE, ignition) + if (ssb - start_time) % 1000 == 0 and ssb < start_time + MOCKED_MAX_OFFROAD_TIME: + assert not pm.should_shutdown(ignition, True, start_time, False) + assert pm.should_shutdown(ignition, True, start_time, False) + + def test_car_voltage(self, mocker): + POWER_DRAW = 0 # To stop shutting down for other reasons + TEST_TIME = 350 + VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 50 + pm_patch(mocker, "VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S", VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S, constant=True) + pm_patch(mocker, "HARDWARE.get_current_power_draw", POWER_DRAW) + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh + ignition = False + start_time = ssb + for i in range(TEST_TIME): + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) + if i % 10 == 0: + assert pm.should_shutdown(ignition, True, start_time, True) == \ + (pm.car_voltage_mV < VBATT_PAUSE_CHARGING * 1e3 and \ + (ssb - start_time) > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S and \ + (ssb - start_time) > DELAY_SHUTDOWN_TIME_S) + assert pm.should_shutdown(ignition, True, start_time, True) + + # Test to check policy of not stopping charging when DisablePowerDown is set + def test_disable_power_down(self, mocker): + POWER_DRAW = 0 # To stop shutting down for other reasons + TEST_TIME = 100 + self.params.put_bool("DisablePowerDown", True) + pm_patch(mocker, "HARDWARE.get_current_power_draw", POWER_DRAW) + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh + ignition = False + for i in range(TEST_TIME): + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) + if i % 10 == 0: + assert not pm.should_shutdown(ignition, True, ssb, False) + assert not pm.should_shutdown(ignition, True, ssb, False) + + # Test to check policy of not stopping charging when ignition + def test_ignition(self, mocker): + POWER_DRAW = 0 # To stop shutting down for other reasons + TEST_TIME = 100 + pm_patch(mocker, "HARDWARE.get_current_power_draw", POWER_DRAW) + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh + ignition = True + for i in range(TEST_TIME): + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) + if i % 10 == 0: + assert not pm.should_shutdown(ignition, True, ssb, False) + assert not pm.should_shutdown(ignition, True, ssb, False) + + # Test to check policy of not stopping charging when harness is not connected + def test_harness_connection(self, mocker): + POWER_DRAW = 0 # To stop shutting down for other reasons + TEST_TIME = 100 + pm_patch(mocker, "HARDWARE.get_current_power_draw", POWER_DRAW) + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh + + ignition = False + for i in range(TEST_TIME): + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) + if i % 10 == 0: + assert not pm.should_shutdown(ignition, False, ssb, False) + assert not pm.should_shutdown(ignition, False, ssb, False) + + def test_delay_shutdown_time(self): + pm = PowerMonitoring() + pm.car_battery_capacity_uWh = 0 + ignition = False + in_car = True + offroad_timestamp = ssb + started_seen = True + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) + + while ssb < offroad_timestamp + DELAY_SHUTDOWN_TIME_S: + assert not pm.should_shutdown(ignition, in_car, + offroad_timestamp, + started_seen), \ + f"Should not shutdown before {DELAY_SHUTDOWN_TIME_S} seconds offroad time" + assert pm.should_shutdown(ignition, in_car, + offroad_timestamp, + started_seen), \ + f"Should shutdown after {DELAY_SHUTDOWN_TIME_S} seconds offroad time" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index e69842cfec..b1862bdef9 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -1,10 +1,10 @@ [ { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-f0de74e139b8b99224738d4e72a5b1831758f20b09ff6bb28f3aaaae1c4c1ebe.img.xz", - "hash": "f0de74e139b8b99224738d4e72a5b1831758f20b09ff6bb28f3aaaae1c4c1ebe", - "hash_raw": "f0de74e139b8b99224738d4e72a5b1831758f20b09ff6bb28f3aaaae1c4c1ebe", - "size": 15636480, + "url": "https://commadist.azureedge.net/agnosupdate/boot-5674ea6767e7198cf1e7def3de66a57061f001ed76d43dc4b4f84de545c53c6f.img.xz", + "hash": "5674ea6767e7198cf1e7def3de66a57061f001ed76d43dc4b4f84de545c53c6f", + "hash_raw": "5674ea6767e7198cf1e7def3de66a57061f001ed76d43dc4b4f84de545c53c6f", + "size": 16029696, "sparse": false, "full_check": true, "has_ab": true @@ -61,17 +61,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-0f69173d5f3058f7197c139442a6556be59e52f15402a263215a329ba5ec41e2.img.xz", - "hash": "4858385ba6284bcaa179ab77ac4263486e4d8670df921e4ac400464dc1dde59c", - "hash_raw": "0f69173d5f3058f7197c139442a6556be59e52f15402a263215a329ba5ec41e2", + "url": "https://commadist.azureedge.net/agnosupdate/system-1badfe72851628d6cf9200a53a6151bb4e797b49c717141409fc57138eae388a.img.xz", + "hash": "328e90c62068222dfd98f71dd3f6251fcb962f082b49c6be66ab2699f5db6f4f", + "hash_raw": "1badfe72851628d6cf9200a53a6151bb4e797b49c717141409fc57138eae388a", "size": 10737418240, "sparse": true, "full_check": false, "has_ab": true, "alt": { - "hash": "42658a6fff660d9b6abb9cb9fbb3481071259c9a9598718af6b1edff2b556009", - "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-0f69173d5f3058f7197c139442a6556be59e52f15402a263215a329ba5ec41e2.img.xz", - "size": 4548292756 + "hash": "bc11d2148f29862ee1326aca2af1cf6bbf5fed831e3f8f6b8f7a0f110dfe8d26", + "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-1badfe72851628d6cf9200a53a6151bb4e797b49c717141409fc57138eae388a.img.xz", + "size": 4548070000 } } ] \ No newline at end of file diff --git a/system/hardware/tici/agnos.py b/system/hardware/tici/agnos.py index 502295be07..7e3536f775 100755 --- a/system/hardware/tici/agnos.py +++ b/system/hardware/tici/agnos.py @@ -10,11 +10,13 @@ from collections.abc import Generator import requests -import openpilot.system.hardware.tici.casync as casync +import openpilot.system.updated.casync.casync as casync SPARSE_CHUNK_FMT = struct.Struct('H2xI4x') CAIBX_URL = "https://commadist.azureedge.net/agnosupdate/" +AGNOS_MANIFEST_FILE = "system/hardware/tici/agnos.json" + class StreamingDecompressor: def __init__(self, url: str) -> None: diff --git a/system/hardware/tici/amplifier.py b/system/hardware/tici/amplifier.py index 5725cb8170..af82067467 100755 --- a/system/hardware/tici/amplifier.py +++ b/system/hardware/tici/amplifier.py @@ -97,7 +97,6 @@ CONFIGS = { AmpConfig("Right DAC input mixer: DAI2 right", 0b1, 0x22, 0, 0b00000001), AmpConfig("Volume adjustment smoothing disabled", 0b1, 0x49, 6, 0b01000000), ], - "mici": [], } class Amplifier: diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 5bb1032ba9..a5dee88b56 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -94,11 +94,7 @@ def get_device_type(): # lru_cache and cache can cause memory leaks when used in classes with open("/sys/firmware/devicetree/base/model") as f: model = f.read().strip('\x00') - model = model.split('comma ')[-1] - # TODO: remove this with AGNOS 7+ - if model.startswith('Qualcomm'): - model = 'tici' - return model + return model.split('comma ')[-1] class Tici(HardwareBase): @cached_property @@ -116,6 +112,8 @@ class Tici(HardwareBase): @cached_property def amplifier(self): + if self.get_device_type() == "mici": + return None return Amplifier() def get_os_version(self): @@ -374,29 +372,26 @@ class Tici(HardwareBase): def set_power_save(self, powersave_enabled): # amplifier, 100mW at idle - self.amplifier.set_global_shutdown(amp_disabled=powersave_enabled) - if not powersave_enabled: - self.amplifier.initialize_configuration(self.get_device_type()) + if self.amplifier is not None: + self.amplifier.set_global_shutdown(amp_disabled=powersave_enabled) + if not powersave_enabled: + self.amplifier.initialize_configuration(self.get_device_type()) # *** CPU config *** - # offline big cluster, leave core 4 online for boardd - for i in range(5, 8): + # offline big cluster, leave core 4 online for pandad + for i in range(4, 8): val = '0' if powersave_enabled else '1' sudo_write(val, f'/sys/devices/system/cpu/cpu{i}/online') for n in ('0', '4'): + if powersave_enabled and n == '4': + continue gov = 'ondemand' if powersave_enabled else 'performance' sudo_write(gov, f'/sys/devices/system/cpu/cpufreq/policy{n}/scaling_governor') # *** IRQ config *** - # boardd core - affine_irq(4, "spi_geni") # SPI - affine_irq(4, "xhci-hcd:usb3") # aux panda USB (or potentially anything else on USB) - if "tici" in self.get_device_type(): - affine_irq(4, "xhci-hcd:usb1") # internal panda USB (also modem) - # GPU affine_irq(5, "kgsl-3d0") @@ -414,9 +409,10 @@ class Tici(HardwareBase): return 0 def initialize_hardware(self): - self.amplifier.initialize_configuration(self.get_device_type()) + if self.amplifier is not None: + self.amplifier.initialize_configuration(self.get_device_type()) - # Allow thermald to write engagement status to kmsg + # Allow hardwared to write engagement status to kmsg os.system("sudo chmod a+w /dev/kmsg") # Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL @@ -453,6 +449,18 @@ class Tici(HardwareBase): sudo_write("N", "/sys/kernel/debug/msm_vidc/clock_scaling") sudo_write("Y", "/sys/kernel/debug/msm_vidc/disable_thermal_mitigation") + # pandad core + affine_irq(3, "spi_geni") # SPI + if "tici" in self.get_device_type(): + affine_irq(3, "xhci-hcd:usb3") # aux panda USB (or potentially anything else on USB) + affine_irq(3, "xhci-hcd:usb1") # internal panda USB (also modem) + try: + pid = subprocess.check_output(["pgrep", "-f", "spi0"], encoding='utf8').strip() + subprocess.call(["sudo", "chrt", "-f", "-p", "1", pid]) + subprocess.call(["sudo", "taskset", "-pc", "3", pid]) + except subprocess.CalledProcessException as e: + print(str(e)) + def configure_modem(self): sim_id = self.get_sim_info().get('sim_id', '') @@ -468,8 +476,9 @@ class Tici(HardwareBase): # use sim slot 'AT^SIMSWAP=1', - # configure ECM mode - 'AT$QCPCFG=usbNet,1' + # ethernet config + 'AT$QCPCFG=usbNet,0', + 'AT$QCNETDEVCTL=3,1', ] else: cmds += [ @@ -478,6 +487,12 @@ class Tici(HardwareBase): 'AT+QNVFW="/nv/item_files/ims/IMS_enable",00', 'AT+QNVFW="/nv/item_files/modem/mmode/ue_usage_setting",01', ] + if self.get_device_type() == "tizi": + cmds += [ + # SIM hot swap + 'AT+QSIMDET=1,0', + 'AT+QSIMSTAT=1', + ] # clear out old blue prime initial APN os.system('mmcli -m any --3gpp-set-initial-eps-bearer-settings="apn="') diff --git a/system/hardware/tici/tests/test_agnos_updater.py b/system/hardware/tici/tests/test_agnos_updater.py old mode 100755 new mode 100644 index 86bc78881e..a1bbd363fd --- a/system/hardware/tici/tests/test_agnos_updater.py +++ b/system/hardware/tici/tests/test_agnos_updater.py @@ -1,14 +1,12 @@ -#!/usr/bin/env python3 import json import os -import unittest import requests TEST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__))) MANIFEST = os.path.join(TEST_DIR, "../agnos.json") -class TestAgnosUpdater(unittest.TestCase): +class TestAgnosUpdater: def test_manifest(self): with open(MANIFEST) as f: @@ -17,10 +15,6 @@ class TestAgnosUpdater(unittest.TestCase): for img in m: r = requests.head(img['url'], timeout=10) r.raise_for_status() - self.assertEqual(r.headers['Content-Type'], "application/x-xz") + assert r.headers['Content-Type'] == "application/x-xz" if not img['sparse']: assert img['hash'] == img['hash_raw'] - - -if __name__ == "__main__": - unittest.main() diff --git a/system/hardware/tici/tests/test_amplifier.py b/system/hardware/tici/tests/test_amplifier.py old mode 100755 new mode 100644 index cd3b0f90fe..3f75436db1 --- a/system/hardware/tici/tests/test_amplifier.py +++ b/system/hardware/tici/tests/test_amplifier.py @@ -1,7 +1,6 @@ -#!/usr/bin/env python3 +import pytest import time import random -import unittest import subprocess from panda import Panda @@ -10,14 +9,14 @@ from openpilot.system.hardware.tici.hardware import Tici from openpilot.system.hardware.tici.amplifier import Amplifier -class TestAmplifier(unittest.TestCase): +class TestAmplifier: @classmethod - def setUpClass(cls): + def setup_class(cls): if not TICI: - raise unittest.SkipTest + pytest.skip() - def setUp(self): + def setup_method(self): # clear dmesg subprocess.check_call("sudo dmesg -C", shell=True) @@ -25,7 +24,7 @@ class TestAmplifier(unittest.TestCase): Panda.wait_for_panda(None, 30) self.panda = Panda() - def tearDown(self): + def teardown_method(self): HARDWARE.reset_internal_panda() def _check_for_i2c_errors(self, expected): @@ -68,8 +67,4 @@ class TestAmplifier(unittest.TestCase): if self._check_for_i2c_errors(True): break else: - self.fail("didn't hit any i2c errors") - - -if __name__ == "__main__": - unittest.main() + pytest.fail("didn't hit any i2c errors") diff --git a/system/hardware/tici/tests/test_casync.py b/system/hardware/tici/tests/test_casync.py deleted file mode 100755 index 94b32a9f76..0000000000 --- a/system/hardware/tici/tests/test_casync.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -import os -import unittest -import tempfile -import subprocess - -import openpilot.system.hardware.tici.casync as casync - -# dd if=/dev/zero of=/tmp/img.raw bs=1M count=2 -# sudo losetup -f /tmp/img.raw -# losetup -a | grep img.raw -LOOPBACK = os.environ.get('LOOPBACK', None) - - -class TestCasync(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.tmpdir = tempfile.TemporaryDirectory() - - # Build example contents - chunk_a = [i % 256 for i in range(1024)] * 512 - chunk_b = [(256 - i) % 256 for i in range(1024)] * 512 - zeroes = [0] * (1024 * 128) - contents = chunk_a + chunk_b + zeroes + chunk_a - - cls.contents = bytes(contents) - - # Write to file - cls.orig_fn = os.path.join(cls.tmpdir.name, 'orig.bin') - with open(cls.orig_fn, 'wb') as f: - f.write(cls.contents) - - # Create casync files - cls.manifest_fn = os.path.join(cls.tmpdir.name, 'orig.caibx') - cls.store_fn = os.path.join(cls.tmpdir.name, 'store') - subprocess.check_output(["casync", "make", "--compression=xz", "--store", cls.store_fn, cls.manifest_fn, cls.orig_fn]) - - target = casync.parse_caibx(cls.manifest_fn) - hashes = [c.sha.hex() for c in target] - - # Ensure we have chunk reuse - assert len(hashes) > len(set(hashes)) - - def setUp(self): - # Clear target_lo - if LOOPBACK is not None: - self.target_lo = LOOPBACK - with open(self.target_lo, 'wb') as f: - f.write(b"0" * len(self.contents)) - - self.target_fn = os.path.join(self.tmpdir.name, next(tempfile._get_candidate_names())) - self.seed_fn = os.path.join(self.tmpdir.name, next(tempfile._get_candidate_names())) - - def tearDown(self): - for fn in [self.target_fn, self.seed_fn]: - try: - os.unlink(fn) - except FileNotFoundError: - pass - - def test_simple_extract(self): - target = casync.parse_caibx(self.manifest_fn) - - sources = [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] - stats = casync.extract(target, sources, self.target_fn) - - with open(self.target_fn, 'rb') as target_f: - self.assertEqual(target_f.read(), self.contents) - - self.assertEqual(stats['remote'], len(self.contents)) - - def test_seed(self): - target = casync.parse_caibx(self.manifest_fn) - - # Populate seed with half of the target contents - with open(self.seed_fn, 'wb') as seed_f: - seed_f.write(self.contents[:len(self.contents) // 2]) - - sources = [('seed', casync.FileChunkReader(self.seed_fn), casync.build_chunk_dict(target))] - sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] - stats = casync.extract(target, sources, self.target_fn) - - with open(self.target_fn, 'rb') as target_f: - self.assertEqual(target_f.read(), self.contents) - - self.assertGreater(stats['seed'], 0) - self.assertLess(stats['remote'], len(self.contents)) - - def test_already_done(self): - """Test that an already flashed target doesn't download any chunks""" - target = casync.parse_caibx(self.manifest_fn) - - with open(self.target_fn, 'wb') as f: - f.write(self.contents) - - sources = [('target', casync.FileChunkReader(self.target_fn), casync.build_chunk_dict(target))] - sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] - - stats = casync.extract(target, sources, self.target_fn) - - with open(self.target_fn, 'rb') as f: - self.assertEqual(f.read(), self.contents) - - self.assertEqual(stats['target'], len(self.contents)) - - def test_chunk_reuse(self): - """Test that chunks that are reused are only downloaded once""" - target = casync.parse_caibx(self.manifest_fn) - - # Ensure target exists - with open(self.target_fn, 'wb'): - pass - - sources = [('target', casync.FileChunkReader(self.target_fn), casync.build_chunk_dict(target))] - sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] - - stats = casync.extract(target, sources, self.target_fn) - - with open(self.target_fn, 'rb') as f: - self.assertEqual(f.read(), self.contents) - - self.assertLess(stats['remote'], len(self.contents)) - - @unittest.skipUnless(LOOPBACK, "requires loopback device") - def test_lo_simple_extract(self): - target = casync.parse_caibx(self.manifest_fn) - sources = [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] - - stats = casync.extract(target, sources, self.target_lo) - - with open(self.target_lo, 'rb') as target_f: - self.assertEqual(target_f.read(len(self.contents)), self.contents) - - self.assertEqual(stats['remote'], len(self.contents)) - - @unittest.skipUnless(LOOPBACK, "requires loopback device") - def test_lo_chunk_reuse(self): - """Test that chunks that are reused are only downloaded once""" - target = casync.parse_caibx(self.manifest_fn) - - sources = [('target', casync.FileChunkReader(self.target_lo), casync.build_chunk_dict(target))] - sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] - - stats = casync.extract(target, sources, self.target_lo) - - with open(self.target_lo, 'rb') as f: - self.assertEqual(f.read(len(self.contents)), self.contents) - - self.assertLess(stats['remote'], len(self.contents)) - - -if __name__ == "__main__": - unittest.main() diff --git a/system/hardware/tici/tests/test_hardware.py b/system/hardware/tici/tests/test_hardware.py old mode 100755 new mode 100644 index 6c41c383a0..75f53e7cdb --- a/system/hardware/tici/tests/test_hardware.py +++ b/system/hardware/tici/tests/test_hardware.py @@ -1,7 +1,5 @@ -#!/usr/bin/env python3 import pytest import time -import unittest import numpy as np from openpilot.system.hardware.tici.hardware import Tici @@ -10,19 +8,19 @@ HARDWARE = Tici() @pytest.mark.tici -class TestHardware(unittest.TestCase): +class TestHardware: def test_power_save_time(self): - ts = [] + ts = {True: [], False: []} for _ in range(5): for on in (True, False): st = time.monotonic() HARDWARE.set_power_save(on) - ts.append(time.monotonic() - st) + ts[on].append(time.monotonic() - st) - assert 0.1 < np.mean(ts) < 0.25 - assert max(ts) < 0.3 + # disabling power save is the main time-critical one + assert 0.1 < np.mean(ts[False]) < 0.15 + assert max(ts[False]) < 0.2 - -if __name__ == "__main__": - unittest.main() + assert 0.1 < np.mean(ts[True]) < 0.35 + assert max(ts[True]) < 0.4 diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py old mode 100755 new mode 100644 index 180ec155e4..866f7d1188 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -1,7 +1,5 @@ -#!/usr/bin/env python3 from collections import defaultdict, deque import pytest -import unittest import time import numpy as np from dataclasses import dataclass @@ -12,8 +10,8 @@ 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.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.system.manager.process_config import managed_processes +from openpilot.system.manager.manager import manager_cleanup SAMPLE_TIME = 8 # seconds to sample power MAX_WARMUP_TIME = 30 # seconds to wait for SAMPLE_TIME consecutive valid samples @@ -36,20 +34,19 @@ PROCS = [ 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']), ] @pytest.mark.tici -class TestPowerDraw(unittest.TestCase): +class TestPowerDraw: - def setUp(self): + def setup_method(self): write_car_param() # wait a bit for power save to disable time.sleep(5) - def tearDown(self): + def teardown_method(self): manager_cleanup() def get_expected_messages(self, proc): @@ -98,7 +95,7 @@ class TestPowerDraw(unittest.TestCase): return now, msg_counts, time.monotonic() - start_time - SAMPLE_TIME @mock_messages(['liveLocationKalman']) - def test_camera_procs(self): + def test_camera_procs(self, subtests): baseline = get_power() prev = baseline @@ -123,12 +120,8 @@ class TestPowerDraw(unittest.TestCase): expected = proc.power msgs_received = sum(msg_counts[msg] for msg in proc.msgs) 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): - 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") + with subtests.test(proc=proc.name): + assert self.valid_msg_count(proc, msg_counts), f"expected {self.get_expected_messages(proc)} msgs, got {msgs_received} msgs" + assert 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() diff --git a/system/logcatd/SConscript b/system/logcatd/SConscript index 6bd7c6ff3e..ac2a79a1f2 100644 --- a/system/logcatd/SConscript +++ b/system/logcatd/SConscript @@ -1,3 +1,3 @@ -Import('env', 'cereal', 'messaging', 'common') +Import('env', 'messaging', 'common') -env.Program('logcatd', 'logcatd_systemd.cc', LIBS=[cereal, messaging, common, 'zmq', 'capnp', 'kj', 'systemd', 'json11']) +env.Program('logcatd', 'logcatd_systemd.cc', LIBS=[messaging, common, 'systemd', 'json11']) diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index d4f52fb5f1..196d18476a 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -1,9 +1,8 @@ -Import('env', 'arch', 'cereal', 'messaging', 'common', 'visionipc') +Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, cereal, messaging, visionipc, - 'zmq', 'capnp', 'kj', 'z', - 'avformat', 'avcodec', 'swscale', 'avutil', - 'yuv', 'OpenCL', 'pthread'] +libs = [common, messaging, visionipc, + 'z', 'avformat', 'avcodec', 'swscale', + 'avutil', 'yuv', 'OpenCL', 'pthread'] src = ['logger.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc'] if arch != "larch64": diff --git a/system/loggerd/encoder/encoder.cc b/system/loggerd/encoder/encoder.cc index c4bd91bcf7..313a0f57a1 100644 --- a/system/loggerd/encoder/encoder.cc +++ b/system/loggerd/encoder/encoder.cc @@ -6,7 +6,12 @@ VideoEncoder::VideoEncoder(const EncoderInfo &encoder_info, int in_width, int in 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})); + + std::vector pubs = {encoder_info.publish_name}; + if (encoder_info.thumbnail_name != NULL) { + pubs.push_back(encoder_info.thumbnail_name); + } + pm.reset(new PubMaster(pubs)); } void VideoEncoder::publisher_publish(VideoEncoder *e, int segment_num, uint32_t idx, VisionIpcBufExtra &extra, @@ -40,4 +45,15 @@ void VideoEncoder::publisher_publish(VideoEncoder *e, int segment_num, uint32_t kj::ArrayOutputStream output_stream(kj::ArrayPtr(e->msg_cache.data(), bytes_size)); capnp::writeMessage(output_stream, msg); e->pm->send(e->encoder_info.publish_name, e->msg_cache.data(), bytes_size); -} + + // Publish keyframe thumbnail + if ((flags & V4L2_BUF_FLAG_KEYFRAME) && e->encoder_info.thumbnail_name != NULL) { + MessageBuilder tm; + auto thumbnail = tm.initEvent().initThumbnail(); + thumbnail.setFrameId(extra.frame_id); + thumbnail.setTimestampEof(extra.timestamp_eof); + thumbnail.setThumbnail(dat); + thumbnail.setEncoding(cereal::Thumbnail::Encoding::KEYFRAME); + pm->send(e->encoder_info.thumbnail_name, tm); + } +} \ No newline at end of file diff --git a/system/loggerd/encoder/encoder.h b/system/loggerd/encoder/encoder.h index 7c203f9193..72848609ef 100644 --- a/system/loggerd/encoder/encoder.h +++ b/system/loggerd/encoder/encoder.h @@ -7,7 +7,7 @@ #include #include "cereal/messaging/messaging.h" -#include "cereal/visionipc/visionipc.h" +#include "msgq/visionipc/visionipc.h" #include "common/queue.h" #include "system/camerad/cameras/camera_common.h" #include "system/loggerd/loggerd.h" @@ -25,6 +25,8 @@ public: void publisher_publish(VideoEncoder *e, int segment_num, uint32_t idx, VisionIpcBufExtra &extra, unsigned int flags, kj::ArrayPtr header, kj::ArrayPtr dat); protected: + void publish_thumbnail(uint32_t frame_id, uint64_t timestamp_eof, kj::ArrayPtr dat); + int in_width, in_height; int out_width, out_height; const EncoderInfo encoder_info; diff --git a/system/loggerd/encoder/v4l_encoder.cc b/system/loggerd/encoder/v4l_encoder.cc index 2bd2863126..853a17abbe 100644 --- a/system/loggerd/encoder/v4l_encoder.cc +++ b/system/loggerd/encoder/v4l_encoder.cc @@ -16,7 +16,12 @@ #define V4L2_QCOM_BUF_FLAG_CODECCONFIG 0x00020000 #define V4L2_QCOM_BUF_FLAG_EOS 0x02000000 -// echo 0x7fffffff > /sys/kernel/debug/msm_vidc/debug_level +/* + kernel debugging: + echo 0xff > /sys/module/videobuf2_core/parameters/debug + echo 0x7fffffff > /sys/kernel/debug/msm_vidc/debug_level + echo 0xff > /sys/devices/platform/soc/aa00000.qcom,vidc/video4linux/video33/dev_debug +*/ const int env_debug_encoder = (getenv("DEBUG_ENCODER") != NULL) ? atoi(getenv("DEBUG_ENCODER")) : 0; static void checked_ioctl(int fd, unsigned long request, void *argp) { diff --git a/system/loggerd/logger.cc b/system/loggerd/logger.cc index 7a829a2f1f..8234cd84ad 100644 --- a/system/loggerd/logger.cc +++ b/system/loggerd/logger.cc @@ -89,15 +89,6 @@ kj::Array logger_build_init_data() { return capnp::messageToFlatArray(msg); } -std::string logger_get_route_name() { - char route_name[64] = {'\0'}; - time_t rawtime = time(NULL); - struct tm timeinfo; - localtime_r(&rawtime, &timeinfo); - strftime(route_name, sizeof(route_name), "%Y-%m-%d--%H-%M-%S", &timeinfo); - return route_name; -} - std::string logger_get_identifier(std::string key) { // a log identifier is a 32 bit counter, plus a 10 character unique ID. // e.g. 000001a3--c20ba54385 @@ -105,7 +96,7 @@ std::string logger_get_identifier(std::string key) { Params params; uint32_t cnt; try { - cnt = std::stol(params.get(key)); + cnt = std::stoul(params.get(key)); } catch (std::exception &e) { cnt = 0; } @@ -122,16 +113,16 @@ std::string logger_get_identifier(std::string key) { return util::string_format("%08x--%s", cnt, ss.str().c_str()); } -static void log_sentinel(LoggerState *log, SentinelType type, int eixt_signal = 0) { +static void log_sentinel(LoggerState *log, SentinelType type, int exit_signal = 0) { MessageBuilder msg; auto sen = msg.initEvent().initSentinel(); sen.setType(type); - sen.setSignal(eixt_signal); + sen.setSignal(exit_signal); log->write(msg.toBytes(), true); } LoggerState::LoggerState(const std::string &log_root) { - route_name = logger_get_route_name(); + route_name = logger_get_identifier("RouteCount"); route_path = log_root + "/" + route_name; init_data = logger_build_init_data(); } diff --git a/system/loggerd/logger.h b/system/loggerd/logger.h index dd3bee150c..7a8490d57a 100644 --- a/system/loggerd/logger.h +++ b/system/loggerd/logger.h @@ -52,5 +52,4 @@ protected: }; kj::Array logger_build_init_data(); -std::string logger_get_route_name(); std::string logger_get_identifier(std::string key); diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index 3c0ffc1667..a324479fb5 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -215,7 +215,7 @@ void loggerd_thread() { const bool encoder = util::ends_with(it.name, "EncodeData"); const bool livestream_encoder = util::starts_with(it.name, "livestream"); if (!it.should_log && (!encoder || livestream_encoder)) continue; - LOGD("logging %s (on port %d)", it.name.c_str(), it.port); + LOGD("logging %s", it.name.c_str()); SubSocket * sock = SubSocket::create(ctx.get(), it.name); assert(sock != NULL); diff --git a/system/loggerd/loggerd.h b/system/loggerd/loggerd.h index ea288f4861..7c80ba51a2 100644 --- a/system/loggerd/loggerd.h +++ b/system/loggerd/loggerd.h @@ -4,7 +4,7 @@ #include "cereal/messaging/messaging.h" #include "cereal/services.h" -#include "cereal/visionipc/visionipc_client.h" +#include "msgq/visionipc/visionipc_client.h" #include "system/camerad/cameras/camera_common.h" #include "system/hardware/hw.h" #include "common/params.h" @@ -33,6 +33,7 @@ constexpr char PRESERVE_ATTR_VALUE = '1'; class EncoderInfo { public: const char *publish_name; + const char *thumbnail_name = NULL; const char *filename = NULL; bool record = true; int frame_width = -1; @@ -76,6 +77,7 @@ const EncoderInfo main_driver_encoder_info = { const EncoderInfo stream_road_encoder_info = { .publish_name = "livestreamRoadEncodeData", + //.thumbnail_name = "thumbnail", .encode_type = cereal::EncodeIndex::Type::QCAMERA_H264, .record = false, .bitrate = LIVESTREAM_BITRATE, diff --git a/system/loggerd/tests/loggerd_tests_common.py b/system/loggerd/tests/loggerd_tests_common.py index 42eec2a0f4..15fd997eb8 100644 --- a/system/loggerd/tests/loggerd_tests_common.py +++ b/system/loggerd/tests/loggerd_tests_common.py @@ -1,6 +1,5 @@ import os import random -import unittest from pathlib import Path @@ -29,12 +28,12 @@ def create_random_file(file_path: Path, size_mb: float, lock: bool = False, uplo if upload_xattr is not None: setxattr(str(file_path), uploader.UPLOAD_ATTR_NAME, upload_xattr) -class MockResponse(): +class MockResponse: def __init__(self, text, status_code): self.text = text self.status_code = status_code -class MockApi(): +class MockApi: def __init__(self, dongle_id): pass @@ -44,7 +43,7 @@ class MockApi(): def get_token(self): return "fake-token" -class MockApiIgnore(): +class MockApiIgnore: def __init__(self, dongle_id): pass @@ -54,7 +53,7 @@ class MockApiIgnore(): def get_token(self): return "fake-token" -class UploaderTestCase(unittest.TestCase): +class UploaderTestCase: f_type = "UNKNOWN" root: Path @@ -66,14 +65,14 @@ class UploaderTestCase(unittest.TestCase): def set_ignore(self): uploader.Api = MockApiIgnore - def setUp(self): + def setup_method(self): uploader.Api = MockApi uploader.fake_upload = True uploader.force_wifi = True uploader.allow_sleep = False self.seg_num = random.randint(1, 300) - self.seg_format = "2019-04-18--12-52-54--{}" - self.seg_format2 = "2019-05-18--11-22-33--{}" + self.seg_format = "00000004--0ac3964c96--{}" + self.seg_format2 = "00000005--4c4e99b08b--{}" self.seg_dir = self.seg_format.format(self.seg_num) self.params = Params() diff --git a/system/loggerd/tests/test_deleter.py b/system/loggerd/tests/test_deleter.py old mode 100755 new mode 100644 index 37d25507e0..6222ea253b --- a/system/loggerd/tests/test_deleter.py +++ b/system/loggerd/tests/test_deleter.py @@ -1,7 +1,5 @@ -#!/usr/bin/env python3 import time import threading -import unittest from collections import namedtuple from pathlib import Path from collections.abc import Sequence @@ -17,9 +15,9 @@ class TestDeleter(UploaderTestCase): def fake_statvfs(self, d): return self.fake_stats - def setUp(self): + def setup_method(self): self.f_type = "fcamera.hevc" - super().setUp() + super().setup_method() self.fake_stats = Stats(f_bavail=0, f_blocks=10, f_frsize=4096) deleter.os.statvfs = self.fake_statvfs @@ -64,7 +62,7 @@ class TestDeleter(UploaderTestCase): finally: self.join_thread() - self.assertEqual(deleted_order, f_paths, "Files not deleted in expected order") + assert deleted_order == f_paths, "Files not deleted in expected order" def test_delete_order(self): self.assertDeleteOrder([ @@ -105,7 +103,7 @@ class TestDeleter(UploaderTestCase): time.sleep(0.01) self.join_thread() - self.assertTrue(f_path.exists(), "File deleted with available space") + assert f_path.exists(), "File deleted with available space" def test_no_delete_with_lock_file(self): f_path = self.make_file_with_data(self.seg_dir, self.f_type, lock=True) @@ -116,8 +114,4 @@ class TestDeleter(UploaderTestCase): time.sleep(0.01) self.join_thread() - self.assertTrue(f_path.exists(), "File deleted when locked") - - -if __name__ == "__main__": - unittest.main() + assert f_path.exists(), "File deleted when locked" diff --git a/system/loggerd/tests/test_encoder.py b/system/loggerd/tests/test_encoder.py old mode 100755 new mode 100644 index bd076dc5f3..75862a9d45 --- a/system/loggerd/tests/test_encoder.py +++ b/system/loggerd/tests/test_encoder.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import math import os import pytest @@ -6,7 +5,6 @@ import random import shutil import subprocess import time -import unittest from pathlib import Path from parameterized import parameterized @@ -15,7 +13,7 @@ from tqdm import trange from openpilot.common.params import Params from openpilot.common.timeout import Timeout from openpilot.system.hardware import TICI -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes from openpilot.tools.lib.logreader import LogReader from openpilot.system.hardware.hw import Paths @@ -29,18 +27,18 @@ CAMERAS = [ ] # we check frame count, so we don't have to be too strict on size -FILE_SIZE_TOLERANCE = 0.5 +FILE_SIZE_TOLERANCE = 0.7 @pytest.mark.tici # TODO: all of loggerd should work on PC -class TestEncoder(unittest.TestCase): +class TestEncoder: - def setUp(self): + def setup_method(self): self._clear_logs() os.environ["LOGGERD_TEST"] = "1" os.environ["LOGGERD_SEGMENT_LENGTH"] = str(SEGMENT_LENGTH) - def tearDown(self): + def teardown_method(self): self._clear_logs() def _clear_logs(self): @@ -85,7 +83,7 @@ class TestEncoder(unittest.TestCase): file_path = f"{route_prefix_path}--{i}/{camera}" # check file exists - self.assertTrue(os.path.exists(file_path), f"segment #{i}: '{file_path}' missing") + assert os.path.exists(file_path), f"segment #{i}: '{file_path}' missing" # TODO: this ffprobe call is really slow # check frame count @@ -98,13 +96,13 @@ class TestEncoder(unittest.TestCase): frame_count = int(probe.split('\n')[0].strip()) counts.append(frame_count) - self.assertEqual(frame_count, expected_frames, - f"segment #{i}: {camera} failed frame count check: expected {expected_frames}, got {frame_count}") + assert frame_count == expected_frames, \ + f"segment #{i}: {camera} failed frame count check: expected {expected_frames}, got {frame_count}" # sanity check file size file_size = os.path.getsize(file_path) - self.assertTrue(math.isclose(file_size, size, rel_tol=FILE_SIZE_TOLERANCE), - f"{file_path} size {file_size} isn't close to target size {size}") + assert math.isclose(file_size, size, rel_tol=FILE_SIZE_TOLERANCE), \ + f"{file_path} size {file_size} isn't close to target size {size}" # Check encodeIdx if encode_idx_name is not None: @@ -118,24 +116,24 @@ class TestEncoder(unittest.TestCase): frame_idxs = [m.frameId for m in encode_msgs] # Check frame count - self.assertEqual(frame_count, len(segment_idxs)) - self.assertEqual(frame_count, len(encode_idxs)) + assert frame_count == len(segment_idxs) + assert frame_count == len(encode_idxs) # Check for duplicates or skips - self.assertEqual(0, segment_idxs[0]) - self.assertEqual(len(set(segment_idxs)), len(segment_idxs)) + assert 0 == segment_idxs[0] + assert len(set(segment_idxs)) == len(segment_idxs) - self.assertTrue(all(valid)) + assert all(valid) - self.assertEqual(expected_frames * i, encode_idxs[0]) + assert expected_frames * i == encode_idxs[0] first_frames.append(frame_idxs[0]) - self.assertEqual(len(set(encode_idxs)), len(encode_idxs)) + assert len(set(encode_idxs)) == len(encode_idxs) - self.assertEqual(1, len(set(first_frames))) + assert 1 == len(set(first_frames)) if TICI: expected_frames = fps * SEGMENT_LENGTH - self.assertEqual(min(counts), expected_frames) + assert min(counts) == expected_frames shutil.rmtree(f"{route_prefix_path}--{i}") try: @@ -150,7 +148,3 @@ class TestEncoder(unittest.TestCase): managed_processes['encoderd'].stop() managed_processes['camerad'].stop() managed_processes['sensord'].stop() - - -if __name__ == "__main__": - unittest.main() diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py old mode 100755 new mode 100644 index c80dc19fce..6a24540acf --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import numpy as np import os import re @@ -19,12 +18,12 @@ from openpilot.common.timeout import Timeout from openpilot.system.hardware.hw import Paths from openpilot.system.loggerd.xattr_cache import getxattr from openpilot.system.loggerd.deleter import PRESERVE_ATTR_NAME, PRESERVE_ATTR_VALUE -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes from openpilot.system.version import get_version from openpilot.tools.lib.helpers import RE from openpilot.tools.lib.logreader import LogReader -from cereal.visionipc import VisionIpcServer, VisionStreamType -from openpilot.common.transformations.camera import tici_f_frame_size, tici_d_frame_size, tici_e_frame_size +from msgq.visionipc import VisionIpcServer, VisionStreamType +from openpilot.common.transformations.camera import DEVICE_CAMERAS SentinelType = log.Sentinel.SentinelType @@ -142,10 +141,11 @@ class TestLoggerd: os.environ["LOGGERD_TEST"] = "1" Params().put("RecordFront", "1") + d = DEVICE_CAMERAS[("tici", "ar0231")] expected_files = {"rlog", "qlog", "qcamera.ts", "fcamera.hevc", "dcamera.hevc", "ecamera.hevc"} - streams = [(VisionStreamType.VISION_STREAM_ROAD, (*tici_f_frame_size, 2048*2346, 2048, 2048*1216), "roadCameraState"), - (VisionStreamType.VISION_STREAM_DRIVER, (*tici_d_frame_size, 2048*2346, 2048, 2048*1216), "driverCameraState"), - (VisionStreamType.VISION_STREAM_WIDE_ROAD, (*tici_e_frame_size, 2048*2346, 2048, 2048*1216), "wideRoadCameraState")] + streams = [(VisionStreamType.VISION_STREAM_ROAD, (d.fcam.width, d.fcam.height, 2048*2346, 2048, 2048*1216), "roadCameraState"), + (VisionStreamType.VISION_STREAM_DRIVER, (d.dcam.width, d.dcam.height, 2048*2346, 2048, 2048*1216), "driverCameraState"), + (VisionStreamType.VISION_STREAM_WIDE_ROAD, (d.ecam.width, d.ecam.height, 2048*2346, 2048, 2048*1216), "wideRoadCameraState")] pm = messaging.PubMaster(["roadCameraState", "driverCameraState", "wideRoadCameraState"]) vipc_server = VisionIpcServer("camerad") diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py old mode 100755 new mode 100644 index 73917a30cf..6591198281 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -1,8 +1,6 @@ -#!/usr/bin/env python3 import os import time import threading -import unittest import logging import json from pathlib import Path @@ -38,8 +36,8 @@ cloudlog.addHandler(log_handler) class TestUploader(UploaderTestCase): - def setUp(self): - super().setUp() + def setup_method(self): + super().setup_method() log_handler.reset() def start_thread(self): @@ -80,13 +78,13 @@ class TestUploader(UploaderTestCase): exp_order = self.gen_order([self.seg_num], []) - self.assertTrue(len(log_handler.upload_ignored) == 0, "Some files were ignored") - self.assertFalse(len(log_handler.upload_order) < len(exp_order), "Some files failed to upload") - self.assertFalse(len(log_handler.upload_order) > len(exp_order), "Some files were uploaded twice") + assert len(log_handler.upload_ignored) == 0, "Some files were ignored" + assert not len(log_handler.upload_order) < len(exp_order), "Some files failed to upload" + assert not len(log_handler.upload_order) > len(exp_order), "Some files were uploaded twice" for f_path in exp_order: - self.assertEqual(os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not uploaded") + assert os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME) == UPLOAD_ATTR_VALUE, "All files not uploaded" - self.assertTrue(log_handler.upload_order == exp_order, "Files uploaded in wrong order") + assert log_handler.upload_order == exp_order, "Files uploaded in wrong order" def test_upload_with_wrong_xattr(self): self.gen_files(lock=False, xattr=b'0') @@ -98,13 +96,13 @@ class TestUploader(UploaderTestCase): exp_order = self.gen_order([self.seg_num], []) - self.assertTrue(len(log_handler.upload_ignored) == 0, "Some files were ignored") - self.assertFalse(len(log_handler.upload_order) < len(exp_order), "Some files failed to upload") - self.assertFalse(len(log_handler.upload_order) > len(exp_order), "Some files were uploaded twice") + assert len(log_handler.upload_ignored) == 0, "Some files were ignored" + assert not len(log_handler.upload_order) < len(exp_order), "Some files failed to upload" + assert not len(log_handler.upload_order) > len(exp_order), "Some files were uploaded twice" for f_path in exp_order: - self.assertEqual(os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not uploaded") + assert os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME) == UPLOAD_ATTR_VALUE, "All files not uploaded" - self.assertTrue(log_handler.upload_order == exp_order, "Files uploaded in wrong order") + assert log_handler.upload_order == exp_order, "Files uploaded in wrong order" def test_upload_ignored(self): self.set_ignore() @@ -117,13 +115,13 @@ class TestUploader(UploaderTestCase): exp_order = self.gen_order([self.seg_num], []) - self.assertTrue(len(log_handler.upload_order) == 0, "Some files were not ignored") - self.assertFalse(len(log_handler.upload_ignored) < len(exp_order), "Some files failed to ignore") - self.assertFalse(len(log_handler.upload_ignored) > len(exp_order), "Some files were ignored twice") + assert len(log_handler.upload_order) == 0, "Some files were not ignored" + assert not len(log_handler.upload_ignored) < len(exp_order), "Some files failed to ignore" + assert not len(log_handler.upload_ignored) > len(exp_order), "Some files were ignored twice" for f_path in exp_order: - self.assertEqual(os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not ignored") + assert os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME) == UPLOAD_ATTR_VALUE, "All files not ignored" - self.assertTrue(log_handler.upload_ignored == exp_order, "Files ignored in wrong order") + assert log_handler.upload_ignored == exp_order, "Files ignored in wrong order" def test_upload_files_in_create_order(self): seg1_nums = [0, 1, 2, 10, 20] @@ -142,13 +140,13 @@ class TestUploader(UploaderTestCase): time.sleep(5) self.join_thread() - self.assertTrue(len(log_handler.upload_ignored) == 0, "Some files were ignored") - self.assertFalse(len(log_handler.upload_order) < len(exp_order), "Some files failed to upload") - self.assertFalse(len(log_handler.upload_order) > len(exp_order), "Some files were uploaded twice") + assert len(log_handler.upload_ignored) == 0, "Some files were ignored" + assert not len(log_handler.upload_order) < len(exp_order), "Some files failed to upload" + assert not len(log_handler.upload_order) > len(exp_order), "Some files were uploaded twice" for f_path in exp_order: - self.assertEqual(os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not uploaded") + assert os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME) == UPLOAD_ATTR_VALUE, "All files not uploaded" - self.assertTrue(log_handler.upload_order == exp_order, "Files uploaded in wrong order") + assert log_handler.upload_order == exp_order, "Files uploaded in wrong order" def test_no_upload_with_lock_file(self): self.start_thread() @@ -163,7 +161,7 @@ class TestUploader(UploaderTestCase): for f_path in f_paths: fn = f_path.with_suffix(f_path.suffix.replace(".bz2", "")) uploaded = UPLOAD_ATTR_NAME in os.listxattr(fn) and os.getxattr(fn, UPLOAD_ATTR_NAME) == UPLOAD_ATTR_VALUE - self.assertFalse(uploaded, "File upload when locked") + assert not uploaded, "File upload when locked" def test_no_upload_with_xattr(self): self.gen_files(lock=False, xattr=UPLOAD_ATTR_VALUE) @@ -173,7 +171,7 @@ class TestUploader(UploaderTestCase): time.sleep(5) self.join_thread() - self.assertEqual(len(log_handler.upload_order), 0, "File uploaded again") + assert len(log_handler.upload_order) == 0, "File uploaded again" def test_clear_locks_on_startup(self): f_paths = self.gen_files(lock=True, boot=False) @@ -183,8 +181,4 @@ class TestUploader(UploaderTestCase): for f_path in f_paths: lock_path = f_path.with_suffix(f_path.suffix + ".lock") - self.assertFalse(lock_path.is_file(), "File lock not cleared on startup") - - -if __name__ == "__main__": - unittest.main() + assert not lock_path.is_file(), "File lock not cleared on startup" diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 33ee8c1850..832a227798 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -44,7 +44,9 @@ class FakeResponse: def get_directory_sort(d: str) -> list[str]: - return [s.rjust(10, '0') for s in d.rsplit('--', 1)] + # ensure old format is sorted sooner + o = ["0", ] if d.startswith("2024-") else ["1", ] + return o + [s.rjust(10, '0') for s in d.rsplit('--', 1)] def listdir_by_creation(d: str) -> list[str]: if not os.path.isdir(d): diff --git a/selfdrive/manager/test/__init__.py b/system/manager/__init__.py similarity index 100% rename from selfdrive/manager/test/__init__.py rename to system/manager/__init__.py diff --git a/selfdrive/manager/build.py b/system/manager/build.py similarity index 93% rename from selfdrive/manager/build.py rename to system/manager/build.py index 067e1b5a1e..956336a604 100755 --- a/selfdrive/manager/build.py +++ b/system/manager/build.py @@ -9,12 +9,12 @@ from openpilot.common.spinner import Spinner from openpilot.common.text_window import TextWindow from openpilot.system.hardware import AGNOS from openpilot.common.swaglog import cloudlog, add_file_handler -from openpilot.system.version import is_dirty +from openpilot.system.version import get_build_metadata MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 CACHE_DIR = Path("/data/scons_cache" if AGNOS else "/tmp/scons_cache") -TOTAL_SCONS_NODES = 2560 +TOTAL_SCONS_NODES = 2820 MAX_BUILD_PROGRESS = 100 def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None: @@ -86,4 +86,5 @@ def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None: if __name__ == "__main__": spinner = Spinner() spinner.update_progress(0, 100) - build(spinner, is_dirty(), minimal = AGNOS) + build_metadata = get_build_metadata() + build(spinner, build_metadata.openpilot.is_dirty, minimal = AGNOS) diff --git a/selfdrive/manager/helpers.py b/system/manager/helpers.py similarity index 100% rename from selfdrive/manager/helpers.py rename to system/manager/helpers.py diff --git a/selfdrive/manager/manager.py b/system/manager/manager.py similarity index 75% rename from selfdrive/manager/manager.py rename to system/manager/manager.py index 24dceaaf08..2a173ac90e 100755 --- a/selfdrive/manager/manager.py +++ b/system/manager/manager.py @@ -7,29 +7,29 @@ import traceback from cereal import log import cereal.messaging as messaging -import openpilot.selfdrive.sentry as sentry +import openpilot.system.sentry as sentry from openpilot.common.params import Params, ParamKeyType from openpilot.common.text_window import TextWindow 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 -from openpilot.selfdrive.manager.process_config import managed_processes -from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID +from openpilot.system.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog +from openpilot.system.manager.process import ensure_running +from openpilot.system.manager.process_config import managed_processes +from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_ID 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, get_commit_date +from openpilot.system.version import get_build_metadata, terms_version, training_version def manager_init() -> None: save_bootlog() + build_metadata = get_build_metadata() + params = Params() params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION) params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION) - if is_release_branch(): + if build_metadata.release_channel: params.clear_all(ParamKeyType.DEVELOPMENT_ONLY) default_params: list[tuple[str, str | bytes]] = [ @@ -42,7 +42,7 @@ def manager_init() -> None: ("LongitudinalPersonality", str(log.LongitudinalPersonality.standard)), ] if not PC: - default_params.append(("LastUpdateTime", datetime.datetime.utcnow().isoformat().encode('utf8'))) + default_params.append(("LastUpdateTime", datetime.datetime.now(datetime.UTC).replace(tzinfo=None).isoformat().encode('utf8'))) if params.get_bool("RecordFrontLock"): params.put_bool("RecordFront", True) @@ -61,15 +61,15 @@ def manager_init() -> None: print("WARNING: failed to make /dev/shm") # set version params - params.put("Version", get_version()) + params.put("Version", build_metadata.openpilot.version) params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) - 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()) + params.put("GitCommit", build_metadata.openpilot.git_commit) + params.put("GitCommitDate", build_metadata.openpilot.git_commit_date) + params.put("GitBranch", build_metadata.channel) + params.put("GitRemote", build_metadata.openpilot.git_origin) + params.put_bool("IsTestedBranch", build_metadata.tested_channel) + params.put_bool("IsReleaseBranch", build_metadata.release_channel) # set dongle id reg_res = register(show_spinner=True) @@ -79,21 +79,21 @@ 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 + os.environ['GIT_ORIGIN'] = build_metadata.openpilot.git_normalized_origin # Needed for swaglog + os.environ['GIT_BRANCH'] = build_metadata.channel # Needed for swaglog + os.environ['GIT_COMMIT'] = build_metadata.openpilot.git_commit # Needed for swaglog - if not is_dirty(): + if not build_metadata.openpilot.is_dirty: os.environ['CLEAN'] = '1' # init logging sentry.init(sentry.SentryProject.SELFDRIVE) cloudlog.bind_global(dongle_id=dongle_id, - version=get_version(), - origin=get_normalized_origin(), - branch=get_short_branch(), - commit=get_commit(), - dirty=is_dirty(), + version=build_metadata.openpilot.version, + origin=build_metadata.openpilot.git_normalized_origin, + branch=build_metadata.channel, + commit=build_metadata.openpilot.git_commit, + dirty=build_metadata.openpilot.is_dirty, device=HARDWARE.get_device_type()) # preimport all processes @@ -145,7 +145,7 @@ def manager_thread() -> None: elif not started and started_prev: params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION) - # update onroad params, which drives boardd's safety setter thread + # update onroad params, which drives pandad's safety setter thread if started != started_prev: write_onroad_params(started, params) diff --git a/selfdrive/manager/process.py b/system/manager/process.py similarity index 98% rename from selfdrive/manager/process.py rename to system/manager/process.py index 7964f5229d..9214e417c1 100644 --- a/selfdrive/manager/process.py +++ b/system/manager/process.py @@ -8,11 +8,11 @@ from collections.abc import Callable, ValuesView from abc import ABC, abstractmethod from multiprocessing import Process -from setproctitle import setproctitle +from openpilot.common.threadname import setthreadname from cereal import car, log import cereal.messaging as messaging -import openpilot.selfdrive.sentry as sentry +import openpilot.system.sentry as sentry from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog @@ -27,7 +27,7 @@ def launcher(proc: str, name: str) -> None: mod = importlib.import_module(proc) # rename the process - setproctitle(proc) + setthreadname(proc) # create new context since we forked messaging.context = messaging.Context() diff --git a/selfdrive/manager/process_config.py b/system/manager/process_config.py similarity index 82% rename from selfdrive/manager/process_config.py rename to system/manager/process_config.py index cb6dd8883f..ced31077c9 100644 --- a/selfdrive/manager/process_config.py +++ b/system/manager/process_config.py @@ -3,7 +3,7 @@ import os from cereal import car from openpilot.common.params import Params from openpilot.system.hardware import PC, TICI -from openpilot.selfdrive.manager.process import PythonProcess, NativeProcess, DaemonProcess +from openpilot.system.manager.process import PythonProcess, NativeProcess, DaemonProcess WEBCAM = os.getenv("USE_WEBCAM") is not None @@ -42,7 +42,7 @@ def only_offroad(started, params, CP: car.CarParams) -> bool: return not started procs = [ - DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), + DaemonProcess("manage_athenad", "system.athena.manage_athenad", "AthenadPid"), NativeProcess("camerad", "system/camerad", ["./camerad"], driverview), NativeProcess("logcatd", "system/logcatd", ["./logcatd"], only_onroad), @@ -56,31 +56,31 @@ procs = [ NativeProcess("stream_encoderd", "system/loggerd", ["./encoderd", "--stream"], notcar), NativeProcess("loggerd", "system/loggerd", ["./loggerd"], logging), NativeProcess("modeld", "selfdrive/modeld", ["./modeld"], only_onroad), - NativeProcess("mapsd", "selfdrive/navd", ["./mapsd"], only_onroad), - PythonProcess("navmodeld", "selfdrive.modeld.navmodeld", only_onroad), NativeProcess("sensord", "system/sensord", ["./sensord"], only_onroad, enabled=not PC), NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), NativeProcess("locationd", "selfdrive/locationd", ["./locationd"], only_onroad), - NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], always_run, enabled=False), + NativeProcess("pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False), PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad), PythonProcess("torqued", "selfdrive.locationd.torqued", only_onroad), PythonProcess("controlsd", "selfdrive.controls.controlsd", only_onroad), + PythonProcess("card", "selfdrive.car.card", only_onroad), PythonProcess("deleter", "system.loggerd.deleter", always_run), PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(not PC or WEBCAM)), PythonProcess("qcomgpsd", "system.qcomgpsd.qcomgpsd", qcomgps, enabled=TICI), + #PythonProcess("ugpsd", "system.ugpsd", only_onroad, enabled=TICI), PythonProcess("navd", "selfdrive.navd.navd", only_onroad), - PythonProcess("pandad", "selfdrive.boardd.pandad", always_run), + PythonProcess("pandad", "selfdrive.pandad.pandad", always_run), PythonProcess("paramsd", "selfdrive.locationd.paramsd", only_onroad), NativeProcess("ubloxd", "system/ubloxd", ["./ubloxd"], ublox, enabled=TICI), - PythonProcess("pigeond", "system.sensord.pigeond", ublox, enabled=TICI), + PythonProcess("pigeond", "system.ubloxd.pigeond", ublox, enabled=TICI), PythonProcess("plannerd", "selfdrive.controls.plannerd", only_onroad), PythonProcess("radard", "selfdrive.controls.radard", only_onroad), - PythonProcess("thermald", "selfdrive.thermald.thermald", always_run), - PythonProcess("tombstoned", "selfdrive.tombstoned", always_run, enabled=not PC), - PythonProcess("updated", "selfdrive.updated", only_offroad, enabled=not PC), + PythonProcess("hardwared", "system.hardware.hardwared", always_run), + PythonProcess("tombstoned", "system.tombstoned", always_run, enabled=not PC), + PythonProcess("updated", "system.updated.updated", only_offroad, enabled=not PC), PythonProcess("uploader", "system.loggerd.uploader", always_run), - PythonProcess("statsd", "selfdrive.statsd", always_run), + PythonProcess("statsd", "system.statsd", always_run), # debug procs NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar), diff --git a/selfdrive/thermald/__init__.py b/system/manager/test/__init__.py similarity index 100% rename from selfdrive/thermald/__init__.py rename to system/manager/test/__init__.py diff --git a/selfdrive/manager/test/test_manager.py b/system/manager/test/test_manager.py old mode 100755 new mode 100644 similarity index 63% rename from selfdrive/manager/test/test_manager.py rename to system/manager/test/test_manager.py index 1ae94b26a1..e5960d1113 --- a/selfdrive/manager/test/test_manager.py +++ b/system/manager/test/test_manager.py @@ -1,17 +1,15 @@ -#!/usr/bin/env python3 import os import pytest 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 -from openpilot.selfdrive.manager.process import ensure_running -from openpilot.selfdrive.manager.process_config import managed_processes +import openpilot.system.manager.manager as manager +from openpilot.system.manager.process import ensure_running +from openpilot.system.manager.process_config import managed_processes from openpilot.system.hardware import HARDWARE os.environ['FAKEUPLOAD'] = "1" @@ -21,15 +19,15 @@ BLACKLIST_PROCS = ['manage_athenad', 'pandad', 'pigeond'] @pytest.mark.tici -class TestManager(unittest.TestCase): - def setUp(self): +class TestManager: + def setup_method(self): HARDWARE.set_power_save(False) # ensure clean CarParams params = Params() params.clear_all() - def tearDown(self): + def teardown_method(self): manager.manager_cleanup() def test_manager_prepare(self): @@ -38,7 +36,7 @@ class TestManager(unittest.TestCase): def test_blacklisted_procs(self): # TODO: ensure there are blacklisted procs until we have a dedicated test - self.assertTrue(len(BLACKLIST_PROCS), "No blacklisted procs to test not_run") + assert len(BLACKLIST_PROCS), "No blacklisted procs to test not_run" @parameterized.expand([(i,) for i in range(10)]) def test_startup_time(self, index): @@ -48,8 +46,8 @@ class TestManager(unittest.TestCase): 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): + @pytest.mark.skip("this test is flaky the way it's currently written, should be moved to test_onroad") + def test_clean_exit(self, subtests): """ Ensure all processes exit cleanly when stopped. """ @@ -62,21 +60,17 @@ class TestManager(unittest.TestCase): time.sleep(10) for p in procs: - with self.subTest(proc=p.name): + with subtests.test(proc=p.name): state = p.get_process_state_msg() - self.assertTrue(state.running, f"{p.name} not running") + assert state.running, f"{p.name} not running" exit_code = p.stop(retry=False) - self.assertNotIn(p.name, BLACKLIST_PROCS, f"{p.name} was started") + assert p.name not in BLACKLIST_PROCS, f"{p.name} was started" - self.assertTrue(exit_code is not None, f"{p.name} failed to exit") + assert exit_code is not None, f"{p.name} failed to exit" # TODO: interrupted blocking read exits with 1 in cereal. use a more unique return code exit_codes = [0, 1] if p.sigkill: exit_codes = [-signal.SIGKILL] - self.assertIn(exit_code, exit_codes, f"{p.name} died with {exit_code}") - - -if __name__ == "__main__": - unittest.main() + assert exit_code in exit_codes, f"{p.name} died with {exit_code}" diff --git a/system/proclogd/SConscript b/system/proclogd/SConscript index 1f4b767011..9ca8e73542 100644 --- a/system/proclogd/SConscript +++ b/system/proclogd/SConscript @@ -1,5 +1,5 @@ -Import('env', 'cereal', 'messaging', 'common') -libs = [cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj', 'common', 'zmq', 'json11'] +Import('env', 'messaging', 'common') +libs = [messaging, 'pthread', 'common', 'zmq', 'json11'] env.Program('proclogd', ['main.cc', 'proclog.cc'], LIBS=libs) if GetOption('extras'): diff --git a/system/qcomgpsd/nmeaport.py b/system/qcomgpsd/nmeaport.py index caff7af646..8b9ab51086 100644 --- a/system/qcomgpsd/nmeaport.py +++ b/system/qcomgpsd/nmeaport.py @@ -127,7 +127,7 @@ def main() -> NoReturn: print("qcomgpsd is running, please kill openpilot before running this script! (aborted)") sys.exit(1) except CalledProcessError as e: - if e.returncode != 1: # 1 == no process found (boardd not running) + if e.returncode != 1: # 1 == no process found (pandad not running) raise e print("power up antenna ...") diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index e8c407a627..43ddb89939 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -17,6 +17,7 @@ from cereal import log import cereal.messaging as messaging from openpilot.common.gpio import gpio_init, gpio_set from openpilot.common.retry import retry +from openpilot.common.time import system_time_valid from openpilot.system.hardware.tici.pins import GPIO from openpilot.common.swaglog import cloudlog from openpilot.system.qcomgpsd.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv @@ -171,8 +172,9 @@ def setup_quectel(diag: ModemDiag) -> bool: inject_assistance() os.remove(ASSIST_DATA_FILE) #at_cmd("AT+QGPSXTRADATA?") - time_str = datetime.datetime.utcnow().strftime("%Y/%m/%d,%H:%M:%S") - at_cmd(f"AT+QGPSXTRATIME=0,\"{time_str}\",1,1,1000") + if system_time_valid(): + time_str = datetime.datetime.now(datetime.UTC).replace(tzinfo=None).strftime("%Y/%m/%d,%H:%M:%S") + at_cmd(f"AT+QGPSXTRATIME=0,\"{time_str}\",1,1,1000") at_cmd("AT+QGPSCFG=\"outport\",\"usbnmea\"") at_cmd("AT+QGPS=1") @@ -205,10 +207,10 @@ def teardown_quectel(diag): try_setup_logs(diag, []) -def wait_for_modem(): +def wait_for_modem(cmd="AT+QGPS?"): cloudlog.warning("waiting for modem to come up") while True: - ret = subprocess.call("mmcli -m any --timeout 10 --command=\"AT+QGPS?\"", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) + ret = subprocess.call(f"mmcli -m any --timeout 10 --command=\"{cmd}\"", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) if ret == 0: return time.sleep(0.1) @@ -352,8 +354,8 @@ def main() -> NoReturn: gps.bearingAccuracyDeg = report["q_FltHeadingUncRad"] * 180/math.pi if (report["q_FltHeadingUncRad"] != 0) else 180 gps.speedAccuracy = math.sqrt(sum([x**2 for x in vNEDsigma])) # quectel gps verticalAccuracy is clipped to 500, set invalid if so - gps.flags = 1 if gps.verticalAccuracy != 500 else 0 - if gps.flags: + gps.hasFix = gps.verticalAccuracy != 500 + if gps.hasFix: want_assistance = False stop_download_event.set() pm.send('gpsLocation', msg) diff --git a/system/qcomgpsd/tests/test_qcomgpsd.py b/system/qcomgpsd/tests/test_qcomgpsd.py old mode 100755 new mode 100644 index 6c93f7dd93..716bc33ed2 --- a/system/qcomgpsd/tests/test_qcomgpsd.py +++ b/system/qcomgpsd/tests/test_qcomgpsd.py @@ -1,38 +1,36 @@ -#!/usr/bin/env python3 import os import pytest import json import time import datetime -import unittest import subprocess import cereal.messaging as messaging from openpilot.system.qcomgpsd.qcomgpsd import at_cmd, wait_for_modem -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes GOOD_SIGNAL = bool(int(os.getenv("GOOD_SIGNAL", '0'))) @pytest.mark.tici -class TestRawgpsd(unittest.TestCase): +class TestRawgpsd: @classmethod - def setUpClass(cls): + def setup_class(cls): os.system("sudo systemctl start systemd-resolved") os.system("sudo systemctl restart ModemManager lte") wait_for_modem() @classmethod - def tearDownClass(cls): + def teardown_class(cls): managed_processes['qcomgpsd'].stop() os.system("sudo systemctl restart systemd-resolved") os.system("sudo systemctl restart ModemManager lte") - def setUp(self): + def setup_method(self): at_cmd("AT+QGPSDEL=0") self.sm = messaging.SubMaster(['qcomGnss', 'gpsLocation', 'gnssMeasurements']) - def tearDown(self): + def teardown_method(self): managed_processes['qcomgpsd'].stop() os.system("sudo systemctl restart systemd-resolved") @@ -57,18 +55,18 @@ class TestRawgpsd(unittest.TestCase): os.system("sudo systemctl restart ModemManager") assert self._wait_for_output(30) - def test_startup_time(self): + def test_startup_time(self, subtests): for internet in (True, False): if not internet: os.system("sudo systemctl stop systemd-resolved") - with self.subTest(internet=internet): + with subtests.test(internet=internet): managed_processes['qcomgpsd'].start() assert self._wait_for_output(7) managed_processes['qcomgpsd'].stop() - def test_turns_off_gnss(self): + def test_turns_off_gnss(self, subtests): for s in (0.1, 1, 5): - with self.subTest(runtime=s): + with subtests.test(runtime=s): managed_processes['qcomgpsd'].start() time.sleep(s) managed_processes['qcomgpsd'].stop() @@ -87,7 +85,7 @@ class TestRawgpsd(unittest.TestCase): if should_be_loaded: assert valid_duration == "10080" # should be max time injected_time = datetime.datetime.strptime(injected_time_str.replace("\"", ""), "%Y/%m/%d,%H:%M:%S") - self.assertLess(abs((datetime.datetime.utcnow() - injected_time).total_seconds()), 60*60*12) + assert abs((datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - injected_time).total_seconds()) < 60*60*12 else: valid_duration, injected_time_str = out.split(",", 1) injected_time_str = injected_time_str.replace('\"', '').replace('\'', '') @@ -119,6 +117,3 @@ class TestRawgpsd(unittest.TestCase): time.sleep(15) managed_processes['qcomgpsd'].stop() self.check_assistance(True) - -if __name__ == "__main__": - unittest.main(failfast=True) diff --git a/system/sensord/SConscript b/system/sensord/SConscript index 7974eb07ae..e2dfb522c6 100644 --- a/system/sensord/SConscript +++ b/system/sensord/SConscript @@ -1,4 +1,4 @@ -Import('env', 'arch', 'common', 'cereal', 'messaging') +Import('env', 'arch', 'common', 'messaging') sensors = [ 'sensors/i2c_sensor.cc', @@ -11,7 +11,7 @@ sensors = [ 'sensors/lsm6ds3_temp.cc', 'sensors/mmc5603nj_magn.cc', ] -libs = [common, cereal, messaging, 'capnp', 'zmq', 'kj', 'pthread'] +libs = [common, messaging, 'pthread'] if arch == "larch64": libs.append('i2c') env.Program('sensord', ['sensors_qcom2.cc'] + sensors, LIBS=libs) diff --git a/system/sensord/tests/test_sensord.py b/system/sensord/tests/test_sensord.py old mode 100755 new mode 100644 index 3075c8a343..1871012dd6 --- a/system/sensord/tests/test_sensord.py +++ b/system/sensord/tests/test_sensord.py @@ -1,8 +1,6 @@ -#!/usr/bin/env python3 import os import pytest import time -import unittest import numpy as np from collections import namedtuple, defaultdict @@ -11,7 +9,7 @@ from cereal import log from cereal.services import SERVICE_LIST from openpilot.common.gpio import get_irqs_for_action from openpilot.common.timeout import Timeout -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes BMX = { ('bmx055', 'acceleration'), @@ -99,9 +97,9 @@ def read_sensor_events(duration_sec): return {k: v for k, v in events.items() if len(v) > 0} @pytest.mark.tici -class TestSensord(unittest.TestCase): +class TestSensord: @classmethod - def setUpClass(cls): + def setup_class(cls): # enable LSM self test os.environ["LSM_SELF_TEST"] = "1" @@ -119,10 +117,10 @@ class TestSensord(unittest.TestCase): managed_processes["sensord"].stop() @classmethod - def tearDownClass(cls): + def teardown_class(cls): managed_processes["sensord"].stop() - def tearDown(self): + def teardown_method(self): managed_processes["sensord"].stop() def test_sensors_present(self): @@ -133,9 +131,9 @@ class TestSensord(unittest.TestCase): m = getattr(measurement, measurement.which()) seen.add((str(m.source), m.which())) - self.assertIn(seen, SENSOR_CONFIGURATIONS) + assert seen in SENSOR_CONFIGURATIONS - def test_lsm6ds3_timing(self): + def test_lsm6ds3_timing(self, subtests): # verify measurements are sampled and published at 104Hz sensor_t = { @@ -152,7 +150,7 @@ class TestSensord(unittest.TestCase): sensor_t[m.sensor].append(m.timestamp) for s, vals in sensor_t.items(): - with self.subTest(sensor=s): + with subtests.test(sensor=s): assert len(vals) > 0 tdiffs = np.diff(vals) / 1e6 # millis @@ -166,9 +164,9 @@ class TestSensord(unittest.TestCase): stddev = np.std(tdiffs) assert stddev < 2.0, f"Standard-dev to big {stddev}" - def test_sensor_frequency(self): + def test_sensor_frequency(self, subtests): for s, msgs in self.events.items(): - with self.subTest(sensor=s): + with subtests.test(sensor=s): freq = len(msgs) / self.sample_secs ef = SERVICE_LIST[s].frequency assert ef*0.85 <= freq <= ef*1.15 @@ -246,6 +244,3 @@ class TestSensord(unittest.TestCase): state_two = get_irq_count(self.sensord_irq) assert state_one == state_two, "Interrupts received after sensord stop!" - -if __name__ == "__main__": - unittest.main() diff --git a/system/sensord/tests/ttff_test.py b/system/sensord/tests/ttff_test.py index e023489ed5..a9cc16d707 100755 --- a/system/sensord/tests/ttff_test.py +++ b/system/sensord/tests/ttff_test.py @@ -4,7 +4,7 @@ import time import atexit from cereal import messaging -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes TIMEOUT = 10*60 diff --git a/selfdrive/sentry.py b/system/sentry.py similarity index 76% rename from selfdrive/sentry.py rename to system/sentry.py index 5b63a9fe2d..5f4772fa71 100644 --- a/selfdrive/sentry.py +++ b/system/sentry.py @@ -4,11 +4,10 @@ from enum import Enum from sentry_sdk.integrations.threading import ThreadingIntegration from openpilot.common.params import Params -from openpilot.selfdrive.athena.registration import is_registered_device +from openpilot.system.athena.registration import is_registered_device from openpilot.system.hardware import HARDWARE, PC from openpilot.common.swaglog import cloudlog -from openpilot.system.version import get_branch, get_commit, get_origin, get_version, \ - is_comma_remote, is_dirty, is_tested_branch +from openpilot.system.version import get_build_metadata, get_version class SentryProject(Enum): @@ -43,12 +42,13 @@ def set_tag(key: str, value: str) -> None: def init(project: SentryProject) -> bool: + build_metadata = get_build_metadata() # forks like to mess with this, so double check - comma_remote = is_comma_remote() and "commaai" in get_origin() + comma_remote = build_metadata.openpilot.comma_remote and "commaai" in build_metadata.openpilot.git_origin if not comma_remote or not is_registered_device() or PC: return False - env = "release" if is_tested_branch() else "master" + env = "release" if build_metadata.tested_channel else "master" dongle_id = Params().get("DongleId", encoding='utf-8') integrations = [] @@ -63,11 +63,13 @@ def init(project: SentryProject) -> bool: max_value_length=8192, environment=env) + build_metadata = get_build_metadata() + sentry_sdk.set_user({"id": dongle_id}) - sentry_sdk.set_tag("dirty", is_dirty()) - sentry_sdk.set_tag("origin", get_origin()) - sentry_sdk.set_tag("branch", get_branch()) - sentry_sdk.set_tag("commit", get_commit()) + sentry_sdk.set_tag("dirty", build_metadata.openpilot.is_dirty) + sentry_sdk.set_tag("origin", build_metadata.openpilot.git_origin) + sentry_sdk.set_tag("branch", build_metadata.channel) + sentry_sdk.set_tag("commit", build_metadata.openpilot.git_commit) sentry_sdk.set_tag("device", HARDWARE.get_device_type()) if project == SentryProject.SELFDRIVE: diff --git a/selfdrive/statsd.py b/system/statsd.py similarity index 93% rename from selfdrive/statsd.py rename to system/statsd.py index 299aa295d7..5e76b73ae9 100755 --- a/selfdrive/statsd.py +++ b/system/statsd.py @@ -4,7 +4,7 @@ import zmq import time from pathlib import Path from collections import defaultdict -from datetime import datetime, timezone +from datetime import datetime, UTC from typing import NoReturn from openpilot.common.params import Params @@ -13,7 +13,7 @@ from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE from openpilot.common.file_helpers import atomic_write_in_dir -from openpilot.system.version import get_normalized_origin, get_short_branch, get_short_version, is_dirty +from openpilot.system.version import get_build_metadata from openpilot.system.loggerd.config import STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S @@ -86,13 +86,15 @@ def main() -> NoReturn: # initialize stats directory Path(STATS_DIR).mkdir(parents=True, exist_ok=True) + build_metadata = get_build_metadata() + # initialize tags tags = { 'started': False, - 'version': get_short_version(), - 'branch': get_short_branch(), - 'dirty': is_dirty(), - 'origin': get_normalized_origin(), + 'version': build_metadata.openpilot.version, + 'branch': build_metadata.channel, + 'dirty': build_metadata.openpilot.is_dirty, + 'origin': build_metadata.openpilot.git_normalized_origin, 'deviceType': HARDWARE.get_device_type(), } @@ -131,7 +133,7 @@ def main() -> NoReturn: # flush when started state changes or after FLUSH_TIME_S if (time.monotonic() > last_flush_time + STATS_FLUSH_TIME_S) or (sm['deviceState'].started != started_prev): result = "" - current_time = datetime.utcnow().replace(tzinfo=timezone.utc) + current_time = datetime.now(UTC) tags['started'] = sm['deviceState'].started for key, value in gauges.items(): diff --git a/system/tests/test_logmessaged.py b/system/tests/test_logmessaged.py old mode 100755 new mode 100644 index d27dae46ad..3baf5300c0 --- a/system/tests/test_logmessaged.py +++ b/system/tests/test_logmessaged.py @@ -1,17 +1,15 @@ -#!/usr/bin/env python3 import glob import os import time -import unittest import cereal.messaging as messaging -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog, ipchandler -class TestLogmessaged(unittest.TestCase): - def setUp(self): +class TestLogmessaged: + def setup_method(self): # clear the IPC buffer in case some other tests used cloudlog and filled it ipchandler.close() ipchandler.connect() @@ -25,7 +23,7 @@ class TestLogmessaged(unittest.TestCase): messaging.drain_sock(self.sock) messaging.drain_sock(self.error_sock) - def tearDown(self): + def teardown_method(self): del self.sock del self.error_sock managed_processes['logmessaged'].stop(block=True) @@ -55,6 +53,3 @@ class TestLogmessaged(unittest.TestCase): logsize = sum([os.path.getsize(f) for f in self._get_log_files()]) assert (n*len(msg)) < logsize < (n*(len(msg)+1024)) - -if __name__ == "__main__": - unittest.main() diff --git a/system/timed.py b/system/timed.py index 39acb2ba12..2b9a42c455 100755 --- a/system/timed.py +++ b/system/timed.py @@ -8,6 +8,7 @@ from typing import NoReturn from timezonefinder import TimezoneFinder import cereal.messaging as messaging +from openpilot.common.time import system_time_valid from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import AGNOS @@ -56,7 +57,6 @@ def main() -> NoReturn: """ params = Params() - tf = TimezoneFinder() # Restore timezone from param tz = params.get("Timezone", encoding='utf8') @@ -70,7 +70,8 @@ def main() -> NoReturn: while True: sm.update(1000) - msg = messaging.new_message('clocks', valid=True) + msg = messaging.new_message('clocks') + msg.valid = system_time_valid() msg.clocks.wallTimeNanos = time.time_ns() pm.send('clocks', msg) diff --git a/selfdrive/tombstoned.py b/system/tombstoned.py similarity index 94% rename from selfdrive/tombstoned.py rename to system/tombstoned.py index ba3582d130..5bcced2666 100755 --- a/selfdrive/tombstoned.py +++ b/system/tombstoned.py @@ -9,10 +9,10 @@ import time import glob from typing import NoReturn -import openpilot.selfdrive.sentry as sentry +import openpilot.system.sentry as sentry from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog -from openpilot.system.version import get_commit +from openpilot.system.version import get_build_metadata MAX_SIZE = 1_000_000 * 100 # allow up to 100M MAX_TOMBSTONE_FN_LEN = 62 # 85 - 23 ("/crash/") @@ -124,7 +124,9 @@ 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() or 'nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] + build_metadata = get_build_metadata() + + new_fn = f"{date}_{(build_metadata.openpilot.git_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/system/ubloxd/SConscript b/system/ubloxd/SConscript index 67d9856dad..ce09e235e6 100644 --- a/system/ubloxd/SConscript +++ b/system/ubloxd/SConscript @@ -1,6 +1,6 @@ -Import('env', 'common', 'cereal', 'messaging') +Import('env', 'common', 'messaging') -loc_libs = [cereal, messaging, 'zmq', common, 'capnp', 'kj', 'kaitai', 'pthread'] +loc_libs = [messaging, common, 'kaitai', 'pthread'] if GetOption('kaitai'): generated = Dir('generated').srcnode().abspath diff --git a/system/sensord/pigeond.py b/system/ubloxd/pigeond.py similarity index 99% rename from system/sensord/pigeond.py rename to system/ubloxd/pigeond.py index 21b3a86f97..8809689dcd 100755 --- a/system/sensord/pigeond.py +++ b/system/ubloxd/pigeond.py @@ -6,7 +6,7 @@ import serial import struct import requests import urllib.parse -from datetime import datetime +from datetime import datetime, UTC from cereal import messaging from openpilot.common.params import Params @@ -61,7 +61,7 @@ def get_assistnow_messages(token: bytes) -> list[bytes]: return msgs -class TTYPigeon(): +class TTYPigeon: def __init__(self): self.tty = serial.VTIMESerial(UBLOX_TTY, baudrate=9600, timeout=0) @@ -196,7 +196,7 @@ def initialize_pigeon(pigeon: TTYPigeon) -> bool: cloudlog.error(f"failed to restore almanac backup, status: {restore_status}") # sending time to ublox - t_now = datetime.utcnow() + t_now = datetime.now(UTC).replace(tzinfo=None) if t_now >= datetime(2021, 6, 1): cloudlog.warning("Sending current time to ublox") diff --git a/system/ubloxd/tests/print_gps_stats.py b/system/ubloxd/tests/print_gps_stats.py index cfddb7f8b8..edf94c060a 100755 --- a/system/ubloxd/tests/print_gps_stats.py +++ b/system/ubloxd/tests/print_gps_stats.py @@ -13,7 +13,7 @@ if __name__ == "__main__": cnos = [] for m in ug.measurementReport.measurements: cnos.append(m.cno) - print("Sats: %d Accuracy: %.2f m cnos" % (ug.measurementReport.numMeas, gle.accuracy), sorted(cnos)) + print("Sats: %d Accuracy: %.2f m cnos" % (ug.measurementReport.numMeas, gle.horizontalAccuracy), sorted(cnos)) except Exception: pass sm.update() diff --git a/system/sensord/tests/test_pigeond.py b/system/ubloxd/tests/test_pigeond.py old mode 100755 new mode 100644 similarity index 87% rename from system/sensord/tests/test_pigeond.py rename to system/ubloxd/tests/test_pigeond.py index 742e20bb90..202820e412 --- a/system/sensord/tests/test_pigeond.py +++ b/system/ubloxd/tests/test_pigeond.py @@ -1,21 +1,19 @@ -#!/usr/bin/env python3 import pytest import time -import unittest import cereal.messaging as messaging from cereal.services import SERVICE_LIST from openpilot.common.gpio import gpio_read from openpilot.selfdrive.test.helpers import with_processes -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.manager.process_config import managed_processes from openpilot.system.hardware.tici.pins import GPIO # TODO: test TTFF when we have good A-GNSS @pytest.mark.tici -class TestPigeond(unittest.TestCase): +class TestPigeond: - def tearDown(self): + def teardown_method(self): managed_processes['pigeond'].stop() @with_processes(['pigeond']) @@ -54,7 +52,3 @@ class TestPigeond(unittest.TestCase): assert gpio_read(GPIO.UBLOX_RST_N) == 0 assert gpio_read(GPIO.GNSS_PWR_EN) == 0 - - -if __name__ == "__main__": - unittest.main() diff --git a/system/ubloxd/ublox_msg.cc b/system/ubloxd/ublox_msg.cc index 7b4505dc81..26b33a1e32 100644 --- a/system/ubloxd/ublox_msg.cc +++ b/system/ubloxd/ublox_msg.cc @@ -127,12 +127,13 @@ kj::Array UbloxMsgParser::gen_nav_pvt(ubx_t::nav_pvt_t *msg) { auto gpsLoc = msg_builder.initEvent().initGpsLocationExternal(); gpsLoc.setSource(cereal::GpsLocationData::SensorSource::UBLOX); gpsLoc.setFlags(msg->flags()); + gpsLoc.setHasFix((msg->flags() % 2) == 1); gpsLoc.setLatitude(msg->lat() * 1e-07); gpsLoc.setLongitude(msg->lon() * 1e-07); gpsLoc.setAltitude(msg->height() * 1e-03); gpsLoc.setSpeed(msg->g_speed() * 1e-03); gpsLoc.setBearingDeg(msg->head_mot() * 1e-5); - gpsLoc.setAccuracy(msg->h_acc() * 1e-03); + gpsLoc.setHorizontalAccuracy(msg->h_acc() * 1e-03); std::tm timeinfo = std::tm(); timeinfo.tm_year = msg->year() - 1900; timeinfo.tm_mon = msg->month() - 1; diff --git a/system/ugpsd.py b/system/ugpsd.py new file mode 100755 index 0000000000..34b20b01c8 --- /dev/null +++ b/system/ugpsd.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +import os +import time +import traceback +import serial +import datetime +import numpy as np +from collections import defaultdict + +from cereal import log +import cereal.messaging as messaging +from openpilot.common.retry import retry +from openpilot.common.swaglog import cloudlog +from openpilot.system.qcomgpsd.qcomgpsd import at_cmd, wait_for_modem + + +def sfloat(n: str): + return float(n) if len(n) > 0 else 0 + +def checksum(s: str): + ret = 0 + for c in s[1:-3]: + ret ^= ord(c) + return format(ret, '02X') + +class Unicore: + def __init__(self): + self.s = serial.Serial('/dev/ttyHS0', 115200) + self.s.timeout = 1 + self.s.writeTimeout = 1 + self.s.newline = b'\r\n' + + self.s.flush() + self.s.reset_input_buffer() + self.s.reset_output_buffer() + self.s.read(2048) + + def send(self, cmd): + self.s.write(cmd.encode('utf8') + b'\r') + resp = self.s.read(2048) + print(len(resp), cmd, "\n", resp) + assert b"OK" in resp + + def recv(self): + return self.s.readline() + +def build_msg(state): + """ + NMEA sentences: + https://campar.in.tum.de/twiki/pub/Chair/NaviGpsDemon/nmea.html#RMC + NAV messages: + https://www.unicorecomm.com/assets/upload/file/UFirebird_Standard_Positioning_Products_Protocol_Specification_CH.pdf + """ + + msg = messaging.new_message('gpsLocation', valid=True) + gps = msg.gpsLocation + + gnrmc = state['$GNRMC'] + gps.hasFix = gnrmc[1] == 'A' + gps.source = log.GpsLocationData.SensorSource.unicore + gps.latitude = (sfloat(gnrmc[3][:2]) + (sfloat(gnrmc[3][2:]) / 60)) * (1 if gnrmc[4] == "N" else -1) + gps.longitude = (sfloat(gnrmc[5][:3]) + (sfloat(gnrmc[5][3:]) / 60)) * (1 if gnrmc[6] == "E" else -1) + + try: + date = gnrmc[9][:6] + dt = datetime.datetime.strptime(f"{date} {gnrmc[1]}", '%d%m%y %H%M%S.%f') + gps.unixTimestampMillis = dt.timestamp()*1e3 + except Exception: + pass + + gps.bearingDeg = sfloat(gnrmc[8]) + + if len(state['$GNGGA']): + gngga = state['$GNGGA'] + if gngga[10] == 'M': + gps.altitude = sfloat(gngga[9]) + + if len(state['$GNGSA']): + gngsa = state['$GNGSA'] + gps.horizontalAccuracy = sfloat(gngsa[4]) + gps.verticalAccuracy = sfloat(gngsa[5]) + + #if len(state['$NAVACC']): + # # $NAVVEL,264415000,5,3,0.375,0.141,-0.735,-65.450*2A + # navacc = state['$NAVACC'] + # gps.horizontalAccuracy = sfloat(navacc[3]) + # gps.speedAccuracy = sfloat(navacc[4]) + # gps.bearingAccuracyDeg = sfloat(navacc[5]) + + if len(state['$NAVVEL']): + # $NAVVEL,264415000,5,3,0.375,0.141,-0.735,-65.450*2A + navvel = state['$NAVVEL'] + vECEF = [ + sfloat(navvel[4]), + sfloat(navvel[5]), + sfloat(navvel[6]), + ] + + lat = np.radians(gps.latitude) + lon = np.radians(gps.longitude) + R = np.array([ + [-np.sin(lat) * np.cos(lon), -np.sin(lon), -np.cos(lat) * np.cos(lon)], + [-np.sin(lat) * np.sin(lon), np.cos(lon), -np.cos(lat) * np.sin(lon)], + [np.cos(lat), 0, -np.sin(lat)] + ]) + + vNED = [float(x) for x in R.dot(vECEF)] + gps.vNED = vNED + gps.speed = np.linalg.norm(vNED) + + # TODO: set these from the module + gps.bearingAccuracyDeg = 5. + gps.speedAccuracy = 3. + + return msg + + +@retry(attempts=10, delay=0.1) +def setup(u): + at_cmd('AT+CGPS=0') + at_cmd('AT+CGPS=1') + time.sleep(1.0) + + # setup NAVXXX outputs + for i in range(4): + u.send(f"$CFGMSG,1,{i},1") + for i in (1, 3): + u.send(f"$CFGMSG,3,{i},1") + + # 10Hz NAV outputs + u.send("$CFGNAV,100,100,1000") + + +def main(): + wait_for_modem("AT+CGPS?") + + u = Unicore() + setup(u) + + state = defaultdict(list) + pm = messaging.PubMaster(['gpsLocation']) + while True: + try: + msg = u.recv().decode('utf8').strip() + if "DEBUG" in os.environ: + print(repr(msg)) + + if len(msg) > 0: + if checksum(msg) != msg.split('*')[1]: + cloudlog.error(f"invalid checksum: {repr(msg)}") + continue + + k = msg.split(',')[0] + state[k] = msg.split(',') + if '$GNRMC' not in msg: + continue + + pm.send('gpsLocation', build_msg(state)) + except Exception: + traceback.print_exc() + cloudlog.exception("gps.issue") + + +if __name__ == "__main__": + main() diff --git a/system/hardware/tici/casync.py b/system/updated/casync/casync.py similarity index 81% rename from system/hardware/tici/casync.py rename to system/updated/casync/casync.py index 986228c1cd..7a3303a9e9 100755 --- a/system/hardware/tici/casync.py +++ b/system/updated/casync/casync.py @@ -2,15 +2,19 @@ import io import lzma import os +import pathlib import struct import sys import time from abc import ABC, abstractmethod from collections import defaultdict, namedtuple from collections.abc import Callable +from typing import IO import requests from Crypto.Hash import SHA512 +from openpilot.system.updated.casync import tar +from openpilot.system.updated.casync.common import create_casync_tar_package CA_FORMAT_INDEX = 0x96824d9c7b129ff9 CA_FORMAT_TABLE = 0xe75b9e112f17417d @@ -37,20 +41,25 @@ class ChunkReader(ABC): ... -class FileChunkReader(ChunkReader): +class BinaryChunkReader(ChunkReader): """Reads chunks from a local file""" - def __init__(self, fn: str) -> None: + def __init__(self, file_like: IO[bytes]) -> None: super().__init__() - self.f = open(fn, 'rb') - - def __del__(self): - self.f.close() + self.f = file_like def read(self, chunk: Chunk) -> bytes: self.f.seek(chunk.offset) return self.f.read(chunk.length) +class FileChunkReader(BinaryChunkReader): + def __init__(self, path: str) -> None: + super().__init__(open(path, 'rb')) + + def __del__(self): + self.f.close() + + class RemoteChunkReader(ChunkReader): """Reads lzma compressed chunks from a remote store""" @@ -83,6 +92,20 @@ class RemoteChunkReader(ChunkReader): return decompressor.decompress(contents) +class DirectoryTarChunkReader(BinaryChunkReader): + """creates a tar archive of a directory and reads chunks from it""" + + def __init__(self, path: str, cache_file: str) -> None: + create_casync_tar_package(pathlib.Path(path), pathlib.Path(cache_file)) + + self.f = open(cache_file, "rb") + return super().__init__(self.f) + + def __del__(self): + self.f.close() + os.unlink(self.f.name) + + 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""" @@ -181,6 +204,21 @@ def extract(target: list[Chunk], return stats +def extract_directory(target: list[Chunk], + sources: list[tuple[str, ChunkReader, ChunkDict]], + out_path: str, + tmp_file: str, + progress: Callable[[int], None] = None): + """extract a directory stored as a casync tar archive""" + + stats = extract(target, sources, tmp_file, progress) + + with open(tmp_file, "rb") as f: + tar.extract_tar_archive(f, pathlib.Path(out_path)) + + return stats + + def print_stats(stats: dict[str, int]): total_bytes = sum(stats.values()) print(f"Total size: {total_bytes / 1024 / 1024:.2f} MB") diff --git a/system/updated/casync/common.py b/system/updated/casync/common.py new file mode 100644 index 0000000000..6979f5cb06 --- /dev/null +++ b/system/updated/casync/common.py @@ -0,0 +1,61 @@ +import dataclasses +import json +import pathlib +import subprocess + +from openpilot.system.version import BUILD_METADATA_FILENAME, BuildMetadata +from openpilot.system.updated.casync import tar + + +CASYNC_ARGS = ["--with=symlinks", "--with=permissions", "--compression=xz", "--chunk-size=16M"] +CASYNC_FILES = [BUILD_METADATA_FILENAME] + + +def run(cmd): + return subprocess.check_output(cmd) + + +def get_exclude_set(path) -> set[str]: + exclude_set = set(CASYNC_FILES) + + for file in path.rglob("*"): + if file.is_file() or file.is_symlink(): + + while file.resolve() != path.resolve(): + exclude_set.add(str(file.relative_to(path))) + + file = file.parent + + return exclude_set + + +def create_build_metadata_file(path: pathlib.Path, build_metadata: BuildMetadata): + with open(path / BUILD_METADATA_FILENAME, "w") as f: + build_metadata_dict = dataclasses.asdict(build_metadata) + build_metadata_dict["openpilot"].pop("is_dirty") # this is determined at runtime + build_metadata_dict.pop("channel") # channel is unrelated to the build itself + f.write(json.dumps(build_metadata_dict)) + + +def is_not_git(path: pathlib.Path) -> bool: + return ".git" not in path.parts + + +def create_casync_tar_package(target_dir: pathlib.Path, output_path: pathlib.Path): + tar.create_tar_archive(output_path, target_dir, is_not_git) + + +def create_casync_from_file(file: pathlib.Path, output_dir: pathlib.Path, caibx_name: str): + caibx_file = output_dir / f"{caibx_name}.caibx" + run(["casync", "make", *CASYNC_ARGS, caibx_file, str(file)]) + + return caibx_file + + +def create_casync_release(target_dir: pathlib.Path, output_dir: pathlib.Path, caibx_name: str): + tar_file = output_dir / f"{caibx_name}.tar" + create_casync_tar_package(target_dir, tar_file) + caibx_file = create_casync_from_file(tar_file, output_dir, caibx_name) + tar_file.unlink() + digest = run(["casync", "digest", *CASYNC_ARGS, target_dir]).decode("utf-8").strip() + return digest, caibx_file diff --git a/system/updated/casync/tar.py b/system/updated/casync/tar.py new file mode 100644 index 0000000000..a5a8238bba --- /dev/null +++ b/system/updated/casync/tar.py @@ -0,0 +1,39 @@ +import pathlib +import tarfile +from typing import IO +from collections.abc import Callable + + +def include_default(_) -> bool: + return True + + +def create_tar_archive(filename: pathlib.Path, directory: pathlib.Path, include: Callable[[pathlib.Path], bool] = include_default): + """Creates a tar archive of a directory""" + + with tarfile.open(filename, 'w') as tar: + for file in sorted(directory.rglob("*"), key=lambda f: f.stat().st_size if f.is_file() else 0, reverse=True): + if not include(file): + continue + relative_path = str(file.relative_to(directory)) + if file.is_symlink(): + info = tarfile.TarInfo(relative_path) + info.type = tarfile.SYMTYPE + info.linkpath = str(file.readlink()) + tar.addfile(info) + + elif file.is_file(): + info = tarfile.TarInfo(relative_path) + info.size = file.stat().st_size + info.type = tarfile.REGTYPE + info.mode = file.stat().st_mode + with file.open('rb') as f: + tar.addfile(info, f) + + +def extract_tar_archive(fh: IO[bytes], directory: pathlib.Path): + """Extracts a tar archive to a directory""" + + tar = tarfile.open(fileobj=fh, mode='r') + tar.extractall(str(directory), filter=lambda info, path: info) + tar.close() diff --git a/system/updated/casync/tests/test_casync.py b/system/updated/casync/tests/test_casync.py new file mode 100644 index 0000000000..bc171e7432 --- /dev/null +++ b/system/updated/casync/tests/test_casync.py @@ -0,0 +1,264 @@ +import pytest +import os +import pathlib +import tempfile +import subprocess + +from openpilot.system.updated.casync import casync +from openpilot.system.updated.casync import tar + +# dd if=/dev/zero of=/tmp/img.raw bs=1M count=2 +# sudo losetup -f /tmp/img.raw +# losetup -a | grep img.raw +LOOPBACK = os.environ.get('LOOPBACK', None) + + +@pytest.mark.skip("not used yet") +class TestCasync: + @classmethod + def setup_class(cls): + cls.tmpdir = tempfile.TemporaryDirectory() + + # Build example contents + chunk_a = [i % 256 for i in range(1024)] * 512 + chunk_b = [(256 - i) % 256 for i in range(1024)] * 512 + zeroes = [0] * (1024 * 128) + contents = chunk_a + chunk_b + zeroes + chunk_a + + cls.contents = bytes(contents) + + # Write to file + cls.orig_fn = os.path.join(cls.tmpdir.name, 'orig.bin') + with open(cls.orig_fn, 'wb') as f: + f.write(cls.contents) + + # Create casync files + cls.manifest_fn = os.path.join(cls.tmpdir.name, 'orig.caibx') + cls.store_fn = os.path.join(cls.tmpdir.name, 'store') + subprocess.check_output(["casync", "make", "--compression=xz", "--store", cls.store_fn, cls.manifest_fn, cls.orig_fn]) + + target = casync.parse_caibx(cls.manifest_fn) + hashes = [c.sha.hex() for c in target] + + # Ensure we have chunk reuse + assert len(hashes) > len(set(hashes)) + + def setup_method(self): + # Clear target_lo + if LOOPBACK is not None: + self.target_lo = LOOPBACK + with open(self.target_lo, 'wb') as f: + f.write(b"0" * len(self.contents)) + + self.target_fn = os.path.join(self.tmpdir.name, next(tempfile._get_candidate_names())) + self.seed_fn = os.path.join(self.tmpdir.name, next(tempfile._get_candidate_names())) + + def teardown_method(self): + for fn in [self.target_fn, self.seed_fn]: + try: + os.unlink(fn) + except FileNotFoundError: + pass + + def test_simple_extract(self): + target = casync.parse_caibx(self.manifest_fn) + + sources = [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + stats = casync.extract(target, sources, self.target_fn) + + with open(self.target_fn, 'rb') as target_f: + assert target_f.read() == self.contents + + assert stats['remote'] == len(self.contents) + + def test_seed(self): + target = casync.parse_caibx(self.manifest_fn) + + # Populate seed with half of the target contents + with open(self.seed_fn, 'wb') as seed_f: + seed_f.write(self.contents[:len(self.contents) // 2]) + + sources = [('seed', casync.FileChunkReader(self.seed_fn), casync.build_chunk_dict(target))] + sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + stats = casync.extract(target, sources, self.target_fn) + + with open(self.target_fn, 'rb') as target_f: + assert target_f.read() == self.contents + + assert stats['seed'] > 0 + assert stats['remote'] < len(self.contents) + + def test_already_done(self): + """Test that an already flashed target doesn't download any chunks""" + target = casync.parse_caibx(self.manifest_fn) + + with open(self.target_fn, 'wb') as f: + f.write(self.contents) + + sources = [('target', casync.FileChunkReader(self.target_fn), casync.build_chunk_dict(target))] + sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + + stats = casync.extract(target, sources, self.target_fn) + + with open(self.target_fn, 'rb') as f: + assert f.read() == self.contents + + assert stats['target'] == len(self.contents) + + def test_chunk_reuse(self): + """Test that chunks that are reused are only downloaded once""" + target = casync.parse_caibx(self.manifest_fn) + + # Ensure target exists + with open(self.target_fn, 'wb'): + pass + + sources = [('target', casync.FileChunkReader(self.target_fn), casync.build_chunk_dict(target))] + sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + + stats = casync.extract(target, sources, self.target_fn) + + with open(self.target_fn, 'rb') as f: + assert f.read() == self.contents + + assert stats['remote'] < len(self.contents) + + @pytest.mark.skipif(not LOOPBACK, reason="requires loopback device") + def test_lo_simple_extract(self): + target = casync.parse_caibx(self.manifest_fn) + sources = [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + + stats = casync.extract(target, sources, self.target_lo) + + with open(self.target_lo, 'rb') as target_f: + assert target_f.read(len(self.contents)) == self.contents + + assert stats['remote'] == len(self.contents) + + @pytest.mark.skipif(not LOOPBACK, reason="requires loopback device") + def test_lo_chunk_reuse(self): + """Test that chunks that are reused are only downloaded once""" + target = casync.parse_caibx(self.manifest_fn) + + sources = [('target', casync.FileChunkReader(self.target_lo), casync.build_chunk_dict(target))] + sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + + stats = casync.extract(target, sources, self.target_lo) + + with open(self.target_lo, 'rb') as f: + assert f.read(len(self.contents)) == self.contents + + assert stats['remote'] < len(self.contents) + + +@pytest.mark.skip("not used yet") +class TestCasyncDirectory: + """Tests extracting a directory stored as a casync tar archive""" + + NUM_FILES = 16 + + @classmethod + def setup_cache(cls, directory, files=None): + if files is None: + files = range(cls.NUM_FILES) + + chunk_a = [i % 256 for i in range(1024)] * 512 + chunk_b = [(256 - i) % 256 for i in range(1024)] * 512 + zeroes = [0] * (1024 * 128) + cls.contents = chunk_a + chunk_b + zeroes + chunk_a + cls.contents = bytes(cls.contents) + + for i in files: + with open(os.path.join(directory, f"file_{i}.txt"), "wb") as f: + f.write(cls.contents) + + os.symlink(f"file_{i}.txt", os.path.join(directory, f"link_{i}.txt")) + + @classmethod + def setup_class(cls): + cls.tmpdir = tempfile.TemporaryDirectory() + + # Create casync files + cls.manifest_fn = os.path.join(cls.tmpdir.name, 'orig.caibx') + cls.store_fn = os.path.join(cls.tmpdir.name, 'store') + + cls.directory_to_extract = tempfile.TemporaryDirectory() + cls.setup_cache(cls.directory_to_extract.name) + + cls.orig_fn = os.path.join(cls.tmpdir.name, 'orig.tar') + tar.create_tar_archive(cls.orig_fn, pathlib.Path(cls.directory_to_extract.name)) + + subprocess.check_output(["casync", "make", "--compression=xz", "--store", cls.store_fn, cls.manifest_fn, cls.orig_fn]) + + @classmethod + def teardown_class(cls): + cls.tmpdir.cleanup() + cls.directory_to_extract.cleanup() + + def setup_method(self): + self.cache_dir = tempfile.TemporaryDirectory() + self.working_dir = tempfile.TemporaryDirectory() + self.out_dir = tempfile.TemporaryDirectory() + + def teardown_method(self): + self.cache_dir.cleanup() + self.working_dir.cleanup() + self.out_dir.cleanup() + + def run_test(self): + target = casync.parse_caibx(self.manifest_fn) + + cache_filename = os.path.join(self.working_dir.name, "cache.tar") + tmp_filename = os.path.join(self.working_dir.name, "tmp.tar") + + sources = [('cache', casync.DirectoryTarChunkReader(self.cache_dir.name, cache_filename), casync.build_chunk_dict(target))] + sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + + stats = casync.extract_directory(target, sources, pathlib.Path(self.out_dir.name), tmp_filename) + + with open(os.path.join(self.out_dir.name, "file_0.txt"), "rb") as f: + assert f.read() == self.contents + + with open(os.path.join(self.out_dir.name, "link_0.txt"), "rb") as f: + assert f.read() == self.contents + assert os.readlink(os.path.join(self.out_dir.name, "link_0.txt")) == "file_0.txt" + + return stats + + def test_no_cache(self): + self.setup_cache(self.cache_dir.name, []) + stats = self.run_test() + assert stats['remote'] > 0 + assert stats['cache'] == 0 + + def test_full_cache(self): + self.setup_cache(self.cache_dir.name, range(self.NUM_FILES)) + stats = self.run_test() + assert stats['remote'] == 0 + assert stats['cache'] > 0 + + def test_one_file_cache(self): + self.setup_cache(self.cache_dir.name, range(1)) + stats = self.run_test() + assert stats['remote'] > 0 + assert stats['cache'] > 0 + assert stats['cache'] < stats['remote'] + + def test_one_file_incorrect_cache(self): + self.setup_cache(self.cache_dir.name, range(self.NUM_FILES)) + with open(os.path.join(self.cache_dir.name, "file_0.txt"), "wb") as f: + f.write(b"1234") + + stats = self.run_test() + assert stats['remote'] > 0 + assert stats['cache'] > 0 + assert stats['cache'] > stats['remote'] + + def test_one_file_missing_cache(self): + self.setup_cache(self.cache_dir.name, range(self.NUM_FILES)) + os.unlink(os.path.join(self.cache_dir.name, "file_12.txt")) + + stats = self.run_test() + assert stats['remote'] > 0 + assert stats['cache'] > 0 + assert stats['cache'] > stats['remote'] diff --git a/system/updated/common.py b/system/updated/common.py new file mode 100644 index 0000000000..6bb745f6b0 --- /dev/null +++ b/system/updated/common.py @@ -0,0 +1,16 @@ +import os +import pathlib + + +def get_consistent_flag(path: str) -> bool: + consistent_file = pathlib.Path(os.path.join(path, ".overlay_consistent")) + return consistent_file.is_file() + +def set_consistent_flag(path: str, consistent: bool) -> None: + os.sync() + consistent_file = pathlib.Path(os.path.join(path, ".overlay_consistent")) + if consistent: + consistent_file.touch() + elif not consistent: + consistent_file.unlink(missing_ok=True) + os.sync() diff --git a/system/updated/tests/test_base.py b/system/updated/tests/test_base.py new file mode 100644 index 0000000000..928d07cbe3 --- /dev/null +++ b/system/updated/tests/test_base.py @@ -0,0 +1,259 @@ +import os +import pathlib +import shutil +import signal +import stat +import subprocess +import tempfile +import time +import pytest + +from openpilot.common.params import Params +from openpilot.system.manager.process import ManagerProcess +from openpilot.selfdrive.test.helpers import processes_context + + +def get_consistent_flag(path: str) -> bool: + consistent_file = pathlib.Path(os.path.join(path, ".overlay_consistent")) + return consistent_file.is_file() + + +def run(args, **kwargs): + return subprocess.check_output(args, **kwargs) + + +def update_release(directory, name, version, agnos_version, release_notes): + with open(directory / "RELEASES.md", "w") as f: + f.write(release_notes) + + (directory / "common").mkdir(exist_ok=True) + + with open(directory / "common" / "version.h", "w") as f: + f.write(f'#define COMMA_VERSION "{version}"') + + launch_env = directory / "launch_env.sh" + with open(launch_env, "w") as f: + f.write(f'export AGNOS_VERSION="{agnos_version}"') + + st = os.stat(launch_env) + os.chmod(launch_env, st.st_mode | stat.S_IEXEC) + + test_symlink = directory / "test_symlink" + if not os.path.exists(str(test_symlink)): + os.symlink("common/version.h", test_symlink) + + +def get_version(path: str) -> str: + with open(os.path.join(path, "common", "version.h")) as f: + return f.read().split('"')[1] + + +@pytest.mark.slow # TODO: can we test overlayfs in GHA? +class TestBaseUpdate: + @classmethod + def setup_class(cls): + if "Base" in cls.__name__: + pytest.skip() + + def setup_method(self): + self.tmpdir = tempfile.mkdtemp() + + run(["sudo", "mount", "-t", "tmpfs", "tmpfs", self.tmpdir]) # overlayfs doesn't work inside of docker unless this is a tmpfs + + self.mock_update_path = pathlib.Path(self.tmpdir) + + self.params = Params() + + self.basedir = self.mock_update_path / "openpilot" + self.basedir.mkdir() + + self.staging_root = self.mock_update_path / "safe_staging" + self.staging_root.mkdir() + + self.remote_dir = self.mock_update_path / "remote" + self.remote_dir.mkdir() + + os.environ["UPDATER_STAGING_ROOT"] = str(self.staging_root) + os.environ["UPDATER_LOCK_FILE"] = str(self.mock_update_path / "safe_staging_overlay.lock") + + self.MOCK_RELEASES = { + "release3": ("0.1.2", "1.2", "0.1.2 release notes"), + "master": ("0.1.3", "1.2", "0.1.3 release notes"), + } + + @pytest.fixture(autouse=True) + def mock_basedir(self, mocker): + mocker.patch("openpilot.common.basedir.BASEDIR", self.basedir) + + def set_target_branch(self, branch): + self.params.put("UpdaterTargetBranch", branch) + + def setup_basedir_release(self, release): + self.params = Params() + self.set_target_branch(release) + + def update_remote_release(self, release): + raise NotImplementedError("") + + def setup_remote_release(self, release): + raise NotImplementedError("") + + def additional_context(self): + raise NotImplementedError("") + + def teardown_method(self): + try: + run(["sudo", "umount", "-l", str(self.staging_root / "merged")]) + run(["sudo", "umount", "-l", self.tmpdir]) + shutil.rmtree(self.tmpdir) + except Exception: + print("cleanup failed...") + + def wait_for_condition(self, condition, timeout=12): + start = time.monotonic() + while True: + waited = time.monotonic() - start + if condition(): + print(f"waited {waited}s for condition ") + return waited + + if waited > timeout: + raise TimeoutError("timed out waiting for condition") + + time.sleep(1) + + def _test_finalized_update(self, branch, version, agnos_version, release_notes): + assert get_version(str(self.staging_root / "finalized")) == version + assert get_consistent_flag(str(self.staging_root / "finalized")) + assert os.access(str(self.staging_root / "finalized" / "launch_env.sh"), os.X_OK) + + with open(self.staging_root / "finalized" / "test_symlink") as f: + assert version in f.read() + +class ParamsBaseUpdateTest(TestBaseUpdate): + def _test_finalized_update(self, branch, version, agnos_version, release_notes): + assert self.params.get("UpdaterNewDescription", encoding="utf-8").startswith(f"{version} / {branch}") + assert self.params.get("UpdaterNewReleaseNotes", encoding="utf-8") == f"

    {release_notes}

    \n" + super()._test_finalized_update(branch, version, agnos_version, release_notes) + + def send_check_for_updates_signal(self, updated: ManagerProcess): + updated.signal(signal.SIGUSR1.value) + + def send_download_signal(self, updated: ManagerProcess): + updated.signal(signal.SIGHUP.value) + + def _test_params(self, branch, fetch_available, update_available): + assert self.params.get("UpdaterTargetBranch", encoding="utf-8") == branch + assert self.params.get_bool("UpdaterFetchAvailable") == fetch_available + assert self.params.get_bool("UpdateAvailable") == update_available + + def wait_for_idle(self): + self.wait_for_condition(lambda: self.params.get("UpdaterState", encoding="utf-8") == "idle") + + def wait_for_failed(self): + self.wait_for_condition(lambda: self.params.get("UpdateFailedCount", encoding="utf-8") is not None and \ + int(self.params.get("UpdateFailedCount", encoding="utf-8")) > 0) + + def wait_for_fetch_available(self): + self.wait_for_condition(lambda: self.params.get_bool("UpdaterFetchAvailable")) + + def wait_for_update_available(self): + self.wait_for_condition(lambda: self.params.get_bool("UpdateAvailable")) + + def test_no_update(self): + # Start on release3, ensure we don't fetch any updates + self.setup_remote_release("release3") + self.setup_basedir_release("release3") + + with self.additional_context(), processes_context(["updated"]) as [updated]: + self._test_params("release3", False, False) + self.wait_for_idle() + self._test_params("release3", False, False) + + self.send_check_for_updates_signal(updated) + + self.wait_for_idle() + + self._test_params("release3", False, False) + + def test_new_release(self): + # Start on release3, simulate a release3 commit, ensure we fetch that update properly + self.setup_remote_release("release3") + self.setup_basedir_release("release3") + + with self.additional_context(), processes_context(["updated"]) as [updated]: + self._test_params("release3", False, False) + self.wait_for_idle() + self._test_params("release3", False, False) + + self.MOCK_RELEASES["release3"] = ("0.1.3", "1.2", "0.1.3 release notes") + self.update_remote_release("release3") + + self.send_check_for_updates_signal(updated) + + self.wait_for_fetch_available() + + self._test_params("release3", True, False) + + self.send_download_signal(updated) + + self.wait_for_update_available() + + self._test_params("release3", False, True) + self._test_finalized_update("release3", *self.MOCK_RELEASES["release3"]) + + def test_switch_branches(self): + # Start on release3, request to switch to master manually, ensure we switched + self.setup_remote_release("release3") + self.setup_remote_release("master") + self.setup_basedir_release("release3") + + with self.additional_context(), processes_context(["updated"]) as [updated]: + self._test_params("release3", False, False) + self.wait_for_idle() + self._test_params("release3", False, False) + + self.set_target_branch("master") + self.send_check_for_updates_signal(updated) + + self.wait_for_fetch_available() + + self._test_params("master", True, False) + + self.send_download_signal(updated) + + self.wait_for_update_available() + + self._test_params("master", False, True) + self._test_finalized_update("master", *self.MOCK_RELEASES["master"]) + + def test_agnos_update(self, mocker): + # Start on release3, push an update with an agnos change + self.setup_remote_release("release3") + self.setup_basedir_release("release3") + + with self.additional_context(), processes_context(["updated"]) as [updated]: + mocker.patch("openpilot.system.hardware.AGNOS", "True") + mocker.patch("openpilot.system.hardware.tici.hardware.Tici.get_os_version", "1.2") + mocker.patch("openpilot.system.hardware.tici.agnos.get_target_slot_number") + mocker.patch("openpilot.system.hardware.tici.agnos.flash_agnos_update") + + self._test_params("release3", False, False) + self.wait_for_idle() + self._test_params("release3", False, False) + + self.MOCK_RELEASES["release3"] = ("0.1.3", "1.3", "0.1.3 release notes") + self.update_remote_release("release3") + + self.send_check_for_updates_signal(updated) + + self.wait_for_fetch_available() + + self._test_params("release3", True, False) + + self.send_download_signal(updated) + + self.wait_for_update_available() + + self._test_params("release3", False, True) + self._test_finalized_update("release3", *self.MOCK_RELEASES["release3"]) diff --git a/system/updated/tests/test_git.py b/system/updated/tests/test_git.py new file mode 100644 index 0000000000..5a5a27000b --- /dev/null +++ b/system/updated/tests/test_git.py @@ -0,0 +1,22 @@ +import contextlib +from openpilot.system.updated.tests.test_base import ParamsBaseUpdateTest, run, update_release + + +class TestUpdateDGitStrategy(ParamsBaseUpdateTest): + def update_remote_release(self, release): + update_release(self.remote_dir, release, *self.MOCK_RELEASES[release]) + run(["git", "add", "."], cwd=self.remote_dir) + run(["git", "commit", "-m", f"openpilot release {release}"], cwd=self.remote_dir) + + def setup_remote_release(self, release): + run(["git", "init"], cwd=self.remote_dir) + run(["git", "checkout", "-b", release], cwd=self.remote_dir) + self.update_remote_release(release) + + def setup_basedir_release(self, release): + super().setup_basedir_release(release) + run(["git", "clone", "-b", release, self.remote_dir, self.basedir]) + + @contextlib.contextmanager + def additional_context(self): + yield diff --git a/selfdrive/updated.py b/system/updated/updated.py similarity index 96% rename from selfdrive/updated.py rename to system/updated/updated.py index b6b395f254..005c52bc8b 100755 --- a/selfdrive/updated.py +++ b/system/updated/updated.py @@ -11,15 +11,15 @@ import time import threading from collections import defaultdict from pathlib import Path -from markdown_it import MarkdownIt from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.time import system_time_valid -from openpilot.system.hardware import AGNOS, HARDWARE +from openpilot.common.markdown import parse_markdown from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert -from openpilot.system.version import is_tested_branch +from openpilot.system.hardware import AGNOS, HARDWARE +from openpilot.system.version import get_build_metadata LOCK_FILE = os.getenv("UPDATER_LOCK_FILE", "/tmp/safe_staging_overlay.lock") STAGING_ROOT = os.getenv("UPDATER_STAGING_ROOT", "/data/safe_staging") @@ -60,7 +60,7 @@ class WaitTimeHelper: self.ready_event.wait(timeout=t) def write_time_to_param(params, param) -> None: - t = datetime.datetime.utcnow() + t = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) params.put(param, t.isoformat().encode('utf8')) def read_time_from_param(params, param) -> datetime.datetime | None: @@ -89,7 +89,7 @@ def parse_release_notes(basedir: str) -> bytes: with open(os.path.join(basedir, "RELEASES.md"), "rb") as f: r = f.read().split(b'\n\n', 1)[0] # Slice latest release notes try: - return bytes(MarkdownIt().render(r.decode("utf-8")), encoding="utf-8") + return bytes(parse_markdown(r.decode("utf-8")), encoding="utf-8") except Exception: return r + b"\n" except FileNotFoundError: @@ -279,7 +279,7 @@ class Updater: if len(self.branches): self.params.put("UpdaterAvailableBranches", ','.join(self.branches.keys())) - last_update = datetime.datetime.utcnow() + last_update = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) if update_success: write_time_to_param(self.params, "LastUpdateTime") else: @@ -323,10 +323,11 @@ class Updater: for alert in ("Offroad_UpdateFailed", "Offroad_ConnectivityNeeded", "Offroad_ConnectivityNeededPrompt"): set_offroad_alert(alert, False) - now = datetime.datetime.utcnow() + now = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) dt = now - last_update + build_metadata = get_build_metadata() if failed_count > 15 and exception is not None and self.has_internet: - if is_tested_branch(): + if build_metadata.tested_channel: extra_text = "Ensure the software is correctly installed. Uninstall and re-install if this error persists." else: extra_text = exception @@ -428,7 +429,7 @@ def main() -> None: cloudlog.event("update installed") if not params.get("InstallDate"): - t = datetime.datetime.utcnow().isoformat() + t = datetime.datetime.now(datetime.UTC).replace(tzinfo=None).isoformat() params.put("InstallDate", t.encode('utf8')) updater = Updater() @@ -468,7 +469,7 @@ def main() -> None: # 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)) + timed_out = last_fetch is None or (datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - 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") diff --git a/system/version.py b/system/version.py index 4319ef2140..94964526b3 100755 --- a/system/version.py +++ b/system/version.py @@ -1,105 +1,42 @@ #!/usr/bin/env python3 +from dataclasses import dataclass +from functools import cache +import json import os +import pathlib import subprocess -from typing import TypeVar -from collections.abc import Callable -from functools import lru_cache from openpilot.common.basedir import BASEDIR from openpilot.common.swaglog import cloudlog +from openpilot.common.git import get_commit, get_origin, get_branch, get_short_branch, get_commit_date RELEASE_BRANCHES = ['release3-staging', 'release3', 'nightly'] TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging'] +BUILD_METADATA_FILENAME = "build.json" + training_version: bytes = b"0.2.0" terms_version: bytes = b"2" -_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: - return subprocess.check_output(cmd, encoding='utf8').strip() - - -def run_cmd_default(cmd: list[str], default: str = "") -> str: - try: - return run_cmd(cmd) - except subprocess.CalledProcessError: - return default - - -@cache -def get_commit(branch: str = "HEAD") -> str: - return run_cmd_default(["git", "rev-parse", branch]) - - -@cache -def get_commit_date(commit: str = "HEAD") -> str: - return run_cmd_default(["git", "show", "--no-patch", "--format='%ct %ci'", commit]) - - -@cache -def get_short_branch() -> str: - return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"]) - - -@cache -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"]) - - -@cache -def get_normalized_origin() -> str: - return get_origin() \ - .replace("git@", "", 1) \ - .replace(".git", "", 1) \ - .replace("https://", "", 1) \ - .replace(":", "/", 1) - - -@cache -def get_version() -> str: - with open(os.path.join(BASEDIR, "common", "version.h")) as _versionf: +def get_version(path: str = BASEDIR) -> str: + with open(os.path.join(path, "common", "version.h")) as _versionf: version = _versionf.read().split('"')[1] return version -@cache -def get_short_version() -> str: - return get_version().split('-')[0] -@cache -def is_prebuilt() -> bool: - return os.path.exists(os.path.join(BASEDIR, 'prebuilt')) +def get_release_notes(path: str = BASEDIR) -> str: + with open(os.path.join(path, "RELEASES.md")) as f: + return f.read().split('\n\n', 1)[0] @cache -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 - return get_normalized_origin() == "github.com/commaai/openpilot" +def is_prebuilt(path: str = BASEDIR) -> bool: + return os.path.exists(os.path.join(path, 'prebuilt')) + @cache -def is_tested_branch() -> bool: - return get_short_branch() in TESTED_BRANCHES - -@cache -def is_release_branch() -> bool: - return get_short_branch() in RELEASE_BRANCHES - -@cache -def is_dirty() -> bool: +def is_dirty(cwd: str = BASEDIR) -> bool: origin = get_origin() branch = get_branch() if not origin or not branch: @@ -108,14 +45,14 @@ def is_dirty() -> bool: dirty = False try: # Actually check dirty files - if not is_prebuilt(): + if not is_prebuilt(cwd): # This is needed otherwise touched files might show up as modified try: - subprocess.check_call(["git", "update-index", "--refresh"]) + subprocess.check_call(["git", "update-index", "--refresh"], cwd=cwd) except subprocess.CalledProcessError: pass - dirty = (subprocess.call(["git", "diff-index", "--quiet", branch, "--"]) != 0) + dirty = (subprocess.call(["git", "diff-index", "--quiet", branch, "--"], cwd=cwd)) != 0 except subprocess.CalledProcessError: cloudlog.exception("git subprocess failed while checking dirty") dirty = True @@ -123,6 +60,101 @@ def is_dirty() -> bool: return dirty +@dataclass +class OpenpilotMetadata: + version: str + release_notes: str + git_commit: str + git_origin: str + git_commit_date: str + build_style: str + is_dirty: bool # whether there are local changes + + @property + def short_version(self) -> str: + return self.version.split('-')[0] + + @property + def comma_remote(self) -> 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 + return self.git_normalized_origin == "github.com/commaai/openpilot" + + @property + def git_normalized_origin(self) -> str: + return self.git_origin \ + .replace("git@", "", 1) \ + .replace(".git", "", 1) \ + .replace("https://", "", 1) \ + .replace(":", "/", 1) + + +@dataclass +class BuildMetadata: + channel: str + openpilot: OpenpilotMetadata + + @property + def tested_channel(self) -> bool: + return self.channel in TESTED_BRANCHES + + @property + def release_channel(self) -> bool: + return self.channel in RELEASE_BRANCHES + + @property + def canonical(self) -> str: + return f"{self.openpilot.version}-{self.openpilot.git_commit}-{self.openpilot.build_style}" + + @property + def ui_description(self) -> str: + return f"{self.openpilot.version} / {self.openpilot.git_commit[:6]} / {self.channel}" + + +def build_metadata_from_dict(build_metadata: dict) -> BuildMetadata: + channel = build_metadata.get("channel", "unknown") + openpilot_metadata = build_metadata.get("openpilot", {}) + version = openpilot_metadata.get("version", "unknown") + release_notes = openpilot_metadata.get("release_notes", "unknown") + git_commit = openpilot_metadata.get("git_commit", "unknown") + git_origin = openpilot_metadata.get("git_origin", "unknown") + git_commit_date = openpilot_metadata.get("git_commit_date", "unknown") + build_style = openpilot_metadata.get("build_style", "unknown") + return BuildMetadata(channel, + OpenpilotMetadata( + version=version, + release_notes=release_notes, + git_commit=git_commit, + git_origin=git_origin, + git_commit_date=git_commit_date, + build_style=build_style, + is_dirty=False)) + + +def get_build_metadata(path: str = BASEDIR) -> BuildMetadata: + build_metadata_path = pathlib.Path(path) / BUILD_METADATA_FILENAME + + if build_metadata_path.exists(): + build_metadata = json.loads(build_metadata_path.read_text()) + return build_metadata_from_dict(build_metadata) + + git_folder = pathlib.Path(path) / ".git" + + if git_folder.exists(): + return BuildMetadata(get_short_branch(path), + OpenpilotMetadata( + version=get_version(path), + release_notes=get_release_notes(path), + git_commit=get_commit(path), + git_origin=get_origin(path), + git_commit_date=get_commit_date(path), + build_style="unknown", + is_dirty=is_dirty(path))) + + cloudlog.exception("unable to get build metadata") + raise Exception("invalid build metadata") + + if __name__ == "__main__": from openpilot.common.params import Params @@ -130,12 +162,4 @@ if __name__ == "__main__": params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) - print(f"Dirty: {is_dirty()}") - print(f"Version: {get_version()}") - print(f"Short version: {get_short_version()}") - print(f"Origin: {get_origin()}") - print(f"Normalized origin: {get_normalized_origin()}") - print(f"Branch: {get_branch()}") - print(f"Short branch: {get_short_branch()}") - print(f"Prebuilt: {is_prebuilt()}") - print(f"Commit date: {get_commit_date()}") + print(get_build_metadata()) diff --git a/system/webrtc/tests/test_stream_session.py b/system/webrtc/tests/test_stream_session.py old mode 100755 new mode 100644 index 2173c3806b..46d55ecd90 --- a/system/webrtc/tests/test_stream_session.py +++ b/system/webrtc/tests/test_stream_session.py @@ -1,17 +1,14 @@ -#!/usr/bin/env python3 import asyncio -import unittest -from unittest.mock import Mock, MagicMock, patch import json # for aiortc and its dependencies import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) +warnings.filterwarnings("ignore", category=RuntimeWarning) # TODO: remove this when google-crc32c publish a python3.12 wheel from aiortc import RTCDataChannel from aiortc.mediastreams import VIDEO_CLOCK_RATE, VIDEO_TIME_BASE import capnp import pyaudio - from cereal import messaging, log from openpilot.system.webrtc.webrtcd import CerealOutgoingMessageProxy, CerealIncomingMessageProxy @@ -20,15 +17,15 @@ from openpilot.system.webrtc.device.audio import AudioInputStreamTrack from openpilot.common.realtime import DT_DMON -class TestStreamSession(unittest.TestCase): - def setUp(self): +class TestStreamSession: + def setup_method(self): self.loop = asyncio.new_event_loop() - def tearDown(self): + def teardown_method(self): self.loop.stop() self.loop.close() - def test_outgoing_proxy(self): + def test_outgoing_proxy(self, mocker): test_msg = log.Event.new_message() test_msg.logMonoTime = 123 test_msg.valid = True @@ -36,27 +33,27 @@ class TestStreamSession(unittest.TestCase): expected_dict = {"type": "customReservedRawData0", "logMonoTime": 123, "valid": True, "data": "test"} expected_json = json.dumps(expected_dict).encode() - channel = Mock(spec=RTCDataChannel) + channel = mocker.Mock(spec=RTCDataChannel) mocked_submaster = messaging.SubMaster(["customReservedRawData0"]) def mocked_update(t): mocked_submaster.update_msgs(0, [test_msg]) - with patch.object(messaging.SubMaster, "update", side_effect=mocked_update): - proxy = CerealOutgoingMessageProxy(mocked_submaster) - proxy.add_channel(channel) + mocker.patch.object(messaging.SubMaster, "update", side_effect=mocked_update) + proxy = CerealOutgoingMessageProxy(mocked_submaster) + proxy.add_channel(channel) - proxy.update() + proxy.update() - channel.send.assert_called_once_with(expected_json) + channel.send.assert_called_once_with(expected_json) - def test_incoming_proxy(self): + def test_incoming_proxy(self, mocker): tested_msgs = [ {"type": "customReservedRawData0", "data": "test"}, # primitive {"type": "can", "data": [{"address": 0, "busTime": 0, "dat": "", "src": 0}]}, # list {"type": "testJoystick", "data": {"axes": [0, 0], "buttons": [False]}}, # dict ] - mocked_pubmaster = MagicMock(spec=messaging.PubMaster) + mocked_pubmaster = mocker.MagicMock(spec=messaging.PubMaster) proxy = CerealIncomingMessageProxy(mocked_pubmaster) @@ -65,44 +62,40 @@ class TestStreamSession(unittest.TestCase): mocked_pubmaster.send.assert_called_once() mt, md = mocked_pubmaster.send.call_args.args - self.assertEqual(mt, msg["type"]) - self.assertIsInstance(md, capnp._DynamicStructBuilder) - self.assertTrue(hasattr(md, msg["type"])) + assert mt == msg["type"] + assert isinstance(md, capnp._DynamicStructBuilder) + assert hasattr(md, msg["type"]) mocked_pubmaster.reset_mock() - def test_livestream_track(self): + def test_livestream_track(self, mocker): fake_msg = messaging.new_message("livestreamDriverEncodeData") config = {"receive.return_value": fake_msg.to_bytes()} - with patch("cereal.messaging.SubSocket", spec=True, **config): - track = LiveStreamVideoStreamTrack("driver") + mocker.patch("msgq.SubSocket", spec=True, **config) + track = LiveStreamVideoStreamTrack("driver") - self.assertTrue(track.id.startswith("driver")) - self.assertEqual(track.codec_preference(), "H264") + assert track.id.startswith("driver") + assert track.codec_preference() == "H264" - for i in range(5): - packet = self.loop.run_until_complete(track.recv()) - self.assertEqual(packet.time_base, VIDEO_TIME_BASE) - self.assertEqual(packet.pts, int(i * DT_DMON * VIDEO_CLOCK_RATE)) - self.assertEqual(packet.size, 0) + for i in range(5): + packet = self.loop.run_until_complete(track.recv()) + assert packet.time_base == VIDEO_TIME_BASE + assert packet.pts == int(i * DT_DMON * VIDEO_CLOCK_RATE) + assert packet.size == 0 - def test_input_audio_track(self): + def test_input_audio_track(self, mocker): packet_time, rate = 0.02, 16000 sample_count = int(packet_time * rate) - mocked_stream = MagicMock(spec=pyaudio.Stream) + mocked_stream = mocker.MagicMock(spec=pyaudio.Stream) mocked_stream.read.return_value = b"\x00" * 2 * sample_count config = {"open.side_effect": lambda *args, **kwargs: mocked_stream} - with patch("pyaudio.PyAudio", spec=True, **config): - track = AudioInputStreamTrack(audio_format=pyaudio.paInt16, packet_time=packet_time, rate=rate) + mocker.patch("pyaudio.PyAudio", spec=True, **config) + track = AudioInputStreamTrack(audio_format=pyaudio.paInt16, packet_time=packet_time, rate=rate) - for i in range(5): - frame = self.loop.run_until_complete(track.recv()) - self.assertEqual(frame.rate, rate) - self.assertEqual(frame.samples, sample_count) - self.assertEqual(frame.pts, i * sample_count) - - -if __name__ == "__main__": - unittest.main() + for i in range(5): + frame = self.loop.run_until_complete(track.recv()) + assert frame.rate == rate + assert frame.samples == sample_count + assert frame.pts == i * sample_count diff --git a/system/webrtc/tests/test_webrtcd.py b/system/webrtc/tests/test_webrtcd.py old mode 100755 new mode 100644 index c31b63d02b..4fa6d8953f --- a/system/webrtc/tests/test_webrtcd.py +++ b/system/webrtc/tests/test_webrtcd.py @@ -1,31 +1,38 @@ -#!/usr/bin/env python +import pytest import asyncio import json -import unittest -from unittest.mock import MagicMock, AsyncMock # for aiortc and its dependencies import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) +warnings.filterwarnings("ignore", category=RuntimeWarning) # TODO: remove this when google-crc32c publish a python3.12 wheel from openpilot.system.webrtc.webrtcd import get_stream import aiortc from teleoprtc import WebRTCOfferBuilder +from parameterized import parameterized_class -class TestWebrtcdProc(unittest.IsolatedAsyncioTestCase): +@parameterized_class(("in_services", "out_services"), [ + (["testJoystick"], ["carState"]), + ([], ["carState"]), + (["testJoystick"], []), + ([], []), +]) +@pytest.mark.asyncio +class TestWebrtcdProc: async def assertCompletesWithTimeout(self, awaitable, timeout=1): try: async with asyncio.timeout(timeout): await awaitable except TimeoutError: - self.fail("Timeout while waiting for awaitable to complete") + pytest.fail("Timeout while waiting for awaitable to complete") - async def test_webrtcd(self): - mock_request = MagicMock() + async def test_webrtcd(self, mocker): + mock_request = mocker.MagicMock() async def connect(offer): - body = {'sdp': offer.sdp, 'cameras': offer.video, 'bridge_services_in': [], 'bridge_services_out': ['carState']} - mock_request.json.side_effect = AsyncMock(return_value=body) + body = {'sdp': offer.sdp, 'cameras': offer.video, 'bridge_services_in': self.in_services, 'bridge_services_out': self.out_services} + mock_request.json.side_effect = mocker.AsyncMock(return_value=body) response = await get_stream(mock_request) response_json = json.loads(response.text) return aiortc.RTCSessionDescription(**response_json) @@ -33,16 +40,17 @@ class TestWebrtcdProc(unittest.IsolatedAsyncioTestCase): builder = WebRTCOfferBuilder(connect) builder.offer_to_receive_video_stream("road") builder.offer_to_receive_audio_stream() - builder.add_messaging() + if len(self.in_services) > 0 or len(self.out_services) > 0: + builder.add_messaging() stream = builder.stream() await self.assertCompletesWithTimeout(stream.start()) await self.assertCompletesWithTimeout(stream.wait_for_connection()) - self.assertTrue(stream.has_incoming_video_track("road")) - self.assertTrue(stream.has_incoming_audio_track()) - self.assertTrue(stream.has_messaging_channel()) + assert stream.has_incoming_video_track("road") + assert stream.has_incoming_audio_track() + assert stream.has_messaging_channel() == (len(self.in_services) > 0 or len(self.out_services) > 0) video_track, audio_track = stream.get_incoming_video_track("road"), stream.get_incoming_audio_track() await self.assertCompletesWithTimeout(video_track.recv()) @@ -51,10 +59,7 @@ class TestWebrtcdProc(unittest.IsolatedAsyncioTestCase): await self.assertCompletesWithTimeout(stream.stop()) # cleanup, very implementation specific, test may break if it changes - self.assertTrue(mock_request.app["streams"].__setitem__.called, "Implementation changed, please update this test") + assert mock_request.app["streams"].__setitem__.called, "Implementation changed, please update this test" _, session = mock_request.app["streams"].__setitem__.call_args.args await self.assertCompletesWithTimeout(session.post_run_cleanup()) - -if __name__ == "__main__": - unittest.main() diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index 6c1370eae9..79c5b4888f 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -11,6 +11,7 @@ from typing import Any, TYPE_CHECKING # aiortc and its dependencies have lots of internal warnings :( import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) +warnings.filterwarnings("ignore", category=RuntimeWarning) # TODO: remove this when google-crc32c publish a python3.12 wheel import capnp from aiohttp import web @@ -24,7 +25,7 @@ from cereal import messaging, log class CerealOutgoingMessageProxy: def __init__(self, sm: messaging.SubMaster): self.sm = sm - self.channels: list['RTCDataChannel'] = [] + self.channels: list[RTCDataChannel] = [] def add_channel(self, channel: 'RTCDataChannel'): self.channels.append(channel) @@ -97,12 +98,26 @@ class CerealProxyRunner: except InvalidStateError: self.logger.warning("Cereal outgoing proxy invalid state (connection closed)") break - except Exception as ex: - self.logger.error("Cereal outgoing proxy failure: %s", ex) + except Exception: + self.logger.exception("Cereal outgoing proxy failure") await asyncio.sleep(0.01) +class DynamicPubMaster(messaging.PubMaster): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.lock = asyncio.Lock() + + async def add_services_if_needed(self, services): + async with self.lock: + for service in services: + if service not in self.sock: + self.sock[service] = messaging.pub_sock(service) + + class StreamSession: + shared_pub_master = DynamicPubMaster([]) + 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 @@ -128,9 +143,15 @@ class StreamSession: self.stream = builder.stream() self.identifier = str(uuid.uuid4()) - self.outgoing_bridge = CerealOutgoingMessageProxy(messaging.SubMaster(outgoing_services)) - self.incoming_bridge = CerealIncomingMessageProxy(messaging.PubMaster(incoming_services)) - self.outgoing_bridge_runner = CerealProxyRunner(self.outgoing_bridge) + self.incoming_bridge: CerealIncomingMessageProxy | None = None + self.incoming_bridge_services = incoming_services + self.outgoing_bridge: CerealOutgoingMessageProxy | None = None + self.outgoing_bridge_runner: CerealProxyRunner | None = None + if len(incoming_services) > 0: + self.incoming_bridge = CerealIncomingMessageProxy(self.shared_pub_master) + if len(outgoing_services) > 0: + self.outgoing_bridge = CerealOutgoingMessageProxy(messaging.SubMaster(outgoing_services)) + self.outgoing_bridge_runner = CerealProxyRunner(self.outgoing_bridge) self.audio_output: AudioOutputSpeaker | MediaBlackhole | None = None self.run_task: asyncio.Task | None = None @@ -152,19 +173,23 @@ class StreamSession: return await self.stream.start() async def message_handler(self, message: bytes): + assert self.incoming_bridge is not None try: self.incoming_bridge.send(message) - except Exception as ex: - self.logger.error("Cereal incoming proxy failure: %s", ex) + except Exception: + self.logger.exception("Cereal incoming proxy failure") async def run(self): try: await self.stream.wait_for_connection() if self.stream.has_messaging_channel(): - self.stream.set_message_handler(self.message_handler) - channel = self.stream.get_messaging_channel() - self.outgoing_bridge_runner.proxy.add_channel(channel) - self.outgoing_bridge_runner.start() + if self.incoming_bridge is not None: + await self.shared_pub_master.add_services_if_needed(self.incoming_bridge_services) + self.stream.set_message_handler(self.message_handler) + if self.outgoing_bridge_runner is not None: + channel = self.stream.get_messaging_channel() + self.outgoing_bridge_runner.proxy.add_channel(channel) + self.outgoing_bridge_runner.start() if self.stream.has_incoming_audio_track(): track = self.stream.get_incoming_audio_track(buffered=False) self.audio_output = self.audio_output_cls() @@ -176,12 +201,13 @@ class StreamSession: await self.post_run_cleanup() self.logger.info("Stream session (%s) ended", self.identifier) - except Exception as ex: - self.logger.error("Stream session failure: %s", ex) + except Exception: + self.logger.exception("Stream session failure") async def post_run_cleanup(self): await self.stream.stop() - self.outgoing_bridge_runner.stop() + if self.outgoing_bridge is not None: + self.outgoing_bridge_runner.stop() if self.audio_output: self.audio_output.stop() diff --git a/teleoprtc_repo b/teleoprtc_repo index 3f9e8176d1..fdcff87aaf 160000 --- a/teleoprtc_repo +++ b/teleoprtc_repo @@ -1 +1 @@ -Subproject commit 3f9e8176d1be3d217528baee09fc418fa980a0c3 +Subproject commit fdcff87aaf2b1ca099be4fc820044334cec02cc5 diff --git a/third_party/acados/acados_template/gnsf/__init__.py b/third_party/acados/acados_template/gnsf/__init__.py index 8b13789179..e69de29bb2 100644 --- a/third_party/acados/acados_template/gnsf/__init__.py +++ b/third_party/acados/acados_template/gnsf/__init__.py @@ -1 +0,0 @@ - diff --git a/third_party/libyuv/.gitignore b/third_party/libyuv/.gitignore new file mode 100644 index 0000000000..450712e47d --- /dev/null +++ b/third_party/libyuv/.gitignore @@ -0,0 +1 @@ +libyuv/ diff --git a/third_party/libyuv/build.sh b/third_party/libyuv/build.sh index e044312d05..b960f60ef5 100755 --- a/third_party/libyuv/build.sh +++ b/third_party/libyuv/build.sh @@ -1,12 +1,39 @@ #!/usr/bin/env bash set -e -git clone https://chromium.googlesource.com/libyuv/libyuv +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + +ARCHNAME=$(uname -m) +if [ -f /TICI ]; then + ARCHNAME="larch64" +fi + +if [[ "$OSTYPE" == "darwin"* ]]; then + ARCHNAME="Darwin" +fi + +cd $DIR +if [ ! -d libyuv ]; then + git clone --single-branch https://chromium.googlesource.com/libyuv/libyuv +fi + cd libyuv -git reset --hard 4a14cb2e81235ecd656e799aecaaf139db8ce4a2 +git checkout 4a14cb2e81235ecd656e799aecaaf139db8ce4a2 + +# build cmake . +make -j$(nproc) + +INSTALL_DIR="$DIR/$ARCHNAME" +rm -rf $INSTALL_DIR +mkdir -p $INSTALL_DIR + +rm -rf $DIR/include +mkdir -p $INSTALL_DIR/lib +cp $DIR/libyuv/libyuv.a $INSTALL_DIR/lib +cp -r $DIR/libyuv/include $DIR ## To create universal binary on Darwin: ## ``` ## lipo -create -output Darwin/libyuv.a path-to-x64/libyuv.a path-to-arm64/libyuv.a -## ``` \ No newline at end of file +## ``` diff --git a/third_party/maplibre-native-qt/build.sh b/third_party/maplibre-native-qt/build.sh index c64f0fc649..a368026f0f 100755 --- a/third_party/maplibre-native-qt/build.sh +++ b/third_party/maplibre-native-qt/build.sh @@ -3,35 +3,33 @@ set -e DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -ARCHNAME="x86_64" +ARCHNAME=$(uname -m) MAPLIBRE_FLAGS="-DMLN_QT_WITH_LOCATION=OFF" -if [ -f /AGNOS ]; then +if [ -f /TICI ]; 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 + git clone --single-branch https://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 +make -j$(nproc) INSTALL_DIR="$DIR/$ARCHNAME" rm -rf $INSTALL_DIR mkdir -p $INSTALL_DIR -rm -rf $INSTALL_DIR/lib $DIR/include +rm -rf $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 diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 6af4ca08f5..ba4a142451 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -1,7 +1,7 @@ Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'replay_lib', 'cereal', 'widgets') base_frameworks = qt_env['FRAMEWORKS'] -base_libs = [common, messaging, cereal, visionipc, 'qt_util', 'zmq', 'capnp', 'kj', 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] +base_libs = [common, messaging, cereal, visionipc, 'qt_util', 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": base_frameworks.append('OpenCL') @@ -26,7 +26,7 @@ cabana_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "assets/assets.o"])) cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcanstream.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', - 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', + 'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', 'utils/export.cc', 'utils/util.cc', 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) @@ -39,4 +39,4 @@ output_json_file = 'tools/cabana/dbc/car_fingerprint_to_dbc.json' generate_dbc = cabana_env.Command('#' + output_json_file, ['dbc/generate_dbc_json.py'], "python3 tools/cabana/dbc/generate_dbc_json.py --out " + output_json_file) -cabana_env.Depends(generate_dbc, ["#common", "#selfdrive/boardd", '#opendbc', "#cereal", Glob("#opendbc/*.dbc")]) +cabana_env.Depends(generate_dbc, ["#common", "#selfdrive/pandad", '#opendbc', "#cereal", Glob("#opendbc/*.dbc")]) diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 205d795776..4a3b4126dd 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -3,7 +3,6 @@ #include "selfdrive/ui/qt/util.h" #include "tools/cabana/mainwin.h" -#include "tools/cabana/streamselector.h" #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/replaystream.h" @@ -82,28 +81,16 @@ int main(int argc, char *argv[]) { } } - int ret = 0; - { - MainWindow w; - QTimer::singleShot(0, [&]() { - if (!stream) { - StreamSelector dlg(&stream); - dlg.exec(); - dbc_file = dlg.dbcFile(); - } - if (!stream) { - stream = new DummyStream(&app); - } - stream->start(); - if (!dbc_file.isEmpty()) { - w.loadFile(dbc_file); - } - w.show(); - }); - - ret = app.exec(); + MainWindow w; + if (stream) { + stream->start(); + if (!dbc_file.isEmpty()) { + w.loadFile(dbc_file); + } + } else { + w.openStream(); } + w.show(); - delete can; - return ret; + return app.exec(); } diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index 6f08a9f20b..7a056c7674 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -22,6 +22,7 @@ // ChartAxisElement's padding is 4 (https://codebrowser.dev/qt5/qtcharts/src/charts/axis/chartaxiselement_p.h.html) const int AXIS_X_TOP_MARGIN = 4; +const double MIN_ZOOM_SECONDS = 0.01; // 10ms // Define a small value of epsilon to compare double values const float EPSILON = 0.000001; static inline bool xLessThan(const QPointF &p, float x) { return p.x() < (x - EPSILON); } @@ -111,6 +112,8 @@ void ChartView::setTheme(QChart::ChartTheme theme) { axis_y->setLabelsBrush(palette().text()); chart()->legend()->setLabelColor(palette().color(QPalette::Text)); } + axis_x->setLineVisible(false); + axis_y->setLineVisible(false); for (auto &s : sigs) { s.series->setColor(s.sig->color); } @@ -274,7 +277,7 @@ void ChartView::updateSeriesPoints() { } ((QScatterSeries *)s.series)->setMarkerSize(size); } else { - s.series->setPointsVisible(pixels_per_point > 20); + s.series->setPointsVisible(num_points == 1 || pixels_per_point > 20); } } } @@ -509,8 +512,8 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { if (rubber->width() <= 0) { // no rubber dragged, seek to mouse position can->seekTo(min); - } else if (rubber->width() > 10 && (max - min) > 0.01) { // Minimum range is 10 milliseconds. - charts_widget->zoom_undo_stack->push(new ZoomCommand(charts_widget, {min, max})); + } else if (rubber->width() > 10 && (max - min) > MIN_ZOOM_SECONDS) { + charts_widget->zoom_undo_stack->push(new ZoomCommand({min, max})); } else { viewport()->update(); } @@ -579,7 +582,7 @@ void ChartView::showTip(double sec) { // use reverse iterator to find last item <= sec. auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), sec, [](auto &p, double x) { return p.x() > x; }); if (it != s.vals.crend() && it->x() >= axis_x->min()) { - value = QString::number(it->y()); + value = s.sig->formatValue(it->y(), false); s.track_pt = *it; x = std::max(x, chart()->mapToPosition(*it).x()); } @@ -745,8 +748,8 @@ void ChartView::drawTimeline(QPainter *painter) { const auto plot_area = chart()->plotArea(); // draw vertical time line qreal x = std::clamp(chart()->mapToPosition(QPointF{cur_sec, 0}).x(), plot_area.left(), plot_area.right()); - painter->setPen(QPen(chart()->titleBrush().color(), 2)); - painter->drawLine(QPointF{x, plot_area.top()}, QPointF{x, plot_area.bottom() + 1}); + painter->setPen(QPen(chart()->titleBrush().color(), 1)); + painter->drawLine(QPointF{x, plot_area.top() - 1}, QPointF{x, plot_area.bottom() + 1}); // draw current time under the axis-x QString time_str = QString::number(cur_sec, 'f', 2); diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index 63d6091cac..59c188afbc 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -103,6 +103,8 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); QObject::connect(can, &AbstractStream::eventsMerged, this, &ChartsWidget::eventsMerged); QObject::connect(can, &AbstractStream::msgsReceived, this, &ChartsWidget::updateState); + QObject::connect(can, &AbstractStream::seeking, this, &ChartsWidget::updateState); + QObject::connect(can, &AbstractStream::timeRangeChanged, this, &ChartsWidget::timeRangeChanged); QObject::connect(range_slider, &QSlider::valueChanged, this, &ChartsWidget::setMaxChartRange); QObject::connect(new_plot_btn, &QToolButton::clicked, this, &ChartsWidget::newChart); QObject::connect(remove_all_btn, &QToolButton::clicked, this, &ChartsWidget::removeAll); @@ -159,16 +161,13 @@ void ChartsWidget::eventsMerged(const MessageEventsMap &new_events) { } } -void ChartsWidget::setZoom(double min, double max) { - zoomed_range = {min, max}; - is_zoomed = zoomed_range != display_range; +void ChartsWidget::timeRangeChanged(const std::optional> &time_range) { updateToolBar(); updateState(); - emit rangeChanged(min, max, is_zoomed); } void ChartsWidget::zoomReset() { - setZoom(display_range.first, display_range.second); + can->setTimeRange(std::nullopt); zoom_undo_stack->clear(); } @@ -186,8 +185,9 @@ void ChartsWidget::showValueTip(double sec) { void ChartsWidget::updateState() { if (charts.isEmpty()) return; + const auto &time_range = can->timeRange(); const double cur_sec = can->currentSec(); - if (!is_zoomed) { + if (!time_range.has_value()) { double pos = (cur_sec - display_range.first) / std::max(1.0, max_chart_range); if (pos < 0 || pos > 0.8) { display_range.first = std::max(0.0, cur_sec - max_chart_range * 0.1); @@ -195,12 +195,9 @@ void ChartsWidget::updateState() { double max_sec = std::min(display_range.first + max_chart_range, can->totalSeconds()); display_range.first = std::max(0.0, max_sec - max_chart_range); display_range.second = display_range.first + max_chart_range; - } else if (cur_sec < (zoomed_range.first - 0.1) || cur_sec >= zoomed_range.second) { - // loop in zoomed range - can->seekTo(zoomed_range.first); } - const auto &range = is_zoomed ? zoomed_range : display_range; + const auto &range = time_range ? *time_range : display_range; for (auto c : charts) { c->updatePlot(cur_sec, range.first, range.second); } @@ -216,12 +213,14 @@ void ChartsWidget::updateToolBar() { title_label->setText(tr("Charts: %1").arg(charts.size())); columns_action->setText(tr("Column: %1").arg(column_count)); range_lb->setText(utils::formatSeconds(max_chart_range)); + + bool is_zoomed = can->timeRange().has_value(); range_lb_action->setVisible(!is_zoomed); range_slider_action->setVisible(!is_zoomed); undo_zoom_action->setVisible(is_zoomed); redo_zoom_action->setVisible(is_zoomed); reset_zoom_action->setVisible(is_zoomed); - reset_zoom_btn->setText(is_zoomed ? tr("%1-%2").arg(zoomed_range.first, 0, 'f', 2).arg(zoomed_range.second, 0, 'f', 2) : ""); + reset_zoom_btn->setText(is_zoomed ? tr("%1-%2").arg(can->timeRange()->first, 0, 'f', 2).arg(can->timeRange()->second, 0, 'f', 2) : ""); remove_all_btn->setEnabled(!charts.isEmpty()); dock_btn->setIcon(docking ? "arrow-up-right-square" : "arrow-down-left-square"); dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); @@ -251,7 +250,7 @@ ChartView *ChartsWidget::findChart(const MessageId &id, const cabana::Signal *si } ChartView *ChartsWidget::createChart() { - auto chart = new ChartView(is_zoomed ? zoomed_range : display_range, this); + auto chart = new ChartView(can->timeRange().value_or(display_range), this); chart->setFixedHeight(settings.chart_height); chart->setMinimumWidth(CHART_MIN_WIDTH); chart->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h index a39b4d73a5..a9ac05db23 100644 --- a/tools/cabana/chart/chartswidget.h +++ b/tools/cabana/chart/chartswidget.h @@ -46,11 +46,10 @@ public: public slots: void setColumnCount(int n); void removeAll(); - void setZoom(double min, double max); + void timeRangeChanged(const std::optional> &time_range); signals: void dock(bool floating); - void rangeChanged(double min, double max, bool is_zommed); void seriesChanged(); private: @@ -102,9 +101,7 @@ private: ChartsContainer *charts_container; QScrollArea *charts_scroll; uint32_t max_chart_range = 0; - bool is_zoomed = false; std::pair display_range; - std::pair zoomed_range; QAction *columns_action; int column_count = 1; int current_column_count = 0; @@ -119,12 +116,11 @@ private: class ZoomCommand : public QUndoCommand { public: - ZoomCommand(ChartsWidget *charts, std::pair range) : charts(charts), range(range), QUndoCommand() { - prev_range = charts->is_zoomed ? charts->zoomed_range : charts->display_range; + ZoomCommand(std::pair range) : range(range), QUndoCommand() { + prev_range = can->timeRange(); setText(QObject::tr("Zoom to %1-%2").arg(range.first, 0, 'f', 2).arg(range.second, 0, 'f', 2)); } - void undo() override { charts->setZoom(prev_range.first, prev_range.second); } - void redo() override { charts->setZoom(range.first, range.second); } - ChartsWidget *charts; - std::pair prev_range, range; + void undo() override { can->setTimeRange(prev_range); } + void redo() override { can->setTimeRange(range); } + std::optional> prev_range, range; }; diff --git a/tools/cabana/dbc/dbc.cc b/tools/cabana/dbc/dbc.cc index b1256098eb..149bb9f59a 100644 --- a/tools/cabana/dbc/dbc.cc +++ b/tools/cabana/dbc/dbc.cc @@ -142,7 +142,7 @@ void cabana::Signal::update() { precision = std::max(num_decimals(factor), num_decimals(offset)); } -QString cabana::Signal::formatValue(double value) const { +QString cabana::Signal::formatValue(double value, bool with_unit) const { // Show enum string int64_t raw_value = round((value - offset) / factor); for (const auto &[val, desc] : val_desc) { @@ -152,7 +152,7 @@ QString cabana::Signal::formatValue(double value) const { } QString val_str = QString::number(value, 'f', precision); - if (!unit.isEmpty()) { + if (with_unit && !unit.isEmpty()) { val_str += " " + unit; } return val_str; diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h index 0262a546f6..da44319b5c 100644 --- a/tools/cabana/dbc/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -29,11 +29,11 @@ struct MessageId { } bool operator<(const MessageId &other) const { - return std::pair{source, address} < std::pair{other.source, other.address}; + return std::tie(source, address) < std::tie(other.source, other.address); } bool operator>(const MessageId &other) const { - return std::pair{source, address} > std::pair{other.source, other.address}; + return std::tie(source, address) > std::tie(other.source, other.address); } }; @@ -55,7 +55,7 @@ public: Signal(const Signal &other) = default; void update(); bool getValue(const uint8_t *data, size_t data_size, double *val) const; - QString formatValue(double value) const; + QString formatValue(double value, bool with_unit = true) const; bool operator==(const cabana::Signal &other) const; inline bool operator!=(const cabana::Signal &other) const { return !(*this == other); } diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc index fbbb54fd58..4a1c52819a 100644 --- a/tools/cabana/dbc/dbcfile.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -3,7 +3,6 @@ #include #include #include -#include DBCFile::DBCFile(const QString &dbc_file_name) { QFile file(dbc_file_name); @@ -76,112 +75,50 @@ cabana::Msg *DBCFile::msg(const QString &name) { return it != msgs.end() ? &(it->second) : nullptr; } +cabana::Signal *DBCFile::signal(uint32_t address, const QString name) { + auto m = msg(address); + return m ? (cabana::Signal *)m->sig(name) : nullptr; +} + void DBCFile::parse(const QString &content) { - static QRegularExpression bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))"); - static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); - static QRegularExpression sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); - static QRegularExpression msg_comment_regexp(R"(^CM_ BO_ *(\w+) *\"([^"]*)\"\s*;)"); - static QRegularExpression sg_comment_regexp(R"(^CM_ SG_ *(\w+) *(\w+) *\"([^"]*)\"\s*;)"); - static QRegularExpression val_regexp(R"(VAL_ (\w+) (\w+) (\s*[-+]?[0-9]+\s+\".+?\"[^;]*))"); + msgs.clear(); int line_num = 0; QString line; - auto dbc_assert = [&line_num, &line, this](bool condition, const QString &msg = "") { - if (!condition) throw std::runtime_error(QString("[%1:%2]%3: %4").arg(filename).arg(line_num).arg(msg).arg(line).toStdString()); - }; - auto get_sig = [this](uint32_t address, const QString &name) -> cabana::Signal * { - auto m = (cabana::Msg *)msg(address); - return m ? (cabana::Signal *)m->sig(name) : nullptr; - }; - - msgs.clear(); - QTextStream stream((QString *)&content); cabana::Msg *current_msg = nullptr; int multiplexor_cnt = 0; + bool seen_first = false; + QTextStream stream((QString *)&content); + while (!stream.atEnd()) { ++line_num; QString raw_line = stream.readLine(); line = raw_line.trimmed(); - if (line.startsWith("BO_ ")) { - multiplexor_cnt = 0; - auto match = bo_regexp.match(line); - dbc_assert(match.hasMatch()); - auto address = match.captured(1).toUInt(); - dbc_assert(msgs.count(address) == 0, QString("Duplicate message address: %1").arg(address)); - current_msg = &msgs[address]; - current_msg->address = address; - current_msg->name = match.captured(2); - current_msg->size = match.captured(3).toULong(); - current_msg->transmitter = match.captured(4).trimmed(); - } else if (line.startsWith("SG_ ")) { - int offset = 0; - auto match = sg_regexp.match(line); - if (!match.hasMatch()) { - match = sgm_regexp.match(line); - offset = 1; - } - dbc_assert(match.hasMatch()); - dbc_assert(current_msg, "No Message"); - auto name = match.captured(1); - dbc_assert(current_msg->sig(name) == nullptr, "Duplicate signal name"); - cabana::Signal s{}; - if (offset == 1) { - auto indicator = match.captured(2); - if (indicator == "M") { - // Only one signal within a single message can be the multiplexer switch. - dbc_assert(++multiplexor_cnt < 2, "Multiple multiplexor"); - s.type = cabana::Signal::Type::Multiplexor; - } else { - s.type = cabana::Signal::Type::Multiplexed; - s.multiplex_value = indicator.mid(1).toInt(); - } - } - s.name = name; - s.start_bit = match.captured(offset + 2).toInt(); - s.size = match.captured(offset + 3).toInt(); - s.is_little_endian = match.captured(offset + 4).toInt() == 1; - s.is_signed = match.captured(offset + 5) == "-"; - s.factor = match.captured(offset + 6).toDouble(); - s.offset = match.captured(offset + 7).toDouble(); - s.min = match.captured(8 + offset).toDouble(); - s.max = match.captured(9 + offset).toDouble(); - s.unit = match.captured(10 + offset); - s.receiver_name = match.captured(11 + offset).trimmed(); - current_msg->sigs.push_back(new cabana::Signal(s)); - } else if (line.startsWith("VAL_ ")) { - auto match = val_regexp.match(line); - dbc_assert(match.hasMatch()); - if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) { - QStringList desc_list = match.captured(3).trimmed().split('"'); - for (int i = 0; i < desc_list.size(); i += 2) { - auto val = desc_list[i].trimmed(); - if (!val.isEmpty() && (i + 1) < desc_list.size()) { - auto desc = desc_list[i + 1].trimmed(); - s->val_desc.push_back({val.toDouble(), desc}); - } - } - } - } else if (line.startsWith("CM_ BO_")) { - if (!line.endsWith("\";")) { - int pos = stream.pos() - raw_line.length() - 1; - line = content.mid(pos, content.indexOf("\";", pos)); - } - auto match = msg_comment_regexp.match(line); - dbc_assert(match.hasMatch()); - if (auto m = (cabana::Msg *)msg(match.captured(1).toUInt())) { - m->comment = match.captured(2).trimmed(); - } - } else if (line.startsWith("CM_ SG_ ")) { - if (!line.endsWith("\";")) { - int pos = stream.pos() - raw_line.length() - 1; - line = content.mid(pos, content.indexOf("\";", pos)); - } - auto match = sg_comment_regexp.match(line); - dbc_assert(match.hasMatch()); - if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) { - s->comment = match.captured(3).trimmed(); + bool seen = true; + try { + if (line.startsWith("BO_ ")) { + multiplexor_cnt = 0; + current_msg = parseBO(line); + } else if (line.startsWith("SG_ ")) { + parseSG(line, current_msg, multiplexor_cnt); + } else if (line.startsWith("VAL_ ")) { + parseVAL(line); + } else if (line.startsWith("CM_ BO_")) { + parseCM_BO(line, content, raw_line, stream); + } else if (line.startsWith("CM_ SG_ ")) { + parseCM_SG(line, content, raw_line, stream); + } else { + seen = false; } + } catch (std::exception &e) { + throw std::runtime_error(QString("[%1:%2]%3: %4").arg(filename).arg(line_num).arg(e.what()).arg(line).toStdString()); + } + + if (seen) { + seen_first = true; + } else if (!seen_first) { + header += raw_line + "\n"; } } @@ -190,13 +127,134 @@ void DBCFile::parse(const QString &content) { } } +cabana::Msg *DBCFile::parseBO(const QString &line) { + static QRegularExpression bo_regexp(R"(^BO_ (?
    \w+) (?\w+) *: (?\w+) (?\w+))"); + + QRegularExpressionMatch match = bo_regexp.match(line); + if (!match.hasMatch()) + throw std::runtime_error("Invalid BO_ line format"); + + uint32_t address = match.captured("address").toUInt(); + if (msgs.count(address) > 0) + throw std::runtime_error(QString("Duplicate message address: %1").arg(address).toStdString()); + + // Create a new message object + cabana::Msg *msg = &msgs[address]; + msg->address = address; + msg->name = match.captured("name"); + msg->size = match.captured("size").toULong(); + msg->transmitter = match.captured("transmitter").trimmed(); + return msg; +} + +void DBCFile::parseCM_BO(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream) { + static QRegularExpression msg_comment_regexp(R"(^CM_ BO_ *(?
    \w+) *\"(?(?:[^"\\]|\\.)*)\"\s*;)"); + + QString parse_line = line; + if (!parse_line.endsWith("\";")) { + int pos = stream.pos() - raw_line.length() - 1; + parse_line = content.mid(pos, content.indexOf("\";", pos)); + } + auto match = msg_comment_regexp.match(parse_line); + if (!match.hasMatch()) + throw std::runtime_error("Invalid message comment format"); + + if (auto m = (cabana::Msg *)msg(match.captured("address").toUInt())) + m->comment = match.captured("comment").trimmed().replace("\\\"", "\""); +} + +void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multiplexor_cnt) { + static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); + static QRegularExpression sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); + + if (!current_msg) + throw std::runtime_error("No Message"); + + int offset = 0; + auto match = sg_regexp.match(line); + if (!match.hasMatch()) { + match = sgm_regexp.match(line); + offset = 1; + } + if (!match.hasMatch()) + throw std::runtime_error("Invalid SG_ line format"); + + QString name = match.captured(1); + if (current_msg->sig(name) != nullptr) + throw std::runtime_error("Duplicate signal name"); + + cabana::Signal s{}; + if (offset == 1) { + auto indicator = match.captured(2); + if (indicator == "M") { + ++multiplexor_cnt; + // Only one signal within a single message can be the multiplexer switch. + if (multiplexor_cnt >= 2) + throw std::runtime_error("Multiple multiplexor"); + + s.type = cabana::Signal::Type::Multiplexor; + } else { + s.type = cabana::Signal::Type::Multiplexed; + s.multiplex_value = indicator.mid(1).toInt(); + } + } + s.name = name; + s.start_bit = match.captured(offset + 2).toInt(); + s.size = match.captured(offset + 3).toInt(); + s.is_little_endian = match.captured(offset + 4).toInt() == 1; + s.is_signed = match.captured(offset + 5) == "-"; + s.factor = match.captured(offset + 6).toDouble(); + s.offset = match.captured(offset + 7).toDouble(); + s.min = match.captured(8 + offset).toDouble(); + s.max = match.captured(9 + offset).toDouble(); + s.unit = match.captured(10 + offset); + s.receiver_name = match.captured(11 + offset).trimmed(); + current_msg->sigs.push_back(new cabana::Signal(s)); +} + +void DBCFile::parseCM_SG(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream) { + static QRegularExpression sg_comment_regexp(R"(^CM_ SG_ *(\w+) *(\w+) *\"((?:[^"\\]|\\.)*)\"\s*;)"); + + QString parse_line = line; + if (!parse_line.endsWith("\";")) { + int pos = stream.pos() - raw_line.length() - 1; + parse_line = content.mid(pos, content.indexOf("\";", pos)); + } + auto match = sg_comment_regexp.match(parse_line); + if (!match.hasMatch()) + throw std::runtime_error("Invalid CM_ SG_ line format"); + + if (auto s = signal(match.captured(1).toUInt(), match.captured(2))) { + s->comment = match.captured(3).trimmed().replace("\\\"", "\""); + } +} + +void DBCFile::parseVAL(const QString &line) { + static QRegularExpression val_regexp(R"(VAL_ (\w+) (\w+) (\s*[-+]?[0-9]+\s+\".+?\"[^;]*))"); + + auto match = val_regexp.match(line); + if (!match.hasMatch()) + throw std::runtime_error("invalid VAL_ line format"); + + if (auto s = signal(match.captured(1).toUInt(), match.captured(2))) { + QStringList desc_list = match.captured(3).trimmed().split('"'); + for (int i = 0; i < desc_list.size(); i += 2) { + auto val = desc_list[i].trimmed(); + if (!val.isEmpty() && (i + 1) < desc_list.size()) { + auto desc = desc_list[i + 1].trimmed(); + s->val_desc.push_back({val.toDouble(), desc}); + } + } + } +} + QString DBCFile::generateDBC() { - QString dbc_string, signal_comment, message_comment, val_desc; + QString dbc_string, comment, val_desc; for (const auto &[address, m] : msgs) { const QString transmitter = m.transmitter.isEmpty() ? DEFAULT_NODE_NAME : m.transmitter; dbc_string += QString("BO_ %1 %2: %3 %4\n").arg(address).arg(m.name).arg(m.size).arg(transmitter); if (!m.comment.isEmpty()) { - message_comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(m.comment); + comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(QString(m.comment).replace("\"", "\\\"")); } for (auto sig : m.getSignals()) { QString multiplexer_indicator; @@ -219,7 +277,7 @@ QString DBCFile::generateDBC() { .arg(sig->unit) .arg(sig->receiver_name.isEmpty() ? DEFAULT_NODE_NAME : sig->receiver_name); if (!sig->comment.isEmpty()) { - signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(sig->comment); + comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(QString(sig->comment).replace("\"", "\\\"")); } if (!sig->val_desc.empty()) { QStringList text; @@ -231,5 +289,5 @@ QString DBCFile::generateDBC() { } dbc_string += "\n"; } - return dbc_string + message_comment + signal_comment + val_desc; + return header + dbc_string + comment + val_desc; } diff --git a/tools/cabana/dbc/dbcfile.h b/tools/cabana/dbc/dbcfile.h index 551bac4946..20de04a861 100644 --- a/tools/cabana/dbc/dbcfile.h +++ b/tools/cabana/dbc/dbcfile.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "tools/cabana/dbc/dbc.h" @@ -26,6 +27,7 @@ public: cabana::Msg *msg(uint32_t address); cabana::Msg *msg(const QString &name); inline cabana::Msg *msg(const MessageId &id) { return msg(id.address); } + cabana::Signal *signal(uint32_t address, const QString name); inline QString name() const { return name_.isEmpty() ? "untitled" : name_; } inline bool isEmpty() const { return msgs.empty() && name_.isEmpty(); } @@ -34,6 +36,13 @@ public: private: void parse(const QString &content); + cabana::Msg *parseBO(const QString &line); + void parseSG(const QString &line, cabana::Msg *current_msg, int &multiplexor_cnt); + void parseCM_BO(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream); + void parseCM_SG(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream); + void parseVAL(const QString &line); + + QString header; std::map msgs; QString name_; }; diff --git a/tools/cabana/dbc/generate_dbc_json.py b/tools/cabana/dbc/generate_dbc_json.py index da19e27b77..2d3fbe9fc4 100755 --- a/tools/cabana/dbc/generate_dbc_json.py +++ b/tools/cabana/dbc/generate_dbc_json.py @@ -2,13 +2,17 @@ import argparse import json -from openpilot.selfdrive.car.car_helpers import get_interface_attr +from openpilot.selfdrive.car.fingerprints import MIGRATION +from openpilot.selfdrive.car.values import PLATFORMS def generate_dbc_json() -> str: - all_cars_by_brand = get_interface_attr("CAR_INFO") - all_dbcs_by_brand = get_interface_attr("DBC") - dbc_map = {car: all_dbcs_by_brand[brand][car]['pt'] for brand, cars in all_cars_by_brand.items() for car in cars if car != 'mock'} + dbc_map = {platform.name: platform.config.dbc_dict['pt'] for platform in PLATFORMS.values() if platform != "MOCK"} + + for m in MIGRATION: + if MIGRATION[m] in dbc_map: + dbc_map[m] = dbc_map[MIGRATION[m]] + return json.dumps(dict(sorted(dbc_map.items())), indent=2) diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 90bd9a8f76..55ba0a4919 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -10,61 +10,49 @@ #include "tools/cabana/utils/export.h" QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { - const bool show_signals = display_signals_mode && sigs.size() > 0; const auto &m = messages[index.row()]; + const int col = index.column(); if (role == Qt::DisplayRole) { - if (index.column() == 0) { - return QString::number((m.mono_time / (double)1e9) - can->routeStartTime(), 'f', 2); - } - int i = index.column() - 1; - return show_signals ? QString::number(m.sig_values[i], 'f', sigs[i]->precision) : QString(); - } else if (role == ColorsRole) { - return QVariant::fromValue((void *)(&m.colors)); - } else if (role == BytesRole) { - return QVariant::fromValue((void *)(&m.data)); + if (col == 0) return QString::number((m.mono_time / (double)1e9) - can->routeStartTime(), 'f', 3); + if (!isHexMode()) return sigs[col - 1]->formatValue(m.sig_values[col - 1], false); } else if (role == Qt::TextAlignmentRole) { return (uint32_t)(Qt::AlignRight | Qt::AlignVCenter); } + + if (isHexMode() && col == 1) { + if (role == ColorsRole) return QVariant::fromValue((void *)(&m.colors)); + if (role == BytesRole) return QVariant::fromValue((void *)(&m.data)); + } return {}; } void HistoryLogModel::setMessage(const MessageId &message_id) { msg_id = message_id; + reset(); } -void HistoryLogModel::refresh(bool fetch_message) { +void HistoryLogModel::reset() { beginResetModel(); sigs.clear(); if (auto dbc_msg = dbc()->msg(msg_id)) { sigs = dbc_msg->getSignals(); } - last_fetch_time = 0; - has_more_data = true; messages.clear(); hex_colors = {}; - if (fetch_message) { - updateState(); - } endResetModel(); + setFilter(0, "", nullptr); } QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { - const bool show_signals = display_signals_mode && !sigs.empty(); if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { - if (section == 0) { - return "Time"; - } - if (show_signals) { - QString name = sigs[section - 1]->name; - if (!sigs[section - 1]->unit.isEmpty()) { - name += QString(" (%1)").arg(sigs[section - 1]->unit); - } - return name; - } else { - return "Data"; - } - } else if (role == Qt::BackgroundRole && section > 0 && show_signals) { + if (section == 0) return "Time"; + if (isHexMode()) return "Data"; + + QString name = sigs[section - 1]->name; + QString unit = sigs[section - 1]->unit; + return unit.isEmpty() ? name : QString("%1 (%2)").arg(name, unit); + } else if (role == Qt::BackgroundRole && section > 0 && !isHexMode()) { // Alpha-blend the signal color with the background to ensure contrast QColor sigColor = sigs[section - 1]->color; sigColor.setAlpha(128); @@ -74,110 +62,80 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i return {}; } -void HistoryLogModel::setDynamicMode(int state) { - dynamic_mode = state != 0; - refresh(); -} - -void HistoryLogModel::setDisplayType(int type) { - display_signals_mode = type == 0; - refresh(); -} - -void HistoryLogModel::segmentsMerged() { - if (!dynamic_mode) { - has_more_data = true; - } +void HistoryLogModel::setHexMode(bool hex) { + hex_mode = hex; + reset(); } void HistoryLogModel::setFilter(int sig_idx, const QString &value, std::function cmp) { filter_sig_idx = sig_idx; filter_value = value.toDouble(); filter_cmp = value.isEmpty() ? nullptr : cmp; + updateState(true); } -void HistoryLogModel::updateState() { - uint64_t current_time = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9 + 1; - auto new_msgs = dynamic_mode ? fetchData(current_time, last_fetch_time) : fetchData(0); - if (!new_msgs.empty()) { - beginInsertRows({}, 0, new_msgs.size() - 1); - messages.insert(messages.begin(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end())); - endInsertRows(); +void HistoryLogModel::updateState(bool clear) { + if (clear && !messages.empty()) { + beginRemoveRows({}, 0, messages.size() - 1); + messages.clear(); + endRemoveRows(); } - has_more_data = new_msgs.size() >= batch_size; - last_fetch_time = current_time; + uint64_t current_time = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9 + 1; + fetchData(messages.begin(), current_time, messages.empty() ? 0 : messages.front().mono_time); +} + +bool HistoryLogModel::canFetchMore(const QModelIndex &parent) const { + const auto &events = can->events(msg_id); + return !events.empty() && !messages.empty() && messages.back().mono_time > events.front()->mono_time; } void HistoryLogModel::fetchMore(const QModelIndex &parent) { - if (!messages.empty()) { - auto new_msgs = fetchData(messages.back().mono_time); - if (!new_msgs.empty()) { - beginInsertRows({}, messages.size(), messages.size() + new_msgs.size() - 1); - messages.insert(messages.end(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end())); - endInsertRows(); - } - has_more_data = new_msgs.size() >= batch_size; - } + if (!messages.empty()) + fetchData(messages.end(), messages.back().mono_time, 0); } -template -std::deque HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) { - std::deque msgs; +void HistoryLogModel::fetchData(std::deque::iterator insert_pos, uint64_t from_time, uint64_t min_time) { + const auto &events = can->events(msg_id); + auto first = std::upper_bound(events.rbegin(), events.rend(), from_time, [](uint64_t ts, auto e) { + return ts > e->mono_time; + }); + + std::vector msgs; std::vector values(sigs.size()); - for (; first != last && (*first)->mono_time > min_time; ++first) { + msgs.reserve(batch_size); + for (; first != events.rend() && (*first)->mono_time > min_time; ++first) { const CanEvent *e = *first; for (int i = 0; i < sigs.size(); ++i) { sigs[i]->getValue(e->dat, e->size, &values[i]); } if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) { - auto &m = msgs.emplace_back(); - m.mono_time = e->mono_time; - m.data.assign(e->dat, e->dat + e->size); - m.sig_values = values; + msgs.emplace_back(Message{e->mono_time, values, {e->dat, e->dat + e->size}}); if (msgs.size() >= batch_size && min_time == 0) { - return msgs; + break; } } } - return msgs; -} -std::deque HistoryLogModel::fetchData(uint64_t from_time, uint64_t min_time) { - const auto &events = can->events(msg_id); - const auto freq = can->lastMessage(msg_id).freq; - const bool update_colors = !display_signals_mode || sigs.empty(); - const std::vector no_mask; - const auto speed = can->getSpeed(); - if (dynamic_mode) { - auto first = std::upper_bound(events.rbegin(), events.rend(), from_time, [](uint64_t ts, auto e) { - return ts > e->mono_time; - }); - auto msgs = fetchData(first, events.rend(), min_time); - if (update_colors && (min_time > 0 || messages.empty())) { - for (auto it = msgs.rbegin(); it != msgs.rend(); ++it) { - hex_colors.compute(msg_id, it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, no_mask, freq); - it->colors = hex_colors.colors; + if (!msgs.empty()) { + if (isHexMode() && (min_time > 0 || messages.empty())) { + const auto freq = can->lastMessage(msg_id).freq; + const std::vector no_mask; + for (auto &m : msgs) { + hex_colors.compute(msg_id, m.data.data(), m.data.size(), m.mono_time / (double)1e9, can->getSpeed(), no_mask, freq); + m.colors = hex_colors.colors; } } - return msgs; - } else { - assert(min_time == 0); - auto first = std::upper_bound(events.cbegin(), events.cend(), from_time, CompareCanEvent()); - auto msgs = fetchData(first, events.cend(), 0); - if (update_colors) { - for (auto it = msgs.begin(); it != msgs.end(); ++it) { - hex_colors.compute(msg_id, it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, no_mask, freq); - it->colors = hex_colors.colors; - } - } - return msgs; + int pos = std::distance(messages.begin(), insert_pos); + beginInsertRows({}, pos , pos + msgs.size() - 1); + messages.insert(insert_pos, std::move_iterator(msgs.begin()), std::move_iterator(msgs.end())); + endInsertRows(); } } // HeaderView QSize HeaderView::sectionSizeFromContents(int logicalIndex) const { - static const QSize time_col_size = fontMetrics().boundingRect({0, 0, 200, 200}, defaultAlignment(), "000000.000").size() + QSize(10, 6); + static const QSize time_col_size = fontMetrics().size(Qt::TextSingleLine, "000000.000") + QSize(10, 6); if (logicalIndex == 0) { return time_col_size; } else { @@ -220,8 +178,7 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { filter_layout->addWidget(value_edit = new QLineEdit(this)); h->addWidget(filters_widget); h->addStretch(0); - h->addWidget(dynamic_mode = new QCheckBox(tr("Dynamic")), 0, Qt::AlignRight); - ToolButton *export_btn = new ToolButton("filetype-csv", tr("Export to CSV file...")); + export_btn = new ToolButton("filetype-csv", tr("Export to CSV file...")); h->addWidget(export_btn, 0, Qt::AlignRight); display_type_cb->addItems({"Signal", "Hex"}); @@ -229,8 +186,6 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { comp_box->addItems({">", "=", "!=", "<"}); value_edit->setClearButtonEnabled(true); value_edit->setValidator(new DoubleValidator(this)); - dynamic_mode->setChecked(true); - dynamic_mode->setEnabled(!can->liveStreaming()); main_layout->addWidget(toolbar); QFrame *line = new QFrame(this); @@ -238,52 +193,38 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { main_layout->addWidget(line); main_layout->addWidget(logs = new QTableView(this)); logs->setModel(model = new HistoryLogModel(this)); - delegate = new MessageBytesDelegate(this); + logs->setItemDelegate(delegate = new MessageBytesDelegate(this)); logs->setHorizontalHeader(new HeaderView(Qt::Horizontal, this)); logs->horizontalHeader()->setDefaultAlignment(Qt::AlignRight | (Qt::Alignment)Qt::TextWordWrap); logs->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); logs->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); logs->verticalHeader()->setDefaultSectionSize(delegate->sizeForBytes(8).height()); - logs->verticalHeader()->setVisible(false); logs->setFrameShape(QFrame::NoFrame); - QObject::connect(display_type_cb, qOverload(&QComboBox::activated), [this](int index) { - logs->setItemDelegateForColumn(1, index == 1 ? delegate : nullptr); - model->setDisplayType(index); - }); - QObject::connect(dynamic_mode, &QCheckBox::stateChanged, model, &HistoryLogModel::setDynamicMode); - QObject::connect(signals_cb, SIGNAL(activated(int)), this, SLOT(setFilter())); - QObject::connect(comp_box, SIGNAL(activated(int)), this, SLOT(setFilter())); - QObject::connect(value_edit, &QLineEdit::textChanged, this, &LogsWidget::setFilter); + QObject::connect(display_type_cb, qOverload(&QComboBox::activated), model, &HistoryLogModel::setHexMode); + QObject::connect(signals_cb, SIGNAL(activated(int)), this, SLOT(filterChanged())); + QObject::connect(comp_box, SIGNAL(activated(int)), this, SLOT(filterChanged())); + QObject::connect(value_edit, &QLineEdit::textEdited, this, &LogsWidget::filterChanged); QObject::connect(export_btn, &QToolButton::clicked, this, &LogsWidget::exportToCSV); - QObject::connect(can, &AbstractStream::seekedTo, model, &HistoryLogModel::refresh); - QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &LogsWidget::refresh); - QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &LogsWidget::refresh); - QObject::connect(can, &AbstractStream::eventsMerged, model, &HistoryLogModel::segmentsMerged); + QObject::connect(can, &AbstractStream::seekedTo, model, &HistoryLogModel::reset); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &HistoryLogModel::reset); + QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, model, &HistoryLogModel::reset); + QObject::connect(model, &HistoryLogModel::modelReset, this, &LogsWidget::modelReset); + QObject::connect(model, &HistoryLogModel::rowsInserted, [this]() { export_btn->setEnabled(true); }); } -void LogsWidget::setMessage(const MessageId &message_id) { - model->setMessage(message_id); - refresh(); -} - -void LogsWidget::refresh() { - model->setFilter(0, "", nullptr); - model->refresh(isVisible()); - bool has_signal = model->sigs.size(); - if (has_signal) { - signals_cb->clear(); - for (auto s : model->sigs) { - signals_cb->addItem(s->name); - } +void LogsWidget::modelReset() { + signals_cb->clear(); + for (auto s : model->sigs) { + signals_cb->addItem(s->name); } - logs->setItemDelegateForColumn(1, !has_signal || display_type_cb->currentIndex() == 1 ? delegate : nullptr); + export_btn->setEnabled(false); value_edit->clear(); comp_box->setCurrentIndex(0); - filters_widget->setVisible(has_signal); + filters_widget->setVisible(!model->sigs.empty()); } -void LogsWidget::setFilter() { +void LogsWidget::filterChanged() { if (value_edit->text().isEmpty() && !value_edit->isModified()) return; std::function cmp = nullptr; @@ -294,19 +235,6 @@ void LogsWidget::setFilter() { case 3: cmp = std::less{}; break; } model->setFilter(signals_cb->currentIndex(), value_edit->text(), cmp); - model->refresh(); -} - -void LogsWidget::updateState() { - if (isVisible() && dynamic_mode->isChecked()) { - model->updateState(); - } -} - -void LogsWidget::showEvent(QShowEvent *event) { - if (dynamic_mode->isChecked() || model->canFetchMore({}) && model->rowCount() == 0) { - model->refresh(); - } } void LogsWidget::exportToCSV() { @@ -314,7 +242,7 @@ void LogsWidget::exportToCSV() { QString fn = QFileDialog::getSaveFileName(this, QString("Export %1 to CSV file").arg(msgName(model->msg_id)), dir, tr("csv (*.csv)")); if (!fn.isEmpty()) { - const bool export_signals = model->display_signals_mode && model->sigs.size() > 0; - export_signals ? utils::exportSignalsToCSV(fn, model->msg_id) : utils::exportToCSV(fn, model->msg_id); + model->isHexMode() ? utils::exportToCSV(fn, model->msg_id) + : utils::exportSignalsToCSV(fn, model->msg_id); } } diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index 21df6a622e..1ac6e5bbad 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -11,7 +10,6 @@ #include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" -#include "tools/cabana/utils/util.h" class HeaderView : public QHeaderView { public: @@ -26,24 +24,18 @@ class HistoryLogModel : public QAbstractTableModel { public: HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {} void setMessage(const MessageId &message_id); - void updateState(); + void updateState(bool clear = false); void setFilter(int sig_idx, const QString &value, std::function cmp); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; void fetchMore(const QModelIndex &parent) override; - inline bool canFetchMore(const QModelIndex &parent) const override { return has_more_data; } + bool canFetchMore(const QModelIndex &parent) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override { return messages.size(); } - int columnCount(const QModelIndex &parent = QModelIndex()) const override { - return display_signals_mode && !sigs.empty() ? sigs.size() + 1 : 2; - } - void refresh(bool fetch_message = true); + int columnCount(const QModelIndex &parent = QModelIndex()) const override { return !isHexMode() ? sigs.size() + 1 : 2; } + inline bool isHexMode() const { return sigs.empty() || hex_mode; } + void reset(); + void setHexMode(bool hex_mode); -public slots: - void setDisplayType(int type); - void setDynamicMode(int state); - void segmentsMerged(); - -public: struct Message { uint64_t mono_time = 0; std::vector sig_values; @@ -51,22 +43,17 @@ public: std::vector colors; }; - template - std::deque fetchData(InputIt first, InputIt last, uint64_t min_time); - std::deque fetchData(uint64_t from_time, uint64_t min_time = 0); + void fetchData(std::deque::iterator insert_pos, uint64_t from_time, uint64_t min_time); MessageId msg_id; CanData hex_colors; - bool has_more_data = true; const int batch_size = 50; int filter_sig_idx = -1; double filter_value = 0; - uint64_t last_fetch_time = 0; std::function filter_cmp = nullptr; std::deque messages; std::vector sigs; - bool dynamic_mode = true; - bool display_signals_mode = true; + bool hex_mode = false; }; class LogsWidget : public QFrame { @@ -74,22 +61,21 @@ class LogsWidget : public QFrame { public: LogsWidget(QWidget *parent); - void setMessage(const MessageId &message_id); - void updateState(); - void showEvent(QShowEvent *event) override; + void setMessage(const MessageId &message_id) { model->setMessage(message_id); } + void updateState() { model->updateState(); } + void showEvent(QShowEvent *event) override { model->updateState(true); } private slots: - void setFilter(); + void filterChanged(); void exportToCSV(); + void modelReset(); private: - void refresh(); - QTableView *logs; HistoryLogModel *model; - QCheckBox *dynamic_mode; QComboBox *signals_cb, *comp_box, *display_type_cb; QLineEdit *value_edit; QWidget *filters_widget; + ToolButton *export_btn; MessageBytesDelegate *delegate; }; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index bcb65e2e3e..d099fcec99 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -179,6 +179,7 @@ void MainWindow::createDockWindows() { void MainWindow::createDockWidgets() { messages_widget = new MessagesWidget(this); messages_dock->setWidget(messages_widget); + QObject::connect(messages_widget, &MessagesWidget::titleChanged, messages_dock, &QDockWidget::setWindowTitle); // right panel charts_widget = new ChartsWidget(this); @@ -191,7 +192,6 @@ void MainWindow::createDockWidgets() { video_splitter = new QSplitter(Qt::Vertical, this); video_widget = new VideoWidget(this); video_splitter->addWidget(video_widget); - QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::updateTimeRange); video_splitter->addWidget(charts_container); video_splitter->setStretchFactor(1, 1); @@ -261,6 +261,9 @@ void MainWindow::openStream() { } stream->start(); statusBar()->showMessage(tr("Route %1 loaded").arg(can->routeName()), 2000); + } else if (!can) { + stream = new DummyStream(this); + stream->start(); } } diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 0cec551017..396cfdc38b 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -6,16 +6,29 @@ #include #include #include +#include #include #include #include #include "tools/cabana/commands.h" +static bool isMessageActive(const MessageId &id) { + if (auto dummy_stream = dynamic_cast(can)) { + return true; + } + if (id.source == INVALID_SOURCE) { + return false; + } + // Check if the message is active based on time difference and frequency + const auto &m = can->lastMessage(id); + float delta = can->currentSec() - m.ts; + return (m.freq == 0 && delta < 1.5) || (m.freq > 0 && ((delta - 1.0 / settings.fps) < (5.0 / m.freq))); +} + MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); - main_layout->setSpacing(0); // toolbar main_layout->addWidget(createToolBar()); // message table @@ -30,7 +43,6 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget view->setItemsExpandable(false); view->setIndentation(0); view->setRootIsDecorated(false); - view->setUniformRowHeights(!settings.multiple_lines_hex); // Must be called before setting any header parameters to avoid overriding restoreHeaderState(settings.message_header_state); @@ -39,23 +51,10 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget header->setStretchLastSection(true); header->setContextMenuPolicy(Qt::CustomContextMenu); - // suppress - QHBoxLayout *suppress_layout = new QHBoxLayout(); - suppress_layout->addWidget(suppress_add = new QPushButton("Suppress Highlighted")); - suppress_layout->addWidget(suppress_clear = new QPushButton()); - suppress_clear->setToolTip(tr("Clear suppressed")); - suppress_layout->addStretch(1); - QCheckBox *suppress_defined_signals = new QCheckBox(tr("Suppress Signals"), this); - suppress_defined_signals->setToolTip(tr("Suppress defined signals")); - suppress_defined_signals->setChecked(settings.suppress_defined_signals); - suppress_layout->addWidget(suppress_defined_signals); - main_layout->addLayout(suppress_layout); - // signals/slots QObject::connect(menu, &QMenu::aboutToShow, this, &MessagesWidget::menuAboutToShow); QObject::connect(header, &MessageViewHeader::customContextMenuRequested, this, &MessagesWidget::headerContextMenuEvent); QObject::connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, header, &MessageViewHeader::updateHeaderPositions); - QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, can, &AbstractStream::suppressDefinedSignals); QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived); QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::dbcModified); QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, model, &MessageListModel::dbcModified); @@ -75,9 +74,6 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget } } }); - QObject::connect(suppress_add, &QPushButton::clicked, this, &MessagesWidget::suppressHighlighted); - QObject::connect(suppress_clear, &QPushButton::clicked, this, &MessagesWidget::suppressHighlighted); - suppressHighlighted(); setWhatsThis(tr(R"( Message View
    @@ -85,22 +81,36 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget Byte color
    ■ constant changing
    ■ increasing
    - ■ decreasing + ■ decreasing
    + Shortcuts
    + Horizontal Scrolling:  shift+wheel  )")); } -QToolBar *MessagesWidget::createToolBar() { - QToolBar *toolbar = new QToolBar(this); - toolbar->setIconSize({12, 12}); - toolbar->addWidget(num_msg_label = new QLabel(this)); - num_msg_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); +QWidget *MessagesWidget::createToolBar() { + QWidget *toolbar = new QWidget(this); + QHBoxLayout *layout = new QHBoxLayout(toolbar); + layout->setContentsMargins(0, 9, 0, 0); + layout->addWidget(suppress_add = new QPushButton("Suppress Highlighted")); + layout->addWidget(suppress_clear = new QPushButton()); + suppress_clear->setToolTip(tr("Clear suppressed")); + layout->addStretch(1); + QCheckBox *suppress_defined_signals = new QCheckBox(tr("Suppress Signals"), this); + suppress_defined_signals->setToolTip(tr("Suppress defined signals")); + suppress_defined_signals->setChecked(settings.suppress_defined_signals); + layout->addWidget(suppress_defined_signals); - auto views_btn = toolbar->addAction(utils::icon("three-dots"), tr("View...")); - views_btn->setMenu(menu); - auto view_button = qobject_cast(toolbar->widgetForAction(views_btn)); + auto view_button = new ToolButton("three-dots", tr("View...")); + view_button->setMenu(menu); view_button->setPopupMode(QToolButton::InstantPopup); - view_button->setToolButtonStyle(Qt::ToolButtonIconOnly); view_button->setStyleSheet("QToolButton::menu-indicator { image: none; }"); + layout->addWidget(view_button); + + QObject::connect(suppress_add, &QPushButton::clicked, this, &MessagesWidget::suppressHighlighted); + QObject::connect(suppress_clear, &QPushButton::clicked, this, &MessagesWidget::suppressHighlighted); + QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, can, &AbstractStream::suppressDefinedSignals); + + suppressHighlighted(); return toolbar; } @@ -111,7 +121,7 @@ void MessagesWidget::updateTitle() { auto m = dbc()->msg(item.id); return m ? std::make_pair(pair.first + 1, pair.second + m->sigs.size()) : pair; }); - num_msg_label->setText(tr("%1 Messages (%2 DBC Messages, %3 Signals)") + emit titleChanged(tr("%1 Messages (%2 DBC Messages, %3 Signals)") .arg(model->items_.size()).arg(stats.first).arg(stats.second)); } @@ -124,15 +134,9 @@ void MessagesWidget::selectMessage(const MessageId &msg_id) { } void MessagesWidget::suppressHighlighted() { - if (sender() == suppress_add) { - size_t n = can->suppressHighlighted(); - suppress_clear->setText(tr("Clear (%1)").arg(n)); - suppress_clear->setEnabled(true); - } else { - can->clearSuppressed(); - suppress_clear->setText(tr("Clear")); - suppress_clear->setEnabled(false); - } + int n = sender() == suppress_add ? can->suppressHighlighted() : (can->clearSuppressed(), 0); + suppress_clear->setText(n > 0 ? tr("Clear (%1)").arg(n) : tr("Clear")); + suppress_clear->setEnabled(n > 0); } void MessagesWidget::headerContextMenuEvent(const QPoint &pos) { @@ -154,12 +158,15 @@ void MessagesWidget::menuAboutToShow() { auto action = menu->addAction(tr("Multi-Line bytes"), this, &MessagesWidget::setMultiLineBytes); action->setCheckable(true); action->setChecked(settings.multiple_lines_hex); + + action = menu->addAction(tr("Show inactive Messages"), model, &MessageListModel::showInactivemessages); + action->setCheckable(true); + action->setChecked(model->show_inactive_messages); } void MessagesWidget::setMultiLineBytes(bool multi) { settings.multiple_lines_hex = multi; delegate->setMultipleLines(multi); - view->setUniformRowHeights(!multi); view->updateBytesSectionSize(); view->doItemsLayout(); } @@ -184,30 +191,32 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, QVariant MessageListModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= items_.size()) return {}; - auto getFreq = [](const CanData &d) { - if (d.freq > 0 && (can->currentSec() - d.ts - 1.0 / settings.fps) < (5.0 / d.freq)) { - return d.freq >= 0.95 ? QString::number(std::nearbyint(d.freq)) : QString::number(d.freq, 'f', 2); + auto getFreq = [](float freq) { + if (freq > 0) { + return freq >= 0.95 ? QString::number(std::nearbyint(freq)) : QString::number(freq, 'f', 2); } else { return QStringLiteral("--"); } }; + const static QString NA = QStringLiteral("N/A"); const auto &item = items_[index.row()]; - const auto &data = can->lastMessage(item.id); if (role == Qt::DisplayRole) { switch (index.column()) { case Column::NAME: return item.name; - case Column::SOURCE: return item.id.source != INVALID_SOURCE ? QString::number(item.id.source) : "N/A"; + case Column::SOURCE: return item.id.source != INVALID_SOURCE ? QString::number(item.id.source) : NA; case Column::ADDRESS: return QString::number(item.id.address, 16); case Column::NODE: return item.node; - case Column::FREQ: return item.id.source != INVALID_SOURCE ? getFreq(data) : "N/A"; - case Column::COUNT: return item.id.source != INVALID_SOURCE ? QString::number(data.count) : "N/A"; - case Column::DATA: return item.id.source != INVALID_SOURCE ? "" : "N/A"; + case Column::FREQ: return item.id.source != INVALID_SOURCE ? getFreq(can->lastMessage(item.id).freq) : NA; + case Column::COUNT: return item.id.source != INVALID_SOURCE ? QString::number(can->lastMessage(item.id).count) : NA; + case Column::DATA: return item.id.source != INVALID_SOURCE ? "" : NA; } } else if (role == ColorsRole) { - return QVariant::fromValue((void*)(&data.colors)); + return QVariant::fromValue((void*)(&can->lastMessage(item.id).colors)); } else if (role == BytesRole && index.column() == Column::DATA && item.id.source != INVALID_SOURCE) { - return QVariant::fromValue((void*)(&data.dat)); + return QVariant::fromValue((void*)(&can->lastMessage(item.id).dat)); + } else if (role == Qt::ForegroundRole && !item.active) { + return settings.theme == DARK_THEME ? QApplication::palette().color(QPalette::Text).darker(150) : QColor(Qt::gray); } else if (role == Qt::ToolTipRole && index.column() == Column::NAME) { auto msg = dbc()->msg(item.id); auto tooltip = item.name; @@ -222,6 +231,11 @@ void MessageListModel::setFilterStrings(const QMap &filters) { filterAndSort(); } +void MessageListModel::showInactivemessages(bool show) { + show_inactive_messages = show; + filterAndSort(); +} + void MessageListModel::dbcModified() { dbc_messages_.clear(); for (const auto &[_, m] : dbc()->getMessages(-1)) { @@ -231,19 +245,22 @@ void MessageListModel::dbcModified() { } void MessageListModel::sortItems(std::vector &items) { - auto do_sort = [order = sort_order](std::vector &m, auto proj) { - std::stable_sort(m.begin(), m.end(), [order, proj = std::move(proj)](auto &l, auto &r) { - return order == Qt::AscendingOrder ? proj(l) < proj(r) : proj(l) > proj(r); - }); + auto compare = [this](const auto &l, const auto &r) { + switch (sort_column) { + case Column::NAME: return std::tie(l.name, l.id) < std::tie(r.name, r.id); + case Column::SOURCE: return std::tie(l.id.source, l.id.address) < std::tie(r.id.source, r.id.address); + case Column::ADDRESS: return std::tie(l.id.address, l.id.source) < std::tie(r.id.address, r.id.source); + case Column::NODE: return std::tie(l.node, l.id) < std::tie(r.node, r.id); + case Column::FREQ: return std::tie(can->lastMessage(l.id).freq, l.id) < std::tie(can->lastMessage(r.id).freq, r.id); + case Column::COUNT: return std::tie(can->lastMessage(l.id).count, l.id) < std::tie(can->lastMessage(r.id).count, r.id); + default: return false; // Default case to suppress compiler warning + } }; - switch (sort_column) { - case Column::NAME: do_sort(items, [](auto &item) { return std::tie(item.name, item.id); }); break; - case Column::SOURCE: do_sort(items, [](auto &item) { return std::tie(item.id.source, item.id); }); break; - case Column::ADDRESS: do_sort(items, [](auto &item) { return std::tie(item.id.address, item.id);}); break; - case Column::NODE: do_sort(items, [](auto &item) { return std::tie(item.node, item.id);}); break; - case Column::FREQ: do_sort(items, [](auto &item) { return std::make_pair(can->lastMessage(item.id).freq, item.id); }); break; - case Column::COUNT: do_sort(items, [](auto &item) { return std::make_pair(can->lastMessage(item.id).count, item.id); }); break; - } + + if (sort_order == Qt::DescendingOrder) + std::stable_sort(items.rbegin(), items.rend(), compare); + else + std::stable_sort(items.begin(), items.end(), compare); } static bool parseRange(const QString &filter, uint32_t value, int base = 10) { @@ -290,7 +307,6 @@ bool MessageListModel::match(const MessageListModel::Item &item) { match = item.node.contains(txt, Qt::CaseInsensitive); break; case Column::FREQ: - // TODO: Hide stale messages? match = parseRange(txt, data.freq); break; case Column::COUNT: @@ -304,7 +320,7 @@ bool MessageListModel::match(const MessageListModel::Item &item) { return match; } -void MessageListModel::filterAndSort() { +bool MessageListModel::filterAndSort() { // merge CAN and DBC messages std::vector all_messages; all_messages.reserve(can->lastMessages().size() + dbc_messages_.size()); @@ -317,13 +333,18 @@ void MessageListModel::filterAndSort() { // filter and sort std::vector items; + items.reserve(all_messages.size()); for (const auto &id : all_messages) { - auto msg = dbc()->msg(id); - Item item = {.id = id, - .name = msg ? msg->name : UNTITLED, - .node = msg ? msg->transmitter : QString()}; - if (match(item)) - items.emplace_back(item); + bool active = isMessageActive(id); + if (active || show_inactive_messages) { + auto msg = dbc()->msg(id); + Item item = {.id = id, + .active = active, + .name = msg ? msg->name : UNTITLED, + .node = msg ? msg->transmitter : QString()}; + if (match(item)) + items.emplace_back(item); + } } sortItems(items); @@ -331,19 +352,23 @@ void MessageListModel::filterAndSort() { beginResetModel(); items_ = std::move(items); endResetModel(); + return true; } + return false; } void MessageListModel::msgsReceived(const std::set *new_msgs, bool has_new_ids) { - if (has_new_ids || filters_.contains(Column::FREQ) || filters_.contains(Column::COUNT) || filters_.contains(Column::DATA)) { - filterAndSort(); + if (has_new_ids || ((filters_.count(Column::FREQ) || filters_.count(Column::COUNT) || filters_.count(Column::DATA)) && + ++sort_threshold_ == settings.fps)) { + sort_threshold_ = 0; + if (filterAndSort()) return; } - for (int i = 0; i < items_.size(); ++i) { - if (!new_msgs || new_msgs->count(items_[i].id)) { - for (int col = Column::FREQ; col < columnCount(); ++col) - emit dataChanged(index(i, col), index(i, col), {Qt::DisplayRole}); - } + + for (auto &item : items_) { + item.active = isMessageActive(item.id); } + // Update viewport + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } void MessageListModel::sort(int column, Qt::SortOrder order) { @@ -358,20 +383,18 @@ void MessageListModel::sort(int column, Qt::SortOrder order) { void MessageView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QTreeView::drawRow(painter, option, index); - const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this); - const QColor gridColor = QColor::fromRgba(static_cast(gridHint)); - QPen old_pen = painter->pen(); - painter->setPen(gridColor); - painter->drawLine(option.rect.left(), option.rect.bottom(), option.rect.right(), option.rect.bottom()); - auto y = option.rect.y(); - painter->translate(visualRect(model()->index(0, 0)).x() - indentation() - .5, -.5); + QPen oldPen = painter->pen(); + const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this); + painter->setPen(QColor::fromRgba(static_cast(gridHint))); + // Draw bottom border for the row + painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight()); + // Draw vertical borders for each column for (int i = 0; i < header()->count(); ++i) { - painter->translate(header()->sectionSize(header()->logicalIndex(i)), 0); - painter->drawLine(0, y, 0, y + option.rect.height()); + int sectionX = header()->sectionViewportPosition(i); + painter->drawLine(sectionX, option.rect.top(), sectionX, option.rect.bottom()); } - painter->setPen(old_pen); - painter->resetTransform(); + painter->setPen(oldPen); } void MessageView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { @@ -388,9 +411,18 @@ void MessageView::updateBytesSectionSize() { max_bytes = std::max(max_bytes, m.dat.size()); } } + setUniformRowHeights(!delegate->multipleLines() || max_bytes <= 8); header()->resizeSection(MessageListModel::Column::DATA, delegate->sizeForBytes(max_bytes).width()); } +void MessageView::wheelEvent(QWheelEvent *event) { + if (event->modifiers() == Qt::ShiftModifier) { + QApplication::sendEvent(horizontalScrollBar(), event); + } else { + QTreeView::wheelEvent(event); + } +} + // MessageViewHeader MessageViewHeader::MessageViewHeader(QWidget *parent) : QHeaderView(Qt::Horizontal, parent) { diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index 4f54941c64..823fcb74e3 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -7,11 +7,10 @@ #include #include -#include #include #include -#include #include +#include #include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" @@ -37,19 +36,22 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override { return items_.size(); } void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; void setFilterStrings(const QMap &filters); + void showInactivemessages(bool show); void msgsReceived(const std::set *new_msgs, bool has_new_ids); - void filterAndSort(); + bool filterAndSort(); void dbcModified(); struct Item { MessageId id; QString name; QString node; + bool active; bool operator==(const Item &other) const { return id == other.id && name == other.name && node == other.node; } }; std::vector items_; + bool show_inactive_messages = true; private: void sortItems(std::vector &items); @@ -59,16 +61,20 @@ private: std::set dbc_messages_; int sort_column = 0; Qt::SortOrder sort_order = Qt::AscendingOrder; + int sort_threshold_ = 0; }; class MessageView : public QTreeView { Q_OBJECT public: MessageView(QWidget *parent) : QTreeView(parent) {} + void updateBytesSectionSize(); + +protected: void drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override {} void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override; - void updateBytesSectionSize(); + void wheelEvent(QWheelEvent *event) override; }; class MessageViewHeader : public QHeaderView { @@ -96,9 +102,10 @@ public: signals: void msgSelectionChanged(const MessageId &message_id); + void titleChanged(const QString &title); protected: - QToolBar *createToolBar(); + QWidget *createToolBar(); void headerContextMenuEvent(const QPoint &pos); void menuAboutToShow(); void setMultiLineBytes(bool multi); @@ -111,6 +118,5 @@ protected: MessageListModel *model; QPushButton *suppress_add; QPushButton *suppress_clear; - QLabel *num_msg_label; QMenu *menu; }; diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index b63789a026..523cbf3be7 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -12,6 +12,9 @@ #include "tools/cabana/utils/util.h" +const int MIN_CACHE_MINIUTES = 30; +const int MAX_CACHE_MINIUTES = 120; + Settings settings; template @@ -72,7 +75,7 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { fps->setValue(settings.fps); form_layout->addRow(tr("Max Cached Minutes"), cached_minutes = new QSpinBox(this)); - cached_minutes->setRange(5, 60); + cached_minutes->setRange(MIN_CACHE_MINIUTES, MAX_CACHE_MINIUTES); cached_minutes->setSingleStep(1); cached_minutes->setValue(settings.max_cached_minutes); main_layout->addWidget(groupbox); diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index 8a20086b5c..2584106ce4 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -3,6 +3,7 @@ #include #include +#include #include "common/timing.h" #include "tools/cabana/settings.h" @@ -19,6 +20,7 @@ AbstractStream::AbstractStream(QObject *parent) : QObject(parent) { assert(parent != nullptr); event_buffer_ = std::make_unique(EVENT_NEXT_BUFFER_SIZE); + QObject::connect(QApplication::instance(), &QCoreApplication::aboutToQuit, this, &AbstractStream::stop); QObject::connect(this, &AbstractStream::privateUpdateLastMsgsSignal, this, &AbstractStream::updateLastMessages, Qt::QueuedConnection); QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &AbstractStream::updateMasks); @@ -90,13 +92,23 @@ void AbstractStream::updateLastMessages() { std::set msgs; { std::lock_guard lk(mutex_); + double max_sec = 0; for (const auto &id : new_msgs_) { const auto &can_data = messages_[id]; - current_sec_ = std::max(current_sec_, can_data.ts); + max_sec = std::max(max_sec, can_data.ts); last_msgs[id] = can_data; sources.insert(id.source); } - msgs = std::move(new_msgs_); + + if (!new_msgs_.empty()) { + msgs = std::move(new_msgs_); + current_sec_ = max_sec; + } + } + + if (time_range_ && (current_sec_ < time_range_->first || current_sec_ >= time_range_->second)) { + seekTo(time_range_->first); + return; } if (sources.size() != prev_src_size) { @@ -106,6 +118,14 @@ void AbstractStream::updateLastMessages() { emit msgsReceived(&msgs, prev_msg_size != last_msgs.size()); } +void AbstractStream::setTimeRange(const std::optional> &range) { + time_range_ = range; + if (time_range_ && (current_sec_ < time_range_->first || current_sec_ >= time_range_->second)) { + seekTo(time_range_->first); + } + emit timeRangeChanged(time_range_); +} + void AbstractStream::updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size) { std::lock_guard lk(mutex_); messages_[id].compute(id, data, size, sec, getSpeed(), masks_[id]); @@ -127,22 +147,33 @@ const CanData &AbstractStream::lastMessage(const MessageId &id) { // it is thread safe to update data in updateLastMsgsTo. // updateLastMsgsTo is always called in UI thread. void AbstractStream::updateLastMsgsTo(double sec) { - new_msgs_.clear(); - messages_.clear(); - current_sec_ = sec; uint64_t last_ts = (sec + routeStartTime()) * 1e9; + std::unordered_map msgs; + msgs.reserve(events_.size()); + for (const auto &[id, ev] : events_) { auto it = std::upper_bound(ev.begin(), ev.end(), last_ts, CompareCanEvent()); if (it != ev.begin()) { auto prev = std::prev(it); double ts = (*prev)->mono_time / 1e9 - routeStartTime(); - auto &m = messages_[id]; - m.compute(id, (*prev)->dat, (*prev)->size, ts, getSpeed(), {}); + auto &m = msgs[id]; + double freq = 0; + // Keep suppressed bits. + if (auto old_m = messages_.find(id); old_m != messages_.end()) { + freq = old_m->second.freq; + m.last_changes.reserve(old_m->second.last_changes.size()); + std::transform(old_m->second.last_changes.cbegin(), old_m->second.last_changes.cend(), + std::back_inserter(m.last_changes), + [](const auto &change) { return CanData::ByteLastChange{.suppressed = change.suppressed}; }); + } + m.compute(id, (*prev)->dat, (*prev)->size, ts, getSpeed(), {}, freq); m.count = std::distance(ev.begin(), prev) + 1; } } + new_msgs_.clear(); + messages_ = std::move(msgs); bool id_changed = messages_.size() != last_msgs.size() || std::any_of(messages_.cbegin(), messages_.cend(), [this](const auto &m) { return !last_msgs.count(m.first); }); @@ -164,6 +195,8 @@ const CanEvent *AbstractStream::newEvent(uint64_t mono_time, const cereal::CanDa void AbstractStream::mergeEvents(const std::vector &events) { static MessageEventsMap msg_events; std::for_each(msg_events.begin(), msg_events.end(), [](auto &e) { e.second.clear(); }); + + // Group events by message ID for (auto e : events) { msg_events[{.source = e->src, .address = e->address}].push_back(e); } @@ -183,8 +216,6 @@ void AbstractStream::mergeEvents(const std::vector &events) { lastest_event_ts = all_events_.empty() ? 0 : all_events_.back()->mono_time; } -// CanData - namespace { enum Color { GREYISH_BLUE, CYAN, RED}; @@ -202,7 +233,7 @@ inline QColor blend(const QColor &a, const QColor &b) { return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2); } -// Calculate the frequency of the past minute. +// Calculate the frequency from the past one minute data double calc_freq(const MessageId &msg_id, double current_sec) { const auto &events = can->events(msg_id); uint64_t cur_mono_time = (can->routeStartTime() + current_sec) * 1e9; @@ -250,11 +281,8 @@ void CanData::compute(const MessageId &msg_id, const uint8_t *can_data, const in if (last != cur) { const int delta = cur - last; // Keep track if signal is changing randomly, or mostly moving in the same direction - if (std::signbit(delta) == std::signbit(last_change.delta)) { - last_change.same_delta_counter = std::min(16, last_change.same_delta_counter + 1); - } else { - last_change.same_delta_counter = std::max(0, last_change.same_delta_counter - 4); - } + last_change.same_delta_counter += std::signbit(delta) == std::signbit(last_change.delta) ? 1 : -4; + last_change.same_delta_counter = std::clamp(last_change.same_delta_counter, 0, 16); const double delta_t = ts - last_change.ts; // Mostly moves in the same direction, color based on delta up/down @@ -267,10 +295,10 @@ void CanData::compute(const MessageId &msg_id, const uint8_t *can_data, const in } // Track bit level changes - const uint8_t tmp = (cur ^ last); + const uint8_t diff = (cur ^ last); for (int bit = 0; bit < 8; bit++) { - if (tmp & (1 << (7 - bit))) { - last_change.bit_change_counts[bit] += 1; + if (diff & (1u << bit)) { + ++last_change.bit_change_counts[7 - bit]; } } diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 10ffcb184a..822dd03d84 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -3,8 +3,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -13,6 +15,7 @@ #include "cereal/messaging/messaging.h" #include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/utils/util.h" +#include "tools/replay/util.h" struct CanData { void compute(const MessageId &msg_id, const uint8_t *dat, const int size, double current_sec, @@ -63,6 +66,7 @@ public: AbstractStream(QObject *parent); virtual ~AbstractStream() {} virtual void start() = 0; + virtual void stop() {} virtual bool liveStreaming() const { return true; } virtual void seekTo(double ts) {} virtual QString routeName() const = 0; @@ -75,6 +79,8 @@ public: virtual double getSpeed() { return 1; } virtual bool isPaused() const { return false; } virtual void pause(bool pause) {} + void setTimeRange(const std::optional> &range); + const std::optional> &timeRange() const { return time_range_; } inline const std::unordered_map &lastMessages() const { return last_msgs; } inline const MessageEventsMap &eventsMap() const { return events_; } @@ -89,7 +95,9 @@ public: signals: void paused(); void resume(); + void seeking(double sec); void seekedTo(double sec); + void timeRangeChanged(const std::optional> &range); void streamStarted(); void eventsMerged(const MessageEventsMap &events_map); void msgsReceived(const std::set *new_msgs, bool has_new_ids); @@ -106,6 +114,8 @@ protected: uint64_t lastEventMonoTime() const { return lastest_event_ts; } std::vector all_events_; + double current_sec_ = 0; + std::optional> time_range_; uint64_t lastest_event_ts = 0; private: @@ -113,7 +123,6 @@ private: void updateLastMsgsTo(double sec); void updateMasks(); - double current_sec_ = 0; MessageEventsMap events_; std::unordered_map last_msgs; std::unique_ptr event_buffer_; @@ -126,11 +135,15 @@ private: }; class AbstractOpenStreamWidget : public QWidget { + Q_OBJECT public: AbstractOpenStreamWidget(AbstractStream **stream, QWidget *parent = nullptr) : stream(stream), QWidget(parent) {} virtual bool open() = 0; virtual QString title() = 0; +signals: + void enableOpenButton(bool); + protected: AbstractStream **stream = nullptr; }; diff --git a/tools/cabana/streams/livestream.cc b/tools/cabana/streams/livestream.cc index 4e1f6d77d9..d9d96e23b0 100644 --- a/tools/cabana/streams/livestream.cc +++ b/tools/cabana/streams/livestream.cc @@ -42,6 +42,10 @@ LiveStream::LiveStream(QObject *parent) : AbstractStream(parent) { QObject::connect(stream_thread, &QThread::finished, stream_thread, &QThread::deleteLater); } +LiveStream::~LiveStream() { + stop(); +} + void LiveStream::startUpdateTimer() { update_timer.stop(); update_timer.start(1000.0 / settings.fps, this); @@ -55,11 +59,14 @@ void LiveStream::start() { begin_date_time = QDateTime::currentDateTime(); } -LiveStream::~LiveStream() { +void LiveStream::stop() { + if (!stream_thread) return; + update_timer.stop(); stream_thread->requestInterruption(); stream_thread->quit(); stream_thread->wait(); + stream_thread = nullptr; } // called in streamThread diff --git a/tools/cabana/streams/livestream.h b/tools/cabana/streams/livestream.h index 7a0f8cd834..9ca7e8d745 100644 --- a/tools/cabana/streams/livestream.h +++ b/tools/cabana/streams/livestream.h @@ -14,6 +14,7 @@ public: LiveStream(QObject *parent); virtual ~LiveStream(); void start() override; + void stop() override; inline QDateTime beginDateTime() const { return begin_date_time; } inline double routeStartTime() const override { return begin_event_ts / 1e9; } void setSpeed(float speed) override { speed_ = speed; } diff --git a/tools/cabana/streams/pandastream.cc b/tools/cabana/streams/pandastream.cc index bea1fd7480..030783a899 100644 --- a/tools/cabana/streams/pandastream.cc +++ b/tools/cabana/streams/pandastream.cc @@ -6,32 +6,9 @@ #include #include #include -#include - -// TODO: remove clearLayout -static void clearLayout(QLayout* layout) { - while (layout->count() > 0) { - QLayoutItem* item = layout->takeAt(0); - if (QWidget* widget = item->widget()) { - widget->deleteLater(); - } - if (QLayout* childLayout = item->layout()) { - clearLayout(childLayout); - } - delete item; - } -} +#include PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(config_), LiveStream(parent) { - if (config.serial.isEmpty()) { - auto serials = Panda::list(); - if (serials.size() == 0) { - throw std::runtime_error("No panda found"); - } - config.serial = QString::fromStdString(serials[0]); - } - - qDebug() << "Connecting to panda with serial" << config.serial; if (!connect()) { throw std::runtime_error("Failed to connect to panda"); } @@ -39,6 +16,7 @@ PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(co bool PandaStream::connect() { try { + qDebug() << "Connecting to panda " << config.serial; panda.reset(new Panda(config.serial.toStdString())); config.bus_config.resize(3); qDebug() << "Connected"; @@ -47,7 +25,6 @@ bool PandaStream::connect() { } panda->set_safety_model(cereal::CarParams::SafetyModel::SILENT); - for (int bus = 0; bus < config.bus_config.size(); bus++) { panda->set_can_speed_kbps(bus, config.bus_config[bus].can_speed_kbps); @@ -60,7 +37,6 @@ bool PandaStream::connect() { panda->set_data_speed_kbps(bus, 10); } } - } return true; } @@ -108,26 +84,21 @@ AbstractOpenStreamWidget *PandaStream::widget(AbstractStream **stream) { // OpenPandaWidget OpenPandaWidget::OpenPandaWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->addStretch(1); - - QFormLayout *form_layout = new QFormLayout(); + form_layout = new QFormLayout(this); + if (can && dynamic_cast(can) != nullptr) { + form_layout->addWidget(new QLabel(tr("Already connected to %1.").arg(can->routeName()))); + form_layout->addWidget(new QLabel("Close the current connection via [File menu -> Close Stream] before connecting to another Panda.")); + QTimer::singleShot(0, [this]() { emit enableOpenButton(false); }); + return; + } QHBoxLayout *serial_layout = new QHBoxLayout(); - serial_edit = new QComboBox(); - serial_edit->setFixedWidth(300); - serial_layout->addWidget(serial_edit); + serial_layout->addWidget(serial_edit = new QComboBox()); QPushButton *refresh = new QPushButton(tr("Refresh")); - refresh->setFixedWidth(100); + refresh->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); serial_layout->addWidget(refresh); form_layout->addRow(tr("Serial"), serial_layout); - main_layout->addLayout(form_layout); - - config_layout = new QFormLayout(); - main_layout->addLayout(config_layout); - - main_layout->addStretch(1); QObject::connect(refresh, &QPushButton::clicked, this, &OpenPandaWidget::refreshSerials); QObject::connect(serial_edit, &QComboBox::currentTextChanged, this, &OpenPandaWidget::buildConfigForm); @@ -145,17 +116,19 @@ void OpenPandaWidget::refreshSerials() { } void OpenPandaWidget::buildConfigForm() { - clearLayout(config_layout); - QString serial = serial_edit->currentText(); + for (int i = form_layout->rowCount() - 1; i > 0; --i) { + form_layout->removeRow(i); + } + QString serial = serial_edit->currentText(); bool has_fd = false; bool has_panda = !serial.isEmpty(); - if (has_panda) { try { - Panda panda = Panda(serial.toStdString()); + Panda panda(serial.toStdString()); has_fd = (panda.hw_type == cereal::PandaState::PandaType::RED_PANDA) || (panda.hw_type == cereal::PandaState::PandaType::RED_PANDA_V2); } catch (const std::exception& e) { + qDebug() << "failed to open panda" << serial; has_panda = false; } } @@ -201,20 +174,20 @@ void OpenPandaWidget::buildConfigForm() { QObject::connect(enable_fd, &QCheckBox::stateChanged, [=](int state) {config.bus_config[i].can_fd = (bool)state;}); } - config_layout->addRow(tr("Bus %1:").arg(i), bus_layout); + form_layout->addRow(tr("Bus %1:").arg(i), bus_layout); } } else { config.serial = ""; - config_layout->addWidget(new QLabel(tr("No panda found"))); + form_layout->addWidget(new QLabel(tr("No panda found"))); } } bool OpenPandaWidget::open() { try { *stream = new PandaStream(qApp, config); + return true; } catch (std::exception &e) { QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to panda: '%1'").arg(e.what())); return false; } - return true; } diff --git a/tools/cabana/streams/pandastream.h b/tools/cabana/streams/pandastream.h index 919156f400..3e76247680 100644 --- a/tools/cabana/streams/pandastream.h +++ b/tools/cabana/streams/pandastream.h @@ -7,7 +7,7 @@ #include #include "tools/cabana/streams/livestream.h" -#include "selfdrive/boardd/panda.h" +#include "selfdrive/pandad/panda.h" const uint32_t speeds[] = {10U, 20U, 50U, 100U, 125U, 250U, 500U, 1000U}; const uint32_t data_speeds[] = {10U, 20U, 50U, 100U, 125U, 250U, 500U, 1000U, 2000U, 5000U}; @@ -21,14 +21,15 @@ class PandaStream : public LiveStream { Q_OBJECT public: PandaStream(QObject *parent, PandaStreamConfig config_ = {}); + ~PandaStream() { stop(); } static AbstractOpenStreamWidget *widget(AbstractStream **stream); inline QString routeName() const override { - return QString("Live Streaming From Panda %1").arg(config.serial); + return QString("Panda: %1").arg(config.serial); } protected: - void streamThread() override; bool connect(); + void streamThread() override; std::unique_ptr panda; PandaStreamConfig config = {}; @@ -47,6 +48,6 @@ private: void buildConfigForm(); QComboBox *serial_edit; - QFormLayout *config_layout; + QFormLayout *form_layout; PandaStreamConfig config = {}; }; diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index aff0122b47..5d4925a67a 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -7,6 +7,7 @@ #include #include "common/timing.h" +#include "tools/cabana/streams/routes.h" ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) { unsetenv("ZMQ"); @@ -33,11 +34,12 @@ void ReplayStream::mergeSegments() { std::vector new_events; new_events.reserve(seg->log->events.size()); - for (auto it = seg->log->events.cbegin(); it != seg->log->events.cend(); ++it) { - if ((*it)->which == cereal::Event::Which::CAN) { - const uint64_t ts = (*it)->mono_time; - for (const auto &c : (*it)->event.getCan()) { - new_events.push_back(newEvent(ts, c)); + for (const Event &e : seg->log->events) { + if (e.which == cereal::Event::Which::CAN) { + capnp::FlatArrayMessageReader reader(e.data); + auto event = reader.getRoot(); + for (const auto &c : event.getCan()) { + new_events.push_back(newEvent(e.mono_time, c)); } } } @@ -51,9 +53,9 @@ bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint {}, nullptr, replay_flags, data_dir, this)); replay->setSegmentCacheLimit(settings.max_cached_minutes); replay->installEventFilter(event_filter, this); + QObject::connect(replay.get(), &Replay::seeking, this, &AbstractStream::seeking); QObject::connect(replay.get(), &Replay::seekedTo, this, &AbstractStream::seekedTo); QObject::connect(replay.get(), &Replay::segmentsMerged, this, &ReplayStream::mergeSegments); - QObject::connect(replay.get(), &Replay::qLogLoaded, this, &ReplayStream::qLogLoaded, Qt::QueuedConnection); return replay->load(); } @@ -62,11 +64,19 @@ void ReplayStream::start() { replay->start(); } +void ReplayStream::stop() { + if (replay) { + replay->stop(); + } +} + bool ReplayStream::eventFilter(const Event *event) { static double prev_update_ts = 0; if (event->which == cereal::Event::Which::CAN) { double current_sec = event->mono_time / 1e9 - routeStartTime(); - for (const auto &c : event->event.getCan()) { + capnp::FlatArrayMessageReader reader(event->data); + auto e = reader.getRoot(); + for (const auto &c : e.getCan()) { MessageId id = {.source = c.getSrc(), .address = c.getAddress()}; const auto dat = c.getDat(); updateEvent(id, current_sec, (const uint8_t*)dat.begin(), dat.size()); @@ -81,6 +91,11 @@ bool ReplayStream::eventFilter(const Event *event) { return true; } +void ReplayStream::seekTo(double ts) { + current_sec_ = ts; + replay->seekTo(std::max(double(0), ts), false); +} + void ReplayStream::pause(bool pause) { replay->pause(pause); emit(pause ? paused() : resume()); @@ -94,29 +109,36 @@ AbstractOpenStreamWidget *ReplayStream::widget(AbstractStream **stream) { // OpenReplayWidget OpenReplayWidget::OpenReplayWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) { - // TODO: get route list from api.comma.ai QGridLayout *grid_layout = new QGridLayout(this); grid_layout->addWidget(new QLabel(tr("Route")), 0, 0); grid_layout->addWidget(route_edit = new QLineEdit(this), 0, 1); - route_edit->setPlaceholderText(tr("Enter remote route name or click browse to select a local route")); - auto file_btn = new QPushButton(tr("Browse..."), this); - grid_layout->addWidget(file_btn, 0, 2); + route_edit->setPlaceholderText(tr("Enter route name or browse for local/remote route")); + auto browse_remote_btn = new QPushButton(tr("Remote route..."), this); + grid_layout->addWidget(browse_remote_btn, 0, 2); + auto browse_local_btn = new QPushButton(tr("Local route..."), this); + grid_layout->addWidget(browse_local_btn, 0, 3); - grid_layout->addWidget(new QLabel(tr("Camera")), 1, 0); QHBoxLayout *camera_layout = new QHBoxLayout(); for (auto c : {tr("Road camera"), tr("Driver camera"), tr("Wide road camera")}) camera_layout->addWidget(cameras.emplace_back(new QCheckBox(c, this))); + cameras[0]->setChecked(true); camera_layout->addStretch(1); grid_layout->addItem(camera_layout, 1, 1); setMinimumWidth(550); - QObject::connect(file_btn, &QPushButton::clicked, [=]() { + QObject::connect(browse_local_btn, &QPushButton::clicked, [=]() { QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), settings.last_route_dir); if (!dir.isEmpty()) { route_edit->setText(dir); settings.last_route_dir = QFileInfo(dir).absolutePath(); } }); + QObject::connect(browse_remote_btn, &QPushButton::clicked, [this]() { + RoutesDialog route_dlg(this); + if (route_dlg.exec()) { + route_edit->setText(route_dlg.route()); + } + }); } bool OpenReplayWidget::open() { diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index b4e4be4db6..ced78680d1 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -16,14 +16,15 @@ class ReplayStream : public AbstractStream { public: ReplayStream(QObject *parent); void start() override; + void stop() override; bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE); bool eventFilter(const Event *event); - void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); } + void seekTo(double ts) override; bool liveStreaming() const override { return false; } inline QString routeName() const override { return replay->route()->name(); } inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); } double totalSeconds() const override { return replay->totalSeconds(); } - inline QDateTime beginDateTime() const { return replay->route()->datetime(); } + inline QDateTime beginDateTime() const { return replay->routeDateTime(); } inline double routeStartTime() const override { return replay->routeStartTime() / (double)1e9; } inline const Route *route() const { return replay->route(); } inline void setSpeed(float speed) override { replay->setSpeed(speed); } @@ -33,9 +34,6 @@ public: void pause(bool pause) override; static AbstractOpenStreamWidget *widget(AbstractStream **stream); -signals: - void qLogLoaded(int segnum, std::shared_ptr qlog); - private: void mergeSegments(); std::unique_ptr replay = nullptr; diff --git a/tools/cabana/streams/routes.cc b/tools/cabana/streams/routes.cc new file mode 100644 index 0000000000..c805e7d60d --- /dev/null +++ b/tools/cabana/streams/routes.cc @@ -0,0 +1,123 @@ +#include "tools/cabana/streams/routes.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "system/hardware/hw.h" + +// The RouteListWidget class extends QListWidget to display a custom message when empty +class RouteListWidget : public QListWidget { +public: + RouteListWidget(QWidget *parent = nullptr) : QListWidget(parent) {} + void setEmptyText(const QString &text) { + empty_text_ = text; + viewport()->update(); + } + void paintEvent(QPaintEvent *event) override { + QListWidget::paintEvent(event); + if (count() == 0) { + QPainter painter(viewport()); + painter.drawText(viewport()->rect(), Qt::AlignCenter, empty_text_); + } + } + QString empty_text_ = tr("No items"); +}; + +RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent) { + setWindowTitle(tr("Remote routes")); + + QFormLayout *layout = new QFormLayout(this); + layout->addRow(tr("Device"), device_list_ = new QComboBox(this)); + layout->addRow(tr("Duration"), period_selector_ = new QComboBox(this)); + layout->addRow(route_list_ = new RouteListWidget(this)); + auto button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + layout->addRow(button_box); + + device_list_->addItem(tr("Loading...")); + // Populate period selector with predefined durations + period_selector_->addItem(tr("Last week"), 7); + period_selector_->addItem(tr("Last 2 weeks"), 14); + period_selector_->addItem(tr("Last month"), 30); + period_selector_->addItem(tr("Last 6 months"), 180); + + // Connect signals and slots + connect(device_list_, QOverload::of(&QComboBox::currentIndexChanged), this, &RoutesDialog::fetchRoutes); + connect(period_selector_, QOverload::of(&QComboBox::currentIndexChanged), this, &RoutesDialog::fetchRoutes); + connect(route_list_, &QListWidget::itemDoubleClicked, this, &QDialog::accept); + QObject::connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + QObject::connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + // Send request to fetch devices + HttpRequest *http = new HttpRequest(this, !Hardware::PC()); + QObject::connect(http, &HttpRequest::requestDone, this, &RoutesDialog::parseDeviceList); + http->sendRequest(CommaApi::BASE_URL + "/v1/me/devices/"); +} + +void RoutesDialog::parseDeviceList(const QString &json, bool success, QNetworkReply::NetworkError err) { + if (success) { + device_list_->clear(); + auto devices = QJsonDocument::fromJson(json.toUtf8()).array(); + for (const QJsonValue &device : devices) { + QString dongle_id = device["dongle_id"].toString(); + device_list_->addItem(dongle_id, dongle_id); + } + } else { + bool unauthorized = (err == QNetworkReply::ContentAccessDenied || err == QNetworkReply::AuthenticationRequiredError); + QMessageBox::warning(this, tr("Error"), unauthorized ? tr("Unauthorized, Authenticate with tools/lib/auth.py") : tr("Network error")); + reject(); + } + sender()->deleteLater(); +} + +void RoutesDialog::fetchRoutes() { + if (device_list_->currentIndex() == -1 || device_list_->currentData().isNull()) + return; + + route_list_->clear(); + route_list_->setEmptyText(tr("Loading...")); + + HttpRequest *http = new HttpRequest(this, !Hardware::PC()); + QObject::connect(http, &HttpRequest::requestDone, this, &RoutesDialog::parseRouteList); + + // Construct URL with selected device and date range + auto dongle_id = device_list_->currentData().toString(); + QDateTime current = QDateTime::currentDateTime(); + QString url = QString("%1/v1/devices/%2/routes_segments?start=%3&end=%4") + .arg(CommaApi::BASE_URL).arg(dongle_id) + .arg(current.addDays(-(period_selector_->currentData().toInt())).toMSecsSinceEpoch()) + .arg(current.toMSecsSinceEpoch()); + http->sendRequest(url); +} + +void RoutesDialog::parseRouteList(const QString &json, bool success, QNetworkReply::NetworkError err) { + if (success) { + for (const QJsonValue &route : QJsonDocument::fromJson(json.toUtf8()).array()) { + uint64_t start_time = route["start_time_utc_millis"].toDouble(); + uint64_t end_time = route["end_time_utc_millis"].toDouble(); + auto datetime = QDateTime::fromMSecsSinceEpoch(start_time); + auto item = new QListWidgetItem(QString("%1 %2min").arg(datetime.toString()).arg((end_time - start_time) / (1000 * 60))); + item->setData(Qt::UserRole, route["fullname"].toString()); + route_list_->addItem(item); + } + // Select first route if available + if (route_list_->count() > 0) route_list_->setCurrentRow(0); + } else { + QMessageBox::warning(this, tr("Error"), tr("Failed to fetch routes. Check your network connection.")); + reject(); + } + route_list_->setEmptyText(tr("No items")); + sender()->deleteLater(); +} + +void RoutesDialog::accept() { + if (auto current_item = route_list_->currentItem()) { + route_ = current_item->data(Qt::UserRole).toString(); + } + QDialog::accept(); +} diff --git a/tools/cabana/streams/routes.h b/tools/cabana/streams/routes.h new file mode 100644 index 0000000000..31e42fb075 --- /dev/null +++ b/tools/cabana/streams/routes.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include "selfdrive/ui/qt/api.h" + +class RouteListWidget; + +class RoutesDialog : public QDialog { + Q_OBJECT +public: + RoutesDialog(QWidget *parent); + QString route() const { return route_; } + +protected: + void accept() override; + void parseDeviceList(const QString &json, bool success, QNetworkReply::NetworkError err); + void parseRouteList(const QString &json, bool success, QNetworkReply::NetworkError err); + void fetchRoutes(); + + QComboBox *device_list_; + QComboBox *period_selector_; + RouteListWidget *route_list_; + QString route_; +}; diff --git a/tools/cabana/streams/socketcanstream.h b/tools/cabana/streams/socketcanstream.h index e0fb826acb..952f1aaa5c 100644 --- a/tools/cabana/streams/socketcanstream.h +++ b/tools/cabana/streams/socketcanstream.h @@ -17,6 +17,7 @@ class SocketCanStream : public LiveStream { Q_OBJECT public: SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {}); + ~SocketCanStream() { stop(); } static AbstractOpenStreamWidget *widget(AbstractStream **stream); static bool available(); diff --git a/tools/cabana/streamselector.cc b/tools/cabana/streamselector.cc index 07755c0fe0..209b577c9a 100644 --- a/tools/cabana/streamselector.cc +++ b/tools/cabana/streamselector.cc @@ -1,6 +1,5 @@ #include "tools/cabana/streamselector.h" -#include #include #include #include @@ -13,12 +12,8 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Open stream")); - QVBoxLayout *main_layout = new QVBoxLayout(this); - - QWidget *w = new QWidget(this); - QVBoxLayout *layout = new QVBoxLayout(w); + QVBoxLayout *layout = new QVBoxLayout(this); tab = new QTabWidget(this); - tab->setTabBarAutoHide(true); layout->addWidget(tab); QHBoxLayout *dbc_layout = new QHBoxLayout(); @@ -35,9 +30,8 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial line->setFrameStyle(QFrame::HLine | QFrame::Sunken); layout->addWidget(line); - main_layout->addWidget(w); - auto btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel); - main_layout->addWidget(btn_box); + btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel); + layout->addWidget(btn_box); addStreamWidget(ReplayStream::widget(stream)); addStreamWidget(PandaStream::widget(stream)); @@ -48,14 +42,11 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); QObject::connect(btn_box, &QDialogButtonBox::accepted, [=]() { - btn_box->button(QDialogButtonBox::Open)->setEnabled(false); - w->setEnabled(false); + setEnabled(false); if (((AbstractOpenStreamWidget *)tab->currentWidget())->open()) { accept(); - } else { - btn_box->button(QDialogButtonBox::Open)->setEnabled(true); - w->setEnabled(true); } + setEnabled(true); }); QObject::connect(file_btn, &QPushButton::clicked, [this]() { QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)"); @@ -68,4 +59,6 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial void StreamSelector::addStreamWidget(AbstractOpenStreamWidget *w) { tab->addTab(w, w->title()); + auto open_btn = btn_box->button(QDialogButtonBox::Open); + QObject::connect(w, &AbstractOpenStreamWidget::enableOpenButton, open_btn, &QPushButton::setEnabled); } diff --git a/tools/cabana/streamselector.h b/tools/cabana/streamselector.h index 19b438d55a..a702fc76ad 100644 --- a/tools/cabana/streamselector.h +++ b/tools/cabana/streamselector.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -17,4 +18,5 @@ public: private: QLineEdit *dbc_file; QTabWidget *tab; + QDialogButtonBox *btn_box; }; diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index bf7550be0b..d9fcae6f21 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -28,6 +28,54 @@ TEST_CASE("DBCFile::generateDBC") { } } +TEST_CASE("DBCFile::generateDBC - comment order") { + // Ensure that message comments are followed by signal comments and in the correct order + auto content = R"(BO_ 160 message_1: 8 EON + SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX + +BO_ 162 message_2: 8 EON + SG_ signal_2 : 0|12@1+ (1,0) [0|4095] "unit" XXX + +CM_ BO_ 160 "message comment"; +CM_ SG_ 160 signal_1 "signal comment"; +CM_ BO_ 162 "message comment"; +CM_ SG_ 162 signal_2 "signal comment"; +)"; + DBCFile dbc("", content); + REQUIRE(dbc.generateDBC() == content); +} + +TEST_CASE("DBCFile::generateDBC -- preserve original header") { + QString content = R"(VERSION "1.0" + +NS_ : + CM_ + +BS_: + +BU_: EON + +BO_ 160 message_1: 8 EON + SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX + +CM_ BO_ 160 "message comment"; +CM_ SG_ 160 signal_1 "signal comment"; +)"; + DBCFile dbc("", content); + REQUIRE(dbc.generateDBC() == content); +} + +TEST_CASE("DBCFile::generateDBC - escaped quotes") { + QString content = R"(BO_ 160 message_1: 8 EON + SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX + +CM_ BO_ 160 "message comment with \"escaped quotes\""; +CM_ SG_ 160 signal_1 "signal comment with \"escaped quotes\""; +)"; + DBCFile dbc("", content); + REQUIRE(dbc.generateDBC() == content); +} + TEST_CASE("parse_dbc") { QString content = R"( BO_ 160 message_1: 8 EON @@ -45,7 +93,11 @@ CM_ SG_ 160 signal_1 "signal comment"; CM_ SG_ 160 signal_2 "multiple line comment 1 2 -";)"; +"; + +CM_ BO_ 162 "message comment with \"escaped quotes\""; +CM_ SG_ 162 signal_1 "signal comment with \"escaped quotes\""; +)"; DBCFile file("", content); auto msg = file.msg(160); @@ -84,6 +136,10 @@ CM_ SG_ 160 signal_2 "multiple line comment REQUIRE(msg->sigs[1]->start_bit == 12); REQUIRE(msg->sigs[1]->size == 1); REQUIRE(msg->sigs[1]->receiver_name == "XXX"); + + // escaped quotes + REQUIRE(msg->comment == "message comment with \"escaped quotes\""); + REQUIRE(msg->sigs[0]->comment == "signal comment with \"escaped quotes\""); } TEST_CASE("parse_opendbc") { diff --git a/tools/cabana/utils/util.cc b/tools/cabana/utils/util.cc index c89e39993b..4556f90850 100644 --- a/tools/cabana/utils/util.cc +++ b/tools/cabana/utils/util.cc @@ -51,7 +51,8 @@ std::pair SegmentTree::get_minmax(int n, int left, int right, in // MessageBytesDelegate -MessageBytesDelegate::MessageBytesDelegate(QObject *parent, bool multiple_lines) : multiple_lines(multiple_lines), QStyledItemDelegate(parent) { +MessageBytesDelegate::MessageBytesDelegate(QObject *parent, bool multiple_lines) + : font_metrics(QApplication::font()), multiple_lines(multiple_lines), QStyledItemDelegate(parent) { fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); byte_size = QFontMetrics(fixed_font).size(Qt::TextSingleLine, "00 ") + QSize(0, 2); for (int i = 0; i < 256; ++i) { @@ -73,38 +74,49 @@ QSize MessageBytesDelegate::sizeHint(const QStyleOptionViewItem &option, const Q } void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - auto data = index.data(BytesRole); - if (!data.isValid()) { - return QStyledItemDelegate::paint(painter, option, index); - } - - QFont old_font = painter->font(); - QPen old_pen = painter->pen(); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.brush(QPalette::Normal, QPalette::Highlight)); } - const QPoint pt{option.rect.left() + h_margin, option.rect.top() + v_margin}; - painter->setFont(fixed_font); - const auto &bytes = *static_cast*>(data.value()); - const auto &colors = *static_cast*>(index.data(ColorsRole).value()); + QRect item_rect = option.rect.adjusted(h_margin, v_margin, -h_margin, -v_margin); + QColor highlighted_color = option.palette.color(QPalette::HighlightedText); + auto text_color = index.data(Qt::ForegroundRole).value(); + bool inactive = text_color.isValid(); + if (!inactive) { + text_color = option.palette.color(QPalette::Text); + } + auto data = index.data(BytesRole); + if (!data.isValid()) { + painter->setFont(option.font); + painter->setPen(option.state & QStyle::State_Selected ? highlighted_color : text_color); + QString text = font_metrics.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, item_rect.width()); + painter->drawText(item_rect, Qt::AlignLeft | Qt::AlignVCenter, text); + return; + } + + // Paint hex column + const auto &bytes = *static_cast *>(data.value()); + const auto &colors = *static_cast *>(index.data(ColorsRole).value()); + + painter->setFont(fixed_font); + const QPen text_pen(option.state & QStyle::State_Selected ? highlighted_color : text_color); + const QPoint pt = item_rect.topLeft(); for (int i = 0; i < bytes.size(); ++i) { int row = !multiple_lines ? 0 : i / 8; int column = !multiple_lines ? i : i % 8; - QRect r = QRect({pt.x() + column * byte_size.width(), pt.y() + row * byte_size.height()}, byte_size); - if (i < colors.size() && colors[i].alpha() > 0) { + QRect r({pt.x() + column * byte_size.width(), pt.y() + row * byte_size.height()}, byte_size); + + if (!inactive && i < colors.size() && colors[i].alpha() > 0) { if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.color(QPalette::Text)); painter->fillRect(r, option.palette.color(QPalette::Window)); } painter->fillRect(r, colors[i]); - } else if (option.state & QStyle::State_Selected) { - painter->setPen(option.palette.color(QPalette::HighlightedText)); + } else { + painter->setPen(text_pen); } utils::drawStaticText(painter, r, hex_text_table[bytes[i]]); } - painter->setFont(old_font); - painter->setPen(old_pen); } // TabBar @@ -257,26 +269,3 @@ QString signalToolTip(const cabana::Signal *sig) { )").arg(sig->name).arg(sig->start_bit).arg(sig->size).arg(sig->msb).arg(sig->lsb) .arg(sig->is_little_endian ? "Y" : "N").arg(sig->is_signed ? "Y" : "N"); } - -// MonotonicBuffer - -void *MonotonicBuffer::allocate(size_t bytes, size_t alignment) { - assert(bytes > 0); - void *p = std::align(alignment, bytes, current_buf, available); - if (p == nullptr) { - available = next_buffer_size = std::max(next_buffer_size, bytes); - current_buf = buffers.emplace_back(std::aligned_alloc(alignment, next_buffer_size)); - next_buffer_size *= growth_factor; - p = current_buf; - } - - current_buf = (char *)current_buf + bytes; - available -= bytes; - return p; -} - -MonotonicBuffer::~MonotonicBuffer() { - for (auto buf : buffers) { - free(buf); - } -} diff --git a/tools/cabana/utils/util.h b/tools/cabana/utils/util.h index 158321f784..0ebd7e41f8 100644 --- a/tools/cabana/utils/util.h +++ b/tools/cabana/utils/util.h @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -10,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +77,7 @@ public: private: std::array hex_text_table; + QFontMetrics font_metrics; QFont fixed_font; QSize byte_size = {}; bool multiple_lines = false; @@ -160,20 +161,5 @@ private: QSocketNotifier *sn; }; -class MonotonicBuffer { -public: - MonotonicBuffer(size_t initial_size) : next_buffer_size(initial_size) {} - ~MonotonicBuffer(); - void *allocate(size_t bytes, size_t alignment = 16ul); - void deallocate(void *p) {} - -private: - void *current_buf = nullptr; - size_t next_buffer_size = 0; - size_t available = 0; - std::deque buffers; - static constexpr float growth_factor = 1.5; -}; - int num_decimals(double num); QString signalToolTip(const cabana::Signal *sig); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index a6fd0b2b64..d2f78babc1 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -38,6 +38,8 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState); QObject::connect(can, &AbstractStream::resume, this, &VideoWidget::updatePlayBtnState); QObject::connect(can, &AbstractStream::msgsReceived, this, &VideoWidget::updateState); + QObject::connect(can, &AbstractStream::seeking, this, &VideoWidget::updateState); + QObject::connect(can, &AbstractStream::timeRangeChanged, this, &VideoWidget::timeRangeChanged); updatePlayBtnState(); setWhatsThis(tr(R"( @@ -150,13 +152,16 @@ QWidget *VideoWidget::createCameraWidget() { setMaximumTime(can->totalSeconds()); QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->currentSecond()); }); - QObject::connect(slider, &Slider::updateMaximumTime, this, &VideoWidget::setMaximumTime, Qt::QueuedConnection); - QObject::connect(static_cast(can), &ReplayStream::qLogLoaded, slider, &Slider::parseQLog); + QObject::connect(can, &AbstractStream::eventsMerged, this, [this]() { slider->update(); }); QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(cam_widget, &CameraWidget::vipcAvailableStreamsUpdated, this, &VideoWidget::vipcAvailableStreamsUpdated); QObject::connect(camera_tab, &QTabBar::currentChanged, [this](int index) { if (index != -1) cam_widget->setStreamType((VisionStreamType)camera_tab->tabData(index).toInt()); }); + + auto replay = static_cast(can)->getReplay(); + QObject::connect(replay, &Replay::qLogLoaded, slider, &Slider::parseQLog, Qt::QueuedConnection); + QObject::connect(replay, &Replay::totalSecondsUpdated, this, &VideoWidget::setMaximumTime, Qt::QueuedConnection); return w; } @@ -197,13 +202,13 @@ void VideoWidget::setMaximumTime(double sec) { slider->setTimeRange(0, sec); } -void VideoWidget::updateTimeRange(double min, double max, bool is_zoomed) { +void VideoWidget::timeRangeChanged(const std::optional> &time_range) { if (can->liveStreaming()) { - skip_to_end_btn->setEnabled(!is_zoomed); + skip_to_end_btn->setEnabled(!time_range.has_value()); return; } - is_zoomed ? slider->setTimeRange(min, max) - : slider->setTimeRange(0, maximum_time); + time_range ? slider->setTimeRange(time_range->first, time_range->second) + : slider->setTimeRange(0, maximum_time); } QString VideoWidget::formatTime(double sec, bool include_milliseconds) { @@ -254,28 +259,25 @@ void Slider::setTimeRange(double min, double max) { setRange(min * factor, max * factor); } -void Slider::parseQLog(int segnum, std::shared_ptr qlog) { - const auto &segments = qobject_cast(can)->route()->segments(); - if (segments.size() > 0 && segnum == segments.rbegin()->first && !qlog->events.empty()) { - emit updateMaximumTime(qlog->events.back()->mono_time / 1e9 - can->routeStartTime()); - } - +void Slider::parseQLog(std::shared_ptr qlog) { std::mutex mutex; - QtConcurrent::blockingMap(qlog->events.cbegin(), qlog->events.cend(), [&mutex, this](const Event *e) { - if (e->which == cereal::Event::Which::THUMBNAIL) { - auto thumb = e->event.getThumbnail(); + QtConcurrent::blockingMap(qlog->events.cbegin(), qlog->events.cend(), [&mutex, this](const Event &e) { + if (e.which == cereal::Event::Which::THUMBNAIL) { + capnp::FlatArrayMessageReader reader(e.data); + auto thumb = reader.getRoot().getThumbnail(); auto data = thumb.getThumbnail(); if (QPixmap pm; pm.loadFromData(data.begin(), data.size(), "jpeg")) { QPixmap scaled = pm.scaledToHeight(MIN_VIDEO_HEIGHT - THUMBNAIL_MARGIN * 2, Qt::SmoothTransformation); std::lock_guard lk(mutex); thumbnails[thumb.getTimestampEof()] = scaled; } - } else if (e->which == cereal::Event::Which::CONTROLS_STATE) { - auto cs = e->event.getControlsState(); + } else if (e.which == cereal::Event::Which::CONTROLS_STATE) { + capnp::FlatArrayMessageReader reader(e.data); + auto cs = reader.getRoot().getControlsState(); if (cs.getAlertType().size() > 0 && cs.getAlertText1().size() > 0 && cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE) { std::lock_guard lk(mutex); - alerts.emplace(e->mono_time, AlertInfo{cs.getAlertStatus(), cs.getAlertText1().cStr(), cs.getAlertText2().cStr()}); + alerts.emplace(e.mono_time, AlertInfo{cs.getAlertStatus(), cs.getAlertText1().cStr(), cs.getAlertText2().cStr()}); } } }); @@ -403,7 +405,7 @@ void InfoLabel::paintEvent(QPaintEvent *event) { font.setPixelSize(11); p.setFont(font); } - QRect text_rect = rect().adjusted(2, 2, -2, -2); + QRect text_rect = rect().adjusted(1, 1, -1, -1); QRect r = p.fontMetrics().boundingRect(text_rect, Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap, text); p.fillRect(text_rect.left(), r.top(), text_rect.width(), r.height(), color); p.drawText(text_rect, Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap, text); diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index b2039e09a4..7bd55cbce4 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -40,13 +41,10 @@ public: void setTimeRange(double min, double max); AlertInfo alertInfo(double sec); QPixmap thumbnail(double sec); - void parseQLog(int segnum, std::shared_ptr qlog); + void parseQLog(std::shared_ptr qlog); const double factor = 1000.0; -signals: - void updateMaximumTime(double); - private: void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; @@ -63,11 +61,11 @@ class VideoWidget : public QFrame { public: VideoWidget(QWidget *parnet = nullptr); - void updateTimeRange(double min, double max, bool is_zommed); void setMaximumTime(double sec); protected: QString formatTime(double sec, bool include_milliseconds = false); + void timeRangeChanged(const std::optional> &time_range); void updateState(); void updatePlayBtnState(); QWidget *createCameraWidget(); diff --git a/tools/camerastream/compressed_vipc.py b/tools/camerastream/compressed_vipc.py index f4b8862a84..6c27e861ff 100755 --- a/tools/camerastream/compressed_vipc.py +++ b/tools/camerastream/compressed_vipc.py @@ -6,9 +6,10 @@ import argparse import numpy as np import multiprocessing import time +import signal import cereal.messaging as messaging -from cereal.visionipc import VisionIpcServer, VisionStreamType +from msgq.visionipc import VisionIpcServer, VisionStreamType V4L2_BUF_FLAG_KEYFRAME = 8 @@ -18,14 +19,14 @@ V4L2_BUF_FLAG_KEYFRAME = 8 ENCODE_SOCKETS = { VisionStreamType.VISION_STREAM_ROAD: "roadEncodeData", - VisionStreamType.VISION_STREAM_WIDE_ROAD: "wideRoadEncodeData", VisionStreamType.VISION_STREAM_DRIVER: "driverEncodeData", + VisionStreamType.VISION_STREAM_WIDE_ROAD: "wideRoadEncodeData", } def decoder(addr, vipc_server, vst, nvidia, W, H, debug=False): sock_name = ENCODE_SOCKETS[vst] if debug: - print("start decoder for %s" % sock_name) + print(f"start decoder for {sock_name}, {W}x{H}") if nvidia: os.environ["NV_LOW_LATENCY"] = "3" # both bLowLatency and CUVID_PKT_ENDOFPICTURE @@ -124,7 +125,7 @@ class CompressedVipc: self.procs = [] for vst in vision_streams: ed = sm[ENCODE_SOCKETS[vst]] - p = multiprocessing.Process(target=decoder, args=(addr, self.vipc_server, vst, nvidia, debug, ed.width, ed.height)) + p = multiprocessing.Process(target=decoder, args=(addr, self.vipc_server, vst, nvidia, ed.width, ed.height, debug)) p.start() self.procs.append(p) @@ -147,10 +148,14 @@ if __name__ == "__main__": vision_streams = [ VisionStreamType.VISION_STREAM_ROAD, - VisionStreamType.VISION_STREAM_WIDE_ROAD, VisionStreamType.VISION_STREAM_DRIVER, + VisionStreamType.VISION_STREAM_WIDE_ROAD, ] vsts = [vision_streams[int(x)] for x in args.cams.split(",")] cvipc = CompressedVipc(args.addr, vsts, args.nvidia, debug=(not args.silent)) + + # register exit handler + signal.signal(signal.SIGINT, lambda sig, frame: cvipc.kill()) + cvipc.join() diff --git a/tools/car_porting/README.md b/tools/car_porting/README.md index 8db17b0976..a32e9f96c9 100644 --- a/tools/car_porting/README.md +++ b/tools/car_porting/README.md @@ -21,8 +21,8 @@ Given a route and platform, automatically inserts FW fingerprints from the platf Example: ```bash -> python tools/car_porting/auto_fingerprint.py '1bbe6bf2d62f58a8|2022-07-14--17-11-43' 'SUBARU OUTBACK 6TH GEN' -Attempting to add fw version for: SUBARU OUTBACK 6TH GEN +> python tools/car_porting/auto_fingerprint.py '1bbe6bf2d62f58a8|2022-07-14--17-11-43' 'OUTBACK' +Attempting to add fw version for: OUTBACK ``` ### [selfdrive/car/tests/test_car_interfaces.py](/selfdrive/car/tests/test_car_interfaces.py) diff --git a/tools/car_porting/auto_fingerprint.py b/tools/car_porting/auto_fingerprint.py index f122c2774e..145f38c63f 100755 --- a/tools/car_porting/auto_fingerprint.py +++ b/tools/car_porting/auto_fingerprint.py @@ -4,67 +4,37 @@ import argparse from collections import defaultdict from openpilot.selfdrive.debug.format_fingerprints import format_brand_fw_versions -from openpilot.selfdrive.car.fw_versions import match_fw_to_car -from openpilot.selfdrive.car.interfaces import get_interface_attr +from openpilot.selfdrive.car.fingerprints import MIGRATION +from openpilot.selfdrive.car.fw_versions import MODEL_TO_BRAND, match_fw_to_car from openpilot.tools.lib.logreader import LogReader, ReadMode - -ALL_FW_VERSIONS = get_interface_attr("FW_VERSIONS") -ALL_CARS = get_interface_attr("CAR") - -PLATFORM_TO_PYTHON_CAR_NAME = {brand: {car.value: car.name for car in ALL_CARS[brand]} for brand in ALL_CARS} -BRAND_TO_PLATFORMS = {brand: [car.value for car in ALL_CARS[brand]] for brand in ALL_CARS} -PLATFORM_TO_BRAND = dict(sum([[(platform, brand) for platform in BRAND_TO_PLATFORMS[brand]] for brand in BRAND_TO_PLATFORMS], [])) - - if __name__ == "__main__": parser = argparse.ArgumentParser(description="Auto fingerprint from a route") parser.add_argument("route", help="The route name to use") - parser.add_argument("platform", help="The platform, or leave empty to auto-determine using fuzzy", default=None, nargs='?') + parser.add_argument("platform", help="The platform, or leave empty to auto-determine using fuzzy", default=None, nargs="?") args = parser.parse_args() lr = LogReader(args.route, ReadMode.QLOG) - - carFw = None - carVin = None - carPlatform = None - - platform: str | None = None - CP = lr.first("carParams") + assert CP is not None, "No carParams in route" - if CP is None: - raise Exception("No fw versions in the provided route...") + carPlatform = MIGRATION.get(CP.carFingerprint, CP.carFingerprint) - carFw = CP.carFw - carVin = CP.carVin - carPlatform = CP.carFingerprint - - if args.platform is None: # attempt to auto-determine platform with other fuzzy fingerprints - _, possible_platforms = match_fw_to_car(carFw, log=False) - - if len(possible_platforms) != 1: - print(f"Unable to auto-determine platform, possible platforms: {possible_platforms}") - - if carPlatform != "mock": - print("Using platform from route") - platform = carPlatform - else: - platform = None - else: - platform = list(possible_platforms)[0] - else: + if args.platform is not None: platform = args.platform + elif carPlatform != "MOCK": + platform = carPlatform + else: + _, matches = match_fw_to_car(CP.carFw, CP.carVin, log=False) + assert len(matches) == 1, f"Unable to auto-determine platform, matches: {matches}" + platform = list(matches)[0] - if platform is None: - raise Exception("unable to determine platform, try manually specifying the fingerprint.") - - print("Attempting to add fw version for: ", platform) + print("Attempting to add fw version for:", platform) fw_versions: dict[str, dict[tuple, list[bytes]]] = defaultdict(lambda: defaultdict(list)) - brand = PLATFORM_TO_BRAND[platform] + brand = MODEL_TO_BRAND[platform] - for fw in carFw: + for fw in CP.carFw: if fw.brand == brand and not fw.logging: addr = fw.address subAddr = None if fw.subAddress == 0 else fw.subAddress diff --git a/tools/car_porting/examples/subaru_fuzzy_fingerprint.ipynb b/tools/car_porting/examples/subaru_fuzzy_fingerprint.ipynb index fbd88a769e..1048011c05 100644 --- a/tools/car_porting/examples/subaru_fuzzy_fingerprint.ipynb +++ b/tools/car_porting/examples/subaru_fuzzy_fingerprint.ipynb @@ -2,15 +2,23 @@ "cells": [ { "cell_type": "code", - "execution_count": 148, + "execution_count": null, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "kj/filesystem-disk-unix.c++:1703: warning: PWD environment variable doesn't match current directory; pwd = /home/batman\n" + ] + } + ], "source": [ "from cereal import car\n", - "from openpilot.selfdrive.car.subaru.values import CAR, PREGLOBAL_CARS\n", + "from openpilot.selfdrive.car.subaru.values import CAR, SubaruFlags\n", "from openpilot.selfdrive.car.subaru.fingerprints import FW_VERSIONS\n", "\n", - "TEST_PLATFORMS = set(c.value for c in CAR) - PREGLOBAL_CARS # preglobal cars seem to have a different format for fingerprints, ignore for now\n", + "TEST_PLATFORMS = set(CAR) - CAR.with_flags(SubaruFlags.PREGLOBAL)\n", "\n", "Ecu = car.CarParams.Ecu\n", "\n", @@ -19,33 +27,18 @@ }, { "cell_type": "code", - "execution_count": 149, - "metadata": {}, - "outputs": [], - "source": [ - "from openpilot.selfdrive.car.subaru.values import CAR_INFO\n", - "\n", - "def get_carinfo(model: CAR):\n", - " c = CAR_INFO[model]\n", - " if isinstance(c, list):\n", - " c = c[0]\n", - " return c" - ] - }, - { - "cell_type": "code", - "execution_count": 150, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "PLATFORM_CODES = {\n", " Ecu.abs: {\n", " 0: {\n", - " b'\\xa5': [CAR.ASCENT, CAR.ASCENT_2023],\n", - " b'\\xa2': [CAR.IMPREZA, CAR.IMPREZA_2020, CAR.CROSSTREK_HYBRID],\n", - " b'\\xa1': [CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023],\n", - " b'\\xa3': [CAR.FORESTER, CAR.FORESTER_HYBRID, CAR.FORESTER_2022],\n", - " b'z': [CAR.IMPREZA],\n", + " b'\\xa5': [CAR.SUBARU_ASCENT, CAR.SUBARU_ASCENT_2023],\n", + " b'\\xa2': [CAR.SUBARU_IMPREZA, CAR.SUBARU_IMPREZA_2020, CAR.SUBARU_CROSSTREK_HYBRID],\n", + " b'\\xa1': [CAR.SUBARU_OUTBACK, CAR.SUBARU_LEGACY, CAR.SUBARU_OUTBACK_2023],\n", + " b'\\xa3': [CAR.SUBARU_FORESTER, CAR.SUBARU_FORESTER_HYBRID, CAR.SUBARU_FORESTER_2022],\n", + " b'z': [CAR.SUBARU_IMPREZA],\n", " }\n", " }\n", "}\n", @@ -66,7 +59,7 @@ }, { "cell_type": "code", - "execution_count": 151, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -90,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 152, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -103,56 +96,60 @@ "SUBARU IMPREZA LIMITED 2019 0c not in dict_keys([b'\\x18', b'\\x19', b' ', b'!', b'\"', b'#'])\n", "SUBARU IMPREZA LIMITED 2019 2e not in dict_keys([b'\\x18', b'\\x19', b' ', b'!', b'\"', b'#'])\n", "SUBARU IMPREZA LIMITED 2019 3f not in dict_keys([b'\\x18', b'\\x19', b' ', b'!', b'\"', b'#'])\n", - "correct_year=True platform=SUBARU ASCENT LIMITED 2019 year=2019 years=[2019, 2020, 2021]\n", - "correct_year=True platform=SUBARU ASCENT LIMITED 2019 year=2021 years=[2019, 2020, 2021]\n", - "correct_year=False platform=SUBARU IMPREZA SPORT 2020 year=2019 years=[2020, 2021, 2022]\n", - "correct_year=False platform=SUBARU IMPREZA SPORT 2020 year=2019 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2021 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2021 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2021 years=[2020, 2021, 2022]\n", - "correct_year=False platform=SUBARU FORESTER 2019 year=2018 years=[2019, 2020, 2021]\n", - "correct_year=False platform=SUBARU FORESTER 2019 year=2018 years=[2019, 2020, 2021]\n", - "correct_year=True platform=SUBARU FORESTER 2019 year=2019 years=[2019, 2020, 2021]\n", - "correct_year=True platform=SUBARU FORESTER 2019 year=2019 years=[2019, 2020, 2021]\n", - "correct_year=True platform=SUBARU FORESTER 2019 year=2020 years=[2019, 2020, 2021]\n", - "correct_year=True platform=SUBARU FORESTER 2019 year=2020 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU OUTBACK 7TH GEN year=2023 years=[2023]\n", + "correct_year=True platform=SUBARU OUTBACK 7TH GEN year=2023 years=[2023]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2022 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2022 years=[2020, 2021, 2022]\n", + "correct_year=False platform=SUBARU CROSSTREK HYBRID 2020 year=2019 years=[2020]\n", + "correct_year=False platform=SUBARU CROSSTREK HYBRID 2020 year=2021 years=[2020]\n", + "correct_year=False platform=SUBARU FORESTER HYBRID 2020 year=2019 years=[2020]\n", + "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2019 years=[2017, 2018, 2019]\n", "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2019 years=[2017, 2018, 2019]\n", "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2018 years=[2017, 2018, 2019]\n", "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2019 years=[2017, 2018, 2019]\n", "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2019 years=[2017, 2018, 2019]\n", "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2019 years=[2017, 2018, 2019]\n", - "correct_year=True platform=SUBARU OUTBACK 7TH GEN year=2023 years=[2023]\n", - "correct_year=True platform=SUBARU OUTBACK 7TH GEN year=2023 years=[2023]\n", - "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=False platform=SUBARU FORESTER 2022 year=2021 years=[2022, 2023]\n", - "correct_year=False platform=SUBARU FORESTER 2022 year=2021 years=[2022, 2023]\n", - "correct_year=True platform=SUBARU FORESTER 2022 year=2022 years=[2022, 2023]\n", - "correct_year=True platform=SUBARU FORESTER 2022 year=2022 years=[2022, 2023]\n", - "correct_year=False platform=SUBARU CROSSTREK HYBRID 2020 year=2019 years=[2020]\n", - "correct_year=False platform=SUBARU CROSSTREK HYBRID 2020 year=2021 years=[2020]\n", - "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2022 years=[2020, 2021, 2022]\n", - "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2022 years=[2020, 2021, 2022]\n", + "correct_year=False platform=SUBARU FORESTER 2022 year=2021 years=[2022, 2023, 2024]\n", + "correct_year=False platform=SUBARU FORESTER 2022 year=2021 years=[2022, 2023, 2024]\n", + "correct_year=True platform=SUBARU FORESTER 2022 year=2022 years=[2022, 2023, 2024]\n", + "correct_year=True platform=SUBARU FORESTER 2022 year=2022 years=[2022, 2023, 2024]\n", + "correct_year=False platform=SUBARU IMPREZA SPORT 2020 year=2019 years=[2020, 2021, 2022]\n", + "correct_year=False platform=SUBARU IMPREZA SPORT 2020 year=2019 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2021 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2021 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2021 years=[2020, 2021, 2022]\n", "correct_year=True platform=SUBARU ASCENT 2023 year=2023 years=[2023]\n", - "correct_year=False platform=SUBARU FORESTER HYBRID 2020 year=2019 years=[2020]\n" + "correct_year=True platform=SUBARU ASCENT LIMITED 2019 year=2019 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU ASCENT LIMITED 2019 year=2021 years=[2019, 2020, 2021]\n", + "correct_year=False platform=SUBARU FORESTER 2019 year=2018 years=[2019, 2020, 2021]\n", + "correct_year=False platform=SUBARU FORESTER 2019 year=2018 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU FORESTER 2019 year=2019 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU FORESTER 2019 year=2019 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU FORESTER 2019 year=2020 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU FORESTER 2019 year=2020 years=[2019, 2020, 2021]\n" ] } ], "source": [ "def test_year_code(platform, year):\n", - " years = [int(y) for y in get_carinfo(platform).year_list]\n", + " car_docs = CAR(platform).config.car_docs\n", + " if isinstance(car_docs, list):\n", + " car_docs = car_docs[0]\n", + " years = [int(y) for y in car_docs.year_list]\n", " correct_year = year in years\n", " print(f\"{correct_year=!s: <6} {platform=: <32} {year=: <5} {years=}\")\n", "\n", @@ -163,63 +160,64 @@ }, { "cell_type": "code", - "execution_count": 153, + "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "in_possible_platforms=True platform=SUBARU ASCENT LIMITED 2019 platforms=['SUBARU ASCENT LIMITED 2019', 'SUBARU ASCENT 2023']\n", - "in_possible_platforms=True platform=SUBARU ASCENT LIMITED 2019 platforms=['SUBARU ASCENT LIMITED 2019', 'SUBARU ASCENT 2023']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", - "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", - "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", - "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", - "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", - "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", - "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", - "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", "in_possible_platforms=True platform=SUBARU OUTBACK 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", "in_possible_platforms=True platform=SUBARU OUTBACK 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", - "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", - "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", - "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", "in_possible_platforms=True platform=SUBARU CROSSTREK HYBRID 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", "in_possible_platforms=True platform=SUBARU CROSSTREK HYBRID 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", - "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", - "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU FORESTER HYBRID 2020 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", "in_possible_platforms=True platform=SUBARU ASCENT 2023 platforms=['SUBARU ASCENT LIMITED 2019', 'SUBARU ASCENT 2023']\n", - "in_possible_platforms=True platform=SUBARU FORESTER HYBRID 2020 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n" + "in_possible_platforms=True platform=SUBARU ASCENT LIMITED 2019 platforms=['SUBARU ASCENT LIMITED 2019', 'SUBARU ASCENT 2023']\n", + "in_possible_platforms=True platform=SUBARU ASCENT LIMITED 2019 platforms=['SUBARU ASCENT LIMITED 2019', 'SUBARU ASCENT 2023']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n" ] } ], diff --git a/tools/car_porting/examples/subaru_long_accel.ipynb b/tools/car_porting/examples/subaru_long_accel.ipynb index 24470b44d2..9d18a114df 100644 --- a/tools/car_porting/examples/subaru_long_accel.ipynb +++ b/tools/car_porting/examples/subaru_long_accel.ipynb @@ -9,7 +9,7 @@ "segments = [\n", " \"d9df6f87e8feff94|2023-03-28--17-41-10/1:12\"\n", "]\n", - "platform = \"SUBARU OUTBACK 6TH GEN\"\n" + "platform = \"SUBARU_OUTBACK\"\n" ] }, { @@ -56,7 +56,7 @@ " es_status_history.append(copy.copy(cp.vl[\"ES_Status\"]))\n", "\n", " acceleration_history.append(last_acc)\n", - " \n", + "\n", " if msg.which() == \"carState\":\n", " last_acc = msg.carState.aEgo" ] diff --git a/tools/car_porting/examples/subaru_steer_temp_fault.ipynb b/tools/car_porting/examples/subaru_steer_temp_fault.ipynb index 46d8dc413e..3d5055cbc2 100644 --- a/tools/car_porting/examples/subaru_steer_temp_fault.ipynb +++ b/tools/car_porting/examples/subaru_steer_temp_fault.ipynb @@ -11,7 +11,7 @@ "segments = [\n", " \"c3d1ccb52f5f9d65|2023-07-22--01-23-20/6:10\",\n", "]\n", - "platform = \"SUBARU OUTBACK 6TH GEN\"" + "platform = \"SUBARU_OUTBACK\"" ] }, { @@ -52,7 +52,7 @@ " for msg in can_msgs:\n", " cp.update_strings([msg.as_builder().to_bytes()])\n", " steering_torque_history.append(copy.copy(cp.vl[\"Steering_Torque\"]))\n", - " \n", + "\n", " steer_warning_last = False\n", " for i, steering_torque_msg in enumerate(steering_torque_history):\n", " steer_warning = steering_torque_msg[\"Steer_Warning\"]\n", diff --git a/tools/car_porting/test_car_model.py b/tools/car_porting/test_car_model.py index 86980b054b..cf0be1a80a 100755 --- a/tools/car_porting/test_car_model.py +++ b/tools/car_porting/test_car_model.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import argparse import sys -import unittest +import unittest # noqa: TID251 from openpilot.selfdrive.car.tests.routes import CarTestRoute from openpilot.selfdrive.car.tests.test_models import TestCarModel @@ -12,7 +12,7 @@ def create_test_models_suite(routes: list[CarTestRoute], ci=False) -> unittest.T test_suite = unittest.TestSuite() for test_route in routes: # create new test case and discover tests - test_case_args = {"car_model": test_route.car_model, "test_route": test_route, "ci": ci} + test_case_args = {"platform": test_route.car_model, "test_route": test_route, "test_route_on_bucket": ci} CarModelTestCase = type("CarModelTestCase", (TestCarModel,), test_case_args) test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(CarModelTestCase)) return test_suite @@ -31,6 +31,7 @@ if __name__ == "__main__": route_or_segment_name = SegmentName(args.route_or_segment_name.strip(), allow_route_name=True) segment_num = route_or_segment_name.segment_num if route_or_segment_name.segment_num != -1 else None + test_route = CarTestRoute(route_or_segment_name.route_name.canonical_name, args.car, segment=segment_num) test_suite = create_test_models_suite([test_route], ci=args.ci) diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh index f7ba316480..af25ec6424 100755 --- a/tools/install_python_dependencies.sh +++ b/tools/install_python_dependencies.sh @@ -41,7 +41,7 @@ fi export MAKEFLAGS="-j$(nproc)" -PYENV_PYTHON_VERSION=$(cat $ROOT/.python-version) +PYENV_PYTHON_VERSION="3.12.4" if ! pyenv prefix ${PYENV_PYTHON_VERSION} &> /dev/null; then # no pyenv update on mac if [ "$(uname)" == "Linux" ]; then @@ -54,8 +54,8 @@ fi eval "$(pyenv init --path)" echo "update pip" -pip install pip==23.3 -pip install poetry==1.6.1 +pip install pip==24.0 +pip install poetry==1.7.0 poetry config virtualenvs.prefer-active-python true --local poetry config virtualenvs.in-project true --local diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 78a01f2b75..1f8ef24eaf 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -12,24 +12,16 @@ if [[ ! $(id -u) -eq 0 ]]; then SUDO="sudo" fi -# Install packages present in all supported versions of Ubuntu +# Install common packages function install_ubuntu_common_requirements() { $SUDO apt-get update $SUDO apt-get install -y --no-install-recommends \ - autoconf \ - build-essential \ ca-certificates \ - casync \ clang \ - cmake \ - make \ cppcheck \ - libtool \ + build-essential \ gcc-arm-none-eabi \ - bzip2 \ liblzma-dev \ - libarchive-dev \ - libbz2-dev \ capnproto \ libcapnp-dev \ curl \ @@ -42,45 +34,56 @@ function install_ubuntu_common_requirements() { libavdevice-dev \ libavutil-dev \ libavfilter-dev \ + libbz2-dev \ libeigen3-dev \ libffi-dev \ libglew-dev \ libgles2-mesa-dev \ libglfw3-dev \ libglib2.0-0 \ + libqt5charts5-dev \ libncurses5-dev \ - libncursesw5-dev \ - libomp-dev \ - libopencv-dev \ - libpng16-16 \ - libportaudio2 \ libssl-dev \ - libsqlite3-dev \ libusb-1.0-0-dev \ libzmq3-dev \ + libsqlite3-dev \ libsystemd-dev \ locales \ opencl-headers \ ocl-icd-libopencl1 \ ocl-icd-opencl-dev \ - clinfo \ portaudio19-dev \ - qml-module-qtquick2 \ qtmultimedia5-dev \ qtlocation5-dev \ qtpositioning5-dev \ qttools5-dev-tools \ - libqt5sql5-sqlite \ libqt5svg5-dev \ - libqt5charts5-dev \ libqt5serialbus5-dev \ libqt5x11extras5-dev \ - libreadline-dev \ - libdw1 \ - valgrind + libqt5opengl5-dev } -# Install Ubuntu 22.04 LTS packages +# Install extra packages +function install_extra_packages() { + echo "Installing extra packages..." + $SUDO apt-get install -y --no-install-recommends \ + casync \ + cmake \ + make \ + clinfo \ + libqt5sql5-sqlite \ + libreadline-dev \ + libdw1 \ + autoconf \ + libtool \ + bzip2 \ + libarchive-dev \ + libncursesw5-dev \ + libportaudio2 \ + locales +} + +# Install Ubuntu 24.04 LTS packages function install_ubuntu_lts_latest_requirements() { install_ubuntu_common_requirements @@ -107,10 +110,7 @@ function install_ubuntu_focal_requirements() { if [ -f "/etc/os-release" ]; then source /etc/os-release case "$VERSION_CODENAME" in - "jammy") - install_ubuntu_lts_latest_requirements - ;; - "kinetic") + "jammy" | "kinetic" | "noble") install_ubuntu_lts_latest_requirements ;; "focal") @@ -123,13 +123,25 @@ if [ -f "/etc/os-release" ]; then if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi - if [ "$UBUNTU_CODENAME" = "jammy" ] || [ "$UBUNTU_CODENAME" = "kinetic" ]; then - install_ubuntu_lts_latest_requirements - else + if [ "$UBUNTU_CODENAME" = "focal" ]; then install_ubuntu_focal_requirements + else + install_ubuntu_lts_latest_requirements fi esac + + # Install extra packages + if [[ -z "$INSTALL_EXTRA_PACKAGES" ]]; then + read -p "Base setup done. Do you want to install extra development packages? [Y/n]: " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Yy]$ ]]; then + INSTALL_EXTRA_PACKAGES="yes" + fi + fi + if [[ "$INSTALL_EXTRA_PACKAGES" == "yes" ]]; then + install_extra_packages + fi else - echo "No /etc/os-release in the system" + echo "No /etc/os-release in the system. Make sure you're running on Ubuntu, or similar." exit 1 fi diff --git a/tools/latencylogger/README.md b/tools/latencylogger/README.md index a961f83619..421a6a0b4a 100644 --- a/tools/latencylogger/README.md +++ b/tools/latencylogger/README.md @@ -76,7 +76,7 @@ Frame ID: 1202 Events updated 111.183541 sendcan published 112.981692 controlsState published 113.731994 - boardd + pandad sending sendcan to panda: 250027001751393037323631 81.928119 sendcan sent to panda: 250027001751393037323631 82.164834 sending sendcan to panda: 250027001751393037323631 93.569986 diff --git a/tools/latencylogger/latency_logger.py b/tools/latencylogger/latency_logger.py index 8c6af56b6e..8691149e94 100755 --- a/tools/latencylogger/latency_logger.py +++ b/tools/latencylogger/latency_logger.py @@ -12,7 +12,7 @@ from openpilot.tools.lib.logreader import LogReader DEMO_ROUTE = "9f583b1d93915c31|2022-05-18--10-49-51--0" -SERVICES = ['camerad', 'modeld', 'plannerd', 'controlsd', 'boardd'] +SERVICES = ['camerad', 'modeld', 'plannerd', 'controlsd', 'pandad'] MONOTIME_KEYS = ['modelMonoTime', 'lateralPlanMonoTime'] MSGQ_TO_SERVICE = { 'roadCameraState': 'camerad', @@ -137,7 +137,7 @@ def insert_cloudlogs(lr, timestamps, start_times, end_times): timestamps[int(jmsg['msg']['timestamp']['frame_id'])][service].append((event, time)) continue - if service == "boardd": + if service == "pandad": timestamps[latest_controls_frameid][service].append((event, time)) end_times[latest_controls_frameid][service] = time else: @@ -153,7 +153,7 @@ def insert_cloudlogs(lr, timestamps, start_times, end_times): failed_inserts += 1 if latest_controls_frameid == 0: - print("Warning: failed to bind boardd logs to a frame ID. Add a timestamp cloudlog in controlsd.") + print("Warning: failed to bind pandad logs to a frame ID. Add a timestamp cloudlog in controlsd.") elif failed_inserts > len(timestamps): print(f"Warning: failed to bind {failed_inserts} cloudlog timestamps to a frame ID") diff --git a/tools/lib/api.py b/tools/lib/api.py index 6ff9242f29..92a75d2a3b 100644 --- a/tools/lib/api.py +++ b/tools/lib/api.py @@ -2,7 +2,7 @@ import os import requests API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') -class CommaApi(): +class CommaApi: def __init__(self, token=None): self.session = requests.Session() self.session.headers['User-agent'] = 'OpenpilotTools' diff --git a/tools/lib/azure_container.py b/tools/lib/azure_container.py index 52b2f37dbf..a5d650e98f 100644 --- a/tools/lib/azure_container.py +++ b/tools/lib/azure_container.py @@ -1,5 +1,5 @@ import os -from datetime import datetime, timedelta +from datetime import datetime, timedelta, UTC from functools import lru_cache from pathlib import Path from typing import IO @@ -20,7 +20,7 @@ def get_azure_credential(): @lru_cache def get_container_sas(account_name: str, container_name: str): from azure.storage.blob import BlobServiceClient, ContainerSasPermissions, generate_container_sas - start_time = datetime.utcnow() + start_time = datetime.now(UTC).replace(tzinfo=None) expiry_time = start_time + timedelta(hours=1) blob_service = BlobServiceClient( account_url=f"https://{account_name}.blob.core.windows.net", @@ -57,18 +57,18 @@ class AzureContainer: ext = "hevc" if log_type.endswith('camera') else "bz2" return self.BASE_URL + f"{route_name.replace('|', '/')}/{segment_num}/{log_type}.{ext}" - def upload_bytes(self, data: bytes | IO, blob_name: str) -> str: + def upload_bytes(self, data: bytes | IO, blob_name: str, overwrite=False) -> str: from azure.storage.blob import BlobClient blob = BlobClient( account_url=self.ACCOUNT_URL, container_name=self.CONTAINER, blob_name=blob_name, credential=get_azure_credential(), - overwrite=False, + overwrite=overwrite, ) - blob.upload_blob(data) + blob.upload_blob(data, overwrite=overwrite) return self.BASE_URL + blob_name - def upload_file(self, path: str | os.PathLike, blob_name: str) -> str: + def upload_file(self, path: str | os.PathLike, blob_name: str, overwrite=False) -> str: with open(path, "rb") as f: - return self.upload_bytes(f, blob_name) + return self.upload_bytes(f, blob_name, overwrite) diff --git a/tools/lib/comma_car_segments.py b/tools/lib/comma_car_segments.py index 9027fec637..78825504e6 100644 --- a/tools/lib/comma_car_segments.py +++ b/tools/lib/comma_car_segments.py @@ -1,13 +1,22 @@ import os import requests + # Forks with additional car support can fork the commaCarSegments repo on huggingface or host the LFS files themselves COMMA_CAR_SEGMENTS_REPO = os.environ.get("COMMA_CAR_SEGMENTS_REPO", "https://huggingface.co/datasets/commaai/commaCarSegments") COMMA_CAR_SEGMENTS_BRANCH = os.environ.get("COMMA_CAR_SEGMENTS_BRANCH", "main") COMMA_CAR_SEGMENTS_LFS_INSTANCE = os.environ.get("COMMA_CAR_SEGMENTS_LFS_INSTANCE", COMMA_CAR_SEGMENTS_REPO) def get_comma_car_segments_database(): - return requests.get(get_repo_raw_url("database.json")).json() + from openpilot.selfdrive.car.fingerprints import MIGRATION + + database = requests.get(get_repo_raw_url("database.json")).json() + + ret = {} + for platform in database: + ret[MIGRATION.get(platform, platform)] = database[platform] + + return ret # Helpers related to interfacing with the commaCarSegments repository, which contains a collection of public segments for users to perform validation on. diff --git a/tools/lib/filereader.py b/tools/lib/filereader.py index e9b8b4b2ce..3bfdc75feb 100644 --- a/tools/lib/filereader.py +++ b/tools/lib/filereader.py @@ -10,10 +10,11 @@ DATA_ENDPOINT = os.getenv("DATA_ENDPOINT", "http://data-raw.comma.internal/") def internal_source_available(): try: hostname = urlparse(DATA_ENDPOINT).hostname - if hostname: - socket.gethostbyname(hostname) - return True - except socket.gaierror: + port = urlparse(DATA_ENDPOINT).port or 80 + with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s: + s.connect((hostname, port)) + return True + except (socket.gaierror, ConnectionRefusedError): pass return False diff --git a/tools/lib/helpers.py b/tools/lib/helpers.py index 653eb3d7e0..e9b4b85e70 100644 --- a/tools/lib/helpers.py +++ b/tools/lib/helpers.py @@ -1,7 +1,4 @@ import bz2 -import datetime - -TIME_FMT = "%Y-%m-%d--%H-%M-%S" # regex patterns @@ -23,13 +20,6 @@ class RE: OP_SEGMENT_DIR = fr'^(?P{SEGMENT_NAME})$' -def timestamp_to_datetime(t: str) -> datetime.datetime: - """ - Convert an openpilot route timestamp to a python datetime - """ - return datetime.datetime.strptime(t, TIME_FMT) - - def save_log(dest, log_msgs, compress=True): dat = b"".join(msg.as_builder().to_bytes() for msg in log_msgs) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 6247bbc9db..98c10941bb 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -10,6 +10,7 @@ import sys import tqdm import urllib.parse import warnings +import zstd from collections.abc import Callable, Iterable, Iterator from urllib.parse import parse_qs, urlparse @@ -34,8 +35,8 @@ class _LogFileReader: ext = None if not dat: _, ext = os.path.splitext(urllib.parse.urlparse(fn).path) - if ext not in ('', '.bz2'): - # old rlogs weren't bz2 compressed + if ext not in ('', '.bz2', '.zst'): + # old rlogs weren't compressed raise Exception(f"unknown extension {ext}") with FileReader(fn) as f: @@ -43,18 +44,21 @@ class _LogFileReader: if ext == ".bz2" or dat.startswith(b'BZh9'): dat = bz2.decompress(dat) + elif ext == ".zst" or dat.startswith(b'\x28\xB5\x2F\xFD'): + # https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#zstandard-frames + dat = zstd.decompress(dat) ents = capnp_log.Event.read_multiple_bytes(dat) - _ents = [] + self._ents = [] try: for e in ents: - _ents.append(e) + self._ents.append(e) except capnp.KjException: warnings.warn("Corrupted events detected", RuntimeWarning, stacklevel=1) - self._ents = list(sorted(_ents, key=lambda x: x.logMonoTime) if sort_by_time else _ents) - self._ts = [x.logMonoTime for x in self._ents] + if sort_by_time: + self._ents.sort(key=lambda x: x.logMonoTime) def __iter__(self) -> Iterator[capnp._DynamicStructReader]: for ent in self._ents: @@ -89,7 +93,7 @@ def default_valid_file(fn: LogPath) -> bool: def auto_strategy(rlog_paths: LogPaths, qlog_paths: LogPaths, interactive: bool, valid_file: ValidFileCallable) -> LogPaths: # auto select logs based on availability - if any(rlog is None or not valid_file(rlog) for rlog in rlog_paths): + if any(rlog is None or not valid_file(rlog) for rlog in rlog_paths) and all(qlog is not None and valid_file(qlog) for qlog in qlog_paths): if interactive: if input("Some rlogs were not found, would you like to fallback to qlogs for those segments? (y/n) ").lower() != "y": return rlog_paths @@ -126,12 +130,12 @@ def comma_api_source(sr: SegmentRange, mode: ReadMode) -> LogPaths: return apply_strategy(mode, rlog_paths, qlog_paths, valid_file=valid_file) -def internal_source(sr: SegmentRange, mode: ReadMode) -> LogPaths: +def internal_source(sr: SegmentRange, mode: ReadMode, file_ext: str = "bz2") -> LogPaths: if not internal_source_available(): raise InternalUnavailableException def get_internal_url(sr: SegmentRange, seg, file): - return f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{file}.bz2" + return f"cd:/{sr.dongle_id}/{sr.log_id}/{seg}/{file}.{file_ext}" rlog_paths = [get_internal_url(sr, seg, "rlog") for seg in sr.seg_idxs] qlog_paths = [get_internal_url(sr, seg, "qlog") for seg in sr.seg_idxs] @@ -139,6 +143,10 @@ def internal_source(sr: SegmentRange, mode: ReadMode) -> LogPaths: return apply_strategy(mode, rlog_paths, qlog_paths) +def internal_source_zst(sr: SegmentRange, mode: ReadMode, file_ext: str = "zst") -> LogPaths: + return internal_source(sr, mode, file_ext) + + def openpilotci_source(sr: SegmentRange, mode: ReadMode) -> LogPaths: rlog_paths = [get_url(sr.route_name, seg, "rlog") for seg in sr.seg_idxs] qlog_paths = [get_url(sr.route_name, seg, "qlog") for seg in sr.seg_idxs] @@ -162,7 +170,8 @@ def get_invalid_files(files): def check_source(source: Source, *args) -> LogPaths: files = source(*args) - assert next(get_invalid_files(files), False) is False + assert len(files) > 0, "No files on source" + assert next(get_invalid_files(files), False) is False, "Some files are invalid" return files @@ -170,16 +179,26 @@ def auto_source(sr: SegmentRange, mode=ReadMode.RLOG) -> LogPaths: if mode == ReadMode.SANITIZED: return comma_car_segments_source(sr, mode) - SOURCES: list[Source] = [internal_source, openpilotci_source, comma_api_source, comma_car_segments_source,] - exceptions = [] + SOURCES: list[Source] = [internal_source, internal_source_zst, openpilotci_source, comma_api_source, comma_car_segments_source,] + exceptions = {} + + # for automatic fallback modes, auto_source needs to first check if rlogs exist for any source + if mode in [ReadMode.AUTO, ReadMode.AUTO_INTERACTIVE]: + for source in SOURCES: + try: + return check_source(source, sr, ReadMode.RLOG) + except Exception: + pass + # Automatically determine viable source for source in SOURCES: try: return check_source(source, sr, mode) except Exception as e: - exceptions.append(e) + exceptions[source.__name__] = e - raise Exception(f"auto_source could not find any valid source, exceptions for sources: {exceptions}") + raise Exception("auto_source could not find any valid source, exceptions for sources:\n - " + + "\n - ".join([f"{k}: {repr(v)}" for k, v in exceptions.items()])) def parse_useradmin(identifier: str): @@ -248,7 +267,7 @@ are uploaded or auto fallback to qlogs with '/a' selector at the end of the rout def _get_lr(self, i): if i not in self.__lrs: - self.__lrs[i] = _LogFileReader(self.logreader_identifiers[i]) + self.__lrs[i] = _LogFileReader(self.logreader_identifiers[i], sort_by_time=self.sort_by_time, only_union_types=self.only_union_types) return self.__lrs[i] def __iter__(self): diff --git a/tools/lib/route.py b/tools/lib/route.py index bd0ccc1fc3..6ff5d19208 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -4,13 +4,12 @@ from functools import cache from urllib.parse import urlparse from collections import defaultdict from itertools import chain -from typing import cast from openpilot.tools.lib.auth_config import get_token from openpilot.tools.lib.api import CommaApi from openpilot.tools.lib.helpers import RE -QLOG_FILENAMES = ['qlog', 'qlog.bz2'] +QLOG_FILENAMES = ['qlog', 'qlog.bz2', 'qlog.zst'] QCAMERA_FILENAMES = ['qcamera.ts'] LOG_FILENAMES = ['rlog', 'rlog.bz2', 'raw_log.bz2'] CAMERA_FILENAMES = ['fcamera.hevc', 'video.hevc'] @@ -240,7 +239,9 @@ class SegmentName: def get_max_seg_number_cached(sr: 'SegmentRange') -> int: try: api = CommaApi(get_token()) - return cast(int, api.get("/v1/route/" + sr.route_name.replace("/", "|"))["segment_numbers"][-1]) + max_seg_number = api.get("/v1/route/" + sr.route_name.replace("/", "|"))["maxqlog"] + assert isinstance(max_seg_number, int) + return max_seg_number except Exception as e: raise Exception("unable to get max_segment_number. ensure you have access to this route or the route is public.") from e @@ -260,8 +261,8 @@ class SegmentRange: return self.m.group("dongle_id") @property - def timestamp(self) -> str: - return self.m.group("timestamp") + def log_id(self) -> str: + return self.m.group("log_id") @property def slice(self) -> str: @@ -291,7 +292,7 @@ class SegmentRange: return list(range(end + 1))[s] def __str__(self) -> str: - return f"{self.dongle_id}/{self.timestamp}" + (f"/{self.slice}" if self.slice else "") + (f"/{self.selector}" if self.selector else "") + return f"{self.dongle_id}/{self.log_id}" + (f"/{self.slice}" if self.slice else "") + (f"/{self.selector}" if self.selector else "") def __repr__(self) -> str: return self.__str__() diff --git a/tools/lib/tests/test_caching.py b/tools/lib/tests/test_caching.py old mode 100755 new mode 100644 index 5d3dfeba42..2bb63b4dce --- a/tools/lib/tests/test_caching.py +++ b/tools/lib/tests/test_caching.py @@ -1,13 +1,10 @@ -#!/usr/bin/env python3 -from functools import partial import http.server import os import shutil import socket -import unittest +import pytest -from parameterized import parameterized -from openpilot.selfdrive.test.helpers import with_http_server +from openpilot.selfdrive.test.helpers import http_server_context from openpilot.system.hardware.hw import Paths from openpilot.tools.lib.url_file import URLFile @@ -31,22 +28,23 @@ class CachingTestRequestHandler(http.server.BaseHTTPRequestHandler): self.end_headers() -with_caching_server = partial(with_http_server, handler=CachingTestRequestHandler) +@pytest.fixture +def host(): + with http_server_context(handler=CachingTestRequestHandler) as (host, port): + yield f"http://{host}:{port}" +class TestFileDownload: -class TestFileDownload(unittest.TestCase): - - @with_caching_server def test_pipeline_defaults(self, host): # TODO: parameterize the defaults so we don't rely on hard-coded values in xx - self.assertEqual(URLFile.pool_manager().pools._maxsize, 10) # PoolManager num_pools param + assert URLFile.pool_manager().pools._maxsize == 10# PoolManager num_pools param pool_manager_defaults = { "maxsize": 100, "socket_options": [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),], } for k, v in pool_manager_defaults.items(): - self.assertEqual(URLFile.pool_manager().connection_pool_kw.get(k), v) + assert URLFile.pool_manager().connection_pool_kw.get(k) == v retry_defaults = { "total": 5, @@ -54,7 +52,7 @@ class TestFileDownload(unittest.TestCase): "status_forcelist": [409, 429, 503, 504], } for k, v in retry_defaults.items(): - self.assertEqual(getattr(URLFile.pool_manager().connection_pool_kw["retries"], k), v) + assert getattr(URLFile.pool_manager().connection_pool_kw["retries"], k) == v # ensure caching off by default and cache dir doesn't get created os.environ.pop("FILEREADER_CACHE", None) @@ -62,7 +60,7 @@ class TestFileDownload(unittest.TestCase): shutil.rmtree(Paths.download_cache_root()) URLFile(f"{host}/test.txt").get_length() URLFile(f"{host}/test.txt").read() - self.assertEqual(os.path.exists(Paths.download_cache_root()), False) + assert not os.path.exists(Paths.download_cache_root()) def compare_loads(self, url, start=0, length=None): """Compares range between cached and non cached version""" @@ -72,21 +70,21 @@ class TestFileDownload(unittest.TestCase): file_cached.seek(start) file_downloaded.seek(start) - self.assertEqual(file_cached.get_length(), file_downloaded.get_length()) - self.assertLessEqual(length + start if length is not None else 0, file_downloaded.get_length()) + assert file_cached.get_length() == file_downloaded.get_length() + assert length + start if length is not None else 0 <= file_downloaded.get_length() response_cached = file_cached.read(ll=length) response_downloaded = file_downloaded.read(ll=length) - self.assertEqual(response_cached, response_downloaded) + assert response_cached == response_downloaded # Now test with cache in place file_cached = URLFile(url, cache=True) file_cached.seek(start) response_cached = file_cached.read(ll=length) - self.assertEqual(file_cached.get_length(), file_downloaded.get_length()) - self.assertEqual(response_cached, response_downloaded) + assert file_cached.get_length() == file_downloaded.get_length() + assert response_cached == response_downloaded def test_small_file(self): # Make sure we don't force cache @@ -117,22 +115,16 @@ class TestFileDownload(unittest.TestCase): self.compare_loads(large_file_url, length - 100, 100) self.compare_loads(large_file_url) - @parameterized.expand([(True, ), (False, )]) - @with_caching_server - def test_recover_from_missing_file(self, cache_enabled, host): + @pytest.mark.parametrize("cache_enabled", [True, False]) + def test_recover_from_missing_file(self, host, cache_enabled): os.environ["FILEREADER_CACHE"] = "1" if cache_enabled else "0" file_url = f"{host}/test.png" CachingTestRequestHandler.FILE_EXISTS = False length = URLFile(file_url).get_length() - self.assertEqual(length, -1) + assert length == -1 CachingTestRequestHandler.FILE_EXISTS = True length = URLFile(file_url).get_length() - self.assertEqual(length, 4) - - - -if __name__ == "__main__": - unittest.main() + assert length == 4 diff --git a/tools/lib/tests/test_comma_car_segments.py b/tools/lib/tests/test_comma_car_segments.py index 50d4200b4f..b9a4def75f 100644 --- a/tools/lib/tests/test_comma_car_segments.py +++ b/tools/lib/tests/test_comma_car_segments.py @@ -1,12 +1,13 @@ -import unittest - +import pytest import requests +from openpilot.selfdrive.car.fingerprints import MIGRATION from openpilot.tools.lib.comma_car_segments import get_comma_car_segments_database, get_url from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.route import SegmentRange -class TestCommaCarSegments(unittest.TestCase): +@pytest.mark.skip(reason="huggingface is flaky, run this test manually to check for issues") +class TestCommaCarSegments: def test_database(self): database = get_comma_car_segments_database() @@ -17,7 +18,7 @@ class TestCommaCarSegments(unittest.TestCase): def test_download_segment(self): database = get_comma_car_segments_database() - fp = "SUBARU FORESTER 2019" + fp = "SUBARU_FORESTER" segment = database[fp][0] @@ -26,14 +27,8 @@ class TestCommaCarSegments(unittest.TestCase): url = get_url(sr.route_name, sr.slice) resp = requests.get(url) - self.assertEqual(resp.status_code, 200) + assert resp.status_code == 200 lr = LogReader(url) - CP = lr.first("carParams") - - self.assertEqual(CP.carFingerprint, fp) - - -if __name__ == "__main__": - unittest.main() + assert MIGRATION.get(CP.carFingerprint, CP.carFingerprint) == fp diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py old mode 100755 new mode 100644 index 2141915b87..6bc7ba8773 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -1,16 +1,15 @@ -#!/usr/bin/env python3 +import capnp import contextlib import io import shutil import tempfile import os -import unittest import pytest import requests from parameterized import parameterized -from unittest import mock +from cereal import log as capnp_log from openpilot.tools.lib.logreader import LogIterable, LogReader, comma_api_source, parse_indirect, ReadMode, InternalUnavailableException from openpilot.tools.lib.route import SegmentRange from openpilot.tools.lib.url_file import URLFileException @@ -26,24 +25,22 @@ def noop(segment: LogIterable): @contextlib.contextmanager -def setup_source_scenario(is_internal=False): - with ( - mock.patch("openpilot.tools.lib.logreader.internal_source") as internal_source_mock, - mock.patch("openpilot.tools.lib.logreader.openpilotci_source") as openpilotci_source_mock, - mock.patch("openpilot.tools.lib.logreader.comma_api_source") as comma_api_source_mock, - ): - if is_internal: - internal_source_mock.return_value = [QLOG_FILE] - else: - internal_source_mock.side_effect = InternalUnavailableException +def setup_source_scenario(mocker, is_internal=False): + internal_source_mock = mocker.patch("openpilot.tools.lib.logreader.internal_source") + openpilotci_source_mock = mocker.patch("openpilot.tools.lib.logreader.openpilotci_source") + comma_api_source_mock = mocker.patch("openpilot.tools.lib.logreader.comma_api_source") + if is_internal: + internal_source_mock.return_value = [QLOG_FILE] + else: + internal_source_mock.side_effect = InternalUnavailableException - openpilotci_source_mock.return_value = [None] - comma_api_source_mock.return_value = [QLOG_FILE] + openpilotci_source_mock.return_value = [None] + comma_api_source_mock.return_value = [QLOG_FILE] - yield + yield -class TestLogReader(unittest.TestCase): +class TestLogReader: @parameterized.expand([ (f"{TEST_ROUTE}", ALL_SEGS), (f"{TEST_ROUTE.replace('/', '|')}", ALL_SEGS), @@ -72,7 +69,7 @@ class TestLogReader(unittest.TestCase): def test_indirect_parsing(self, identifier, expected): parsed, _, _ = parse_indirect(identifier) sr = SegmentRange(parsed) - self.assertListEqual(list(sr.seg_idxs), expected, identifier) + assert list(sr.seg_idxs) == expected, identifier @parameterized.expand([ (f"{TEST_ROUTE}", f"{TEST_ROUTE}"), @@ -84,11 +81,11 @@ class TestLogReader(unittest.TestCase): ]) def test_canonical_name(self, identifier, expected): sr = SegmentRange(identifier) - self.assertEqual(str(sr), expected) + assert str(sr) == expected - @parameterized.expand([(True,), (False,)]) - @mock.patch("openpilot.tools.lib.logreader.file_exists") - def test_direct_parsing(self, cache_enabled, file_exists_mock): + @pytest.mark.parametrize("cache_enabled", [True, False]) + def test_direct_parsing(self, mocker, cache_enabled): + file_exists_mock = mocker.patch("openpilot.tools.lib.logreader.file_exists") os.environ["FILEREADER_CACHE"] = "1" if cache_enabled else "0" qlog = tempfile.NamedTemporaryFile(mode='wb', delete=False) @@ -98,13 +95,13 @@ class TestLogReader(unittest.TestCase): for f in [QLOG_FILE, qlog.name]: l = len(list(LogReader(f))) - self.assertGreater(l, 100) + assert l > 100 - with self.assertRaises(URLFileException) if not cache_enabled else self.assertRaises(AssertionError): + with pytest.raises(URLFileException) if not cache_enabled else pytest.raises(AssertionError): l = len(list(LogReader(QLOG_FILE.replace("/3/", "/200/")))) # file_exists should not be called for direct files - self.assertEqual(file_exists_mock.call_count, 0) + assert file_exists_mock.call_count == 0 @parameterized.expand([ (f"{TEST_ROUTE}///",), @@ -119,103 +116,136 @@ class TestLogReader(unittest.TestCase): (f"{TEST_ROUTE}--3a",), ]) def test_bad_ranges(self, segment_range): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): _ = SegmentRange(segment_range).seg_idxs - @parameterized.expand([ + @pytest.mark.parametrize("segment_range, api_call", [ (f"{TEST_ROUTE}/0", False), (f"{TEST_ROUTE}/:2", False), (f"{TEST_ROUTE}/0:", True), (f"{TEST_ROUTE}/-1", True), (f"{TEST_ROUTE}", True), ]) - def test_slicing_api_call(self, segment_range, api_call): - with mock.patch("openpilot.tools.lib.route.get_max_seg_number_cached") as max_seg_mock: - max_seg_mock.return_value = NUM_SEGS - _ = SegmentRange(segment_range).seg_idxs - self.assertEqual(api_call, max_seg_mock.called) + def test_slicing_api_call(self, mocker, segment_range, api_call): + max_seg_mock = mocker.patch("openpilot.tools.lib.route.get_max_seg_number_cached") + max_seg_mock.return_value = NUM_SEGS + _ = SegmentRange(segment_range).seg_idxs + assert api_call == max_seg_mock.called @pytest.mark.slow def test_modes(self): qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0", ReadMode.QLOG))) rlog_len = len(list(LogReader(f"{TEST_ROUTE}/0", ReadMode.RLOG))) - self.assertLess(qlog_len * 6, rlog_len) + assert qlog_len * 6 < rlog_len @pytest.mark.slow def test_modes_from_name(self): qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/q"))) rlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/r"))) - self.assertLess(qlog_len * 6, rlog_len) + assert qlog_len * 6 < rlog_len @pytest.mark.slow def test_list(self): qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/q"))) qlog_len_2 = len(list(LogReader([f"{TEST_ROUTE}/0/q", f"{TEST_ROUTE}/0/q"]))) - self.assertEqual(qlog_len * 2, qlog_len_2) + assert qlog_len * 2 == qlog_len_2 @pytest.mark.slow - @mock.patch("openpilot.tools.lib.logreader._LogFileReader") - def test_multiple_iterations(self, init_mock): + def test_multiple_iterations(self, mocker): + init_mock = mocker.patch("openpilot.tools.lib.logreader._LogFileReader") lr = LogReader(f"{TEST_ROUTE}/0/q") qlog_len1 = len(list(lr)) qlog_len2 = len(list(lr)) # ensure we don't create multiple instances of _LogFileReader, which means downloading the files twice - self.assertEqual(init_mock.call_count, 1) + assert init_mock.call_count == 1 - self.assertEqual(qlog_len1, qlog_len2) + assert qlog_len1 == qlog_len2 @pytest.mark.slow def test_helpers(self): lr = LogReader(f"{TEST_ROUTE}/0/q") - self.assertEqual(lr.first("carParams").carFingerprint, "SUBARU OUTBACK 6TH GEN") - self.assertTrue(0 < len(list(lr.filter("carParams"))) < len(list(lr))) + assert lr.first("carParams").carFingerprint == "SUBARU OUTBACK 6TH GEN" + assert 0 < len(list(lr.filter("carParams"))) < len(list(lr)) @parameterized.expand([(True,), (False,)]) @pytest.mark.slow def test_run_across_segments(self, cache_enabled): os.environ["FILEREADER_CACHE"] = "1" if cache_enabled else "0" lr = LogReader(f"{TEST_ROUTE}/0:4") - self.assertEqual(len(lr.run_across_segments(4, noop)), len(list(lr))) + assert len(lr.run_across_segments(4, noop)) == len(list(lr)) @pytest.mark.slow - def test_auto_mode(self): + def test_auto_mode(self, subtests, mocker): lr = LogReader(f"{TEST_ROUTE}/0/q") qlog_len = len(list(lr)) - with mock.patch("openpilot.tools.lib.route.Route.log_paths") as log_paths_mock: - log_paths_mock.return_value = [None] * NUM_SEGS - # Should fall back to qlogs since rlogs are not available + log_paths_mock = mocker.patch("openpilot.tools.lib.route.Route.log_paths") + log_paths_mock.return_value = [None] * NUM_SEGS + # Should fall back to qlogs since rlogs are not available - with self.subTest("interactive_yes"): - with mock.patch("sys.stdin", new=io.StringIO("y\n")): - lr = LogReader(f"{TEST_ROUTE}/0", default_mode=ReadMode.AUTO_INTERACTIVE, default_source=comma_api_source) - log_len = len(list(lr)) - self.assertEqual(qlog_len, log_len) + with subtests.test("interactive_yes"): + mocker.patch("sys.stdin", new=io.StringIO("y\n")) + lr = LogReader(f"{TEST_ROUTE}/0", default_mode=ReadMode.AUTO_INTERACTIVE, default_source=comma_api_source) + log_len = len(list(lr)) + assert qlog_len == log_len - with self.subTest("interactive_no"): - with mock.patch("sys.stdin", new=io.StringIO("n\n")): - with self.assertRaises(AssertionError): - lr = LogReader(f"{TEST_ROUTE}/0", default_mode=ReadMode.AUTO_INTERACTIVE, default_source=comma_api_source) + with subtests.test("interactive_no"): + mocker.patch("sys.stdin", new=io.StringIO("n\n")) + with pytest.raises(AssertionError): + lr = LogReader(f"{TEST_ROUTE}/0", default_mode=ReadMode.AUTO_INTERACTIVE, default_source=comma_api_source) - with self.subTest("non_interactive"): - lr = LogReader(f"{TEST_ROUTE}/0", default_mode=ReadMode.AUTO, default_source=comma_api_source) - log_len = len(list(lr)) - self.assertEqual(qlog_len, log_len) + with subtests.test("non_interactive"): + lr = LogReader(f"{TEST_ROUTE}/0", default_mode=ReadMode.AUTO, default_source=comma_api_source) + log_len = len(list(lr)) + assert qlog_len == log_len - @parameterized.expand([(True,), (False,)]) + @pytest.mark.parametrize("is_internal", [True, False]) @pytest.mark.slow - def test_auto_source_scenarios(self, is_internal): + def test_auto_source_scenarios(self, mocker, is_internal): lr = LogReader(QLOG_FILE) qlog_len = len(list(lr)) - with setup_source_scenario(is_internal=is_internal): + with setup_source_scenario(mocker, is_internal=is_internal): lr = LogReader(f"{TEST_ROUTE}/0/q") log_len = len(list(lr)) - self.assertEqual(qlog_len, log_len) + assert qlog_len == log_len + @pytest.mark.slow + def test_sort_by_time(self): + msgs = list(LogReader(f"{TEST_ROUTE}/0/q")) + assert msgs != sorted(msgs, key=lambda m: m.logMonoTime) -if __name__ == "__main__": - unittest.main() + msgs = list(LogReader(f"{TEST_ROUTE}/0/q", sort_by_time=True)) + assert msgs == sorted(msgs, key=lambda m: m.logMonoTime) + + def test_only_union_types(self): + with tempfile.NamedTemporaryFile() as qlog: + # write valid Event messages + num_msgs = 100 + with open(qlog.name, "wb") as f: + f.write(b"".join(capnp_log.Event.new_message().to_bytes() for _ in range(num_msgs))) + + msgs = list(LogReader(qlog.name)) + assert len(msgs) == num_msgs + [m.which() for m in msgs] + + # append non-union Event message + event_msg = capnp_log.Event.new_message() + non_union_bytes = bytearray(event_msg.to_bytes()) + non_union_bytes[event_msg.total_size.word_count * 8] = 0xff # set discriminant value out of range using Event word offset + with open(qlog.name, "ab") as f: + f.write(non_union_bytes) + + # ensure new message is added, but is not a union type + msgs = list(LogReader(qlog.name)) + assert len(msgs) == num_msgs + 1 + with pytest.raises(capnp.KjException): + [m.which() for m in msgs] + + # should not be added when only_union_types=True + msgs = list(LogReader(qlog.name, only_union_types=True)) + assert len(msgs) == num_msgs + [m.which() for m in msgs] diff --git a/tools/lib/tests/test_readers.py b/tools/lib/tests/test_readers.py old mode 100755 new mode 100644 index 1f24ae5c8e..624531a1a8 --- a/tools/lib/tests/test_readers.py +++ b/tools/lib/tests/test_readers.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -import unittest +import pytest import requests import tempfile @@ -9,16 +8,16 @@ from openpilot.tools.lib.framereader import FrameReader from openpilot.tools.lib.logreader import LogReader -class TestReaders(unittest.TestCase): - @unittest.skip("skip for bandwidth reasons") +class TestReaders: + @pytest.mark.skip("skip for bandwidth reasons") def test_logreader(self): def _check_data(lr): hist = defaultdict(int) for l in lr: hist[l.which()] += 1 - self.assertEqual(hist['carControl'], 6000) - self.assertEqual(hist['logMessage'], 6857) + assert hist['carControl'] == 6000 + assert hist['logMessage'] == 6857 with tempfile.NamedTemporaryFile(suffix=".bz2") as fp: r = requests.get("https://github.com/commaai/comma2k19/blob/master/Example_1/b0c9d2329ad1606b%7C2018-08-02--08-34-47/40/raw_log.bz2?raw=true", timeout=10) @@ -31,15 +30,15 @@ class TestReaders(unittest.TestCase): lr_url = LogReader("https://github.com/commaai/comma2k19/blob/master/Example_1/b0c9d2329ad1606b%7C2018-08-02--08-34-47/40/raw_log.bz2?raw=true") _check_data(lr_url) - @unittest.skip("skip for bandwidth reasons") + @pytest.mark.skip("skip for bandwidth reasons") def test_framereader(self): def _check_data(f): - self.assertEqual(f.frame_count, 1200) - self.assertEqual(f.w, 1164) - self.assertEqual(f.h, 874) + assert f.frame_count == 1200 + assert f.w == 1164 + assert f.h == 874 frame_first_30 = f.get(0, 30) - self.assertEqual(len(frame_first_30), 30) + assert len(frame_first_30) == 30 print(frame_first_30[15]) @@ -62,6 +61,3 @@ class TestReaders(unittest.TestCase): fr_url = FrameReader("https://github.com/commaai/comma2k19/blob/master/Example_1/b0c9d2329ad1606b%7C2018-08-02--08-34-47/40/video.hevc?raw=true") _check_data(fr_url) - -if __name__ == "__main__": - unittest.main() diff --git a/tools/lib/tests/test_route_library.py b/tools/lib/tests/test_route_library.py old mode 100755 new mode 100644 index 7977f17be2..491bb81327 --- a/tools/lib/tests/test_route_library.py +++ b/tools/lib/tests/test_route_library.py @@ -1,10 +1,8 @@ -#!/usr/bin/env python -import unittest from collections import namedtuple from openpilot.tools.lib.route import SegmentName -class TestRouteLibrary(unittest.TestCase): +class TestRouteLibrary: def test_segment_name_formats(self): Case = namedtuple('Case', ['input', 'expected_route', 'expected_segment_num', 'expected_data_dir']) @@ -21,12 +19,9 @@ class TestRouteLibrary(unittest.TestCase): s = SegmentName(route_or_segment_name, allow_route_name=True) - self.assertEqual(str(s.route_name), case.expected_route) - self.assertEqual(s.segment_num, case.expected_segment_num) - self.assertEqual(s.data_dir, case.expected_data_dir) + assert str(s.route_name) == case.expected_route + assert s.segment_num == case.expected_segment_num + assert s.data_dir == case.expected_data_dir for case in cases: _validate(case) - -if __name__ == "__main__": - unittest.main() diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 9ec2097ea4..b7f7cb93a2 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -6,7 +6,7 @@ if [ -z "$SKIP_PROMPT" ]; then echo "--------------- macOS support ---------------" echo "Running openpilot natively on macOS is not officially supported." echo "It might build, some parts of it might work, but it's not fully tested, so there might be some issues." - echo + echo echo "Check out devcontainers for a seamless experience (see tools/README.md)." echo "-------------------------------------------------" echo -n "Are you sure you want to continue? [y/N] " @@ -28,7 +28,7 @@ fi # Install brew if required if [[ $(command -v brew) == "" ]]; then - echo "Installing Hombrew" + echo "Installing Homebrew" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" echo "[ ] installed brew t=$SECONDS" @@ -63,7 +63,6 @@ brew "pyenv" brew "pyenv-virtualenv" brew "qt@5" brew "zeromq" -brew "gcc@13" cask "gcc-arm-embedded" brew "portaudio" EOS diff --git a/tools/plotjuggler/README.md b/tools/plotjuggler/README.md index ca9946145d..dc301efcb7 100644 --- a/tools/plotjuggler/README.md +++ b/tools/plotjuggler/README.md @@ -12,7 +12,7 @@ Once you've [set up the openpilot environment](../README.md), this command will ``` $ ./juggle.py -h -usage: juggle.py [-h] [--demo] [--qlog] [--ci] [--can] [--stream] [--layout [LAYOUT]] [--install] [--dbc DBC] +usage: juggle.py [-h] [--demo] [--can] [--stream] [--layout [LAYOUT]] [--install] [--dbc DBC] [route_or_segment_name] [segment_count] A helper to run PlotJuggler on openpilot routes @@ -25,8 +25,6 @@ positional arguments: optional arguments: -h, --help show this help message and exit --demo Use the demo route instead of providing one (default: False) - --qlog Use qlogs (default: False) - --ci Download data from openpilot CI bucket (default: False) --can Parse CAN data (default: False) --stream Start PlotJuggler in streaming mode (default: False) --layout [LAYOUT] Run PlotJuggler with a pre-defined layout (default: None) @@ -38,11 +36,13 @@ optional arguments: Examples using route name: -`./juggle.py "a2a0ccea32023010|2023-07-27--13-01-19"` +`./juggle.py "a2a0ccea32023010/2023-07-27--13-01-19"` -Examples using segment name: +Examples using segment range: -`./juggle.py "a2a0ccea32023010|2023-07-27--13-01-19--1"` +`./juggle.py "a2a0ccea32023010/2023-07-27--13-01-19/1"` + +`./juggle.py "a2a0ccea32023010/2023-07-27--13-01-19/1/q" # use qlogs` ## Streaming @@ -58,7 +58,7 @@ If streaming to PlotJuggler from a replay on your PC, simply run: `./juggle.py - For a quick demo, go through the installation step and run this command: -`./juggle.py --demo --qlog --layout=layouts/demo.xml` +`./juggle.py --demo --layout=layouts/demo.xml` ## Layouts diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 0caf5a18ff..1d1612a1cd 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -11,19 +11,20 @@ import argparse from functools import partial from openpilot.common.basedir import BASEDIR +from openpilot.selfdrive.car.fingerprints import MIGRATION from openpilot.tools.lib.helpers import save_log - from openpilot.tools.lib.logreader import LogReader, ReadMode juggle_dir = os.path.dirname(os.path.realpath(__file__)) DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" -RELEASES_URL="https://github.com/commaai/PlotJuggler/releases/download/latest" +RELEASES_URL = "https://github.com/commaai/PlotJuggler/releases/download/latest" INSTALL_DIR = os.path.join(juggle_dir, "bin") PLOTJUGGLER_BIN = os.path.join(juggle_dir, "bin/plotjuggler") MINIMUM_PLOTJUGGLER_VERSION = (3, 5, 2) MAX_STREAMING_BUFFER_SIZE = 1000 + def install(): m = f"{platform.system()}-{platform.machine()}" supported = ("Linux-x86_64", "Linux-aarch64", "Darwin-arm64", "Darwin-x86_64") @@ -38,7 +39,7 @@ def install(): with requests.get(url, stream=True, timeout=10) as r, tempfile.NamedTemporaryFile() as tmp: r.raise_for_status() with open(tmp.name, 'wb') as tmpf: - for chunk in r.iter_content(chunk_size=1024*1024): + for chunk in r.iter_content(chunk_size=1024 * 1024): tmpf.write(chunk) with tarfile.open(tmp.name) as tar: @@ -69,9 +70,11 @@ def start_juggler(fn=None, dbc=None, layout=None, route_or_segment_name=None): cmd = f'{PLOTJUGGLER_BIN} --buffer_size {MAX_STREAMING_BUFFER_SIZE} --plugin_folders {INSTALL_DIR}{extra_args}' subprocess.call(cmd, shell=True, env=env, cwd=juggle_dir) + def process(can, lr): return [d for d in lr if can or d.which() not in ['can', 'sendcan']] + def juggle_route(route_or_segment_name, can, layout, dbc=None): sr = LogReader(route_or_segment_name, default_mode=ReadMode.AUTO_INTERACTIVE) @@ -82,7 +85,8 @@ def juggle_route(route_or_segment_name, can, layout, dbc=None): for cp in [m for m in all_data if m.which() == 'carParams']: try: DBC = __import__(f"openpilot.selfdrive.car.{cp.carParams.carName}.values", fromlist=['DBC']).DBC - dbc = DBC[cp.carParams.carFingerprint]['pt'] + fingerprint = cp.carParams.carFingerprint + dbc = DBC[MIGRATION.get(fingerprint, fingerprint)]['pt'] except Exception: pass break diff --git a/tools/plotjuggler/layouts/controls_mismatch_debug.xml b/tools/plotjuggler/layouts/controls_mismatch_debug.xml index 54586d6e7b..7f9def379c 100644 --- a/tools/plotjuggler/layouts/controls_mismatch_debug.xml +++ b/tools/plotjuggler/layouts/controls_mismatch_debug.xml @@ -24,6 +24,7 @@ + diff --git a/tools/plotjuggler/layouts/gps_vs_llk.xml b/tools/plotjuggler/layouts/gps_vs_llk.xml index 44980712ed..69b8f20058 100644 --- a/tools/plotjuggler/layouts/gps_vs_llk.xml +++ b/tools/plotjuggler/layouts/gps_vs_llk.xml @@ -32,7 +32,7 @@ - + diff --git a/tools/plotjuggler/layouts/max-torque-debug.xml b/tools/plotjuggler/layouts/max-torque-debug.xml index 20a49c2181..8cfd30599e 100644 --- a/tools/plotjuggler/layouts/max-torque-debug.xml +++ b/tools/plotjuggler/layouts/max-torque-debug.xml @@ -24,7 +24,7 @@ - + diff --git a/tools/plotjuggler/layouts/torque-controller.xml b/tools/plotjuggler/layouts/torque-controller.xml index 443255968a..d6e4684d63 100644 --- a/tools/plotjuggler/layouts/torque-controller.xml +++ b/tools/plotjuggler/layouts/torque-controller.xml @@ -24,8 +24,8 @@ - - + + diff --git a/tools/plotjuggler/layouts/ublox-debug.xml b/tools/plotjuggler/layouts/ublox-debug.xml index d595a9ecc7..ea4dd35535 100644 --- a/tools/plotjuggler/layouts/ublox-debug.xml +++ b/tools/plotjuggler/layouts/ublox-debug.xml @@ -8,7 +8,7 @@ - + diff --git a/tools/plotjuggler/test_plotjuggler.py b/tools/plotjuggler/test_plotjuggler.py old mode 100755 new mode 100644 index 17287fb803..a2c509f943 --- a/tools/plotjuggler/test_plotjuggler.py +++ b/tools/plotjuggler/test_plotjuggler.py @@ -1,10 +1,8 @@ -#!/usr/bin/env python3 import os import glob import signal import subprocess import time -import unittest from openpilot.common.basedir import BASEDIR from openpilot.common.timeout import Timeout @@ -12,7 +10,7 @@ from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE, install PJ_DIR = os.path.join(BASEDIR, "tools/plotjuggler") -class TestPlotJuggler(unittest.TestCase): +class TestPlotJuggler: def test_demo(self): install() @@ -27,14 +25,14 @@ class TestPlotJuggler(unittest.TestCase): output += p.stderr.readline().decode("utf-8") # ensure plotjuggler didn't crash after exiting the plugin - time.sleep(15) - self.assertEqual(p.poll(), None) + time.sleep(2) + assert p.poll() is None os.killpg(os.getpgid(p.pid), signal.SIGTERM) - self.assertNotIn("Raw file read failed", output) + assert "Raw file read failed" not in output # TODO: also test that layouts successfully load - def test_layouts(self): + def test_layouts(self, subtests): bad_strings = ( # if a previously loaded file is defined, # PJ will throw a warning when loading the layout @@ -43,12 +41,8 @@ class TestPlotJuggler(unittest.TestCase): ) for fn in glob.glob(os.path.join(PJ_DIR, "layouts/*")): name = os.path.basename(fn) - with self.subTest(layout=name): + with subtests.test(layout=name): with open(fn) as f: layout = f.read() violations = [s for s in bad_strings if s in layout] assert len(violations) == 0, f"These should be stripped out of the layout: {str(violations)}" - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/replay/SConscript b/tools/replay/SConscript index db8447003b..cf9d74a894 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -1,16 +1,14 @@ Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'cereal') base_frameworks = qt_env['FRAMEWORKS'] -base_libs = [common, messaging, cereal, visionipc, 'zmq', - 'capnp', 'kj', 'm', 'ssl', 'crypto', 'pthread', 'qt_util'] + qt_env["LIBS"] +base_libs = [common, messaging, cereal, visionipc, + 'm', 'ssl', 'crypto', 'pthread', 'qt_util'] + qt_env["LIBS"] if arch == "Darwin": base_frameworks.append('OpenCL') else: base_libs.append('OpenCL') -qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] - replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc"] replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=base_libs, FRAMEWORKS=base_frameworks) Export('replay_lib') diff --git a/tools/replay/camera.cc b/tools/replay/camera.cc index 49f3010c6c..b6d4ddc3a2 100644 --- a/tools/replay/camera.cc +++ b/tools/replay/camera.cc @@ -1,11 +1,13 @@ #include "tools/replay/camera.h" +#include #include -#include #include "third_party/linux/include/msm_media_info.h" #include "tools/replay/util.h" +const int BUFFER_COUNT = 40; + std::tuple get_nv12_info(int width, int height) { int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, width); int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height); @@ -35,10 +37,12 @@ CameraServer::~CameraServer() { void CameraServer::startVipcServer() { vipc_server_.reset(new VisionIpcServer("camerad")); for (auto &cam : cameras_) { + cam.cached_buf.clear(); + if (cam.width > 0 && cam.height > 0) { rInfo("camera[%d] frame size %dx%d", cam.type, cam.width, cam.height); auto [nv12_width, nv12_height, nv12_buffer_size] = get_nv12_info(cam.width, cam.height); - vipc_server_->create_buffers_with_sizes(cam.stream_type, YUV_BUFFER_COUNT, false, cam.width, cam.height, + vipc_server_->create_buffers_with_sizes(cam.stream_type, BUFFER_COUNT, false, cam.width, cam.height, nv12_buffer_size, nv12_width, nv12_width * nv12_height); if (!cam.thread.joinable()) { cam.thread = std::thread(&CameraServer::cameraThread, this, std::ref(cam)); @@ -49,41 +53,50 @@ void CameraServer::startVipcServer() { } void CameraServer::cameraThread(Camera &cam) { - auto read_frame = [&](FrameReader *fr, int frame_id) { - VisionBuf *yuv_buf = vipc_server_->get_buffer(cam.stream_type); - assert(yuv_buf); - bool ret = fr->get(frame_id, yuv_buf); - return ret ? yuv_buf : nullptr; - }; - while (true) { - const auto [fr, eidx] = cam.queue.pop(); + const auto [fr, event] = cam.queue.pop(); if (!fr) break; - const int id = eidx.getSegmentId(); - bool prefetched = (id == cam.cached_id && eidx.getSegmentNum() == cam.cached_seg); - auto yuv = prefetched ? cam.cached_buf : read_frame(fr, id); - if (yuv) { + capnp::FlatArrayMessageReader reader(event->data); + auto evt = reader.getRoot(); + auto eidx = capnp::AnyStruct::Reader(evt).getPointerSection()[0].getAs(); + + int segment_id = eidx.getSegmentId(); + uint32_t frame_id = eidx.getFrameId(); + if (auto yuv = getFrame(cam, fr, segment_id, frame_id)) { VisionIpcBufExtra extra = { - .frame_id = eidx.getFrameId(), + .frame_id = frame_id, .timestamp_sof = eidx.getTimestampSof(), .timestamp_eof = eidx.getTimestampEof(), }; - yuv->set_frame_id(eidx.getFrameId()); vipc_server_->send(yuv, &extra); } else { - rError("camera[%d] failed to get frame: %lu", cam.type, eidx.getSegmentId()); + rError("camera[%d] failed to get frame: %lu", cam.type, segment_id); } - cam.cached_id = id + 1; - cam.cached_seg = eidx.getSegmentNum(); - cam.cached_buf = read_frame(fr, cam.cached_id); + // Prefetch the next frame + getFrame(cam, fr, segment_id + 1, frame_id + 1); --publishing_; } } -void CameraServer::pushFrame(CameraType type, FrameReader *fr, const cereal::EncodeIndex::Reader &eidx) { +VisionBuf *CameraServer::getFrame(Camera &cam, FrameReader *fr, int32_t segment_id, uint32_t frame_id) { + // Check if the frame is cached + auto buf_it = std::find_if(cam.cached_buf.begin(), cam.cached_buf.end(), + [frame_id](VisionBuf *buf) { return buf->get_frame_id() == frame_id; }); + if (buf_it != cam.cached_buf.end()) return *buf_it; + + VisionBuf *yuv_buf = vipc_server_->get_buffer(cam.stream_type); + if (fr->get(segment_id, yuv_buf)) { + yuv_buf->set_frame_id(frame_id); + cam.cached_buf.insert(yuv_buf); + return yuv_buf; + } + return nullptr; +} + +void CameraServer::pushFrame(CameraType type, FrameReader *fr, const Event *event) { auto &cam = cameras_[type]; if (cam.width != fr->width || cam.height != fr->height) { cam.width = fr->width; @@ -93,7 +106,7 @@ void CameraServer::pushFrame(CameraType type, FrameReader *fr, const cereal::Enc } ++publishing_; - cam.queue.push({fr, eidx}); + cam.queue.push({fr, event}); } void CameraServer::waitForSent() { diff --git a/tools/replay/camera.h b/tools/replay/camera.h index 9f43c5a362..21c3d98dcf 100644 --- a/tools/replay/camera.h +++ b/tools/replay/camera.h @@ -1,12 +1,11 @@ #pragma once -#include - #include +#include #include #include -#include "cereal/visionipc/visionipc_server.h" +#include "msgq/visionipc/visionipc_server.h" #include "common/queue.h" #include "tools/replay/framereader.h" #include "tools/replay/logreader.h" @@ -17,7 +16,7 @@ class CameraServer { public: CameraServer(std::pair camera_size[MAX_CAMERAS] = nullptr); ~CameraServer(); - void pushFrame(CameraType type, FrameReader* fr, const cereal::EncodeIndex::Reader& eidx); + void pushFrame(CameraType type, FrameReader* fr, const Event *event); void waitForSent(); protected: @@ -27,13 +26,12 @@ protected: int width; int height; std::thread thread; - SafeQueue> queue; - int cached_id = -1; - int cached_seg = -1; - VisionBuf * cached_buf; + SafeQueue> queue; + std::set cached_buf; }; void startVipcServer(); void cameraThread(Camera &cam); + VisionBuf *getFrame(Camera &cam, FrameReader *fr, int32_t segment_id, uint32_t frame_id); Camera cameras_[MAX_CAMERAS] = { {.type = RoadCam, .stream_type = VISION_STREAM_ROAD}, diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index bf6b1e9ae2..3ab33a1dfd 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -8,7 +8,7 @@ import threading os.environ['FILEREADER_CACHE'] = '1' from openpilot.common.realtime import config_realtime_process, Ratekeeper, DT_CTRL -from openpilot.selfdrive.boardd.boardd import can_capnp_to_can_list +from openpilot.selfdrive.pandad import can_capnp_to_can_list from openpilot.tools.lib.logreader import LogReader from panda import PandaJungle @@ -26,12 +26,10 @@ def send_thread(j: PandaJungle, flock): with flock: j.flash() + j.reset() for i in [0, 1, 2, 3, 0xFFFF]: j.can_clear(i) j.set_can_speed_kbps(i, 500) - j.set_can_data_speed_kbps(i, 500) - j.set_ignition(False) - time.sleep(5) j.set_ignition(True) j.set_panda_power(True) j.set_can_loopback(False) @@ -98,6 +96,10 @@ if __name__ == "__main__": sr = LogReader(args.route_or_segment_name) + CP = sr.first("carParams") + + print(f"carFingerprint (for hardcoding fingerprint): '{CP.carFingerprint}'") + CAN_MSGS = sr.run_across_segments(24, process) print("Finished loading...") diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index 54056c6cd5..eaff78c691 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -172,7 +172,7 @@ void ConsoleUI::updateStatus() { if (status != Status::Paused) { auto events = replay->events(); uint64_t current_mono_time = replay->routeStartTime() + replay->currentSeconds() * 1e9; - bool playing = !events->empty() && events->back()->mono_time > current_mono_time; + bool playing = !events->empty() && events->back().mono_time > current_mono_time; status = playing ? Status::Playing : Status::Waiting; } auto [status_str, status_color] = status_text[status]; @@ -368,7 +368,6 @@ void ConsoleUI::handleKey(char c) { } else if (c == ' ') { pauseReplay(!replay->isPaused()); } else if (c == 'q' || c == 'Q') { - replay->stop(); qApp->exit(); } } diff --git a/tools/replay/filereader.cc b/tools/replay/filereader.cc index 22af7f5f86..d74aaebaba 100644 --- a/tools/replay/filereader.cc +++ b/tools/replay/filereader.cc @@ -35,7 +35,10 @@ std::string FileReader::read(const std::string &file, std::atomic *abort) std::string FileReader::download(const std::string &url, std::atomic *abort) { for (int i = 0; i <= max_retries_ && !(abort && *abort); ++i) { - if (i > 0) rWarning("download failed, retrying %d", i); + if (i > 0) { + rWarning("download failed, retrying %d", i); + util::sleep_for(3000); + } std::string result = httpGet(url, chunk_size_, abort); if (!result.empty()) { diff --git a/tools/replay/framereader.cc b/tools/replay/framereader.cc index 303f0c60ca..26ef4684a4 100644 --- a/tools/replay/framereader.cc +++ b/tools/replay/framereader.cc @@ -1,9 +1,13 @@ #include "tools/replay/framereader.h" -#include "tools/replay/util.h" -#include -#include +#include +#include +#include +#include + +#include "common/util.h" #include "third_party/libyuv/include/libyuv.h" +#include "tools/replay/util.h" #ifdef __APPLE__ #define HW_DEVICE_TYPE AV_HWDEVICE_TYPE_VIDEOTOOLBOX @@ -15,34 +19,38 @@ namespace { -struct buffer_data { - const uint8_t *data; - int64_t offset; - size_t size; -}; - -int readPacket(void *opaque, uint8_t *buf, int buf_size) { - struct buffer_data *bd = (struct buffer_data *)opaque; - assert(bd->offset <= bd->size); - buf_size = std::min((size_t)buf_size, (size_t)(bd->size - bd->offset)); - if (!buf_size) return AVERROR_EOF; - - memcpy(buf, bd->data + bd->offset, buf_size); - bd->offset += buf_size; - return buf_size; -} - enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { enum AVPixelFormat *hw_pix_fmt = reinterpret_cast(ctx->opaque); for (const enum AVPixelFormat *p = pix_fmts; *p != -1; p++) { if (*p == *hw_pix_fmt) return *p; } rWarning("Please run replay with the --no-hw-decoder flag!"); - // fallback to YUV420p *hw_pix_fmt = AV_PIX_FMT_NONE; return AV_PIX_FMT_YUV420P; } +struct DecoderManager { + VideoDecoder *acquire(CameraType type, AVCodecParameters *codecpar, bool hw_decoder) { + auto key = std::tuple(type, codecpar->width, codecpar->height); + std::unique_lock lock(mutex_); + if (auto it = decoders_.find(key); it != decoders_.end()) { + return it->second.get(); + } + + auto decoder = std::make_unique(); + if (!decoder->open(codecpar, hw_decoder)) { + decoder.reset(nullptr); + } + decoders_[key] = std::move(decoder); + return decoders_[key].get(); + } + + std::mutex mutex_; + std::map, std::unique_ptr> decoders_; +}; + +DecoderManager decoder_manager; + } // namespace FrameReader::FrameReader() { @@ -50,121 +58,105 @@ FrameReader::FrameReader() { } FrameReader::~FrameReader() { - for (AVPacket *pkt : packets) { - av_packet_free(&pkt); - } - - if (decoder_ctx) avcodec_free_context(&decoder_ctx); if (input_ctx) avformat_close_input(&input_ctx); - if (hw_device_ctx) av_buffer_unref(&hw_device_ctx); - - if (avio_ctx_) { - av_freep(&avio_ctx_->buffer); - avio_context_free(&avio_ctx_); - } } -bool FrameReader::load(const std::string &url, bool no_hw_decoder, std::atomic *abort, bool local_cache, int chunk_size, int retries) { - FileReader f(local_cache, chunk_size, retries); - std::string data = f.read(url, abort); - if (data.empty()) { - rWarning("URL %s returned no data", url.c_str()); - return false; +bool FrameReader::load(CameraType type, const std::string &url, bool no_hw_decoder, std::atomic *abort, bool local_cache, int chunk_size, int retries) { + auto local_file_path = url.find("https://") == 0 ? cacheFilePath(url) : url; + if (!util::file_exists(local_file_path)) { + FileReader f(local_cache, chunk_size, retries); + if (f.read(url, abort).empty()) { + return false; + } } - - return load((std::byte *)data.data(), data.size(), no_hw_decoder, abort); + return loadFromFile(type, local_file_path, no_hw_decoder, abort); } -bool FrameReader::load(const std::byte *data, size_t size, bool no_hw_decoder, std::atomic *abort) { - input_ctx = avformat_alloc_context(); - if (!input_ctx) { - rError("Error calling avformat_alloc_context"); +bool FrameReader::loadFromFile(CameraType type, const std::string &file, bool no_hw_decoder, std::atomic *abort) { + if (avformat_open_input(&input_ctx, file.c_str(), nullptr, nullptr) != 0 || + avformat_find_stream_info(input_ctx, nullptr) < 0) { + rError("Failed to open input file or find video stream"); return false; } - - struct buffer_data bd = { - .data = (const uint8_t*)data, - .offset = 0, - .size = size, - }; - const int avio_ctx_buffer_size = 64 * 1024; - unsigned char *avio_ctx_buffer = (unsigned char *)av_malloc(avio_ctx_buffer_size); - avio_ctx_ = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, &bd, readPacket, nullptr, nullptr); - input_ctx->pb = avio_ctx_; - input_ctx->probesize = 10 * 1024 * 1024; // 10MB - int ret = avformat_open_input(&input_ctx, nullptr, nullptr, nullptr); - if (ret != 0) { - char err_str[1024] = {0}; - av_strerror(ret, err_str, std::size(err_str)); - rError("Error loading video - %s", err_str); + + decoder_ = decoder_manager.acquire(type, input_ctx->streams[0]->codecpar, !no_hw_decoder); + if (!decoder_) { return false; } + width = decoder_->width; + height = decoder_->height; - ret = avformat_find_stream_info(input_ctx, nullptr); - if (ret < 0) { - rError("cannot find a video stream in the input file"); + AVPacket pkt; + packets_info.reserve(60 * 20); // 20fps, one minute + while (!(abort && *abort) && av_read_frame(input_ctx, &pkt) == 0) { + packets_info.emplace_back(PacketInfo{.flags = pkt.flags, .pos = pkt.pos}); + av_packet_unref(&pkt); + } + avio_seek(input_ctx->pb, 0, SEEK_SET); + return !packets_info.empty(); +} + +bool FrameReader::get(int idx, VisionBuf *buf) { + if (!buf || idx < 0 || idx >= packets_info.size()) { return false; } + return decoder_->decode(this, idx, buf); +} - AVStream *video = input_ctx->streams[0]; - const AVCodec *decoder = avcodec_find_decoder(video->codecpar->codec_id); +// class VideoDecoder + +VideoDecoder::VideoDecoder() { + av_frame_ = av_frame_alloc(); + hw_frame_ = av_frame_alloc(); +} + +VideoDecoder::~VideoDecoder() { + if (hw_device_ctx) av_buffer_unref(&hw_device_ctx); + if (decoder_ctx) avcodec_free_context(&decoder_ctx); + av_frame_free(&av_frame_); + av_frame_free(&hw_frame_); +} + +bool VideoDecoder::open(AVCodecParameters *codecpar, bool hw_decoder) { + const AVCodec *decoder = avcodec_find_decoder(codecpar->codec_id); if (!decoder) return false; decoder_ctx = avcodec_alloc_context3(decoder); - ret = avcodec_parameters_to_context(decoder_ctx, video->codecpar); - if (ret != 0) return false; - + if (!decoder_ctx || avcodec_parameters_to_context(decoder_ctx, codecpar) != 0) { + rError("Failed to allocate or initialize codec context"); + return false; + } width = (decoder_ctx->width + 3) & ~3; height = decoder_ctx->height; - if (has_hw_decoder && !no_hw_decoder) { - if (!initHardwareDecoder(HW_DEVICE_TYPE)) { - rWarning("No device with hardware decoder found. fallback to CPU decoding."); - } + if (hw_decoder && !initHardwareDecoder(HW_DEVICE_TYPE)) { + rWarning("No device with hardware decoder found. fallback to CPU decoding."); } - ret = avcodec_open2(decoder_ctx, decoder, nullptr); - if (ret < 0) { - rError("avcodec_open2 failed %d", ret); + if (avcodec_open2(decoder_ctx, decoder, nullptr) < 0) { + rError("Failed to open codec"); return false; } - - packets.reserve(60 * 20); // 20fps, one minute - while (!(abort && *abort)) { - AVPacket *pkt = av_packet_alloc(); - ret = av_read_frame(input_ctx, pkt); - if (ret < 0) { - av_packet_free(&pkt); - valid_ = (ret == AVERROR_EOF); - break; - } - packets.push_back(pkt); - // some stream seems to contain no keyframes - key_frames_count_ += pkt->flags & AV_PKT_FLAG_KEY; - } - valid_ = valid_ && !packets.empty(); - return valid_; + return true; } -bool FrameReader::initHardwareDecoder(AVHWDeviceType hw_device_type) { - for (int i = 0;; i++) { - const AVCodecHWConfig *config = avcodec_get_hw_config(decoder_ctx->codec, i); - if (!config) { - rWarning("decoder %s does not support hw device type %s.", decoder_ctx->codec->name, - av_hwdevice_get_type_name(hw_device_type)); - return false; - } +bool VideoDecoder::initHardwareDecoder(AVHWDeviceType hw_device_type) { + const AVCodecHWConfig *config = nullptr; + for (int i = 0; (config = avcodec_get_hw_config(decoder_ctx->codec, i)) != nullptr; i++) { if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && config->device_type == hw_device_type) { hw_pix_fmt = config->pix_fmt; break; } } + if (!config) { + rWarning("Hardware configuration not found"); + return false; + } int ret = av_hwdevice_ctx_create(&hw_device_ctx, hw_device_type, nullptr, nullptr, 0); if (ret < 0) { hw_pix_fmt = AV_PIX_FMT_NONE; - has_hw_decoder = false; rWarning("Failed to create specified HW device %d.", ret); return false; } @@ -175,64 +167,55 @@ bool FrameReader::initHardwareDecoder(AVHWDeviceType hw_device_type) { return true; } -bool FrameReader::get(int idx, VisionBuf *buf) { - assert(buf != nullptr); - if (!valid_ || idx < 0 || idx >= packets.size()) { - return false; - } - return decode(idx, buf); -} - -bool FrameReader::decode(int idx, VisionBuf *buf) { +bool VideoDecoder::decode(FrameReader *reader, int idx, VisionBuf *buf) { int from_idx = idx; - if (idx != prev_idx + 1 && key_frames_count_ > 1) { + if (idx != reader->prev_idx + 1) { // seeking to the nearest key frame for (int i = idx; i >= 0; --i) { - if (packets[i]->flags & AV_PKT_FLAG_KEY) { + if (reader->packets_info[i].flags & AV_PKT_FLAG_KEY) { from_idx = i; break; } } + avio_seek(reader->input_ctx->pb, reader->packets_info[from_idx].pos, SEEK_SET); } - prev_idx = idx; + reader->prev_idx = idx; + bool result = false; + AVPacket pkt; for (int i = from_idx; i <= idx; ++i) { - AVFrame *f = decodeFrame(packets[i]); - if (f && i == idx) { - return copyBuffers(f, buf); + if (av_read_frame(reader->input_ctx, &pkt) == 0) { + AVFrame *f = decodeFrame(&pkt); + if (f && i == idx) { + result = copyBuffer(f, buf); + } + av_packet_unref(&pkt); } } - return false; + return result; } -AVFrame *FrameReader::decodeFrame(AVPacket *pkt) { +AVFrame *VideoDecoder::decodeFrame(AVPacket *pkt) { int ret = avcodec_send_packet(decoder_ctx, pkt); if (ret < 0) { rError("Error sending a packet for decoding: %d", ret); return nullptr; } - av_frame_.reset(av_frame_alloc()); - ret = avcodec_receive_frame(decoder_ctx, av_frame_.get()); + ret = avcodec_receive_frame(decoder_ctx, av_frame_); if (ret != 0) { rError("avcodec_receive_frame error: %d", ret); return nullptr; } - if (av_frame_->format == hw_pix_fmt) { - hw_frame.reset(av_frame_alloc()); - if ((ret = av_hwframe_transfer_data(hw_frame.get(), av_frame_.get(), 0)) < 0) { - rError("error transferring the data from GPU to CPU"); - return nullptr; - } - return hw_frame.get(); - } else { - return av_frame_.get(); + if (av_frame_->format == hw_pix_fmt && av_hwframe_transfer_data(hw_frame_, av_frame_, 0) < 0) { + rError("error transferring frame data from GPU to CPU"); + return nullptr; } + return (av_frame_->format == hw_pix_fmt) ? hw_frame_ : av_frame_; } -bool FrameReader::copyBuffers(AVFrame *f, VisionBuf *buf) { - assert(f != nullptr && buf != nullptr); +bool VideoDecoder::copyBuffer(AVFrame *f, VisionBuf *buf) { if (hw_pix_fmt == HW_PIX_FMT) { for (int i = 0; i < height/2; i++) { memcpy(buf->y + (i*2 + 0)*buf->stride, f->data[0] + (i*2 + 0)*f->linesize[0], width); diff --git a/tools/replay/framereader.h b/tools/replay/framereader.h index bb72ac8f8d..7a4d024aa2 100644 --- a/tools/replay/framereader.h +++ b/tools/replay/framereader.h @@ -1,10 +1,10 @@ #pragma once -#include #include #include -#include "cereal/visionipc/visionbuf.h" +#include "msgq/visionipc/visionbuf.h" +#include "system/camerad/cameras/camera_common.h" #include "tools/replay/filereader.h" extern "C" { @@ -12,40 +12,46 @@ extern "C" { #include } -struct AVFrameDeleter { - void operator()(AVFrame* frame) const { av_frame_free(&frame); } -}; +class VideoDecoder; class FrameReader { public: FrameReader(); ~FrameReader(); - bool load(const std::string &url, bool no_hw_decoder = false, std::atomic *abort = nullptr, bool local_cache = false, + bool load(CameraType type, const std::string &url, bool no_hw_decoder = false, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); - bool load(const std::byte *data, size_t size, bool no_hw_decoder = false, std::atomic *abort = nullptr); + bool loadFromFile(CameraType type, const std::string &file, bool no_hw_decoder = false, std::atomic *abort = nullptr); bool get(int idx, VisionBuf *buf); - int getYUVSize() const { return width * height * 3 / 2; } - size_t getFrameCount() const { return packets.size(); } - bool valid() const { return valid_; } + size_t getFrameCount() const { return packets_info.size(); } int width = 0, height = 0; + VideoDecoder *decoder_ = nullptr; + AVFormatContext *input_ctx = nullptr; + int prev_idx = -1; + struct PacketInfo { + int flags; + int64_t pos; + }; + std::vector packets_info; +}; + + +class VideoDecoder { +public: + VideoDecoder(); + ~VideoDecoder(); + bool open(AVCodecParameters *codecpar, bool hw_decoder); + bool decode(FrameReader *reader, int idx, VisionBuf *buf); + int width = 0, height = 0; + private: bool initHardwareDecoder(AVHWDeviceType hw_device_type); - bool decode(int idx, VisionBuf *buf); - AVFrame * decodeFrame(AVPacket *pkt); - bool copyBuffers(AVFrame *f, VisionBuf *buf); + AVFrame *decodeFrame(AVPacket *pkt); + bool copyBuffer(AVFrame *f, VisionBuf *buf); - std::vector packets; - std::unique_ptrav_frame_, hw_frame; - AVFormatContext *input_ctx = nullptr; + AVFrame *av_frame_, *hw_frame_; AVCodecContext *decoder_ctx = nullptr; - int key_frames_count_ = 0; - bool valid_ = false; - AVIOContext *avio_ctx_ = nullptr; - AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE; AVBufferRef *hw_device_ctx = nullptr; - int prev_idx = -1; - inline static std::atomic has_hw_decoder = true; }; diff --git a/tools/replay/lib/ui_helpers.py b/tools/replay/lib/ui_helpers.py index 23f3563084..11b5182a6b 100644 --- a/tools/replay/lib/ui_helpers.py +++ b/tools/replay/lib/ui_helpers.py @@ -7,9 +7,7 @@ import pygame from matplotlib.backends.backend_agg import FigureCanvasAgg -from openpilot.common.transformations.camera import (eon_f_frame_size, eon_f_focal_length, - tici_f_frame_size, tici_f_focal_length, - get_view_frame_from_calib_frame) +from openpilot.common.transformations.camera import get_view_frame_from_calib_frame from openpilot.selfdrive.controls.radard import RADAR_TO_CAMERA @@ -20,9 +18,6 @@ YELLOW = (255, 255, 0) BLACK = (0, 0, 0) WHITE = (255, 255, 255) -_FULL_FRAME_SIZE = { -} - class UIParams: lidar_x, lidar_y, lidar_zoom = 384, 960, 6 lidar_car_x, lidar_car_y = lidar_x / 2., lidar_y / 1.1 @@ -32,45 +27,13 @@ class UIParams: car_color = 110 UP = UIParams -_BB_TO_FULL_FRAME = {} -_CALIB_BB_TO_FULL = {} -_FULL_FRAME_TO_BB = {} -_INTRINSICS = {} - -eon_f_qcam_frame_size = (480, 360) -tici_f_qcam_frame_size = (528, 330) - -cams = [(eon_f_frame_size, eon_f_focal_length, eon_f_frame_size), - (tici_f_frame_size, tici_f_focal_length, tici_f_frame_size), - (eon_f_qcam_frame_size, eon_f_focal_length, eon_f_frame_size), - (tici_f_qcam_frame_size, tici_f_focal_length, tici_f_frame_size)] -for size, focal, full_size in cams: - sz = size[0] * size[1] - _BB_SCALE = size[0] / 640. - _BB_TO_FULL_FRAME[sz] = np.asarray([ - [_BB_SCALE, 0., 0.], - [0., _BB_SCALE, 0.], - [0., 0., 1.]]) - calib_scale = full_size[0] / 640. - _CALIB_BB_TO_FULL[sz] = np.asarray([ - [calib_scale, 0., 0.], - [0., calib_scale, 0.], - [0., 0., 1.]]) - _FULL_FRAME_TO_BB[sz] = np.linalg.inv(_BB_TO_FULL_FRAME[sz]) - _FULL_FRAME_SIZE[sz] = (size[0], size[1]) - _INTRINSICS[sz] = np.array([ - [focal, 0., full_size[0] / 2.], - [0., focal, full_size[1] / 2.], - [0., 0., 1.]]) - - METER_WIDTH = 20 class Calibration: - def __init__(self, num_px, rpy, intrinsic): + def __init__(self, num_px, rpy, intrinsic, calib_scale): self.intrinsic = intrinsic self.extrinsics_matrix = get_view_frame_from_calib_frame(rpy[0], rpy[1], rpy[2], 0.0)[:,:3] - self.zoom = _CALIB_BB_TO_FULL[num_px][0, 0] + self.zoom = calib_scale def car_space_to_ff(self, x, y, z): car_space_projective = np.column_stack((x, y, z)).T diff --git a/tools/replay/logreader.cc b/tools/replay/logreader.cc index c92ff4753f..9b726e067d 100644 --- a/tools/replay/logreader.cc +++ b/tools/replay/logreader.cc @@ -1,97 +1,60 @@ #include "tools/replay/logreader.h" #include +#include #include "tools/replay/filereader.h" #include "tools/replay/util.h" -Event::Event(const kj::ArrayPtr &amsg, bool frame) : reader(amsg), frame(frame) { - words = kj::ArrayPtr(amsg.begin(), reader.getEnd()); - event = reader.getRoot(); - which = event.which(); - mono_time = event.getLogMonoTime(); - - // 1) Send video data at t=timestampEof/timestampSof - // 2) Send encodeIndex packet at t=logMonoTime - if (frame) { - auto idx = capnp::AnyStruct::Reader(event).getPointerSection()[0].getAs(); - // C2 only has eof set, and some older routes have neither - uint64_t sof = idx.getTimestampSof(); - uint64_t eof = idx.getTimestampEof(); - if (sof > 0) { - mono_time = sof; - } else if (eof > 0) { - mono_time = eof; - } - } -} - -// class LogReader - -LogReader::LogReader(size_t memory_pool_block_size) { -#ifdef HAS_MEMORY_RESOURCE - const size_t buf_size = sizeof(Event) * memory_pool_block_size; - mbr_ = std::make_unique(buf_size); -#endif - events.reserve(memory_pool_block_size); -} - -LogReader::~LogReader() { - for (Event *e : events) { - delete e; - } -} - bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache, int chunk_size, int retries) { - raw_ = FileReader(local_cache, chunk_size, retries).read(url, abort); - if (raw_.empty()) return false; + std::string data = FileReader(local_cache, chunk_size, retries).read(url, abort); + if (!data.empty() && url.find(".bz2") != std::string::npos) + data = decompressBZ2(data, abort); - if (url.find(".bz2") != std::string::npos) { - raw_ = decompressBZ2(raw_, abort); - if (raw_.empty()) return false; - } - return parse(abort); + bool success = !data.empty() && load(data.data(), data.size(), abort); + if (filters_.empty()) + raw_ = std::move(data); + return success; } -bool LogReader::load(const std::byte *data, size_t size, std::atomic *abort) { - raw_.assign((const char *)data, size); - return parse(abort); -} - -bool LogReader::parse(std::atomic *abort) { +bool LogReader::load(const char *data, size_t size, std::atomic *abort) { try { - kj::ArrayPtr words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word)); + events.reserve(65000); + kj::ArrayPtr words((const capnp::word *)data, size / sizeof(capnp::word)); while (words.size() > 0 && !(abort && *abort)) { -#ifdef HAS_MEMORY_RESOURCE - Event *evt = new (mbr_.get()) Event(words); -#else - Event *evt = new Event(words); -#endif - // Add encodeIdx packet again as a frame packet for the video stream - if (evt->which == cereal::Event::ROAD_ENCODE_IDX || - evt->which == cereal::Event::DRIVER_ENCODE_IDX || - evt->which == cereal::Event::WIDE_ROAD_ENCODE_IDX) { + capnp::FlatArrayMessageReader reader(words); + auto event = reader.getRoot(); + auto which = event.which(); + auto event_data = kj::arrayPtr(words.begin(), reader.getEnd()); + words = kj::arrayPtr(reader.getEnd(), words.end()); -#ifdef HAS_MEMORY_RESOURCE - Event *frame_evt = new (mbr_.get()) Event(words, true); -#else - Event *frame_evt = new Event(words, true); -#endif - - events.push_back(frame_evt); + if (!filters_.empty()) { + if (which >= filters_.size() || !filters_[which]) + continue; + auto buf = buffer_.allocate(event_data.size() * sizeof(capnp::word)); + memcpy(buf, event_data.begin(), event_data.size() * sizeof(capnp::word)); + event_data = kj::arrayPtr((const capnp::word *)buf, event_data.size()); } - words = kj::arrayPtr(evt->reader.getEnd(), words.end()); - events.push_back(evt); + uint64_t mono_time = event.getLogMonoTime(); + const Event &evt = events.emplace_back(which, mono_time, event_data); + // Add encodeIdx packet again as a frame packet for the video stream + if (evt.which == cereal::Event::ROAD_ENCODE_IDX || + evt.which == cereal::Event::DRIVER_ENCODE_IDX || + evt.which == cereal::Event::WIDE_ROAD_ENCODE_IDX) { + auto idx = capnp::AnyStruct::Reader(event).getPointerSection()[0].getAs(); + if (idx.getType() == cereal::EncodeIndex::Type::FULL_H_E_V_C) { + uint64_t sof = idx.getTimestampSof(); + events.emplace_back(which, sof ? sof : mono_time, event_data, idx.getSegmentNum()); + } + } } } catch (const kj::Exception &e) { - rWarning("failed to parse log : %s", e.getDescription().cStr()); - if (!events.empty()) { - rWarning("read %zu events from corrupt log", events.size()); - } + rWarning("Failed to parse log : %s.\nRetrieved %zu events from corrupt log", e.getDescription().cStr(), events.size()); } if (!events.empty() && !(abort && *abort)) { - std::sort(events.begin(), events.end(), Event::lessThan()); + events.shrink_to_fit(); + std::sort(events.begin(), events.end()); return true; } return false; diff --git a/tools/replay/logreader.h b/tools/replay/logreader.h index 73f822d16c..782c00b90a 100644 --- a/tools/replay/logreader.h +++ b/tools/replay/logreader.h @@ -1,67 +1,40 @@ #pragma once -#if __has_include() -#define HAS_MEMORY_RESOURCE 1 -#include -#endif - -#include #include #include #include "cereal/gen/cpp/log.capnp.h" #include "system/camerad/cameras/camera_common.h" +#include "tools/replay/util.h" const CameraType ALL_CAMERAS[] = {RoadCam, DriverCam, WideRoadCam}; const int MAX_CAMERAS = std::size(ALL_CAMERAS); -const int DEFAULT_EVENT_MEMORY_POOL_BLOCK_SIZE = 65000; class Event { public: - Event(cereal::Event::Which which, uint64_t mono_time) : reader(kj::ArrayPtr{}) { - // construct a dummy Event for binary search, e.g std::upper_bound - this->which = which; - this->mono_time = mono_time; - } - Event(const kj::ArrayPtr &amsg, bool frame = false); - inline kj::ArrayPtr bytes() const { return words.asBytes(); } + Event(cereal::Event::Which which, uint64_t mono_time, const kj::ArrayPtr &data, int eidx_segnum = -1) + : which(which), mono_time(mono_time), data(data), eidx_segnum(eidx_segnum) {} - struct lessThan { - inline bool operator()(const Event *l, const Event *r) { - return l->mono_time < r->mono_time || (l->mono_time == r->mono_time && l->which < r->which); - } - }; - -#if HAS_MEMORY_RESOURCE - void *operator new(size_t size, std::pmr::monotonic_buffer_resource *mbr) { - return mbr->allocate(size); + bool operator<(const Event &other) const { + return mono_time < other.mono_time || (mono_time == other.mono_time && which < other.which); } - void operator delete(void *ptr) { - // No-op. memory used by EventMemoryPool increases monotonically until the logReader is destroyed. - } -#endif uint64_t mono_time; cereal::Event::Which which; - cereal::Event::Reader event; - capnp::FlatArrayMessageReader reader; - kj::ArrayPtr words; - bool frame; + kj::ArrayPtr data; + int32_t eidx_segnum; }; class LogReader { public: - LogReader(size_t memory_pool_block_size = DEFAULT_EVENT_MEMORY_POOL_BLOCK_SIZE); - ~LogReader(); + LogReader(const std::vector &filters = {}) { filters_ = filters; } bool load(const std::string &url, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); - bool load(const std::byte *data, size_t size, std::atomic *abort = nullptr); - std::vector events; + bool load(const char *data, size_t size, std::atomic *abort = nullptr); + std::vector events; private: - bool parse(std::atomic *abort); std::string raw_; -#ifdef HAS_MEMORY_RESOURCE - std::unique_ptr mbr_; -#endif + std::vector filters_; + MonotonicBuffer buffer_{1024 * 1024}; }; diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 9657375be7..c5ad2aa548 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -2,15 +2,20 @@ #include #include - #include +#include #include "cereal/services.h" #include "common/params.h" #include "common/timing.h" #include "tools/replay/util.h" +static void interrupt_sleep_handler(int signal) {} + Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *sm_, uint32_t flags, QString data_dir, QObject *parent) : sm(sm_), flags_(flags), QObject(parent) { + // Register signal handler for SIGUSR1 + std::signal(SIGUSR1, interrupt_sleep_handler); + if (!(flags_ & REPLAY_FLAG_ALL_SERVICES)) { block << "uiDebug" << "userFlag"; } @@ -22,6 +27,11 @@ Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *s sockets_[which] = name.c_str(); } } + if (!allow.isEmpty()) { + for (int i = 0; i < sockets_.size(); ++i) { + filters_.push_back(i == cereal::Event::Which::INIT_DATA || i == cereal::Event::Which::CAR_PARAMS || sockets_[i]); + } + } std::vector s; std::copy_if(sockets_.begin(), sockets_.end(), std::back_inserter(s), @@ -33,8 +43,6 @@ Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *s pm = std::make_unique(s); } route_ = std::make_unique(route, data_dir); - events_ = std::make_unique>(); - new_events_ = std::make_unique>(); } Replay::~Replay() { @@ -46,15 +54,15 @@ void Replay::stop() { rInfo("shutdown: in progress..."); if (stream_thread_ != nullptr) { - exit_ = updating_events_ = true; + exit_ = true; + pauseStreamThread(); stream_cv_.notify_one(); stream_thread_->quit(); stream_thread_->wait(); + stream_thread_->deleteLater(); stream_thread_ = nullptr; } - camera_server_.reset(nullptr); timeline_future.waitForFinished(); - segments_.clear(); rInfo("shutdown: done"); } @@ -81,37 +89,53 @@ bool Replay::load() { } void Replay::start(int seconds) { - seekTo(route_->identifier().segment_id * 60 + seconds, false); + seekTo(route_->identifier().begin_segment * 60 + seconds, false); } -void Replay::updateEvents(const std::function &lambda) { - // set updating_events to true to force stream thread release the lock and wait for events_updated. - updating_events_ = true; +void Replay::updateEvents(const std::function &update_events_function) { + pauseStreamThread(); { std::unique_lock lk(stream_lock_); - events_updated_ = lambda(); - updating_events_ = false; + events_ready_ = update_events_function(); + paused_ = user_paused_; } stream_cv_.notify_one(); } void Replay::seekTo(double seconds, bool relative) { - seconds = relative ? seconds + currentSeconds() : seconds; updateEvents([&]() { - seconds = std::max(double(0.0), seconds); - int seg = (int)seconds / 60; - if (segments_.find(seg) == segments_.end()) { - rWarning("can't seek to %d s segment %d is invalid", seconds, seg); + double target_time = relative ? seconds + currentSeconds() : seconds; + target_time = std::max(double(0.0), target_time); + int target_segment = (int)target_time / 60; + if (segments_.count(target_segment) == 0) { + rWarning("Can't seek to %d s segment %d is invalid", (int)target_time, target_segment); return true; } - rInfo("seeking to %d s, segment %d", (int)seconds, seg); - current_segment_ = seg; - cur_mono_time_ = route_start_ts_ + seconds * 1e9; - emit seekedTo(seconds); - return isSegmentMerged(seg); + rInfo("Seeking to %d s, segment %d", (int)target_time, target_segment); + current_segment_ = target_segment; + cur_mono_time_ = route_start_ts_ + target_time * 1e9; + seeking_to_ = target_time; + return false; }); - queueSegment(); + + checkSeekProgress(); + updateSegmentsCache(); +} + +void Replay::checkSeekProgress() { + if (seeking_to_) { + auto it = segments_.find(int(*seeking_to_ / 60)); + if (it != segments_.end() && it->second && it->second->isLoaded()) { + emit seekedTo(*seeking_to_); + seeking_to_ = std::nullopt; + // wake up stream thread + updateEvents([]() { return true; }); + } else { + // Emit signal indicating the ongoing seek operation + emit seeking(*seeking_to_); + } + } } void Replay::seekToFlag(FindFlag flag) { @@ -138,38 +162,47 @@ void Replay::buildTimeline() { const auto &route_segments = route_->segments(); for (auto it = route_segments.cbegin(); it != route_segments.cend() && !exit_; ++it) { std::shared_ptr log(new LogReader()); - if (!log->load(it->second.qlog.toStdString(), &exit_, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; + if (!log->load(it->second.qlog.toStdString(), &exit_, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3) || log->events.empty()) continue; - for (const Event *e : log->events) { - if (e->which == cereal::Event::Which::CONTROLS_STATE) { - auto cs = e->event.getControlsState(); + std::vector> timeline; + for (const Event &e : log->events) { + if (e.which == cereal::Event::Which::CONTROLS_STATE) { + capnp::FlatArrayMessageReader reader(e.data); + auto event = reader.getRoot(); + auto cs = event.getControlsState(); if (engaged != cs.getEnabled()) { if (engaged) { - std::lock_guard lk(timeline_lock); - timeline.push_back({toSeconds(engaged_begin), toSeconds(e->mono_time), TimelineType::Engaged}); + timeline.push_back({toSeconds(engaged_begin), toSeconds(e.mono_time), TimelineType::Engaged}); } - engaged_begin = e->mono_time; + engaged_begin = e.mono_time; engaged = cs.getEnabled(); } if (alert_type != cs.getAlertType().cStr() || alert_status != cs.getAlertStatus()) { if (!alert_type.empty() && alert_size != cereal::ControlsState::AlertSize::NONE) { - std::lock_guard lk(timeline_lock); - timeline.push_back({toSeconds(alert_begin), toSeconds(e->mono_time), timeline_types[(int)alert_status]}); + timeline.push_back({toSeconds(alert_begin), toSeconds(e.mono_time), timeline_types[(int)alert_status]}); } - alert_begin = e->mono_time; + alert_begin = e.mono_time; alert_type = cs.getAlertType().cStr(); alert_size = cs.getAlertSize(); alert_status = cs.getAlertStatus(); } - } else if (e->which == cereal::Event::Which::USER_FLAG) { - std::lock_guard lk(timeline_lock); - timeline.push_back({toSeconds(e->mono_time), toSeconds(e->mono_time), TimelineType::UserFlag}); + } else if (e.which == cereal::Event::Which::USER_FLAG) { + timeline.push_back({toSeconds(e.mono_time), toSeconds(e.mono_time), TimelineType::UserFlag}); } } - std::sort(timeline.begin(), timeline.end(), [](auto &l, auto &r) { return std::get<2>(l) < std::get<2>(r); }); - emit qLogLoaded(it->first, log); + + { + std::lock_guard lk(timeline_lock); + timeline_.insert(timeline_.end(), timeline.begin(), timeline.end()); + std::sort(timeline_.begin(), timeline_.end(), [](auto &l, auto &r) { return std::get<2>(l) < std::get<2>(r); }); + } + + if (it->first == route_segments.rbegin()->first) { + emit totalSecondsUpdated(toSeconds(log->events.back().mono_time)); + } + emit qLogLoaded(log); } } @@ -195,16 +228,22 @@ std::optional Replay::find(FindFlag flag) { } void Replay::pause(bool pause) { - updateEvents([=]() { - rWarning("%s at %.2f s", pause ? "paused..." : "resuming", currentSeconds()); - paused_ = pause; - return true; - }); + if (user_paused_ != pause) { + pauseStreamThread(); + { + std::unique_lock lk(stream_lock_); + rWarning("%s at %.2f s", pause ? "paused..." : "resuming", currentSeconds()); + paused_ = user_paused_ = pause; + } + stream_cv_.notify_one(); + } } -void Replay::setCurrentSegment(int n) { - if (current_segment_.exchange(n) != n) { - QMetaObject::invokeMethod(this, &Replay::queueSegment, Qt::QueuedConnection); +void Replay::pauseStreamThread() { + paused_ = true; + // Send SIGUSR1 to interrupt clock_nanosleep + if (stream_thread_ && stream_thread_id) { + pthread_kill(stream_thread_id, SIGUSR1); } } @@ -214,26 +253,22 @@ void Replay::segmentLoadFinished(bool success) { rWarning("failed to load segment %d, removing it from current replay list", seg->seg_num); updateEvents([&]() { segments_.erase(seg->seg_num); - return true; + return !segments_.empty(); }); } - queueSegment(); + updateSegmentsCache(); } -void Replay::queueSegment() { +void Replay::updateSegmentsCache() { auto cur = segments_.lower_bound(current_segment_.load()); if (cur == segments_.end()) return; + // Calculate the range of segments to load auto begin = std::prev(cur, std::min(segment_cache_limit / 2, std::distance(segments_.begin(), cur))); - auto end = std::next(begin, std::min(segment_cache_limit, segments_.size())); - // load one segment at a time - auto it = std::find_if(cur, end, [](auto &it) { return !it.second || !it.second->isLoaded(); }); - if (it != end && !it->second) { - rDebug("loading segment %d...", it->first); - it->second = std::make_unique(it->first, route_->at(it->first), flags_); - QObject::connect(it->second.get(), &Segment::loadFinished, this, &Replay::segmentLoadFinished); - } + auto end = std::next(begin, std::min(segment_cache_limit, std::distance(begin, segments_.end()))); + begin = std::prev(end, std::min(segment_cache_limit, std::distance(segments_.begin(), end))); + loadSegmentInRange(begin, cur, end); mergeSegments(begin, end); // free segments out of current semgnt window. @@ -244,62 +279,93 @@ void Replay::queueSegment() { const auto &cur_segment = cur->second; if (stream_thread_ == nullptr && cur_segment->isLoaded()) { startStream(cur_segment.get()); - emit streamStarted(); + } +} + +void Replay::loadSegmentInRange(SegmentMap::iterator begin, SegmentMap::iterator cur, SegmentMap::iterator end) { + auto loadNextSegment = [this](auto first, auto last) { + auto it = std::find_if(first, last, [](const auto &seg_it) { return !seg_it.second || !seg_it.second->isLoaded(); }); + if (it != last && !it->second) { + rDebug("loading segment %d...", it->first); + it->second = std::make_unique(it->first, route_->at(it->first), flags_, filters_); + QObject::connect(it->second.get(), &Segment::loadFinished, this, &Replay::segmentLoadFinished); + return true; + } + return false; + }; + + // Try loading forward segments, then reverse segments + if (!loadNextSegment(cur, end)) { + loadNextSegment(std::make_reverse_iterator(cur), std::make_reverse_iterator(begin)); } } void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::iterator &end) { - std::vector segments_need_merge; + std::set segments_to_merge; size_t new_events_size = 0; for (auto it = begin; it != end; ++it) { if (it->second && it->second->isLoaded()) { - segments_need_merge.push_back(it->first); + segments_to_merge.insert(it->first); new_events_size += it->second->log->events.size(); } } - if (segments_need_merge != segments_merged_) { - std::string s; - for (int i = 0; i < segments_need_merge.size(); ++i) { - s += std::to_string(segments_need_merge[i]); - if (i != segments_need_merge.size() - 1) s += ", "; - } - rDebug("merge segments %s", s.c_str()); - new_events_->clear(); - new_events_->reserve(new_events_size); - for (int n : segments_need_merge) { - size_t size = new_events_->size(); - const auto &events = segments_[n]->log->events; - std::copy_if(events.begin(), events.end(), std::back_inserter(*new_events_), - [this](auto e) { return e->which < sockets_.size() && sockets_[e->which] != nullptr; }); - std::inplace_merge(new_events_->begin(), new_events_->begin() + size, new_events_->end(), Event::lessThan()); - } + if (segments_to_merge == merged_segments_) return; - if (stream_thread_) { - emit segmentsMerged(); - } - updateEvents([&]() { - events_.swap(new_events_); - segments_merged_ = segments_need_merge; - // Do not wake up the stream thread if the current segment has not been merged. - return isSegmentMerged(current_segment_) || (segments_.count(current_segment_) == 0); - }); + rDebug("merge segments %s", std::accumulate(segments_to_merge.begin(), segments_to_merge.end(), std::string{}, + [](auto & a, int b) { return a + (a.empty() ? "" : ", ") + std::to_string(b); }).c_str()); + + std::vector new_events; + new_events.reserve(new_events_size); + + // Merge events from segments_to_merge into new_events + for (int n : segments_to_merge) { + size_t size = new_events.size(); + const auto &events = segments_.at(n)->log->events; + std::copy_if(events.begin(), events.end(), std::back_inserter(new_events), + [this](const Event &e) { return e.which < sockets_.size() && sockets_[e.which] != nullptr; }); + std::inplace_merge(new_events.begin(), new_events.begin() + size, new_events.end()); } + + if (stream_thread_) { + emit segmentsMerged(); + } + + updateEvents([&]() { + events_.swap(new_events); + merged_segments_ = segments_to_merge; + // Wake up the stream thread if the current segment is loaded or invalid. + return !seeking_to_ && (isSegmentMerged(current_segment_) || (segments_.count(current_segment_) == 0)); + }); + checkSeekProgress(); } void Replay::startStream(const Segment *cur_segment) { const auto &events = cur_segment->log->events; - - // each segment has an INIT_DATA - route_start_ts_ = events.front()->mono_time; + route_start_ts_ = events.front().mono_time; cur_mono_time_ += route_start_ts_ - 1; + // get datetime from INIT_DATA, fallback to datetime in the route name + route_date_time_ = route()->datetime(); + auto it = std::find_if(events.cbegin(), events.cend(), + [](const Event &e) { return e.which == cereal::Event::Which::INIT_DATA; }); + if (it != events.cend()) { + capnp::FlatArrayMessageReader reader(it->data); + auto event = reader.getRoot(); + uint64_t wall_time = event.getInitData().getWallTimeNanos(); + if (wall_time > 0) { + route_date_time_ = QDateTime::fromMSecsSinceEpoch(wall_time / 1e6); + } + } + // write CarParams - auto it = std::find_if(events.begin(), events.end(), [](auto e) { return e->which == cereal::Event::Which::CAR_PARAMS; }); + it = std::find_if(events.begin(), events.end(), [](const Event &e) { return e.which == cereal::Event::Which::CAR_PARAMS; }); if (it != events.end()) { - car_fingerprint_ = (*it)->event.getCarParams().getCarFingerprint(); + capnp::FlatArrayMessageReader reader(it->data); + auto event = reader.getRoot(); + car_fingerprint_ = event.getCarParams().getCarFingerprint(); capnp::MallocMessageBuilder builder; - builder.setRoot((*it)->event.getCarParams()); + builder.setRoot(event.getCarParams()); auto words = capnp::messageToFlatArray(builder); auto bytes = words.asBytes(); Params().put("CarParams", (const char *)bytes.begin(), bytes.size()); @@ -322,103 +388,79 @@ void Replay::startStream(const Segment *cur_segment) { emit segmentsMerged(); // start stream thread stream_thread_ = new QThread(); - QObject::connect(stream_thread_, &QThread::started, [=]() { stream(); }); - QObject::connect(stream_thread_, &QThread::finished, stream_thread_, &QThread::deleteLater); + QObject::connect(stream_thread_, &QThread::started, [=]() { streamThread(); }); stream_thread_->start(); timeline_future = QtConcurrent::run(this, &Replay::buildTimeline); + emit streamStarted(); } void Replay::publishMessage(const Event *e) { if (event_filter && event_filter(e, filter_opaque)) return; if (sm == nullptr) { - auto bytes = e->bytes(); + auto bytes = e->data.asBytes(); int ret = pm->send(sockets_[e->which], (capnp::byte *)bytes.begin(), bytes.size()); if (ret == -1) { rWarning("stop publishing %s due to multiple publishers error", sockets_[e->which]); sockets_[e->which] = nullptr; } } else { - sm->update_msgs(nanos_since_boot(), {{sockets_[e->which], e->event}}); + capnp::FlatArrayMessageReader reader(e->data); + auto event = reader.getRoot(); + sm->update_msgs(nanos_since_boot(), {{sockets_[e->which], event}}); } } void Replay::publishFrame(const Event *e) { - static const std::map cam_types{ - {cereal::Event::ROAD_ENCODE_IDX, RoadCam}, - {cereal::Event::DRIVER_ENCODE_IDX, DriverCam}, - {cereal::Event::WIDE_ROAD_ENCODE_IDX, WideRoadCam}, - }; - if ((e->which == cereal::Event::DRIVER_ENCODE_IDX && !hasFlag(REPLAY_FLAG_DCAM)) || - (e->which == cereal::Event::WIDE_ROAD_ENCODE_IDX && !hasFlag(REPLAY_FLAG_ECAM))) { - return; + CameraType cam; + switch (e->which) { + case cereal::Event::ROAD_ENCODE_IDX: cam = RoadCam; break; + case cereal::Event::DRIVER_ENCODE_IDX: cam = DriverCam; break; + case cereal::Event::WIDE_ROAD_ENCODE_IDX: cam = WideRoadCam; break; + default: return; // Invalid event type } - auto eidx = capnp::AnyStruct::Reader(e->event).getPointerSection()[0].getAs(); - if (eidx.getType() == cereal::EncodeIndex::Type::FULL_H_E_V_C && isSegmentMerged(eidx.getSegmentNum())) { - CameraType cam = cam_types.at(e->which); - camera_server_->pushFrame(cam, segments_[eidx.getSegmentNum()]->frames[cam].get(), eidx); + + if ((cam == DriverCam && !hasFlag(REPLAY_FLAG_DCAM)) || (cam == WideRoadCam && !hasFlag(REPLAY_FLAG_ECAM))) + return; // Camera isdisabled + + if (isSegmentMerged(e->eidx_segnum)) { + auto &segment = segments_.at(e->eidx_segnum); + if (auto &frame = segment->frames[cam]; frame) { + camera_server_->pushFrame(cam, frame.get(), e); + } } } -void Replay::stream() { +void Replay::streamThread() { + stream_thread_id = pthread_self(); cereal::Event::Which cur_which = cereal::Event::Which::INIT_DATA; - double prev_replay_speed = speed_; std::unique_lock lk(stream_lock_); while (true) { - stream_cv_.wait(lk, [=]() { return exit_ || (events_updated_ && !paused_); }); - events_updated_ = false; + stream_cv_.wait(lk, [=]() { return exit_ || ( events_ready_ && !paused_); }); if (exit_) break; - Event cur_event(cur_which, cur_mono_time_); - auto eit = std::upper_bound(events_->begin(), events_->end(), &cur_event, Event::lessThan()); - if (eit == events_->end()) { + Event event(cur_which, cur_mono_time_, {}); + auto first = std::upper_bound(events_.cbegin(), events_.cend(), event); + if (first == events_.cend()) { rInfo("waiting for events..."); + events_ready_ = false; continue; } - uint64_t evt_start_ts = cur_mono_time_; - uint64_t loop_start_ts = nanos_since_boot(); + auto it = publishEvents(first, events_.cend()); - for (auto end = events_->end(); !updating_events_ && eit != end; ++eit) { - const Event *evt = (*eit); - cur_which = evt->which; - cur_mono_time_ = evt->mono_time; - setCurrentSegment(toSeconds(cur_mono_time_) / 60); - - if (sockets_[cur_which] != nullptr) { - // keep time - long etime = (cur_mono_time_ - evt_start_ts) / speed_; - long rtime = nanos_since_boot() - loop_start_ts; - long behind_ns = etime - rtime; - // if behind_ns is greater than 1 second, it means that an invalid segment is skipped by seeking/replaying - if (behind_ns >= 1 * 1e9 || speed_ != prev_replay_speed) { - // reset event start times - evt_start_ts = cur_mono_time_; - loop_start_ts = nanos_since_boot(); - prev_replay_speed = speed_; - } else if (behind_ns > 0) { - precise_nano_sleep(behind_ns); - } - - if (!evt->frame) { - publishMessage(evt); - } else if (camera_server_) { - if (speed_ > 1.0) { - camera_server_->waitForSent(); - } - publishFrame(evt); - } - } - } - // wait for frame to be sent before unlock.(frameReader may be deleted after unlock) + // Ensure frames are sent before unlocking to prevent race conditions if (camera_server_) { camera_server_->waitForSent(); } - if (eit == events_->end() && !hasFlag(REPLAY_FLAG_NO_LOOP)) { - int last_segment = segments_.empty() ? 0 : segments_.rbegin()->first; + if (it != events_.cend()) { + cur_which = it->which; + } else if (!hasFlag(REPLAY_FLAG_NO_LOOP)) { + // Check for loop end and restart if necessary + int last_segment = segments_.rbegin()->first; if (current_segment_ >= last_segment && isSegmentMerged(last_segment)) { rInfo("reaches the end of route, restart from beginning"); QMetaObject::invokeMethod(this, std::bind(&Replay::seekTo, this, 0, false), Qt::QueuedConnection); @@ -426,3 +468,51 @@ void Replay::stream() { } } } + +std::vector::const_iterator Replay::publishEvents(std::vector::const_iterator first, + std::vector::const_iterator last) { + uint64_t evt_start_ts = cur_mono_time_; + uint64_t loop_start_ts = nanos_since_boot(); + double prev_replay_speed = speed_; + + for (; !paused_ && first != last; ++first) { + const Event &evt = *first; + int segment = toSeconds(evt.mono_time) / 60; + + if (current_segment_ != segment) { + current_segment_ = segment; + QMetaObject::invokeMethod(this, &Replay::updateSegmentsCache, Qt::QueuedConnection); + } + + // Skip events if socket is not present + if (!sockets_[evt.which]) continue; + + cur_mono_time_ = evt.mono_time; + const uint64_t current_nanos = nanos_since_boot(); + const int64_t time_diff = (evt.mono_time - evt_start_ts) / speed_ - (current_nanos - loop_start_ts); + + // Reset timestamps for potential synchronization issues: + // - A negative time_diff may indicate slow execution or system wake-up, + // - A time_diff exceeding 1 second suggests a skipped segment. + if ((time_diff < -1e9 || time_diff >= 1e9) || speed_ != prev_replay_speed) { + evt_start_ts = evt.mono_time; + loop_start_ts = current_nanos; + prev_replay_speed = speed_; + } else if (time_diff > 0) { + precise_nano_sleep(time_diff, paused_); + } + + if (paused_) break; + + if (evt.eidx_segnum == -1) { + publishMessage(&evt); + } else if (camera_server_) { + if (speed_ > 1.0) { + camera_server_->waitForSent(); + } + publishFrame(&evt); + } + } + + return first; +} diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 1a74b69c3d..8b51ab054d 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -57,7 +58,7 @@ public: void pause(bool pause); void seekToFlag(FindFlag flag); void seekTo(double seconds, bool relative); - inline bool isPaused() const { return paused_; } + inline bool isPaused() const { return user_paused_; } // the filter is called in streaming thread.try to return quickly from it to avoid blocking streaming. // the filter function must return true if the event should be filtered. // otherwise it must return false. @@ -72,25 +73,28 @@ public: inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; } inline const Route* route() const { return route_.get(); } inline double currentSeconds() const { return double(cur_mono_time_ - route_start_ts_) / 1e9; } - inline QDateTime currentDateTime() const { return route_->datetime().addSecs(currentSeconds()); } + inline QDateTime routeDateTime() const { return route_date_time_; } + inline QDateTime currentDateTime() const { return route_date_time_.addSecs(currentSeconds()); } inline uint64_t routeStartTime() const { return route_start_ts_; } inline double toSeconds(uint64_t mono_time) const { return (mono_time - route_start_ts_) / 1e9; } inline int totalSeconds() const { return (!segments_.empty()) ? (segments_.rbegin()->first + 1) * 60 : 0; } inline void setSpeed(float speed) { speed_ = speed; } inline float getSpeed() const { return speed_; } - inline const std::vector *events() const { return events_.get(); } + inline const std::vector *events() const { return &events_; } inline const std::map> &segments() const { return segments_; } inline const std::string &carFingerprint() const { return car_fingerprint_; } inline const std::vector> getTimeline() { std::lock_guard lk(timeline_lock); - return timeline; + return timeline_; } signals: void streamStarted(); void segmentsMerged(); + void seeking(double sec); void seekedTo(double sec); - void qLogLoaded(int segnum, std::shared_ptr qlog); + void qLogLoaded(std::shared_ptr qlog); + void totalSecondsUpdated(double sec); protected slots: void segmentLoadFinished(bool success); @@ -98,46 +102,51 @@ protected slots: protected: typedef std::map> SegmentMap; std::optional find(FindFlag flag); + void pauseStreamThread(); void startStream(const Segment *cur_segment); - void stream(); - void setCurrentSegment(int n); - void queueSegment(); + void streamThread(); + void updateSegmentsCache(); + void loadSegmentInRange(SegmentMap::iterator begin, SegmentMap::iterator cur, SegmentMap::iterator end); void mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::iterator &end); - void updateEvents(const std::function& lambda); + void updateEvents(const std::function& update_events_function); + std::vector::const_iterator publishEvents(std::vector::const_iterator first, + std::vector::const_iterator last); void publishMessage(const Event *e); void publishFrame(const Event *e); void buildTimeline(); - inline bool isSegmentMerged(int n) { - return std::find(segments_merged_.begin(), segments_merged_.end(), n) != segments_merged_.end(); - } + void checkSeekProgress(); + inline bool isSegmentMerged(int n) const { return merged_segments_.count(n) > 0; } + pthread_t stream_thread_id = 0; QThread *stream_thread_ = nullptr; std::mutex stream_lock_; + bool user_paused_ = false; std::condition_variable stream_cv_; - std::atomic updating_events_ = false; std::atomic current_segment_ = 0; + std::optional seeking_to_; SegmentMap segments_; // the following variables must be protected with stream_lock_ std::atomic exit_ = false; - bool paused_ = false; - bool events_updated_ = false; + std::atomic paused_ = false; + bool events_ready_ = false; + QDateTime route_date_time_; uint64_t route_start_ts_ = 0; std::atomic cur_mono_time_ = 0; - std::unique_ptr> events_; - std::unique_ptr> new_events_; - std::vector segments_merged_; + std::vector events_; + std::set merged_segments_; // messaging SubMaster *sm = nullptr; std::unique_ptr pm; std::vector sockets_; + std::vector filters_; std::unique_ptr route_; std::unique_ptr camera_server_; std::atomic flags_ = REPLAY_FLAG_NONE; std::mutex timeline_lock; QFuture timeline_future; - std::vector> timeline; + std::vector> timeline_; std::string car_fingerprint_; std::atomic speed_ = 1.0; replayEventFilter event_filter = nullptr; diff --git a/tools/replay/route.cc b/tools/replay/route.cc index d0ddf7f3c8..e8e73459ea 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -18,11 +18,24 @@ Route::Route(const QString &route, const QString &data_dir) : data_dir_(data_dir } RouteIdentifier Route::parseRoute(const QString &str) { - QRegExp rx(R"(^(?:([a-z0-9]{16})([|_/]))?(\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2})(?:(--|/)(\d*))?$)"); - if (rx.indexIn(str) == -1) return {}; - - const QStringList list = rx.capturedTexts(); - return {.dongle_id = list[1], .timestamp = list[3], .segment_id = list[5].toInt(), .str = list[1] + "|" + list[3]}; + RouteIdentifier identifier = {}; + QRegularExpression rx(R"(^((?[a-z0-9]{16})[|_/])?(?.{20})((?--|/)(?((-?\d+(:(-?\d+)?)?)|(:-?\d+))))?$)"); + if (auto match = rx.match(str); match.hasMatch()) { + identifier.dongle_id = match.captured("dongle_id"); + identifier.timestamp = match.captured("timestamp"); + identifier.str = identifier.dongle_id + "|" + identifier.timestamp; + auto range_str = match.captured("range"); + if (auto separator = match.captured("separator"); separator == "/" && !range_str.isEmpty()) { + auto range = range_str.split(":"); + identifier.begin_segment = identifier.end_segment = range[0].toInt(); + if (range.size() == 2) { + identifier.end_segment = range[1].isEmpty() ? -1 : range[1].toInt(); + } + } else if (separator == "--") { + identifier.begin_segment = range_str.toInt(); + } + } + return identifier; } bool Route::load() { @@ -31,21 +44,42 @@ bool Route::load() { return false; } date_time_ = QDateTime::fromString(route_.timestamp, "yyyy-MM-dd--HH-mm-ss"); - return data_dir_.isEmpty() ? loadFromServer() : loadFromLocal(); + bool ret = data_dir_.isEmpty() ? loadFromServer() : loadFromLocal(); + if (ret) { + if (route_.begin_segment == -1) route_.begin_segment = segments_.rbegin()->first; + if (route_.end_segment == -1) route_.end_segment = segments_.rbegin()->first; + for (auto it = segments_.begin(); it != segments_.end(); /**/) { + if (it->first < route_.begin_segment || it->first > route_.end_segment) { + it = segments_.erase(it); + } else { + ++it; + } + } + } + return !segments_.empty(); } -bool Route::loadFromServer() { - QEventLoop loop; - HttpRequest http(nullptr, !Hardware::PC()); - QObject::connect(&http, &HttpRequest::requestDone, [&](const QString &json, bool success, QNetworkReply::NetworkError error) { - if (error == QNetworkReply::ContentAccessDenied || error == QNetworkReply::AuthenticationRequiredError) { - qWarning() << ">> Unauthorized. Authenticate with tools/lib/auth.py <<"; +bool Route::loadFromServer(int retries) { + for (int i = 1; i <= retries; ++i) { + QString result; + QEventLoop loop; + HttpRequest http(nullptr, !Hardware::PC()); + QObject::connect(&http, &HttpRequest::requestDone, [&loop, &result](const QString &json, bool success, QNetworkReply::NetworkError err) { + result = json; + loop.exit((int)err); + }); + http.sendRequest(CommaApi::BASE_URL + "/v1/route/" + route_.str + "/files"); + auto err = (QNetworkReply::NetworkError)loop.exec(); + if (err == QNetworkReply::NoError) { + return loadFromJson(result); + } else if (err == QNetworkReply::ContentAccessDenied || err == QNetworkReply::AuthenticationRequiredError) { + rWarning(">> Unauthorized. Authenticate with tools/lib/auth.py <<"); + return false; } - - loop.exit(success ? loadFromJson(json) : 0); - }); - http.sendRequest(CommaApi::BASE_URL + "/v1/route/" + route_.str + "/files"); - return loop.exec(); + rWarning("Retrying %d/%d", i, retries); + util::sleep_for(3000); + } + return false; } bool Route::loadFromJson(const QString &json) { @@ -62,15 +96,13 @@ bool Route::loadFromJson(const QString &json) { } bool Route::loadFromLocal() { - QDir log_dir(data_dir_); - for (const auto &folder : log_dir.entryList(QDir::Dirs | QDir::NoDot | QDir::NoDotDot, QDir::NoSort)) { - int pos = folder.lastIndexOf("--"); - if (pos != -1 && folder.left(pos) == route_.timestamp) { - const int seg_num = folder.mid(pos + 2).toInt(); - QDir segment_dir(log_dir.filePath(folder)); - for (const auto &f : segment_dir.entryList(QDir::Files)) { - addFileToSegment(seg_num, segment_dir.absoluteFilePath(f)); - } + QDirIterator it(data_dir_, {QString("%1--*").arg(route_.timestamp)}, QDir::Dirs | QDir::NoDotAndDotDot); + while (it.hasNext()) { + QString segment = it.next(); + const int seg_num = segment.mid(segment.lastIndexOf("--") + 2).toInt(); + QDir segment_dir(segment); + for (const auto &f : segment_dir.entryList(QDir::Files)) { + addFileToSegment(seg_num, segment_dir.absoluteFilePath(f)); } } return !segments_.empty(); @@ -99,7 +131,8 @@ void Route::addFileToSegment(int n, const QString &file) { // class Segment -Segment::Segment(int n, const SegmentFile &files, uint32_t flags) : seg_num(n), flags(flags) { +Segment::Segment(int n, const SegmentFile &files, uint32_t flags, const std::vector &filters) + : seg_num(n), flags(flags), filters_(filters) { // [RoadCam, DriverCam, WideRoadCam, log]. fallback to qcamera/qlog const std::array file_list = { (flags & REPLAY_FLAG_QCAMERA) || files.road_cam.isEmpty() ? files.qcamera : files.road_cam, @@ -127,9 +160,9 @@ void Segment::loadFile(int id, const std::string file) { bool success = false; if (id < MAX_CAMERAS) { frames[id] = std::make_unique(); - success = frames[id]->load(file, flags & REPLAY_FLAG_NO_HW_DECODER, &abort_, local_cache, 20 * 1024 * 1024, 3); + success = frames[id]->load((CameraType)id, file, flags & REPLAY_FLAG_NO_HW_DECODER, &abort_, local_cache, 20 * 1024 * 1024, 3); } else { - log = std::make_unique(); + log = std::make_unique(filters_); success = log->load(file, &abort_, local_cache, 0, 3); } diff --git a/tools/replay/route.h b/tools/replay/route.h index 4dad7a1f37..f956497804 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -14,7 +15,8 @@ struct RouteIdentifier { QString dongle_id; QString timestamp; - int segment_id; + int begin_segment = 0; + int end_segment = -1; QString str; }; @@ -41,7 +43,7 @@ public: protected: bool loadFromLocal(); - bool loadFromServer(); + bool loadFromServer(int retries = 3); bool loadFromJson(const QString &json); void addFileToSegment(int seg_num, const QString &file); RouteIdentifier route_ = {}; @@ -54,7 +56,7 @@ class Segment : public QObject { Q_OBJECT public: - Segment(int n, const SegmentFile &files, uint32_t flags); + Segment(int n, const SegmentFile &files, uint32_t flags, const std::vector &filters = {}); ~Segment(); inline bool isLoaded() const { return !loading_ && !abort_; } @@ -72,4 +74,5 @@ protected: std::atomic loading_ = 0; QFutureSynchronizer synchronizer_; uint32_t flags; + std::vector filters_; }; diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc index 92510053ef..6c005f1bd4 100644 --- a/tools/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -1,7 +1,6 @@ #include #include -#include #include #include "catch2/catch.hpp" @@ -67,7 +66,7 @@ TEST_CASE("LogReader") { corrupt_content.resize(corrupt_content.length() / 2); corrupt_content = decompressBZ2(corrupt_content); LogReader log; - REQUIRE(log.load((std::byte *)corrupt_content.data(), corrupt_content.size())); + REQUIRE(log.load(corrupt_content.data(), corrupt_content.size())); REQUIRE(log.events.size() > 0); } } @@ -88,7 +87,7 @@ void read_segment(int n, const SegmentFile &segment_file, uint32_t flags) { // test LogReader & FrameReader REQUIRE(segment.log->events.size() > 0); - REQUIRE(std::is_sorted(segment.log->events.begin(), segment.log->events.end(), Event::lessThan())); + REQUIRE(std::is_sorted(segment.log->events.begin(), segment.log->events.end())); for (auto cam : ALL_CAMERAS) { auto &fr = segment.frames[cam]; @@ -158,63 +157,20 @@ TEST_CASE("Remote route") { } } -// helper class for unit tests -class TestReplay : public Replay { - public: - TestReplay(const QString &route, uint32_t flags = REPLAY_FLAG_NO_FILE_CACHE | REPLAY_FLAG_NO_VIPC) : Replay(route, {}, {}, nullptr, flags) {} - void test_seek(); - void testSeekTo(int seek_to); -}; - -void TestReplay::testSeekTo(int seek_to) { - seekTo(seek_to, false); - - while (true) { - std::unique_lock lk(stream_lock_); - stream_cv_.wait(lk, [=]() { return events_updated_ == true; }); - events_updated_ = false; - if (cur_mono_time_ != route_start_ts_ + seek_to * 1e9) { - // wake up by the previous merging, skip it. - continue; - } - - Event cur_event(cereal::Event::Which::INIT_DATA, cur_mono_time_); - auto eit = std::upper_bound(events_->begin(), events_->end(), &cur_event, Event::lessThan()); - if (eit == events_->end()) { - qDebug() << "waiting for events..."; - continue; - } - - REQUIRE(std::is_sorted(events_->begin(), events_->end(), Event::lessThan())); - const int seek_to_segment = seek_to / 60; - const int event_seconds = ((*eit)->mono_time - route_start_ts_) / 1e9; - current_segment_ = event_seconds / 60; - INFO("seek to [" << seek_to << "s segment " << seek_to_segment << "], events [" << event_seconds << "s segment" << current_segment_ << "]"); - REQUIRE(event_seconds >= seek_to); - if (event_seconds > seek_to) { - auto it = segments_.lower_bound(seek_to_segment); - REQUIRE(it->first == current_segment_); - } - break; - } -} - -void TestReplay::test_seek() { - // create a dummy stream thread - stream_thread_ = new QThread(this); +TEST_CASE("seek_to") { QEventLoop loop; - std::thread thread = std::thread([&]() { - for (int i = 0; i < 10; ++i) { - testSeekTo(util::random_int(0, 2 * 60)); - } + int seek_to = util::random_int(0, 2 * 59); + Replay replay(DEMO_ROUTE, {}, {}, nullptr, REPLAY_FLAG_NO_VIPC); + + QObject::connect(&replay, &Replay::seekedTo, [&](double sec) { + INFO("seek to " << seek_to << "s seeked to" << sec); + REQUIRE(sec >= seek_to); loop.quit(); }); - loop.exec(); - thread.join(); -} -TEST_CASE("Replay") { - TestReplay replay(DEMO_ROUTE); REQUIRE(replay.load()); - replay.test_seek(); + replay.start(); + replay.seekTo(seek_to, false); + + loop.exec(); } diff --git a/tools/replay/ui.py b/tools/replay/ui.py index be80166e76..b1fe70ef3c 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -10,14 +10,15 @@ import pygame import cereal.messaging as messaging from openpilot.common.numpy_fast import clip from openpilot.common.basedir import BASEDIR -from openpilot.tools.replay.lib.ui_helpers import (_BB_TO_FULL_FRAME, UP, - _INTRINSICS, BLACK, GREEN, +from openpilot.common.transformations.camera import DEVICE_CAMERAS +from openpilot.tools.replay.lib.ui_helpers import (UP, + BLACK, GREEN, YELLOW, Calibration, get_blank_lid_overlay, init_plots, maybe_update_radar_points, plot_lead, plot_model, pygame_modules_have_loaded) -from cereal.visionipc import VisionIpcClient, VisionStreamType +from msgq.visionipc import VisionIpcClient, VisionStreamType os.environ['BASEDIR'] = BASEDIR @@ -55,7 +56,7 @@ def ui_thread(addr): top_down_surface = pygame.surface.Surface((UP.lidar_x, UP.lidar_y), 0, 8) sm = messaging.SubMaster(['carState', 'longitudinalPlan', 'carControl', 'radarState', 'liveCalibration', 'controlsState', - 'liveTracks', 'modelV2', 'liveParameters'], addr=addr) + 'liveTracks', 'modelV2', 'liveParameters', 'roadCameraState'], addr=addr) img = np.zeros((480, 640, 3), dtype='uint8') imgff = None @@ -100,8 +101,11 @@ def ui_thread(addr): draw_plots = init_plots(plot_arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_colors, plot_styles) vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_ROAD, True) - while 1: - list(pygame.event.get()) + while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() screen.fill((64, 64, 64)) lid_overlay = lid_overlay_blank.copy() @@ -112,20 +116,27 @@ def ui_thread(addr): vipc_client.connect(True) yuv_img_raw = vipc_client.recv() - if yuv_img_raw is None or not yuv_img_raw.data.any(): continue + sm.update(0) + + camera = DEVICE_CAMERAS[("tici", str(sm['roadCameraState'].sensor))] + imgff = np.frombuffer(yuv_img_raw.data, dtype=np.uint8).reshape((len(yuv_img_raw.data) // vipc_client.stride, vipc_client.stride)) num_px = vipc_client.width * vipc_client.height rgb = cv2.cvtColor(imgff[:vipc_client.height * 3 // 2, :vipc_client.width], cv2.COLOR_YUV2RGB_NV12) - zoom_matrix = _BB_TO_FULL_FRAME[num_px] + qcam = "QCAM" in os.environ + bb_scale = (528 if qcam else camera.fcam.width) / 640. + calib_scale = camera.fcam.width / 640. + zoom_matrix = np.asarray([ + [bb_scale, 0., 0.], + [0., bb_scale, 0.], + [0., 0., 1.]]) cv2.warpAffine(rgb, zoom_matrix[:2], (img.shape[1], img.shape[0]), dst=img, flags=cv2.WARP_INVERSE_MAP) - intrinsic_matrix = _INTRINSICS[num_px] - - sm.update(0) + intrinsic_matrix = camera.fcam.intrinsics w = sm['controlsState'].lateralControlState.which() if w == 'lqrStateDEPRECATED': @@ -147,7 +158,6 @@ def ui_thread(addr): # TODO brake is deprecated plot_arr[-1, name_to_arr_idx['computer_brake']] = clip(-sm['carControl'].actuators.accel/4.0, 0.0, 1.0) plot_arr[-1, name_to_arr_idx['v_ego']] = sm['carState'].vEgo - plot_arr[-1, name_to_arr_idx['v_pid']] = sm['controlsState'].vPid plot_arr[-1, name_to_arr_idx['v_cruise']] = sm['carState'].cruiseState.speed plot_arr[-1, name_to_arr_idx['a_ego']] = sm['carState'].aEgo @@ -165,7 +175,7 @@ def ui_thread(addr): if sm.updated['liveCalibration'] and num_px: rpyCalib = np.asarray(sm['liveCalibration'].rpyCalib) - calibration = Calibration(num_px, rpyCalib, intrinsic_matrix) + calibration = Calibration(num_px, rpyCalib, intrinsic_matrix, calib_scale) # *** blits *** pygame.surfarray.blit_array(camera_surface, img.swapaxes(0, 1)) diff --git a/tools/replay/util.cc b/tools/replay/util.cc index b4f72d0530..a08b3b3d5e 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -4,10 +4,11 @@ #include #include +#include +#include +#include #include #include -#include -#include #include #include #include @@ -158,7 +159,10 @@ size_t getRemoteFileSize(const std::string &url, std::atomic *abort) { int still_running = 1; while (still_running > 0 && !(abort && *abort)) { CURLMcode mc = curl_multi_perform(cm, &still_running); - if (!mc) curl_multi_wait(cm, nullptr, 0, 1000, nullptr); + if (mc != CURLM_OK) break; + if (still_running > 0) { + curl_multi_wait(cm, nullptr, 0, 1000, nullptr); + } } double content_length = -1; @@ -208,10 +212,20 @@ bool httpDownload(const std::string &url, T &buf, size_t chunk_size, size_t cont } int still_running = 1; + size_t prev_written = 0; while (still_running > 0 && !(abort && *abort)) { - curl_multi_wait(cm, nullptr, 0, 1000, nullptr); - curl_multi_perform(cm, &still_running); - download_stats.update(url, written); + CURLMcode mc = curl_multi_perform(cm, &still_running); + if (mc != CURLM_OK) { + break; + } + if (still_running > 0) { + curl_multi_wait(cm, nullptr, 0, 1000, nullptr); + } + + if (((written - prev_written) / (double)content_length) >= 0.01) { + download_stats.update(url, written); + prev_written = written; + } } CURLMsg *msg; @@ -298,26 +312,28 @@ std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic BZ2_bzDecompressEnd(&strm); if (bzerror == BZ_STREAM_END && !(abort && *abort)) { out.resize(strm.total_out_lo32); + out.shrink_to_fit(); return out; } return {}; } -void precise_nano_sleep(long sleep_ns) { - const long estimate_ns = 1 * 1e6; // 1ms - struct timespec req = {.tv_nsec = estimate_ns}; - uint64_t start_sleep = nanos_since_boot(); - while (sleep_ns > estimate_ns) { - nanosleep(&req, nullptr); - uint64_t end_sleep = nanos_since_boot(); - sleep_ns -= (end_sleep - start_sleep); - start_sleep = end_sleep; - } - // spin wait - if (sleep_ns > 0) { - while ((nanos_since_boot() - start_sleep) <= sleep_ns) { - std::this_thread::yield(); - } +void precise_nano_sleep(int64_t nanoseconds, std::atomic &should_exit) { + struct timespec req, rem; + req.tv_sec = nanoseconds / 1000000000; + req.tv_nsec = nanoseconds % 1000000000; + while (!should_exit) { +#ifdef __APPLE_ + int ret = nanosleep(&req, &rem); + if (ret == 0 || errno != EINTR) + break; +#else + int ret = clock_nanosleep(CLOCK_MONOTONIC, 0, &req, &rem); + if (ret == 0 || ret != EINTR) + break; +#endif + // Retry sleep if interrupted by a signal + req = rem; } } @@ -329,3 +345,26 @@ std::string sha256(const std::string &str) { SHA256_Final(hash, &sha256); return util::hexdump(hash, SHA256_DIGEST_LENGTH); } + +// MonotonicBuffer + +void *MonotonicBuffer::allocate(size_t bytes, size_t alignment) { + assert(bytes > 0); + void *p = std::align(alignment, bytes, current_buf, available); + if (p == nullptr) { + available = next_buffer_size = std::max(next_buffer_size, bytes); + current_buf = buffers.emplace_back(std::aligned_alloc(alignment, next_buffer_size)); + next_buffer_size *= growth_factor; + p = current_buf; + } + + current_buf = (char *)current_buf + bytes; + available -= bytes; + return p; +} + +MonotonicBuffer::~MonotonicBuffer() { + for (auto buf : buffers) { + free(buf); + } +} diff --git a/tools/replay/util.h b/tools/replay/util.h index 6c808095e8..97516e1814 100644 --- a/tools/replay/util.h +++ b/tools/replay/util.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -20,8 +21,23 @@ void logMessage(ReplyMsgType type, const char* fmt, ...); #define rWarning(fmt, ...) ::logMessage(ReplyMsgType::Warning, fmt, ## __VA_ARGS__) #define rError(fmt, ...) ::logMessage(ReplyMsgType::Critical , fmt, ## __VA_ARGS__) +class MonotonicBuffer { +public: + MonotonicBuffer(size_t initial_size) : next_buffer_size(initial_size) {} + ~MonotonicBuffer(); + void *allocate(size_t bytes, size_t alignment = 16ul); + void deallocate(void *p) {} + +private: + void *current_buf = nullptr; + size_t next_buffer_size = 0; + size_t available = 0; + std::deque buffers; + static constexpr float growth_factor = 1.5; +}; + std::string sha256(const std::string &str); -void precise_nano_sleep(long sleep_ns); +void precise_nano_sleep(int64_t nanoseconds, std::atomic &should_exit); std::string decompressBZ2(const std::string &in, std::atomic *abort = nullptr); std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); std::string getUrlWithoutQuery(const std::string &url); diff --git a/tools/rerun/run.py b/tools/rerun/run.py new file mode 100755 index 0000000000..9975478a73 --- /dev/null +++ b/tools/rerun/run.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +import sys +import argparse +import multiprocessing +import rerun as rr +import rerun.blueprint as rrb +from functools import partial + +from openpilot.tools.lib.logreader import LogReader +from cereal.services import SERVICE_LIST + + +NUM_CPUS = multiprocessing.cpu_count() +DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" + +def log_msg(msg, parent_key=''): + stack = [(msg, parent_key)] + while stack: + current_msg, current_parent_key = stack.pop() + if isinstance(current_msg, list): + for index, item in enumerate(current_msg): + new_key = f"{current_parent_key}/{index}" + if isinstance(item, (int, float)): + rr.log(str(new_key), rr.Scalar(item)) + elif isinstance(item, dict): + stack.append((item, new_key)) + elif isinstance(current_msg, dict): + for key, value in current_msg.items(): + new_key = f"{current_parent_key}/{key}" + if isinstance(value, (int, float)): + rr.log(str(new_key), rr.Scalar(value)) + elif isinstance(value, dict): + stack.append((value, new_key)) + elif isinstance(value, list): + for index, item in enumerate(value): + if isinstance(item, (int, float)): + rr.log(f"{new_key}/{index}", rr.Scalar(item)) + else: + pass # Not a plottable value + +def createBlueprint(): + blueprint = None + timeSeriesViews = [] + for topic in sorted(SERVICE_LIST.keys()): + timeSeriesViews.append(rrb.TimeSeriesView(name=topic, origin=f"/{topic}/", visible=False)) + rr.log(topic, rr.SeriesLine(name=topic), timeless=True) + blueprint = rrb.Blueprint(rrb.Grid(rrb.Vertical(*timeSeriesViews,rrb.SelectionPanel(expanded=False),rrb.TimePanel(expanded=False)), + rrb.Spatial2DView(name="thumbnail", origin="/thumbnail"))) + return blueprint + +def log_thumbnail(thumbnailMsg): + bytesImgData = thumbnailMsg.get('thumbnail') + rr.log("/thumbnail", rr.ImageEncoded(contents=bytesImgData)) + +@rr.shutdown_at_exit +def process(blueprint, lr): + rr.init("rerun_test") + rr.connect(default_blueprint=blueprint) + + ret = [] + for msg in lr: + ret.append(msg) + rr.set_time_nanos("TIMELINE", msg.logMonoTime) + if msg.which() != "thumbnail": + log_msg(msg.to_dict()[msg.which()], msg.which()) + else: + log_thumbnail(msg.to_dict()[msg.which()]) + return ret + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="A helper to run rerun on openpilot routes", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") + parser.add_argument("route_or_segment_name", nargs='?', help="The route or segment name to plot") + + if len(sys.argv) == 1: + parser.print_help() + sys.exit() + + args = parser.parse_args() + + blueprint = createBlueprint() + rr.init("rerun_test") + rr.spawn(connect=False) # child processes stream data to Viewer + + route_or_segment_name = DEMO_ROUTE if args.demo else args.route_or_segment_name.strip() + print("Getting route log paths") + lr = LogReader(route_or_segment_name) + lr.run_across_segments(NUM_CPUS, partial(process, blueprint)) diff --git a/tools/sim/Dockerfile.sim b/tools/sim/Dockerfile.sim deleted file mode 100644 index cb573b33f2..0000000000 --- a/tools/sim/Dockerfile.sim +++ /dev/null @@ -1,37 +0,0 @@ -FROM ghcr.io/commaai/openpilot-base:latest - -RUN apt-get update && apt-get install -y --no-install-recommends \ - tmux \ - vim \ - && rm -rf /var/lib/apt/lists/* - -# get same tmux config used on NEOS for debugging -RUN cd $HOME && \ - curl -O https://raw.githubusercontent.com/commaai/eon-neos-builder/master/devices/eon/home/.tmux.conf - -ENV OPENPILOT_PATH /tmp/openpilot -ENV PYTHONPATH ${OPENPILOT_PATH}:${PYTHONPATH} - -RUN mkdir -p ${OPENPILOT_PATH} -WORKDIR ${OPENPILOT_PATH} - -COPY SConstruct ${OPENPILOT_PATH} - -COPY ./openpilot ${OPENPILOT_PATH}/openpilot -COPY ./body ${OPENPILOT_PATH}/body -COPY ./third_party ${OPENPILOT_PATH}/third_party -COPY ./site_scons ${OPENPILOT_PATH}/site_scons -COPY ./rednose ${OPENPILOT_PATH}/rednose -COPY ./rednose_repo/site_scons ${OPENPILOT_PATH}/rednose_repo/site_scons -COPY ./common ${OPENPILOT_PATH}/common -COPY ./opendbc ${OPENPILOT_PATH}/opendbc -COPY ./cereal ${OPENPILOT_PATH}/cereal -COPY ./panda ${OPENPILOT_PATH}/panda -COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive -COPY ./system ${OPENPILOT_PATH}/system -COPY ./tools ${OPENPILOT_PATH}/tools -COPY ./release ${OPENPILOT_PATH}/release - -RUN --mount=type=bind,source=.ci_cache/scons_cache,target=/tmp/scons_cache,rw scons -j$(nproc) --cache-readonly - -RUN python -c "from openpilot.selfdrive.test.helpers import set_params_enabled; set_params_enabled()" diff --git a/tools/sim/Dockerfile.sim_nvidia b/tools/sim/Dockerfile.sim_nvidia deleted file mode 100644 index 5e5dd263da..0000000000 --- a/tools/sim/Dockerfile.sim_nvidia +++ /dev/null @@ -1,21 +0,0 @@ -FROM ubuntu:20.04 - -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - apt-utils \ - sudo \ - ssh \ - curl \ - ca-certificates \ - git \ - git-lfs && \ - rm -rf /var/lib/apt/lists/* - -RUN curl -fsSL https://get.docker.com -o get-docker.sh && \ - sudo sh get-docker.sh && \ - distribution=$(. /etc/os-release;echo $ID$VERSION_ID) && \ - curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - && \ - curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list && \ - sudo apt-get update && \ - sudo apt-get install -y nvidia-docker2 diff --git a/tools/sim/bridge/common.py b/tools/sim/bridge/common.py index 10e2a055a3..64407143f7 100644 --- a/tools/sim/bridge/common.py +++ b/tools/sim/bridge/common.py @@ -2,6 +2,8 @@ import signal import threading import functools +from collections import namedtuple +from enum import Enum from multiprocessing import Process, Queue, Value from abc import ABC, abstractmethod @@ -13,8 +15,17 @@ from openpilot.selfdrive.car.honda.values import CruiseButtons from openpilot.tools.sim.lib.common import SimulatorState, World from openpilot.tools.sim.lib.simulated_car import SimulatedCar from openpilot.tools.sim.lib.simulated_sensors import SimulatedSensors -from openpilot.tools.sim.lib.keyboard_ctrl import KEYBOARD_HELP +QueueMessage = namedtuple("QueueMessage", ["type", "info"], defaults=[None]) + +class QueueMessageType(Enum): + START_STATUS = 0 + CONTROL_COMMAND = 1 + TERMINATION_INFO = 2 + CLOSE_STATUS = 3 + +def control_cmd_gen(cmd: str): + return QueueMessage(QueueMessageType.CONTROL_COMMAND, cmd) def rk_loop(function, hz, exit_event: threading.Event): rk = Ratekeeper(hz, None) @@ -29,6 +40,7 @@ class SimulatorBridge(ABC): def __init__(self, dual_camera, high_quality): set_params_enabled() self.params = Params() + self.params.put_bool("ExperimentalLongitudinalEnabled", True) self.rk = Ratekeeper(100, None) @@ -46,6 +58,9 @@ class SimulatorBridge(ABC): self.world: World | None = None self.past_startup_engaged = False + self.startup_button_prev = True + + self.test_run = False def _on_shutdown(self, signal, frame): self.shutdown() @@ -57,14 +72,14 @@ class SimulatorBridge(ABC): try: self._run(q) finally: - self.close() + self.close("bridge terminated") - def close(self): + def close(self, reason): self.started.value = False self._exit_event.set() if self.world is not None: - self.world.close() + self.world.close(reason) def run(self, queue, retries=-1): bridge_p = Process(name="bridge", target=self.bridge_keep_alive, args=(queue, retries)) @@ -74,19 +89,16 @@ class SimulatorBridge(ABC): def print_status(self): print( f""" -Keyboard Commands: -{KEYBOARD_HELP} - State: Ignition: {self.simulator_state.ignition} Engaged: {self.simulator_state.is_engaged} """) @abstractmethod - def spawn_world(self) -> World: + def spawn_world(self, q: Queue) -> World: pass def _run(self, q: Queue): - self.world = self.spawn_world() + self.world = self.spawn_world(q) self.simulated_car = SimulatedCar() self.simulated_sensors = SimulatedSensors(self.dual_camera) @@ -116,33 +128,34 @@ Ignition: {self.simulator_state.ignition} Engaged: {self.simulator_state.is_enga # Read manual controls if not q.empty(): message = q.get() - m = message.split('_') - if m[0] == "steer": - steer_manual = float(m[1]) - elif m[0] == "throttle": - throttle_manual = float(m[1]) - elif m[0] == "brake": - brake_manual = float(m[1]) - elif m[0] == "cruise": - if m[1] == "down": - self.simulator_state.cruise_button = CruiseButtons.DECEL_SET - elif m[1] == "up": - self.simulator_state.cruise_button = CruiseButtons.RES_ACCEL - elif m[1] == "cancel": - self.simulator_state.cruise_button = CruiseButtons.CANCEL - elif m[1] == "main": - self.simulator_state.cruise_button = CruiseButtons.MAIN - elif m[0] == "blinker": - if m[1] == "left": - self.simulator_state.left_blinker = True - elif m[1] == "right": - self.simulator_state.right_blinker = True - elif m[0] == "ignition": - self.simulator_state.ignition = not self.simulator_state.ignition - elif m[0] == "reset": - self.world.reset() - elif m[0] == "quit": - break + if message.type == QueueMessageType.CONTROL_COMMAND: + m = message.info.split('_') + if m[0] == "steer": + steer_manual = float(m[1]) + elif m[0] == "throttle": + throttle_manual = float(m[1]) + elif m[0] == "brake": + brake_manual = float(m[1]) + elif m[0] == "cruise": + if m[1] == "down": + self.simulator_state.cruise_button = CruiseButtons.DECEL_SET + elif m[1] == "up": + self.simulator_state.cruise_button = CruiseButtons.RES_ACCEL + elif m[1] == "cancel": + self.simulator_state.cruise_button = CruiseButtons.CANCEL + elif m[1] == "main": + self.simulator_state.cruise_button = CruiseButtons.MAIN + elif m[0] == "blinker": + if m[1] == "left": + self.simulator_state.left_blinker = True + elif m[1] == "right": + self.simulator_state.right_blinker = True + elif m[0] == "ignition": + self.simulator_state.ignition = not self.simulator_state.ignition + elif m[0] == "reset": + self.world.reset() + elif m[0] == "quit": + break self.simulator_state.user_brake = brake_manual self.simulator_state.user_gas = throttle_manual @@ -164,20 +177,26 @@ Ignition: {self.simulator_state.ignition} Engaged: {self.simulator_state.is_enga self.past_startup_engaged = True elif not self.past_startup_engaged and controlsState.engageable: - self.simulator_state.cruise_button = CruiseButtons.DECEL_SET # force engagement on startup + self.simulator_state.cruise_button = CruiseButtons.DECEL_SET if self.startup_button_prev else CruiseButtons.MAIN # force engagement on startup + self.startup_button_prev = not self.startup_button_prev throttle_out = throttle_op if self.simulator_state.is_engaged else throttle_manual brake_out = brake_op if self.simulator_state.is_engaged else brake_manual steer_out = steer_op if self.simulator_state.is_engaged else steer_manual self.world.apply_controls(steer_out, throttle_out, brake_out) + self.world.read_state() self.world.read_sensors(self.simulator_state) + if self.world.exit_event.is_set(): + self.shutdown() + if self.rk.frame % self.TICKS_PER_FRAME == 0: self.world.tick() self.world.read_cameras() - if self.rk.frame % 25 == 0: + # don't print during test, so no print/IO Block between OP and metadrive processes + if not self.test_run and self.rk.frame % 25 == 0: self.print_status() self.started.value = True diff --git a/tools/sim/bridge/metadrive/metadrive_bridge.py b/tools/sim/bridge/metadrive/metadrive_bridge.py index c94fca2b95..74dc1210b9 100644 --- a/tools/sim/bridge/metadrive/metadrive_bridge.py +++ b/tools/sim/bridge/metadrive/metadrive_bridge.py @@ -1,48 +1,15 @@ -import numpy as np +import math +from multiprocessing import Queue -from metadrive.component.sensors.rgb_camera import RGBCamera from metadrive.component.sensors.base_camera import _cuda_enable from metadrive.component.map.pg_map import MapGenerateMethod -from panda3d.core import Texture, GraphicsOutput from openpilot.tools.sim.bridge.common import SimulatorBridge +from openpilot.tools.sim.bridge.metadrive.metadrive_common import RGBCameraRoad, RGBCameraWide from openpilot.tools.sim.bridge.metadrive.metadrive_world import MetaDriveWorld from openpilot.tools.sim.lib.camerad import W, H - -class CopyRamRGBCamera(RGBCamera): - """Camera which copies its content into RAM during the render process, for faster image grabbing.""" - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.cpu_texture = Texture() - self.buffer.addRenderTexture(self.cpu_texture, GraphicsOutput.RTMCopyRam) - - def get_rgb_array_cpu(self): - origin_img = self.cpu_texture - img = np.frombuffer(origin_img.getRamImage().getData(), dtype=np.uint8) - img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), -1)) - img = img[:,:,:3] # RGBA to RGB - # img = np.swapaxes(img, 1, 0) - img = img[::-1] # Flip on vertical axis - return img - - -class RGBCameraWide(CopyRamRGBCamera): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - lens = self.get_lens() - lens.setFov(120) - lens.setNear(0.1) - -class RGBCameraRoad(CopyRamRGBCamera): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - lens = self.get_lens() - lens.setFov(40) - lens.setNear(0.1) - - def straight_block(length): return { "id": "S", @@ -61,20 +28,21 @@ def curve_block(length, angle=45, direction=0): } def create_map(track_size=60): + curve_len = track_size * 2 return dict( type=MapGenerateMethod.PG_MAP_FILE, lane_num=2, - lane_width=3.5, + lane_width=4.5, config=[ None, straight_block(track_size), - curve_block(track_size*2, 90), + curve_block(curve_len, 90), straight_block(track_size), - curve_block(track_size*2, 90), + curve_block(curve_len, 90), straight_block(track_size), - curve_block(track_size*2, 90), + curve_block(curve_len, 90), straight_block(track_size), - curve_block(track_size*2, 90), + curve_block(curve_len, 90), ] ) @@ -82,12 +50,14 @@ def create_map(track_size=60): class MetaDriveBridge(SimulatorBridge): TICKS_PER_FRAME = 5 - def __init__(self, dual_camera, high_quality): - self.should_render = False - + def __init__(self, dual_camera, high_quality, test_duration=math.inf, test_run=False): super().__init__(dual_camera, high_quality) - def spawn_world(self): + self.should_render = False + self.test_run = test_run + self.test_duration = test_duration if self.test_run else math.inf + + def spawn_world(self, queue: Queue): sensors = { "rgb_road": (RGBCameraRoad, W, H, ) } @@ -109,6 +79,7 @@ class MetaDriveBridge(SimulatorBridge): on_continuous_line_done=False, crash_vehicle_done=False, crash_object_done=False, + arrive_dest_done=False, traffic_density=0.0, # traffic is incredibly expensive map_config=create_map(), decision_repeat=1, @@ -116,4 +87,4 @@ class MetaDriveBridge(SimulatorBridge): preload_models=False ) - return MetaDriveWorld(config, self.dual_camera) + return MetaDriveWorld(queue, config, self.test_duration, self.test_run, self.dual_camera) diff --git a/tools/sim/bridge/metadrive/metadrive_common.py b/tools/sim/bridge/metadrive/metadrive_common.py new file mode 100644 index 0000000000..42a7eb60dd --- /dev/null +++ b/tools/sim/bridge/metadrive/metadrive_common.py @@ -0,0 +1,37 @@ +import numpy as np + +from metadrive.component.sensors.rgb_camera import RGBCamera +from panda3d.core import Texture, GraphicsOutput + + +class CopyRamRGBCamera(RGBCamera): + """Camera which copies its content into RAM during the render process, for faster image grabbing.""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.cpu_texture = Texture() + self.buffer.addRenderTexture(self.cpu_texture, GraphicsOutput.RTMCopyRam) + + def get_rgb_array_cpu(self): + origin_img = self.cpu_texture + img = np.frombuffer(origin_img.getRamImage().getData(), dtype=np.uint8) + img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), -1)) + img = img[:,:,:3] # RGBA to RGB + # img = np.swapaxes(img, 1, 0) + img = img[::-1] # Flip on vertical axis + return img + + +class RGBCameraWide(CopyRamRGBCamera): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + lens = self.get_lens() + lens.setFov(120) + lens.setNear(0.1) + + +class RGBCameraRoad(CopyRamRGBCamera): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + lens = self.get_lens() + lens.setFov(40) + lens.setNear(0.1) diff --git a/tools/sim/bridge/metadrive/metadrive_process.py b/tools/sim/bridge/metadrive/metadrive_process.py index aa6ae57976..38c13a1434 100644 --- a/tools/sim/bridge/metadrive/metadrive_process.py +++ b/tools/sim/bridge/metadrive/metadrive_process.py @@ -1,4 +1,5 @@ import math +import time import numpy as np from collections import namedtuple @@ -19,9 +20,10 @@ C3_POSITION = Vec3(0.0, 0, 1.22) C3_HPR = Vec3(0, 0,0) -metadrive_state = namedtuple("metadrive_state", ["velocity", "position", "bearing", "steering_angle"]) +metadrive_simulation_state = namedtuple("metadrive_simulation_state", ["running", "done", "done_info"]) +metadrive_vehicle_state = namedtuple("metadrive_vehicle_state", ["velocity", "position", "bearing", "steering_angle"]) -def apply_metadrive_patches(): +def apply_metadrive_patches(arrive_dest_done=True): # By default, metadrive won't try to use cuda images unless it's used as a sensor for vehicles, so patch that in def add_image_sensor_patched(self, name: str, cls, args): if self.global_config["image_on_cuda"]:# and name == self.global_config["vehicle_config"]["image_source"]: @@ -43,11 +45,14 @@ def apply_metadrive_patches(): def arrive_destination_patch(self, *args, **kwargs): return False - MetaDriveEnv._is_arrive_destination = arrive_destination_patch + if not arrive_dest_done: + MetaDriveEnv._is_arrive_destination = arrive_destination_patch def metadrive_process(dual_camera: bool, config: dict, camera_array, wide_camera_array, image_lock, - controls_recv: Connection, state_send: Connection, exit_event): - apply_metadrive_patches() + controls_recv: Connection, simulation_state_send: Connection, vehicle_state_send: Connection, + exit_event, op_engaged, test_duration, test_run): + arrive_dest_done = config.pop("arrive_dest_done", True) + apply_metadrive_patches(arrive_dest_done) road_image = np.frombuffer(camera_array.get_obj(), dtype=np.uint8).reshape((H, W, 3)) if dual_camera: @@ -56,18 +61,34 @@ def metadrive_process(dual_camera: bool, config: dict, camera_array, wide_camera env = MetaDriveEnv(config) + def get_current_lane_info(vehicle): + _, lane_info, on_lane = vehicle.navigation._get_current_lane(vehicle) + lane_idx = lane_info[2] if lane_info is not None else None + return lane_idx, on_lane + def reset(): env.reset() env.vehicle.config["max_speed_km_h"] = 1000 + lane_idx_prev, _ = get_current_lane_info(env.vehicle) - reset() + simulation_state = metadrive_simulation_state( + running=True, + done=False, + done_info=None, + ) + simulation_state_send.send(simulation_state) + + return lane_idx_prev + + lane_idx_prev = reset() + start_time = None def get_cam_as_rgb(cam): cam = env.engine.sensors[cam] cam.get_cam().reparentTo(env.vehicle.origin) cam.get_cam().setPos(C3_POSITION) cam.get_cam().setHpr(C3_HPR) - img = cam.perceive(clip=False) + img = cam.perceive(to_float=False) if type(img) != np.ndarray: img = img.get() # convert cupy array to numpy return img @@ -78,14 +99,13 @@ def metadrive_process(dual_camera: bool, config: dict, camera_array, wide_camera vc = [0,0] while not exit_event.is_set(): - state = metadrive_state( + vehicle_state = metadrive_vehicle_state( velocity=vec3(x=float(env.vehicle.velocity[0]), y=float(env.vehicle.velocity[1]), z=0), position=env.vehicle.position, bearing=float(math.degrees(env.vehicle.heading_theta)), steering_angle=env.vehicle.steering * env.vehicle.MAX_STEERING ) - - state_send.send(state) + vehicle_state_send.send(vehicle_state) if controls_recv.poll(0): while controls_recv.poll(0): @@ -97,13 +117,34 @@ def metadrive_process(dual_camera: bool, config: dict, camera_array, wide_camera vc = [steer_metadrive, gas] if should_reset: - reset() + lane_idx_prev = reset() + start_time = None + + is_engaged = op_engaged.is_set() + if is_engaged and start_time is None: + start_time = time.monotonic() if rk.frame % 5 == 0: - obs, _, terminated, _, info = env.step(vc) + _, _, terminated, _, _ = env.step(vc) + timeout = True if start_time is not None and time.monotonic() - start_time >= test_duration else False + lane_idx_curr, on_lane = get_current_lane_info(env.vehicle) + out_of_lane = lane_idx_curr != lane_idx_prev or not on_lane + lane_idx_prev = lane_idx_curr - if terminated: - reset() + if terminated or ((out_of_lane or timeout) and test_run): + if terminated: + done_result = env.done_function("default_agent") + elif out_of_lane: + done_result = (True, {"out_of_lane" : True}) + elif timeout: + done_result = (True, {"timeout" : True}) + + simulation_state = metadrive_simulation_state( + running=False, + done=done_result[0], + done_info=done_result[1], + ) + simulation_state_send.send(simulation_state) if dual_camera: wide_road_image[...] = get_cam_as_rgb("rgb_wide") diff --git a/tools/sim/bridge/metadrive/metadrive_world.py b/tools/sim/bridge/metadrive/metadrive_world.py index 4cbe64393e..5bb4555dc1 100644 --- a/tools/sim/bridge/metadrive/metadrive_world.py +++ b/tools/sim/bridge/metadrive/metadrive_world.py @@ -5,14 +5,18 @@ import numpy as np import time from multiprocessing import Pipe, Array -from openpilot.tools.sim.bridge.metadrive.metadrive_process import metadrive_process, metadrive_state + +from openpilot.tools.sim.bridge.common import QueueMessage, QueueMessageType +from openpilot.tools.sim.bridge.metadrive.metadrive_process import (metadrive_process, metadrive_simulation_state, + metadrive_vehicle_state) from openpilot.tools.sim.lib.common import SimulatorState, World from openpilot.tools.sim.lib.camerad import W, H class MetaDriveWorld(World): - def __init__(self, config, dual_camera = False): + def __init__(self, status_q, config, test_duration, test_run, dual_camera=False): super().__init__(dual_camera) + self.status_q = status_q self.camera_array = Array(ctypes.c_uint8, W*H*3) self.road_image = np.frombuffer(self.camera_array.get_obj(), dtype=np.uint8).reshape((H, W, 3)) self.wide_camera_array = None @@ -21,22 +25,33 @@ class MetaDriveWorld(World): self.wide_road_image = np.frombuffer(self.wide_camera_array.get_obj(), dtype=np.uint8).reshape((H, W, 3)) self.controls_send, self.controls_recv = Pipe() - self.state_send, self.state_recv = Pipe() + self.simulation_state_send, self.simulation_state_recv = Pipe() + self.vehicle_state_send, self.vehicle_state_recv = Pipe() self.exit_event = multiprocessing.Event() + self.op_engaged = multiprocessing.Event() + + self.test_run = test_run + + self.first_engage = None + self.last_check_timestamp = 0 + self.distance_moved = 0 self.metadrive_process = multiprocessing.Process(name="metadrive process", target= functools.partial(metadrive_process, dual_camera, config, self.camera_array, self.wide_camera_array, self.image_lock, - self.controls_recv, self.state_send, self.exit_event)) + self.controls_recv, self.simulation_state_send, + self.vehicle_state_send, self.exit_event, self.op_engaged, test_duration, self.test_run)) self.metadrive_process.start() + self.status_q.put(QueueMessage(QueueMessageType.START_STATUS, "starting")) print("----------------------------------------------------------") print("---- Spawning Metadrive world, this might take awhile ----") print("----------------------------------------------------------") - self.state_recv.recv() # wait for a state message to ensure metadrive is launched + self.vehicle_last_pos = self.vehicle_state_recv.recv().position # wait for a state message to ensure metadrive is launched + self.status_q.put(QueueMessage(QueueMessageType.START_STATUS, "started")) self.steer_ratio = 15 self.vc = [0.0,0.0] @@ -58,15 +73,50 @@ class MetaDriveWorld(World): self.controls_send.send([*self.vc, self.should_reset]) self.should_reset = False + def read_state(self): + while self.simulation_state_recv.poll(0): + md_state: metadrive_simulation_state = self.simulation_state_recv.recv() + if md_state.done: + self.status_q.put(QueueMessage(QueueMessageType.TERMINATION_INFO, md_state.done_info)) + self.exit_event.set() + def read_sensors(self, state: SimulatorState): - while self.state_recv.poll(0): - md_state: metadrive_state = self.state_recv.recv() - state.velocity = md_state.velocity - state.bearing = md_state.bearing - state.steering_angle = md_state.steering_angle - state.gps.from_xy(md_state.position) + while self.vehicle_state_recv.poll(0): + md_vehicle: metadrive_vehicle_state = self.vehicle_state_recv.recv() + curr_pos = md_vehicle.position + + state.velocity = md_vehicle.velocity + state.bearing = md_vehicle.bearing + state.steering_angle = md_vehicle.steering_angle + state.gps.from_xy(curr_pos) state.valid = True + is_engaged = state.is_engaged + if is_engaged and self.first_engage is None: + self.first_engage = time.monotonic() + self.op_engaged.set() + + # check moving 5 seconds after engaged, doesn't move right away + after_engaged_check = is_engaged and time.monotonic() - self.first_engage >= 5 and self.test_run + + x_dist = abs(curr_pos[0] - self.vehicle_last_pos[0]) + y_dist = abs(curr_pos[1] - self.vehicle_last_pos[1]) + dist_threshold = 1 + if x_dist >= dist_threshold or y_dist >= dist_threshold: # position not the same during staying still, > threshold is considered moving + self.distance_moved += x_dist + y_dist + + time_check_threshold = 30 + current_time = time.monotonic() + since_last_check = current_time - self.last_check_timestamp + if since_last_check >= time_check_threshold: + if after_engaged_check and self.distance_moved == 0: + self.status_q.put(QueueMessage(QueueMessageType.TERMINATION_INFO, {"vehicle_not_moving" : True})) + self.exit_event.set() + + self.last_check_timestamp = current_time + self.distance_moved = 0 + self.vehicle_last_pos = curr_pos + def read_cameras(self): pass @@ -76,6 +126,7 @@ class MetaDriveWorld(World): def reset(self): self.should_reset = True - def close(self): + def close(self, reason: str): + self.status_q.put(QueueMessage(QueueMessageType.CLOSE_STATUS, reason)) self.exit_event.set() self.metadrive_process.join() diff --git a/tools/sim/build_container.sh b/tools/sim/build_container.sh deleted file mode 100755 index 451277d590..0000000000 --- a/tools/sim/build_container.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd $DIR/../../ - -docker pull ghcr.io/commaai/openpilot-base:latest -docker build \ - --cache-from ghcr.io/commaai/openpilot-sim:latest \ - -t ghcr.io/commaai/openpilot-sim:latest \ - -f tools/sim/Dockerfile.sim . diff --git a/tools/sim/launch_openpilot.sh b/tools/sim/launch_openpilot.sh index 9532537283..1b4ac4ef84 100755 --- a/tools/sim/launch_openpilot.sh +++ b/tools/sim/launch_openpilot.sh @@ -4,7 +4,7 @@ export PASSIVE="0" export NOBOARD="1" export SIMULATION="1" export SKIP_FW_QUERY="1" -export FINGERPRINT="HONDA CIVIC 2016" +export FINGERPRINT="HONDA_CIVIC_2022" export BLOCK="${BLOCK},camerad,loggerd,encoderd,micd,logmessaged" if [[ "$CI" ]]; then @@ -18,4 +18,4 @@ SCRIPT_DIR=$(dirname "$0") OPENPILOT_DIR=$SCRIPT_DIR/../../ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -cd $OPENPILOT_DIR/selfdrive/manager && exec ./manager.py +cd $OPENPILOT_DIR/system/manager && exec ./manager.py diff --git a/tools/sim/lib/camerad.py b/tools/sim/lib/camerad.py index 95769dd44e..b58cbeaaf2 100644 --- a/tools/sim/lib/camerad.py +++ b/tools/sim/lib/camerad.py @@ -3,7 +3,7 @@ import os import pyopencl as cl import pyopencl.array as cl_array -from cereal.visionipc import VisionIpcServer, VisionStreamType +from msgq.visionipc import VisionIpcServer, VisionStreamType from cereal import messaging from openpilot.common.basedir import BASEDIR diff --git a/tools/sim/lib/common.py b/tools/sim/lib/common.py index 795d7505c7..1324932131 100644 --- a/tools/sim/lib/common.py +++ b/tools/sim/lib/common.py @@ -17,7 +17,7 @@ class GPSState: self.altitude = 0 def from_xy(self, xy): - """Simulates a lat/lon from an xy coordinate on a plane, for simple simlation. TODO: proper global projection?""" + """Simulates a lat/lon from an xy coordinate on a plane, for simple simulation. TODO: proper global projection?""" BASE_LAT = 32.75308505188913 BASE_LON = -117.2095393365393 DEG_TO_METERS = 100000 @@ -69,6 +69,8 @@ class World(ABC): self.road_image = np.zeros((H, W, 3), dtype=np.uint8) self.wide_road_image = np.zeros((H, W, 3), dtype=np.uint8) + self.exit_event = multiprocessing.Event() + @abstractmethod def apply_controls(self, steer_sim, throttle_out, brake_out): pass @@ -77,6 +79,10 @@ class World(ABC): def tick(self): pass + @abstractmethod + def read_state(self): + pass + @abstractmethod def read_sensors(self, simulator_state: SimulatorState): pass @@ -86,7 +92,7 @@ class World(ABC): pass @abstractmethod - def close(self): + def close(self, reason: str): pass @abstractmethod diff --git a/tools/sim/lib/keyboard_ctrl.py b/tools/sim/lib/keyboard_ctrl.py index 339f4ea6bb..0a17f0ee85 100644 --- a/tools/sim/lib/keyboard_ctrl.py +++ b/tools/sim/lib/keyboard_ctrl.py @@ -2,10 +2,13 @@ import sys import termios import time +from multiprocessing import Queue from termios import (BRKINT, CS8, CSIZE, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISTRIP, IXON, PARENB, VMIN, VTIME) from typing import NoReturn +from openpilot.tools.sim.bridge.common import QueueMessage, control_cmd_gen + # Indexes for termios list. IFLAG = 0 OFLAG = 1 @@ -49,34 +52,41 @@ def getch() -> str: termios.tcsetattr(STDIN_FD, termios.TCSADRAIN, old_settings) return ch -def keyboard_poll_thread(q: 'Queue[str]'): +def print_keyboard_help(): + print(f"Keyboard Commands:\n{KEYBOARD_HELP}") + +def keyboard_poll_thread(q: 'Queue[QueueMessage]'): + print_keyboard_help() + while True: c = getch() if c == '1': - q.put("cruise_up") + q.put(control_cmd_gen("cruise_up")) elif c == '2': - q.put("cruise_down") + q.put(control_cmd_gen("cruise_down")) elif c == '3': - q.put("cruise_cancel") + q.put(control_cmd_gen("cruise_cancel")) elif c == 'w': - q.put("throttle_%f" % 1.0) + q.put(control_cmd_gen(f"throttle_{1.0}")) elif c == 'a': - q.put("steer_%f" % -0.15) + q.put(control_cmd_gen(f"steer_{-0.15}")) elif c == 's': - q.put("brake_%f" % 1.0) + q.put(control_cmd_gen(f"brake_{1.0}")) elif c == 'd': - q.put("steer_%f" % 0.15) + q.put(control_cmd_gen(f"steer_{0.15}")) elif c == 'z': - q.put("blinker_left") + q.put(control_cmd_gen("blinker_left")) elif c == 'x': - q.put("blinker_right") + q.put(control_cmd_gen("blinker_right")) elif c == 'i': - q.put("ignition") + q.put(control_cmd_gen("ignition")) elif c == 'r': - q.put("reset") + q.put(control_cmd_gen("reset")) elif c == 'q': - q.put("quit") + q.put(control_cmd_gen("quit")) break + else: + print_keyboard_help() def test(q: 'Queue[str]') -> NoReturn: while True: @@ -85,7 +95,7 @@ def test(q: 'Queue[str]') -> NoReturn: if __name__ == '__main__': from multiprocessing import Process, Queue - q: Queue[str] = Queue() + q: 'Queue[QueueMessage]' = Queue() p = Process(target=test, args=(q,)) p.daemon = True p.start() diff --git a/tools/sim/lib/manual_ctrl.py b/tools/sim/lib/manual_ctrl.py index 8a72296538..972f7023bb 100755 --- a/tools/sim/lib/manual_ctrl.py +++ b/tools/sim/lib/manual_ctrl.py @@ -6,6 +6,8 @@ import struct from fcntl import ioctl from typing import NoReturn +from openpilot.tools.sim.bridge.common import control_cmd_gen + # Iterate over the joystick devices. print('Available devices:') for fn in os.listdir('/dev/input'): @@ -153,33 +155,33 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = (1 - fvalue) * 50 - q.put(f"throttle_{normalized:f}") + q.put(control_cmd_gen(f"throttle_{normalized:f}")) elif axis == "rz": # brake fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = (1 - fvalue) * 50 - q.put(f"brake_{normalized:f}") + q.put(control_cmd_gen(f"brake_{normalized:f}")) elif axis == "x": # steer angle fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = fvalue - q.put(f"steer_{normalized:f}") + q.put(control_cmd_gen(f"steer_{normalized:f}")) elif mtype & 0x01: # buttons if value == 1: # press down if number in [0, 19]: # X - q.put("cruise_down") + q.put(control_cmd_gen("cruise_down")) elif number in [3, 18]: # triangle - q.put("cruise_up") + q.put(control_cmd_gen("cruise_up")) elif number in [1, 6]: # square - q.put("cruise_cancel") + q.put(control_cmd_gen("cruise_cancel")) elif number in [10, 21]: # R3 - q.put("reverse_switch") + q.put(control_cmd_gen("reverse_switch")) if __name__ == '__main__': from multiprocessing import Process, Queue diff --git a/tools/sim/lib/simulated_car.py b/tools/sim/lib/simulated_car.py index f6319dd819..6d324e4780 100644 --- a/tools/sim/lib/simulated_car.py +++ b/tools/sim/lib/simulated_car.py @@ -3,16 +3,14 @@ import cereal.messaging as messaging from opendbc.can.packer import CANPacker from opendbc.can.parser import CANParser from openpilot.common.params import Params -from openpilot.selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp -from openpilot.selfdrive.car import crc8_pedal +from openpilot.selfdrive.pandad.pandad_api_impl import can_list_to_can_capnp from openpilot.tools.sim.lib.common import SimulatorState from panda.python import Panda class SimulatedCar: - """Simulates a honda civic 2016 (panda state + can messages) to OpenPilot""" - packer = CANPacker("honda_civic_touring_2016_can_generated") - rpacker = CANPacker("acura_ilx_2016_nidec") + """Simulates a honda civic 2022 (panda state + can messages) to OpenPilot""" + packer = CANPacker("honda_civic_ex_2022_can_generated") def __init__(self): self.pm = messaging.PubMaster(['can', 'pandaStates']) @@ -24,11 +22,8 @@ class SimulatedCar: @staticmethod def get_car_can_parser(): - dbc_f = 'honda_civic_touring_2016_can_generated' + dbc_f = 'honda_civic_ex_2022_can_generated' checks = [ - (0xe4, 100), - (0x1fa, 50), - (0x200, 50), ] return CANParser(dbc_f, checks, 0) @@ -51,15 +46,6 @@ class SimulatedCar: msg.append(self.packer.make_can_msg("SCM_BUTTONS", 0, {"CRUISE_BUTTONS": simulator_state.cruise_button})) - values = { - "COUNTER_PEDAL": self.idx & 0xF, - "INTERCEPTOR_GAS": simulator_state.user_gas * 2**12, - "INTERCEPTOR_GAS2": simulator_state.user_gas * 2**12, - } - checksum = crc8_pedal(self.packer.make_can_msg("GAS_SENSOR", 0, values)[2][:-1]) - values["CHECKSUM_PEDAL"] = checksum - msg.append(self.packer.make_can_msg("GAS_SENSOR", 0, values)) - msg.append(self.packer.make_can_msg("GEARBOX", 0, {"GEAR": 4, "GEAR_SHIFTER": 8})) msg.append(self.packer.make_can_msg("GAS_PEDAL_2", 0, {})) msg.append(self.packer.make_can_msg("SEATBELT_STATUS", 0, {"SEATBELT_DRIVER_LATCHED": 1})) @@ -72,6 +58,7 @@ class SimulatedCar: msg.append(self.packer.make_can_msg("DOORS_STATUS", 0, {})) msg.append(self.packer.make_can_msg("CRUISE_PARAMS", 0, {})) msg.append(self.packer.make_can_msg("CRUISE", 0, {})) + msg.append(self.packer.make_can_msg("CRUISE_FAULT_STATUS", 0, {})) msg.append(self.packer.make_can_msg("SCM_FEEDBACK", 0, { "MAIN_ON": 1, @@ -84,20 +71,12 @@ class SimulatedCar: "PEDAL_GAS": simulator_state.user_gas, "BRAKE_PRESSED": simulator_state.user_brake > 0 })) - msg.append(self.packer.make_can_msg("HUD_SETTING", 0, {})) msg.append(self.packer.make_can_msg("CAR_SPEED", 0, {})) # *** cam bus *** msg.append(self.packer.make_can_msg("STEERING_CONTROL", 2, {})) msg.append(self.packer.make_can_msg("ACC_HUD", 2, {})) msg.append(self.packer.make_can_msg("LKAS_HUD", 2, {})) - msg.append(self.packer.make_can_msg("BRAKE_COMMAND", 2, {})) - - # *** radar bus *** - if self.idx % 5 == 0: - msg.append(self.rpacker.make_can_msg("RADAR_DIAGNOSTIC", 1, {"RADAR_STATE": 0x79})) - for i in range(16): - msg.append(self.rpacker.make_can_msg("TRACK_%d" % i, 1, {"LONG_DIST": 255.5})) self.pm.send('can', can_list_to_can_capnp(msg)) @@ -114,9 +93,9 @@ class SimulatedCar: 'ignitionLine': simulator_state.ignition, 'pandaType': "blackPanda", 'controlsAllowed': True, - 'safetyModel': 'hondaNidec', + 'safetyModel': 'hondaBosch', 'alternativeExperience': self.sm["carParams"].alternativeExperience, - 'safetyParam': Panda.FLAG_HONDA_GAS_INTERCEPTOR + 'safetyParam': Panda.FLAG_HONDA_RADARLESS | Panda.FLAG_HONDA_BOSCH_LONG, } self.pm.send('pandaStates', dat) diff --git a/tools/sim/lib/simulated_sensors.py b/tools/sim/lib/simulated_sensors.py index df6a0aeeff..e78f984736 100644 --- a/tools/sim/lib/simulated_sensors.py +++ b/tools/sim/lib/simulated_sensors.py @@ -55,7 +55,7 @@ class SimulatedSensors: dat.gpsLocationExternal = { "unixTimestampMillis": int(time.time() * 1000), "flags": 1, # valid fix - "accuracy": 1.0, + "horizontalAccuracy": 1.0, "verticalAccuracy": 1.0, "speedAccuracy": 0.1, "bearingAccuracyDeg": 0.1, diff --git a/tools/sim/start_openpilot_docker.sh b/tools/sim/start_openpilot_docker.sh deleted file mode 100755 index 3dce660579..0000000000 --- a/tools/sim/start_openpilot_docker.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -cd $DIR - -OPENPILOT_DIR="/tmp/openpilot" -if ! [[ -z "$MOUNT_OPENPILOT" ]]; then - OPENPILOT_DIR="$(dirname $(dirname $DIR))" - EXTRA_ARGS="-v $OPENPILOT_DIR:$OPENPILOT_DIR -e PYTHONPATH=$OPENPILOT_DIR:$PYTHONPATH" -fi - -if [[ "$CI" ]]; then - CMD="CI=1 ${OPENPILOT_DIR}/tools/sim/tests/test_metadrive_integration.py" -else - # expose X to the container - xhost +local:root - - docker pull ghcr.io/commaai/openpilot-sim:latest - CMD="./tmux_script.sh $*" - EXTRA_ARGS="${EXTRA_ARGS} -it" -fi - -docker kill openpilot_client || true -docker run --net=host\ - --name openpilot_client \ - --rm \ - --gpus all \ - --device=/dev/dri:/dev/dri \ - --device=/dev/input:/dev/input \ - -v /tmp/.X11-unix:/tmp/.X11-unix \ - --shm-size 1G \ - -e DISPLAY=$DISPLAY \ - -e QT_X11_NO_MITSHM=1 \ - -w "$OPENPILOT_DIR/tools/sim" \ - $EXTRA_ARGS \ - ghcr.io/commaai/openpilot-sim:latest \ - /bin/bash -c "$CMD" diff --git a/tools/sim/tests/conftest.py b/tools/sim/tests/conftest.py new file mode 100644 index 0000000000..ddf6635276 --- /dev/null +++ b/tools/sim/tests/conftest.py @@ -0,0 +1,8 @@ +import pytest + +def pytest_addoption(parser): + parser.addoption("--test_duration", action="store", default=60, type=int, help="Seconds to run metadrive drive") + +@pytest.fixture +def test_duration(request): + return request.config.getoption("--test_duration") diff --git a/tools/sim/tests/test_metadrive_bridge.py b/tools/sim/tests/test_metadrive_bridge.py old mode 100755 new mode 100644 index 6cb8e1465e..8849e901cb --- a/tools/sim/tests/test_metadrive_bridge.py +++ b/tools/sim/tests/test_metadrive_bridge.py @@ -1,15 +1,21 @@ -#!/usr/bin/env python3 import pytest -import unittest +import warnings + +# Since metadrive depends on pkg_resources, and pkg_resources is deprecated as an API +warnings.filterwarnings("ignore", category=DeprecationWarning) from openpilot.tools.sim.bridge.metadrive.metadrive_bridge import MetaDriveBridge from openpilot.tools.sim.tests.test_sim_bridge import TestSimBridgeBase @pytest.mark.slow +@pytest.mark.filterwarnings("ignore::pyopencl.CompilerWarning") # Unimportant warning of non-empty compile log class TestMetaDriveBridge(TestSimBridgeBase): + @pytest.fixture(autouse=True) + def setup_create_bridge(self, test_duration): + # run bridge test for at least 60s, since not-moving check runs every 30s + if test_duration < 60: + test_duration = 60 + self.test_duration = test_duration + def create_bridge(self): - return MetaDriveBridge(False, False) - - -if __name__ == "__main__": - unittest.main() + return MetaDriveBridge(False, False, self.test_duration, True) diff --git a/tools/sim/tests/test_sim_bridge.py b/tools/sim/tests/test_sim_bridge.py index 504914c562..aaa90f153f 100644 --- a/tools/sim/tests/test_sim_bridge.py +++ b/tools/sim/tests/test_sim_bridge.py @@ -1,25 +1,26 @@ import os import subprocess import time -import unittest +import pytest from multiprocessing import Queue from cereal import messaging from openpilot.common.basedir import BASEDIR +from openpilot.tools.sim.bridge.common import QueueMessageType SIM_DIR = os.path.join(BASEDIR, "tools/sim") -class TestSimBridgeBase(unittest.TestCase): +class TestSimBridgeBase: @classmethod - def setUpClass(cls): + def setup_class(cls): if cls is TestSimBridgeBase: - raise unittest.SkipTest("Don't run this base class, run test_metadrive_bridge.py instead") + raise pytest.skip("Don't run this base class, run test_metadrive_bridge.py instead") - def setUp(self): + def setup_method(self): self.processes = [] - def test_engage(self): + def test_driving(self): # Startup manager and bridge.py. Check processes are running, then engage and verify. p_manager = subprocess.Popen("./launch_openpilot.sh", cwd=SIM_DIR) self.processes.append(p_manager) @@ -36,7 +37,7 @@ class TestSimBridgeBase(unittest.TestCase): start_waiting = time.monotonic() while not bridge.started.value and time.monotonic() < start_waiting + max_time_per_step: time.sleep(0.1) - self.assertEqual(p_bridge.exitcode, None, f"Bridge process should be running, but exited with code {p_bridge.exitcode}") + assert p_bridge.exitcode is None, f"Bridge process should be running, but exited with code {p_bridge.exitcode}" start_time = time.monotonic() no_car_events_issues_once = False @@ -52,8 +53,8 @@ class TestSimBridgeBase(unittest.TestCase): no_car_events_issues_once = True break - self.assertTrue(no_car_events_issues_once, - f"Failed because no messages received, or CarEvents '{car_event_issues}' or processes not running '{not_running}'") + assert no_car_events_issues_once, \ + f"Failed because no messages received, or CarEvents '{car_event_issues}' or processes not running '{not_running}'" start_time = time.monotonic() min_counts_control_active = 100 @@ -62,17 +63,27 @@ class TestSimBridgeBase(unittest.TestCase): while time.monotonic() < start_time + max_time_per_step: sm.update() - q.put("cruise_down") # Try engaging - if sm.all_alive() and sm['controlsState'].active: control_active += 1 if control_active == min_counts_control_active: break - self.assertEqual(min_counts_control_active, control_active, f"Simulator did not engage a minimal of {min_counts_control_active} steps was {control_active}") + assert min_counts_control_active == control_active, f"Simulator did not engage a minimal of {min_counts_control_active} steps was {control_active}" - def tearDown(self): + failure_states = [] + while bridge.started.value: + continue + + while not q.empty(): + state = q.get() + if state.type == QueueMessageType.TERMINATION_INFO: + done_info = state.info + failure_states = [done_state for done_state in done_info if done_state != "timeout" and done_info[done_state]] + break + assert len(failure_states) == 0, f"Simulator fails to finish a loop. Failure states: {failure_states}" + + def teardown_method(self): print("Test shutting down. CommIssues are acceptable") for p in reversed(self.processes): p.terminate() @@ -82,7 +93,3 @@ class TestSimBridgeBase(unittest.TestCase): p.wait(15) else: p.join(15) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index f804b328de..6abf1338dc 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -47,7 +47,7 @@ class SteeringAccuracyTool: v_ego = sm['carState'].vEgo active = sm['controlsState'].active - steer = sm['carControl'].actuatorsOutput.steer + steer = sm['carOutput'].actuatorsOutput.steer standstill = sm['carState'].standstill steer_limited = abs(sm['carControl'].actuators.steer - sm['carControl'].actuatorsOutput.steer) > 1e-2 overriding = sm['carState'].steeringPressed @@ -150,7 +150,7 @@ if __name__ == "__main__": messaging.context = messaging.Context() carControl = messaging.sub_sock('carControl', addr=args.addr, conflate=True) - sm = messaging.SubMaster(['carState', 'carControl', 'controlsState', 'modelV2'], addr=args.addr) + sm = messaging.SubMaster(['carState', 'carControl', 'carOutput', 'controlsState', 'modelV2'], addr=args.addr) time.sleep(1) # Make sure all submaster data is available before going further print("waiting for messages...") diff --git a/tools/webcam/Dockerfile b/tools/webcam/Dockerfile deleted file mode 100644 index 78a25e836d..0000000000 --- a/tools/webcam/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -FROM ghcr.io/commaai/openpilot-base:latest - -ENV PYTHONUNBUFFERED 1 -ENV PYTHONPATH /tmp/openpilot:${PYTHONPATH} - -# Install opencv -ENV OPENCV_VERSION '4.2.0' -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y --no-install-recommends \ - libvtk6-dev \ - libdc1394-22-dev \ - libavcodec-dev \ - libavformat-dev \ - libswscale-dev \ - libtheora-dev \ - libvorbis-dev \ - libxvidcore-dev \ - libx264-dev \ - yasm \ - libopencore-amrnb-dev \ - libopencore-amrwb-dev \ - libv4l-dev \ - libxine2-dev \ - libtbb-dev \ - && rm -rf /var/lib/apt/lists/* && \ - - mkdir /tmp/opencv_build && \ - cd /tmp/opencv_build && \ - - curl -L -O https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.tar.gz && \ - tar -xvf ${OPENCV_VERSION}.tar.gz && \ - mv opencv-${OPENCV_VERSION} OpenCV && \ - cd OpenCV && mkdir build && cd build && \ - cmake -DWITH_OPENGL=ON -DFORCE_VTK=ON -DWITH_TBB=ON -DWITH_GDAL=ON \ - -DWITH_XINE=ON -DENABLE_PRECOMPILED_HEADERS=OFF -DBUILD_TESTS=OFF \ - -DBUILD_PERF_TESTS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_opencv_apps=OFF .. && \ - make -j8 && \ - make install && \ - ldconfig && \ - - cd / && rm -rf /tmp/* diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 709e8514c7..7334d87b38 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -14,10 +14,7 @@ cd ~ git clone https://github.com/commaai/openpilot.git ``` - Follow [this readme](https://github.com/commaai/openpilot/tree/master/tools) to install the requirements -- Add line "export PYTHONPATH=$HOME/openpilot" to your ~/.bashrc -- Install tensorflow 2.2 and nvidia drivers: nvidia-xxx/cuda10.0/cudnn7.6.5 - Install [OpenCL Driver](https://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532/l_opencl_p_18.1.0.015.tgz) -- Install [OpenCV4](https://www.pyimagesearch.com/2018/08/15/how-to-install-opencv-4-on-ubuntu/) (ignore the Python part) ## Build openpilot for webcam ``` @@ -32,7 +29,7 @@ USE_WEBCAM=1 scons -j$(nproc) ## GO ``` -cd ~/openpilot/selfdrive/manager +cd ~/openpilot/system/manager NOSENSOR=1 USE_WEBCAM=1 ./manager.py ``` - Start the car, then the UI should show the road webcam's view diff --git a/tools/webcam/camerad.py b/tools/webcam/camerad.py index ce33473f9a..d0b879a086 100755 --- a/tools/webcam/camerad.py +++ b/tools/webcam/camerad.py @@ -3,7 +3,7 @@ import threading import os from collections import namedtuple -from cereal.visionipc import VisionIpcServer, VisionStreamType +from msgq.visionipc import VisionIpcServer, VisionStreamType from cereal import messaging from openpilot.tools.webcam.camera import Camera diff --git a/tools/zookeeper/__init__.py b/tools/zookeeper/__init__.py deleted file mode 100644 index 598e0a0587..0000000000 --- a/tools/zookeeper/__init__.py +++ /dev/null @@ -1,78 +0,0 @@ -import ft4222 -import ft4222.I2CMaster - -DEBUG = False - -INA231_ADDR = 0x40 -INA231_REG_CONFIG = 0x00 -INA231_REG_SHUNT_VOLTAGE = 0x01 -INA231_REG_BUS_VOLTAGE = 0x02 -INA231_REG_POWER = 0x03 -INA231_REG_CURRENT = 0x04 -INA231_REG_CALIBRATION = 0x05 - -INA231_BUS_LSB = 1.25e-3 -INA231_SHUNT_LSB = 2.5e-6 -SHUNT_RESISTOR = 30e-3 -CURRENT_LSB = 1e-5 - -class Zookeeper: - def __init__(self): - if ft4222.createDeviceInfoList() < 2: - raise Exception("No connected zookeeper found!") - self.dev_a = ft4222.openByDescription("FT4222 A") - self.dev_b = ft4222.openByDescription("FT4222 B") - - if DEBUG: - for i in range(ft4222.createDeviceInfoList()): - print(f"Device {i}: {ft4222.getDeviceInfoDetail(i, False)}") - - # Setup GPIO - self.dev_b.gpio_Init(gpio2=ft4222.Dir.OUTPUT, gpio3=ft4222.Dir.OUTPUT) - self.dev_b.setSuspendOut(False) - self.dev_b.setWakeUpInterrut(False) - - # Setup I2C - self.dev_a.i2cMaster_Init(kbps=400) - self._initialize_ina() - - # Helper functions - def _read_ina_register(self, register, length): - self.dev_a.i2cMaster_WriteEx(INA231_ADDR, data=register, flag=ft4222.I2CMaster.Flag.REPEATED_START) - return self.dev_a.i2cMaster_Read(INA231_ADDR, bytesToRead=length) - - def _write_ina_register(self, register, data): - msg = register.to_bytes(1, byteorder="big") + data.to_bytes(2, byteorder="big") - self.dev_a.i2cMaster_Write(INA231_ADDR, data=msg) - - def _initialize_ina(self): - # Config - self._write_ina_register(INA231_REG_CONFIG, 0x4127) - - # Calibration - CAL_VALUE = int(0.00512 / (CURRENT_LSB * SHUNT_RESISTOR)) - if DEBUG: - print(f"Calibration value: {hex(CAL_VALUE)}") - self._write_ina_register(INA231_REG_CALIBRATION, CAL_VALUE) - - def _set_gpio(self, number, enabled): - self.dev_b.gpio_Write(portNum=number, value=enabled) - - # Public API functions - def set_device_power(self, enabled): - self._set_gpio(2, enabled) - - def set_device_ignition(self, enabled): - self._set_gpio(3, enabled) - - def read_current(self): - # Returns in A - return int.from_bytes(self._read_ina_register(INA231_REG_CURRENT, 2), byteorder="big") * CURRENT_LSB - - def read_power(self): - # Returns in W - return int.from_bytes(self._read_ina_register(INA231_REG_POWER, 2), byteorder="big") * CURRENT_LSB * 25 - - def read_voltage(self): - # Returns in V - return int.from_bytes(self._read_ina_register(INA231_REG_BUS_VOLTAGE, 2), byteorder="big") * INA231_BUS_LSB diff --git a/tools/zookeeper/check_consumption.py b/tools/zookeeper/check_consumption.py deleted file mode 100755 index dab948318f..0000000000 --- a/tools/zookeeper/check_consumption.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import time -from openpilot.tools.zookeeper import Zookeeper - -# Usage: check_consumption.py -# Exit code: 0 -> passed -# 1 -> failed - -if __name__ == "__main__": - z = Zookeeper() - - averaging_time_s = int(sys.argv[1]) - max_average_power = float(sys.argv[2]) - - start_time = time.time() - measurements = [] - while time.time() - start_time < averaging_time_s: - measurements.append(z.read_power()) - time.sleep(0.1) - - average_power = sum(measurements)/len(measurements) - print(f"Average power: {round(average_power, 4)}W") - - if average_power > max_average_power: - exit(1) diff --git a/tools/zookeeper/disable.py b/tools/zookeeper/disable.py deleted file mode 100755 index 4bea3e7ed0..0000000000 --- a/tools/zookeeper/disable.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 - -from openpilot.tools.zookeeper import Zookeeper - -if __name__ == "__main__": - z = Zookeeper() - z.set_device_power(False) - diff --git a/tools/zookeeper/enable_and_wait.py b/tools/zookeeper/enable_and_wait.py deleted file mode 100755 index 51b8dc0a5c..0000000000 --- a/tools/zookeeper/enable_and_wait.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import time -from socket import gethostbyname, gaierror -from openpilot.tools.zookeeper import Zookeeper - -def is_online(ip): - try: - addr = gethostbyname(ip) - return (os.system(f"ping -c 1 {addr} > /dev/null") == 0) - except gaierror: - return False - -if __name__ == "__main__": - z = Zookeeper() - z.set_device_power(True) - - - ip = str(sys.argv[1]) - timeout = int(sys.argv[2]) - start_time = time.time() - while not is_online(ip): - print(f"{ip} not online yet!") - - if time.time() - start_time > timeout: - print("Timed out!") - raise TimeoutError() - - time.sleep(1) - diff --git a/tools/zookeeper/ignition.py b/tools/zookeeper/ignition.py deleted file mode 100755 index d5f01c362f..0000000000 --- a/tools/zookeeper/ignition.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from openpilot.tools.zookeeper import Zookeeper - - -if __name__ == "__main__": - z = Zookeeper() - z.set_device_ignition(1 if int(sys.argv[1]) > 0 else 0) - diff --git a/tools/zookeeper/power_monitor.py b/tools/zookeeper/power_monitor.py deleted file mode 100755 index a081baa72a..0000000000 --- a/tools/zookeeper/power_monitor.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 -import sys -import time -import datetime - -from openpilot.common.realtime import Ratekeeper -from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.tools.zookeeper import Zookeeper - -if __name__ == "__main__": - z = Zookeeper() - z.set_device_power(True) - z.set_device_ignition(False) - - duration = None - if len(sys.argv) > 1: - duration = int(sys.argv[1]) - - rate = 123 - rk = Ratekeeper(rate, print_delay_threshold=None) - fltr = FirstOrderFilter(0, 5, 1. / rate, initialized=False) - - measurements = [] - start_time = time.monotonic() - - try: - while duration is None or time.monotonic() - start_time < duration: - fltr.update(z.read_power()) - if rk.frame % rate == 0: - measurements.append(fltr.x) - t = datetime.timedelta(seconds=time.monotonic() - start_time) - avg = sum(measurements) / len(measurements) - print(f"Now: {fltr.x:.2f} W, Avg: {avg:.2f} W over {t}") - rk.keep_time() - except KeyboardInterrupt: - pass - - t = datetime.timedelta(seconds=time.monotonic() - start_time) - avg = sum(measurements) / len(measurements) - print(f"\nAverage power: {avg:.2f}W over {t}") diff --git a/tools/zookeeper/test_zookeeper.py b/tools/zookeeper/test_zookeeper.py deleted file mode 100755 index 89f7f28975..0000000000 --- a/tools/zookeeper/test_zookeeper.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 - -import time -from openpilot.tools.zookeeper import Zookeeper - - -if __name__ == "__main__": - z = Zookeeper() - z.set_device_power(True) - - i = 0 - ign = False - while 1: - voltage = round(z.read_voltage(), 2) - current = round(z.read_current(), 3) - power = round(z.read_power(), 2) - z.set_device_ignition(ign) - print(f"Voltage: {voltage}V, Current: {current}A, Power: {power}W, Ignition: {ign}") - - if i > 200: - ign = not ign - i = 0 - - i += 1 - time.sleep(0.1)