diff --git a/.github/workflows/auto-cache/action.yaml b/.github/workflows/auto-cache/action.yaml deleted file mode 100644 index 377b1eedcd..0000000000 --- a/.github/workflows/auto-cache/action.yaml +++ /dev/null @@ -1,58 +0,0 @@ -name: 'automatically cache based on current runner' - -inputs: - path: - description: 'path to cache' - required: true - key: - description: 'key' - required: true - restore-keys: - description: 'restore-keys' - required: true - save: - description: 'whether to save the cache' - default: 'true' - required: false -outputs: - cache-hit: - description: 'cache hit occurred' - value: ${{ (contains(runner.name, 'nsc') && steps.ns-cache.outputs.cache-hit) || - (!contains(runner.name, 'nsc') && inputs.save != 'false' && steps.gha-cache.outputs.cache-hit) || - (!contains(runner.name, 'nsc') && inputs.save == 'false' && steps.gha-cache-ro.outputs.cache-hit) }} - -runs: - using: "composite" - steps: - - name: setup namespace cache - id: ns-cache - if: ${{ contains(runner.name, 'nsc') }} - uses: namespacelabs/nscloud-cache-action@v1 - with: - path: ${{ inputs.path }} - - - name: setup github cache - id: gha-cache - if: ${{ !contains(runner.name, 'nsc') && inputs.save != 'false' }} - uses: 'actions/cache@v4' - with: - path: ${{ inputs.path }} - key: ${{ inputs.key }} - restore-keys: ${{ inputs.restore-keys }} - - - name: setup github cache - id: gha-cache-ro - if: ${{ !contains(runner.name, 'nsc') && inputs.save == 'false' }} - uses: 'actions/cache/restore@v4' - with: - path: ${{ inputs.path }} - key: ${{ inputs.key }} - restore-keys: ${{ inputs.restore-keys }} - - # make the directory manually in case we didn't get a hit, so it doesn't fail on future steps - - id: scons-cache-setup - shell: bash - run: | - mkdir -p ${{ inputs.path }} - sudo chmod -R 777 ${{ inputs.path }} - sudo chown -R $USER ${{ inputs.path }} diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml index 2d9ba46d0a..d170a96368 100644 --- a/.github/workflows/badges.yaml +++ b/.github/workflows/badges.yaml @@ -5,9 +5,7 @@ on: workflow_dispatch: env: - BASE_IMAGE: sunnypilot-base - DOCKER_REGISTRY: ghcr.io/sunnypilot - RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $DOCKER_REGISTRY/$BASE_IMAGE:latest /bin/bash -c + PYTHONPATH: ${{ github.workspace }} jobs: badges: @@ -20,10 +18,10 @@ jobs: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Push badges run: | - ${{ env.RUN }} "python3 selfdrive/ui/translations/create_badges.py" + python3 selfdrive/ui/translations/create_badges.py rm .gitattributes diff --git a/.github/workflows/build-all-tinygrad-models.yaml b/.github/workflows/build-all-tinygrad-models.yaml index 00864d38a0..e901baee0c 100644 --- a/.github/workflows/build-all-tinygrad-models.yaml +++ b/.github/workflows/build-all-tinygrad-models.yaml @@ -140,7 +140,7 @@ jobs: run: | echo '${{ needs.setup.outputs.model_matrix }}' > matrix.json built=(); while IFS= read -r line; do built+=("$line"); done < <( - ls output | sed -E 's/^model-//' | sed -E 's/-[0-9]+$//' | sed -E 's/ \([^)]*\)//' | awk '{gsub(/^ +| +$/, ""); print}' + find output -maxdepth 1 -name 'model-*' -printf "%f\n" | sed -E 's/^model-//' | sed -E 's/-[0-9]+$//' | sed -E 's/ \([^)]*\)//' | awk '{gsub(/^ +| +$/, ""); print}' ) jq -c --argjson built "$(printf '%s\n' "${built[@]}" | jq -R . | jq -s .)" \ 'map(select(.display_name as $n | ($built | index($n | gsub("^ +| +$"; "")) | not)))' matrix.json > retry_matrix.json @@ -168,6 +168,7 @@ jobs: if: ${{ !cancelled() && (needs.get_and_build.result != 'failure' || needs.retry_get_and_build.result == 'success' || (needs.retry_failed_models.outputs.retry_matrix != '[]' && needs.retry_failed_models.outputs.retry_matrix != '')) }} runs-on: ubuntu-latest strategy: + fail-fast: false max-parallel: 1 matrix: model: ${{ fromJson(needs.setup.outputs.model_matrix) }} diff --git a/.github/workflows/cereal_validation.yaml b/.github/workflows/cereal_validation.yaml index 2290481c56..3a864ebefb 100644 --- a/.github/workflows/cereal_validation.yaml +++ b/.github/workflows/cereal_validation.yaml @@ -20,27 +20,23 @@ concurrency: cancel-in-progress: true env: - PYTHONWARNINGS: error - BASE_IMAGE: openpilot-base - BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c + CI: 1 jobs: generate_cereal_artifact: name: Generate cereal validation artifacts runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Build openpilot - run: ${{ env.RUN }} "scons -j$(nproc) cereal" + run: scons -j$(nproc) cereal - name: Generate the log file run: | - ${{ env.RUN }} "cereal/messaging/tests/validate_sp_cereal_upstream.py -g -f schema_instances.bin" && \ - ls -la - ls -la cereal/messaging/tests + export PYTHONPATH=${{ github.workspace }} + python3 cereal/messaging/tests/validate_sp_cereal_upstream.py -g -f schema_instances.bin - name: 'Prepare artifact' run: | mkdir -p "cereal/messaging/tests/cereal_validations" @@ -57,20 +53,26 @@ jobs: runs-on: ubuntu-24.04 needs: generate_cereal_artifact steps: - - uses: actions/checkout@v4 + - name: Checkout sunnypilot + uses: actions/checkout@v6 + - name: Checkout upstream openpilot + uses: actions/checkout@v6 with: repository: 'commaai/openpilot' + path: openpilot submodules: true ref: "refs/heads/master" - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Build openpilot - run: ${{ env.RUN }} "scons -j$(nproc) cereal" + working-directory: openpilot + run: scons -j$(nproc) cereal - name: Download build artifacts uses: actions/download-artifact@v4 with: name: cereal_validations - path: cereal/messaging/tests/cereal_validations + path: openpilot/cereal/messaging/tests/cereal_validations - name: 'Run the validation' run: | - chmod +x cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py - ${{ env.RUN }} "cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py -r -f cereal/messaging/tests/cereal_validations/schema_instances.bin" + export PYTHONPATH=${{ github.workspace }}/openpilot + chmod +x openpilot/cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py + python3 openpilot/cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py -r -f openpilot/cereal/messaging/tests/cereal_validations/schema_instances.bin diff --git a/.github/workflows/ci_weekly_report.yaml b/.github/workflows/ci_weekly_report.yaml deleted file mode 100644 index eea3cb3f4d..0000000000 --- a/.github/workflows/ci_weekly_report.yaml +++ /dev/null @@ -1,101 +0,0 @@ -name: weekly CI test report -on: - schedule: - - cron: '37 9 * * 1' # 9:37AM UTC -> 2:37AM PST every monday - workflow_dispatch: - inputs: - ci_runs: - description: 'The amount of runs to trigger in CI test report' -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - CI_RUNS: ${{ github.event.inputs.ci_runs || '50' }} - -jobs: - setup: - if: github.repository == 'sunnypilot/sunnypilot' - runs-on: ubuntu-latest - outputs: - ci_runs: ${{ steps.ci_runs_setup.outputs.matrix }} - steps: - - id: ci_runs_setup - name: CI_RUNS=${{ env.CI_RUNS }} - run: | - matrix=$(python3 -c "import json; print(json.dumps({ 'run_number' : list(range(${{ env.CI_RUNS }})) }))") - echo "matrix=$matrix" >> $GITHUB_OUTPUT - - ci_matrix_run: - needs: [ setup ] - strategy: - fail-fast: false - matrix: ${{fromJSON(needs.setup.outputs.ci_runs)}} - uses: sunnypilot/sunnypilot/.github/workflows/ci_weekly_run.yaml@master - with: - run_number: ${{ matrix.run_number }} - - report: - needs: [ci_matrix_run] - runs-on: ubuntu-latest - if: always() && github.repository == 'commaai/openpilot' - steps: - - name: Get job results - uses: actions/github-script@v8 - id: get-job-results - with: - script: | - const jobs = await github - .paginate("GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt}/jobs", { - owner: "commaai", - repo: "${{ github.event.repository.name }}", - run_id: "${{ github.run_id }}", - attempt: "${{ github.run_attempt }}", - }) - var report = {} - jobs.slice(1, jobs.length-1).forEach(job => { - if (job.conclusion === "skipped") return; - const jobName = job.name.split(" / ")[2]; - const runRegex = /\((.*?)\)/; - const run = job.name.match(runRegex)[1]; - report[jobName] = report[jobName] || { successes: [], failures: [], canceled: [] }; - switch (job.conclusion) { - case "success": - report[jobName].successes.push({ "run_number": run, "link": job.html_url}); break; - case "failure": - report[jobName].failures.push({ "run_number": run, "link": job.html_url }); break; - case "canceled": - report[jobName].canceled.push({ "run_number": run, "link": job.html_url }); break; - } - }); - return JSON.stringify({"jobs": report}); - - - name: Add job results to summary - env: - JOB_RESULTS: ${{ fromJSON(steps.get-job-results.outputs.result) }} - run: | - cat <> template.html - - - - - - - - - - - {% for key in jobs.keys() %} - - - - - - {% endfor %} -
Job✅ Passing❌ Failure Details
{% for i in range(5) %}{% if i+1 <= (5 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }}) %}🟩{% else %}🟥{% endif %}{% endfor%}{{ key }}{{ 100 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }} }}%{% if jobs[key]["failures"]|length > 0 %}
{% for failure in jobs[key]["failures"] %}Log for run #{{ failure['run_number'] }}
{% endfor %}
{% else %}{% endif %}
- EOF - - pip install jinja2-cli - echo $JOB_RESULTS | jinja2 template.html > report.html - echo "# CI Test Report - ${{ env.CI_RUNS }} Runs" >> $GITHUB_STEP_SUMMARY - cat report.html >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/ci_weekly_run.yaml b/.github/workflows/ci_weekly_run.yaml deleted file mode 100644 index 4b239f201c..0000000000 --- a/.github/workflows/ci_weekly_run.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: weekly CI test run -on: - workflow_call: - inputs: - run_number: - required: true - type: string - -concurrency: - group: ci-run-${{ inputs.run_number }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - tests: - uses: sunnypilot/sunnypilot/.github/workflows/tests.yaml@master - with: - run_number: ${{ inputs.run_number }} diff --git a/.github/workflows/compile-openpilot/action.yaml b/.github/workflows/compile-openpilot/action.yaml deleted file mode 100644 index 4015746c0e..0000000000 --- a/.github/workflows/compile-openpilot/action.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: 'compile openpilot' - -runs: - using: "composite" - steps: - - shell: bash - name: Build openpilot with all flags - run: | - ${{ env.RUN }} "scons -j$(nproc)" - ${{ env.RUN }} "release/check-dirty.sh" - - shell: bash - name: Cleanup scons cache and rebuild - run: | - ${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \ - scons -j$(nproc) --cache-populate" - - name: Save scons cache - uses: actions/cache/save@v4 - if: github.ref == 'refs/heads/master' - with: - path: .ci_cache/scons_cache - key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} diff --git a/.github/workflows/mici_raylib_ui_preview.yaml b/.github/workflows/mici_raylib_ui_preview.yaml deleted file mode 100644 index fa51666bb6..0000000000 --- a/.github/workflows/mici_raylib_ui_preview.yaml +++ /dev/null @@ -1,151 +0,0 @@ -name: "mici raylib ui preview" -on: - push: - branches: - - master - pull_request_target: - types: [assigned, opened, synchronize, reopened, edited] - branches: - - 'master' - paths: - - 'selfdrive/assets/**' - - 'selfdrive/ui/**' - - 'system/ui/**' - workflow_dispatch: - -env: - UI_JOB_NAME: "Create mici raylib UI Report" - REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} - SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }} - BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-mici-raylib-ui" - MASTER_BRANCH_NAME: "openpilot_master_ui_mici_raylib" - # All report files are pushed here - REPORT_FILES_BRANCH_NAME: "mici-raylib-ui-reports" - -jobs: - preview: - if: github.repository == 'sunnypilot/sunnypilot' - name: preview - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - pull-requests: write - actions: read - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Waiting for ui generation to end - uses: lewagon/wait-on-check-action@v1.3.4 - with: - ref: ${{ env.SHA }} - check-name: ${{ env.UI_JOB_NAME }} - repo-token: ${{ secrets.GITHUB_TOKEN }} - allowed-conclusions: success - wait-interval: 20 - - - name: Getting workflow run ID - id: get_run_id - run: | - echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?[0-9]+)") | .number')" >> $GITHUB_OUTPUT - - - name: Getting proposed ui # filename: pr_ui/mici_ui_replay.mp4 - id: download-artifact - uses: dawidd6/action-download-artifact@v6 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - run_id: ${{ steps.get_run_id.outputs.run_id }} - search_artifacts: true - name: mici-raylib-report-1-${{ env.REPORT_NAME }} - path: ${{ github.workspace }}/pr_ui - - - name: Getting master ui # filename: master_ui_raylib/mici_ui_replay.mp4 - uses: actions/checkout@v6 - with: - repository: sunnypilot/ci-artifacts - ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} - path: ${{ github.workspace }}/master_ui_raylib - ref: ${{ env.MASTER_BRANCH_NAME }} - - - name: Saving new master ui - if: github.ref == 'refs/heads/master' && github.event_name == 'push' - working-directory: ${{ github.workspace }}/master_ui_raylib - run: | - git checkout --orphan=new_master_ui_mici_raylib - git rm -rf * - git branch -D ${{ env.MASTER_BRANCH_NAME }} - git branch -m ${{ env.MASTER_BRANCH_NAME }} - git config user.name "GitHub Actions Bot" - git config user.email "<>" - mv ${{ github.workspace }}/pr_ui/* . - git add . - git commit -m "mici raylib video for commit ${{ env.SHA }}" - git push origin ${{ env.MASTER_BRANCH_NAME }} --force - - - name: Setup FFmpeg - uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae - - - name: Finding diff - if: github.event_name == 'pull_request_target' - id: find_diff - run: | - # Find the video file from PR - pr_video="${{ github.workspace }}/pr_ui/mici_ui_replay_proposed.mp4" - mv "${{ github.workspace }}/pr_ui/mici_ui_replay.mp4" "$pr_video" - - master_video="${{ github.workspace }}/pr_ui/mici_ui_replay_master.mp4" - mv "${{ github.workspace }}/master_ui_raylib/mici_ui_replay.mp4" "$master_video" - - # Run report - export PYTHONPATH=${{ github.workspace }} - baseurl="https://github.com/sunnypilot/ci-artifacts/raw/refs/heads/${{ env.BRANCH_NAME }}" - diff_exit_code=0 - python3 ${{ github.workspace }}/selfdrive/ui/tests/diff/diff.py "${{ github.workspace }}/pr_ui/mici_ui_replay_master.mp4" "${{ github.workspace }}/pr_ui/mici_ui_replay_proposed.mp4" "diff.html" --basedir "$baseurl" --no-open || diff_exit_code=$? - - # Copy diff report files - cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.html ${{ github.workspace }}/pr_ui/ - cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.mp4 ${{ github.workspace }}/pr_ui/ - - REPORT_URL="https://sunnypilot.github.io/ci-artifacts/diff_pr_${{ github.event.number }}.html" - if [ $diff_exit_code -eq 0 ]; then - DIFF="✅ Videos are identical! [View Diff Report]($REPORT_URL)" - else - DIFF="❌ Videos differ! [View Diff Report]($REPORT_URL)" - fi - echo "DIFF=$DIFF" >> "$GITHUB_OUTPUT" - - - name: Saving proposed ui - if: github.event_name == 'pull_request_target' - working-directory: ${{ github.workspace }}/master_ui_raylib - run: | - # Overwrite PR branch w/ proposed ui, and master ui at this point in time for future reference - git config user.name "GitHub Actions Bot" - git config user.email "<>" - git checkout --orphan=${{ env.BRANCH_NAME }} - git rm -rf * - mv ${{ github.workspace }}/pr_ui/* . - git add . - git commit -m "mici raylib video for PR #${{ github.event.number }}" - git push origin ${{ env.BRANCH_NAME }} --force - - # Append diff report to report files branch - git fetch origin ${{ env.REPORT_FILES_BRANCH_NAME }} - git checkout ${{ env.REPORT_FILES_BRANCH_NAME }} - cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.html diff_pr_${{ github.event.number }}.html - git add diff_pr_${{ github.event.number }}.html - git commit -m "mici raylib ui diff report for PR #${{ github.event.number }}" || echo "No changes to commit" - git push origin ${{ env.REPORT_FILES_BRANCH_NAME }} - - - name: Comment Video on PR - if: github.event_name == 'pull_request_target' - uses: thollander/actions-comment-pull-request@v2 - with: - message: | - - ## mici raylib UI Preview - ${{ steps.find_diff.outputs.DIFF }} - comment_tag: run_id_video_mici_raylib - pr_number: ${{ github.event.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/model_review.yaml b/.github/workflows/model_review.yaml index 6b8ce143db..2775dbc574 100644 --- a/.github/workflows/model_review.yaml +++ b/.github/workflows/model_review.yaml @@ -17,6 +17,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + with: + submodules: true - name: Checkout master uses: actions/checkout@v6 with: @@ -25,14 +27,12 @@ jobs: - run: git lfs pull - run: cd base && git lfs pull - - run: pip install onnx - - name: scripts/reporter.py id: report run: | echo "content<> $GITHUB_OUTPUT echo "## Model Review" >> $GITHUB_OUTPUT - MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT + PYTHONPATH=${{ github.workspace }} MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Post model report comment diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index a038cdce63..aeb0f11d84 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -6,7 +6,7 @@ on: env: DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} - BUILD: release/ci/docker_build_sp.sh prebuilt + BUILD: release/ci/docker_build_sp.sh jobs: build_prebuilt: @@ -28,7 +28,7 @@ jobs: wait-interval: 30 running-workflow-name: 'build prebuilt' repo-token: ${{ secrets.GITHUB_TOKEN }} - check-regexp: ^((?!.*(build master-ci).*).)*$ + check-regexp: ^((?!.*(build master-ci|create badges).*).)*$ - uses: actions/checkout@v6 with: submodules: true diff --git a/.github/workflows/raylib_ui_preview.yaml b/.github/workflows/raylib_ui_preview.yaml deleted file mode 100644 index 7f569f3bf1..0000000000 --- a/.github/workflows/raylib_ui_preview.yaml +++ /dev/null @@ -1,175 +0,0 @@ -name: "raylib ui preview" -on: - push: - branches: - - master - pull_request_target: - types: [assigned, opened, synchronize, reopened, edited] - branches: - - 'master' - paths: - - 'selfdrive/assets/**' - - 'selfdrive/ui/**' - - 'system/ui/**' - workflow_dispatch: - -env: - UI_JOB_NAME: "Create raylib UI Report" - REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} - SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }} - BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-raylib-ui" - -jobs: - preview: - if: github.repository == 'sunnypilot/sunnypilot' - name: preview - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - pull-requests: write - actions: read - steps: - - name: Waiting for ui generation to start - run: sleep 30 - - - name: Waiting for ui generation to end - uses: lewagon/wait-on-check-action@v1.3.4 - with: - ref: ${{ env.SHA }} - check-name: ${{ env.UI_JOB_NAME }} - repo-token: ${{ secrets.GITHUB_TOKEN }} - allowed-conclusions: success - wait-interval: 20 - - - name: Getting workflow run ID - id: get_run_id - run: | - echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?[0-9]+)") | .number')" >> $GITHUB_OUTPUT - - - name: Getting proposed ui - id: download-artifact - uses: dawidd6/action-download-artifact@v6 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - run_id: ${{ steps.get_run_id.outputs.run_id }} - search_artifacts: true - name: raylib-report-1-${{ env.REPORT_NAME }} - path: ${{ github.workspace }}/pr_ui - - - name: Getting master ui - uses: actions/checkout@v6 - with: - repository: sunnypilot/ci-artifacts - ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} - path: ${{ github.workspace }}/master_ui_raylib - ref: openpilot_master_ui_raylib - - - name: Saving new master ui - if: github.ref == 'refs/heads/master' && github.event_name == 'push' - working-directory: ${{ github.workspace }}/master_ui_raylib - run: | - git checkout --orphan=new_master_ui_raylib - git rm -rf * - git branch -D openpilot_master_ui_raylib - git branch -m openpilot_master_ui_raylib - git config user.name "GitHub Actions Bot" - git config user.email "<>" - mv ${{ github.workspace }}/pr_ui/*.png . - git add . - git commit -m "raylib screenshots for commit ${{ env.SHA }}" - git push origin openpilot_master_ui_raylib --force - - - name: Finding diff - if: github.event_name == 'pull_request_target' - id: find_diff - run: >- - sudo apt-get update && sudo apt-get install -y imagemagick - - scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device') - A=($scenes) - - DIFF="" - TABLE="
All Screenshots" - TABLE="${TABLE}" - - for ((i=0; i<${#A[*]}; i=i+1)); - do - # Check if the master file exists - if [ ! -f "${{ github.workspace }}/master_ui_raylib/${A[$i]}.png" ]; then - # This is a new file in PR UI that doesn't exist in master - DIFF="${DIFF}
" - DIFF="${DIFF}${A[$i]} : \$\${\\color{cyan}\\text{NEW}}\$\$" - DIFF="${DIFF}
" - - DIFF="${DIFF}" - DIFF="${DIFF} " - DIFF="${DIFF}" - - DIFF="${DIFF}
" - DIFF="${DIFF}
" - elif ! compare -fuzz 2% -highlight-color DeepSkyBlue1 -lowlight-color Black -compose Src ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png; then - convert ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png -transparent black mask.png - composite mask.png ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png composite_diff.png - convert -delay 100 ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png composite_diff.png -loop 0 ${{ github.workspace }}/pr_ui/${A[$i]}_diff.gif - - mv ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_master_ref.png - - DIFF="${DIFF}
" - DIFF="${DIFF}${A[$i]} : \$\${\\color{red}\\text{DIFFERENT}}\$\$" - DIFF="${DIFF}" - - DIFF="${DIFF}" - DIFF="${DIFF} " - DIFF="${DIFF} " - DIFF="${DIFF}" - - DIFF="${DIFF}" - DIFF="${DIFF} " - DIFF="${DIFF} " - DIFF="${DIFF}" - - DIFF="${DIFF}
master proposed
diff composite diff
" - DIFF="${DIFF}
" - else - rm -f ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png - fi - - INDEX=$(($i % 2)) - if [[ $INDEX -eq 0 ]]; then - TABLE="${TABLE}" - fi - TABLE="${TABLE} " - if [[ $INDEX -eq 1 || $(($i + 1)) -eq ${#A[*]} ]]; then - TABLE="${TABLE}" - fi - done - - TABLE="${TABLE}" - - echo "DIFF=$DIFF$TABLE" >> "$GITHUB_OUTPUT" - - - name: Saving proposed ui - if: github.event_name == 'pull_request_target' - working-directory: ${{ github.workspace }}/master_ui_raylib - run: | - git config user.name "GitHub Actions Bot" - git config user.email "<>" - git checkout --orphan=${{ env.BRANCH_NAME }} - git rm -rf * - mv ${{ github.workspace }}/pr_ui/* . - git add . - git commit -m "raylib screenshots for PR #${{ github.event.number }}" - git push origin ${{ env.BRANCH_NAME }} --force - - - name: Comment Screenshots on PR - if: github.event_name == 'pull_request_target' - uses: thollander/actions-comment-pull-request@v2 - with: - message: | - - ## raylib UI Preview - ${{ steps.find_diff.outputs.DIFF }} - comment_tag: run_id_screenshots_raylib - pr_number: ${{ github.event.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3a6cfa59d0..908c570391 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,20 +7,12 @@ on: jobs: build___nightly: name: build __nightly - env: - ImageOS: ubuntu24 - container: - image: ghcr.io/sunnypilot/sunnypilot-base:latest runs-on: ubuntu-latest if: github.repository == 'sunnypilot/sunnypilot' permissions: checks: read contents: write steps: - - name: Install wait-on-check-action dependencies - run: | - sudo apt-get update - sudo apt-get install -y libyaml-dev - name: Wait for green check mark if: ${{ github.event_name == 'schedule' }} uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc @@ -29,14 +21,11 @@ jobs: wait-interval: 30 running-workflow-name: 'build __nightly' repo-token: ${{ secrets.GITHUB_TOKEN }} - check-regexp: ^((?!.*(build prebuilt).*).)*$ + check-regexp: ^((?!.*(build prebuilt|create badges).*).)*$ - uses: actions/checkout@v6 with: submodules: true fetch-depth: 0 - - name: Pull LFS - run: | - git config --global --add safe.directory '*' - git lfs pull + - run: ./tools/op.sh setup - name: Push __nightly run: BRANCH=__nightly release/build_stripped.sh diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 3c652b6ce6..e7933dc470 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -6,9 +6,7 @@ on: workflow_dispatch: env: - BASE_IMAGE: sunnypilot-base - BUILD: release/ci/docker_build_sp.sh base - RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c + PYTHONPATH: ${{ github.workspace }} jobs: update_translations: @@ -16,10 +14,11 @@ jobs: if: github.repository == 'sunnypilot/sunnypilot' steps: - uses: actions/checkout@v6 - - uses: ./.github/workflows/setup-with-retry + with: + submodules: true + - run: ./tools/op.sh setup - name: Update translations - run: | - ${{ env.RUN }} "python3 selfdrive/ui/update_translations.py --vanish" + run: python3 selfdrive/ui/update_translations.py --vanish - name: Create Pull Request uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 with: @@ -35,34 +34,44 @@ jobs: package_updates: name: package_updates runs-on: ubuntu-latest - container: - image: ghcr.io/sunnypilot/sunnypilot-base:latest if: github.repository == 'sunnypilot/sunnypilot' steps: - uses: actions/checkout@v6 with: submodules: true + - run: ./tools/op.sh setup - name: uv lock - if: github.repository == 'commaai/openpilot' - run: | - python3 -m ensurepip --upgrade - pip3 install uv - uv lock --upgrade + run: uv lock --upgrade - name: uv pip tree id: pip_tree run: | echo 'PIP_TREE<> $GITHUB_OUTPUT uv pip tree >> $GITHUB_OUTPUT echo 'EOF' >> $GITHUB_OUTPUT + - name: venv size + id: venv_size + run: | + echo 'VENV_SIZE<> $GITHUB_OUTPUT + echo "Total: $(du -sh .venv | cut -f1)" >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + echo "Top 10 by size:" >> $GITHUB_OUTPUT + du -sh .venv/lib/python*/site-packages/* 2>/dev/null \ + | grep -v '\.dist-info' \ + | grep -v '__pycache__' \ + | sort -rh \ + | head -10 \ + | while IFS=$'\t' read size path; do echo "$size ${path##*/}"; done >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT - name: bump submodules run: | - git config --global --add safe.directory '*' + git config submodule.msgq.update none + git config submodule.rednose_repo.update none + git config submodule.teleoprtc_repo.update none git config submodule.tinygrad.update none git submodule update --remote git add . - name: update car docs run: | - export PYTHONPATH="$PWD" scons -j$(nproc) --minimal opendbc_repo python selfdrive/car/docs.py git add docs/CARS.md @@ -80,6 +89,12 @@ jobs: Automatic PR from repo-maintenance -> package_updates ``` + $ du -sh .venv && du -sh .venv/lib/python*/site-packages/* | sort -rh | head -10 + ${{ steps.venv_size.outputs.VENV_SIZE }} + ``` + + ``` + $ uv pip tree ${{ steps.pip_tree.outputs.PIP_TREE }} ``` labels: bot diff --git a/.github/workflows/setup-with-retry/action.yaml b/.github/workflows/setup-with-retry/action.yaml deleted file mode 100644 index 98a3913600..0000000000 --- a/.github/workflows/setup-with-retry/action.yaml +++ /dev/null @@ -1,52 +0,0 @@ -name: 'openpilot env setup, with retry on failure' - -inputs: - docker_hub_pat: - description: 'Auth token for Docker Hub, required for BuildJet jobs' - required: false - default: '' - sleep_time: - description: 'Time to sleep between retries' - required: false - default: 30 - -outputs: - duration: - description: 'Duration of the setup process in seconds' - value: ${{ steps.get_duration.outputs.duration }} - -runs: - using: "composite" - steps: - - id: start_time - shell: bash - run: echo "START_TIME=$(date +%s)" >> $GITHUB_ENV - - id: setup1 - uses: ./.github/workflows/setup - continue-on-error: true - with: - is_retried: true - - if: steps.setup1.outcome == 'failure' - shell: bash - run: sleep ${{ inputs.sleep_time }} - - id: setup2 - if: steps.setup1.outcome == 'failure' - uses: ./.github/workflows/setup - continue-on-error: true - with: - is_retried: true - - if: steps.setup2.outcome == 'failure' - shell: bash - run: sleep ${{ inputs.sleep_time }} - - id: setup3 - if: steps.setup2.outcome == 'failure' - uses: ./.github/workflows/setup - with: - is_retried: true - - id: get_duration - shell: bash - run: | - END_TIME=$(date +%s) - DURATION=$((END_TIME - START_TIME)) - echo "Total duration: $DURATION seconds" - echo "duration=$DURATION" >> $GITHUB_OUTPUT diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml deleted file mode 100644 index 70177885a7..0000000000 --- a/.github/workflows/setup/action.yaml +++ /dev/null @@ -1,56 +0,0 @@ -name: 'openpilot env setup' - -inputs: - is_retried: - description: 'A mock param that asserts that we use the setup-with-retry instead of this action directly' - required: false - default: 'false' - -runs: - using: "composite" - steps: - # assert that this action is retried using the setup-with-retry - - shell: bash - if: ${{ inputs.is_retried == 'false' }} - run: | - echo "You should not run this action directly. Use setup-with-retry instead" - exit 1 - - - shell: bash - name: No retries! - run: | - if [ "${{ github.run_attempt }}" -gt ${{ github.event.pull_request.head.repo.fork && github.event.pull_request.author_association == 'NONE' && 2 || 1}} ]; then - echo -e "\033[0;31m##################################################" - echo -e "\033[0;31m Retries not allowed! Fix the flaky test! " - echo -e "\033[0;31m##################################################\033[0m" - exit 1 - fi - - # do this after checkout to ensure our custom LFS config is used to pull from GitLab - - shell: bash - run: git lfs pull - - # build cache - - id: date - shell: bash - run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - - shell: bash - run: echo "$CACHE_COMMIT_DATE" - - id: scons-cache - uses: ./.github/workflows/auto-cache - with: - path: .ci_cache/scons_cache - key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} - restore-keys: | - scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }} - scons-${{ runner.arch }} - # as suggested here: https://github.com/moby/moby/issues/32816#issuecomment-910030001 - - id: normalize-file-permissions - shell: bash - name: Normalize file permissions to ensure a consistent docker build cache - run: | - find . -type f -executable -not -perm 755 -exec chmod 755 {} \; - find . -type f -not -executable -not -perm 644 -exec chmod 644 {} \; - # build our docker image - - shell: bash - run: eval ${{ env.BUILD }} diff --git a/.github/workflows/sunnypilot-build-model.yaml b/.github/workflows/sunnypilot-build-model.yaml index bc4ecfe04b..ff09489b92 100644 --- a/.github/workflows/sunnypilot-build-model.yaml +++ b/.github/workflows/sunnypilot-build-model.yaml @@ -173,9 +173,18 @@ jobs: echo "Compiling: $onnx_file -> $output_file" QCOM=1 python3 "${{ env.TINYGRAD_PATH }}/examples/openpilot/compile3.py" "$onnx_file" "$output_file" - QCOM=1 python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true + DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true done + - name: Validate Model Outputs + run: | + source /etc/profile + export UV_PROJECT_ENVIRONMENT=${HOME}/venv + export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT + python3 "${{ github.workspace }}/release/ci/model_generator.py" \ + --validate-only \ + --model-dir "${{ env.MODELS_DIR }}" + - name: Prepare Output run: | sudo rm -rf ${{ env.OUTPUT_DIR }} @@ -184,7 +193,6 @@ jobs: # Copy the model files rsync -avm \ --include='*.dlc' \ - --include='*.thneed' \ --include='*.pkl' \ --include='*.onnx' \ --exclude='*' \ diff --git a/.github/workflows/sunnypilot-build-prebuilt.yaml b/.github/workflows/sunnypilot-build-prebuilt.yaml index dacbacbe26..3966d1a6c9 100644 --- a/.github/workflows/sunnypilot-build-prebuilt.yaml +++ b/.github/workflows/sunnypilot-build-prebuilt.yaml @@ -180,8 +180,6 @@ jobs: ./release/release_files.py | sort | uniq | rsync -rRl${RUNNER_DEBUG:+v} --files-from=- . $BUILD_DIR/ cd $BUILD_DIR sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py - echo "Building sunnypilot's modeld..." - scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/modeld echo "Building sunnypilot's modeld_v2..." scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/modeld_v2 echo "Building sunnypilot's locationd..." @@ -219,7 +217,6 @@ jobs: --exclude='**/.venv/' \ --exclude='selfdrive/modeld/models/driving_vision.onnx' \ --exclude='selfdrive/modeld/models/driving_policy.onnx' \ - --exclude='sunnypilot/modeld*/models/supercombo.onnx' \ --exclude='third_party/*x86*' \ --exclude='third_party/*Darwin*' \ --delete-excluded \ diff --git a/.github/workflows/sunnypilot-master-dev-prep.yaml b/.github/workflows/sunnypilot-master-dev-prep.yaml index 8e8e3c3d9d..1cb574764b 100644 --- a/.github/workflows/sunnypilot-master-dev-prep.yaml +++ b/.github/workflows/sunnypilot-master-dev-prep.yaml @@ -241,10 +241,3 @@ jobs: gh run watch "$RUN_ID" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Trigger prebuilt workflow - if: success() && steps.push-changes.outputs.has_changes == 'true' - run: | - gh workflow run sunnypilot-build-prebuilt.yaml --ref "${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 5cd51f9443..760b16326a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -18,13 +18,8 @@ concurrency: cancel-in-progress: true env: - PYTHONWARNINGS: error - BASE_IMAGE: sunnypilot-base - DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} - BUILD: release/ci/docker_build_sp.sh base - - RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c - + CI: 1 + PYTHONPATH: ${{ github.workspace }} PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical jobs: @@ -34,10 +29,11 @@ jobs: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} env: STRIPPED_DIR: /tmp/releasepilot + PYTHONPATH: /tmp/releasepilot steps: - uses: actions/checkout@v6 with: @@ -51,17 +47,15 @@ jobs: - name: Build devel timeout-minutes: 1 run: TARGET_DIR=$STRIPPED_DIR release/build_stripped.sh - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Build openpilot and run checks - 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 }} "python3 system/manager/build.py" + timeout-minutes: 30 + working-directory: ${{ env.STRIPPED_DIR }} + run: python3 system/manager/build.py - name: Run tests timeout-minutes: 1 - run: | - cd $STRIPPED_DIR - ${{ env.RUN }} "release/check-dirty.sh" + working-directory: ${{ env.STRIPPED_DIR }} + run: release/check-dirty.sh - name: Check submodules if: github.repository == 'sunnypilot/sunnypilot' timeout-minutes: 3 @@ -83,73 +77,20 @@ jobs: fi release/check-submodules.sh - build: - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') - || fromJSON('["ubuntu-24.04"]') }} - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - name: Setup docker push - if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'sunnypilot/sunnypilot' - run: | - echo "PUSH_IMAGE=true" >> "$GITHUB_ENV" - $DOCKER_LOGIN - - uses: ./.github/workflows/setup-with-retry - - uses: ./.github/workflows/compile-openpilot - timeout-minutes: 30 - build_mac: name: build macOS - if: false # tmp disable due to brew install not working runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} steps: - uses: actions/checkout@v6 with: submodules: true - - run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - - name: Homebrew cache - uses: ./.github/workflows/auto-cache - with: - save: false # No need save here if we manually save it later conditionally - path: ~/Library/Caches/Homebrew - key: brew-macos-${{ hashFiles('tools/Brewfile') }}-${{ github.sha }} - restore-keys: | - brew-macos-${{ hashFiles('tools/Brewfile') }} - brew-macos- - - name: Install dependencies - run: ./tools/mac_setup.sh - env: - PYTHONWARNINGS: default # package install has DeprecationWarnings - HOMEBREW_DISPLAY_INSTALL_TIMES: 1 - - name: Save Homebrew cache - uses: actions/cache/save@v4 - if: github.ref == 'refs/heads/master' - with: - path: ~/Library/Caches/Homebrew - key: brew-macos-${{ hashFiles('tools/Brewfile') }}-${{ github.sha }} - - run: git lfs pull - - name: Getting scons cache - uses: ./.github/workflows/auto-cache - with: - save: false # No need save here if we manually save it later conditionally - path: /tmp/scons_cache - key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} - restore-keys: | - scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }} - scons-${{ runner.arch }}-macos + - name: Remove Homebrew from environment + run: | + FILTERED=$(echo "$PATH" | tr ':' '\n' | grep -v '/opt/homebrew' | tr '\n' ':') + echo "PATH=${FILTERED}/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" >> $GITHUB_ENV + - run: ./tools/op.sh setup - name: Building openpilot - run: . .venv/bin/activate && scons -j$(nproc) - - name: Save scons cache - uses: actions/cache/save@v4 - if: github.ref == 'refs/heads/master' - with: - path: /tmp/scons_cache - key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + run: scons static_analysis: name: static analysis @@ -157,18 +98,16 @@ jobs: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} - env: - PYTHONWARNINGS: default steps: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Static analysis timeout-minutes: 1 - run: ${{ env.RUN }} "scripts/lint/lint.sh" + run: scripts/lint/lint.sh unit_tests: name: unit tests @@ -176,24 +115,22 @@ jobs: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} steps: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry - id: setup-step + - run: ./tools/op.sh setup - name: Build openpilot - run: ${{ env.RUN }} "scons -j$(nproc)" + run: scons -j$(nproc) - name: Run unit tests - timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 999 }} + timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 999 }} run: | - ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ - # Pre-compile Python bytecode so each pytest worker doesn't need to - $PYTEST --collect-only -m 'not slow' -qq && \ - MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \ - chmod -R 777 /tmp/comma_download_cache" + source selfdrive/test/setup_xvfb.sh + # Pre-compile Python bytecode so each pytest worker doesn't need to + $PYTEST --collect-only -m 'not slow' -qq + MAX_EXAMPLES=1 $PYTEST -m 'not slow' process_replay: name: process replay @@ -202,29 +139,19 @@ jobs: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} steps: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry - id: setup-step - - name: Cache test routes - id: dependency-cache - uses: actions/cache@v5 - with: - path: .ci_cache/comma_download_cache - key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/test_processes.py') }} + - run: ./tools/op.sh setup - name: Build openpilot - run: | - ${{ env.RUN }} "scons -j$(nproc)" + run: scons -j$(nproc) - name: Run replay - timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.dependency-cache.outputs.cache-hit == 'true') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 20 }} + timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 20 }} continue-on-error: ${{ github.ref == 'refs/heads/master' }} - run: | - ${{ env.RUN }} "selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ - chmod -R 777 /tmp/comma_download_cache" + run: selfdrive/test/process_replay/test_processes.py -j$(nproc) - name: Print diff id: print-diff if: always() @@ -246,21 +173,21 @@ jobs: if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master' working-directory: ${{ github.workspace }}/ci-artifacts run: | - git checkout --orphan process-replay - git rm -rf . git config user.name "GitHub Actions Bot" git config user.email "<>" + git fetch origin process-replay || true + git checkout process-replay 2>/dev/null || git checkout --orphan process-replay cp ${{ github.workspace }}/selfdrive/test/process_replay/fakedata/*.zst . echo "${{ github.sha }}" > ref_commit git add . - git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" - git push origin process-replay --force + git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit" + git push origin process-replay - name: Run regen if: false timeout-minutes: 4 - run: | - ${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \ - chmod -R 777 /tmp/comma_download_cache" + env: + ONNXCPU: 1 + run: $PYTEST selfdrive/test/process_replay/test_regen.py simulator_driving: name: simulator driving @@ -268,73 +195,44 @@ jobs: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} if: false # FIXME: Started to timeout recently steps: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry - id: setup-step + - run: ./tools/op.sh setup - name: Build openpilot - run: | - ${{ env.RUN }} "scons -j$(nproc)" + run: scons -j$(nproc) - name: Driving test - timeout-minutes: ${{ (steps.setup-step.outputs.duration < 18) && 1 || 2 }} + timeout-minutes: 2 run: | - ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ - source selfdrive/test/setup_vsound.sh && \ - CI=1 pytest -s tools/sim/tests/test_metadrive_bridge.py" + source selfdrive/test/setup_xvfb.sh + pytest -s tools/sim/tests/test_metadrive_bridge.py - create_raylib_ui_report: - name: Create raylib UI Report + create_ui_report: + name: Create UI Report runs-on: ${{ (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} steps: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Build openpilot - run: ${{ env.RUN }} "scons -j$(nproc)" - - name: Create raylib UI Report - run: > - ${{ env.RUN }} "PYTHONWARNINGS=ignore && - source selfdrive/test/setup_xvfb.sh && - python3 selfdrive/ui/tests/test_ui/raylib_screenshots.py" - - name: Upload Raylib UI Report + run: scons -j$(nproc) + - name: Create UI Report + run: | + source selfdrive/test/setup_xvfb.sh + python3 selfdrive/ui/tests/diff/replay.py + python3 selfdrive/ui/tests/diff/replay.py --big + - name: Upload UI Report uses: actions/upload-artifact@v6 with: - name: raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} - path: selfdrive/ui/tests/test_ui/raylib_report/screenshots - - create_mici_raylib_ui_report: - name: Create mici raylib UI Report - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') - || fromJSON('["ubuntu-24.04"]') }} - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - uses: ./.github/workflows/setup-with-retry - - name: Build openpilot - run: ${{ env.RUN }} "scons -j$(nproc)" - - name: Create mici raylib UI Report - run: > - ${{ env.RUN }} "PYTHONWARNINGS=ignore && - source selfdrive/test/setup_xvfb.sh && - WINDOWED=1 python3 selfdrive/ui/tests/diff/replay.py" - - name: Upload Raylib UI Report - uses: actions/upload-artifact@v6 - with: - name: mici-raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} + name: ui-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} path: selfdrive/ui/tests/diff/report diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml new file mode 100644 index 0000000000..a02726c3af --- /dev/null +++ b/.github/workflows/ui_preview.yaml @@ -0,0 +1,175 @@ +name: "ui preview" +on: + push: + branches: + - master + pull_request_target: + types: [assigned, opened, synchronize, reopened, edited] + branches: + - 'master' + paths: + - 'selfdrive/assets/**' + - 'selfdrive/ui/**' + - 'system/ui/**' + workflow_dispatch: + +env: + UI_JOB_NAME: "Create UI Report" + REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} + SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }} + BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-ui-preview" + REPORT_FILES_BRANCH_NAME: "mici-raylib-ui-reports" + + # variant:video_prefix:master_branch + VARIANTS: "mici:mici_ui_replay:openpilot_master_ui_mici_raylib big:tizi_ui_replay:openpilot_master_ui_big_raylib" + +jobs: + preview: + if: github.repository == 'sunnypilot/sunnypilot' + name: preview + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + pull-requests: write + actions: read + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Waiting for ui generation to end + uses: lewagon/wait-on-check-action@v1.3.4 + with: + ref: ${{ env.SHA }} + check-name: ${{ env.UI_JOB_NAME }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + allowed-conclusions: success + wait-interval: 20 + + - name: Getting workflow run ID + id: get_run_id + run: | + echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?[0-9]+)") | .number')" >> $GITHUB_OUTPUT + + - name: Getting proposed ui + uses: dawidd6/action-download-artifact@v6 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + run_id: ${{ steps.get_run_id.outputs.run_id }} + search_artifacts: true + name: ui-report-1-${{ env.REPORT_NAME }} + path: ${{ github.workspace }}/pr_ui + + - name: Getting mici master ui + uses: actions/checkout@v6 + with: + repository: sunnypilot/ci-artifacts + ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} + path: ${{ github.workspace }}/master_mici + ref: openpilot_master_ui_mici_raylib + + - name: Getting big master ui + uses: actions/checkout@v6 + with: + repository: sunnypilot/ci-artifacts + ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} + path: ${{ github.workspace }}/master_big + ref: openpilot_master_ui_big_raylib + + - name: Saving new master ui + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + run: | + for variant in $VARIANTS; do + IFS=':' read -r name video branch <<< "$variant" + master_dir="${{ github.workspace }}/master_${name}" + cd "$master_dir" + git checkout --orphan=new_branch + git rm -rf * + git branch -D "$branch" + git branch -m "$branch" + git config user.name "GitHub Actions Bot" + git config user.email "<>" + cp "${{ github.workspace }}/pr_ui/${video}.mp4" . + git add . + git commit -m "${name} video for commit ${{ env.SHA }}" + git push origin "$branch" --force + done + + - name: Setup FFmpeg + uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae + + - name: Finding diffs + if: github.event_name == 'pull_request_target' + id: find_diff + run: | + export PYTHONPATH=${{ github.workspace }} + baseurl="https://github.com/sunnypilot/ci-artifacts/raw/refs/heads/${{ env.BRANCH_NAME }}" + + COMMENT="" + for variant in $VARIANTS; do + IFS=':' read -r name video _ <<< "$variant" + diff_name="${name}_diff" + + mv "${{ github.workspace }}/pr_ui/${video}.mp4" "${{ github.workspace }}/pr_ui/${video}_proposed.mp4" + cp "${{ github.workspace }}/master_${name}/${video}.mp4" "${{ github.workspace }}/pr_ui/${video}_master.mp4" + + diff_exit_code=0 + python3 ${{ github.workspace }}/selfdrive/ui/tests/diff/diff.py \ + "${{ github.workspace }}/pr_ui/${video}_master.mp4" \ + "${{ github.workspace }}/pr_ui/${video}_proposed.mp4" \ + "${diff_name}.html" --basedir "$baseurl" --no-open || diff_exit_code=$? + + cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${{ github.workspace }}/pr_ui/" + cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.mp4" "${{ github.workspace }}/pr_ui/" + + REPORT_URL="https://sunnypilot.github.io/ci-artifacts/${diff_name}_pr_${{ github.event.number }}.html" + if [ $diff_exit_code -eq 0 ]; then + COMMENT+="**${name}**: Videos are identical! [View Diff Report]($REPORT_URL)"$'\n' + else + COMMENT+="**${name}**: ⚠️ Videos differ! [View Diff Report]($REPORT_URL)"$'\n' + fi + done + + { + echo "COMMENT<> "$GITHUB_OUTPUT" + + - name: Saving proposed ui + if: github.event_name == 'pull_request_target' + working-directory: ${{ github.workspace }}/master_mici + run: | + git config user.name "GitHub Actions Bot" + git config user.email "<>" + git checkout --orphan=${{ env.BRANCH_NAME }} + git rm -rf * + mv ${{ github.workspace }}/pr_ui/* . + git add . + git commit -m "ui videos for PR #${{ github.event.number }}" + git push origin ${{ env.BRANCH_NAME }} --force + + # Append diff reports to report files branch + git fetch origin ${{ env.REPORT_FILES_BRANCH_NAME }} + git checkout ${{ env.REPORT_FILES_BRANCH_NAME }} + for variant in $VARIANTS; do + IFS=':' read -r name _ _ <<< "$variant" + diff_name="${name}_diff" + cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${diff_name}_pr_${{ github.event.number }}.html" + git add "${diff_name}_pr_${{ github.event.number }}.html" + done + git commit -m "ui diff reports for PR #${{ github.event.number }}" || echo "No changes to commit" + git push origin ${{ env.REPORT_FILES_BRANCH_NAME }} + + - name: Comment on PR + if: github.event_name == 'pull_request_target' + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + + ## UI Preview + ${{ steps.find_diff.outputs.COMMENT }} + comment_tag: run_id_ui_preview + pr_number: ${{ github.event.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/vendor_third_party.yaml b/.github/workflows/vendor_third_party.yaml deleted file mode 100644 index df50cfad23..0000000000 --- a/.github/workflows/vendor_third_party.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: vendor third_party - -on: - workflow_dispatch: - -jobs: - build: - if: github.ref != 'refs/heads/master' - strategy: - matrix: - os: [ubuntu-24.04, macos-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - name: Build - run: third_party/build.sh - - name: Package artifacts - run: | - git add -A third_party/ - git diff --cached --name-only -- third_party/ | tar -cf /tmp/third_party_build.tar -T - - - uses: actions/upload-artifact@v4 - with: - name: third-party-${{ runner.os }} - path: /tmp/third_party_build.tar - - commit: - needs: build - runs-on: ubuntu-24.04 - permissions: - contents: write - steps: - - uses: actions/checkout@v6 - - uses: actions/download-artifact@v4 - with: - path: /tmp/artifacts - - name: Commit vendored libraries - run: | - for f in /tmp/artifacts/*/third_party_build.tar; do - tar xf "$f" - done - git add third_party/ - if git diff --cached --quiet; then - echo "No changes to commit" - exit 0 - fi - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git commit -m "third_party: rebuild vendor libraries" - git push diff --git a/.gitignore b/.gitignore index 18f494148d..cd5e64e52b 100644 --- a/.gitignore +++ b/.gitignore @@ -64,9 +64,7 @@ flycheck_* cppcheck_report.txt comma*.sh -selfdrive/modeld/models/*.pkl -sunnypilot/modeld*/thneed/compile -sunnypilot/modeld*/models/*.thneed +selfdrive/modeld/models/*.pkl* sunnypilot/modeld*/models/*.pkl # openpilot log files diff --git a/.gitmodules b/.gitmodules index cd6cf2168f..5c5d72a7dc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,7 +15,7 @@ url = https://github.com/commaai/teleoprtc [submodule "tinygrad"] path = tinygrad_repo - url = https://github.com/commaai/tinygrad.git + url = https://github.com/sunnypilot/tinygrad.git [submodule "sunnypilot/neural_network_data"] path = sunnypilot/neural_network_data url = https://github.com/sunnypilot/neural-network-data.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 61bc81bafc..8ad026b12b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,104 @@ -sunnypilot Version 2025.003.000 (20xx-xx-xx) +sunnypilot Version 2026.001.000 (2026-03-xx) ======================== +* What's Changed (sunnypilot/sunnypilot) + * Complete rewrite of the user interface from Qt C++ to Raylib Python + * comma four support + * ui: sunnypilot toggle style by @nayan8teen + * ui: fix scroll panel mouse wheel behavior by @nayan8teen + * ui: sunnypilot panels by @nayan8teen + * sunnylink: centralize key pair handling in sunnylink registration by @devtekve + * ui: reimplement sunnypilot branding with Raylib by @sunnyhaibin + * ui: Platform Selector by @Discountchubbs + * ui: vehicle brand settings by @Discountchubbs + * ui: sunnylink client-side implementation by @nayan8teen + * ui: `NetworkUISP` by @Discountchubbs + * ui: add sunnypilot font by @nayan8teen + * ui: sunnypilot sponsor tier color mapping by @sunnyhaibin + * ui: sunnylink panel by @nayan8teen + * ui: Models panel by @Discountchubbs + * ui: software panel by @Discountchubbs + * modeld_v2: support planplus outputs by @Discountchubbs + * ui: OSM panel by @Discountchubbs + * ui: Developer panel extension by @Discountchubbs + * sunnylink: Vehicle Selector support by @sunnyhaibin + * [TIZI/TICI] ui: Developer Metrics by @rav4kumar + * [comma 4] ui: sunnylink panel by @nayan8teen + * ui: lateral-only and longitudinal-only UI statuses support by @royjr + * sunnylink: elliptic curve keys support and improve key path handling by @nayan8teen + * sunnylink: block remote modification of SSH key parameters by @zikeji + * [TIZI/TICI] ui: rainbow path by @rav4kumar + * [TIZI/TICI] ui: chevron metrics by @rav4kumar + * ui: include MADS enabled state to `engaged` check by @sunnyhaibin + * Toyota: Enforce Factory Longitudinal Control by @sunnyhaibin + * ui: fix malformed dongle ID display on the PC if dongleID is not set by @dzid26 + * SL: Re enable and validate ingestion of swaglogs by @devtekve + * modeld_v2: planplus model tuning by @Discountchubbs + * ui: fix Always Offroad button visibility by @nayan8teen + * Reimplement sunnypilot Terms of Service & sunnylink Consent Screens by @sunnyhaibin + * [TIZI/TICI] ui: update dmoji position and Developer UI adjustments by @rav4kumar + * modeld: configurable camera offset by @Discountchubbs + * [TIZI/TICI] ui: sunnylink status on sidebar by @Copilot + * ui: Global Brightness Override by @nayan8teen + * ui: Customizable Interactive Timeout by @sunnyhaibin + * sunnylink: add units to param metadata by @nayan8teen + * ui: Customizable Onroad Brightness by @sunnyhaibin + * [TIZI/TICI] ui: Steering panel by @nayan8teen + * [TIZI/TICI] ui: Rocket Fuel by @rav4kumar + * [TIZI/TICI] ui: MICI style turn signals by @rav4kumar + * [TIZI/TICI] ui: MICI style blindspot indicators by @sunnyhaibin + * [MICI] ui: display blindspot indicators when available by @rav4kumar + * [TIZI/TICI] ui: Road Name by @rav4kumar + * [TIZI/TICI] ui: Blue "Exit Always Offroad" button by @dzid26 + * [TIZI/TICI] ui: Speed Limit by @rav4kumar + * Reapply "latcontrol_torque: lower kp and lower friction threshold (commaai/openpilot#36619)" by @sunnyhaibin + * [TIZI/TICI] ui: steering arc by @royjr + * [TIZI/TICI] ui: Smart Cruise Control elements by @sunnyhaibin + * [TIZI/TICI] ui: Green Light and Lead Departure elements by @sunnyhaibin + * [TIZI/TICI] ui: standstill timer by @sunnyhaibin + * [MICI] ui: driving models selector by @Discountchubbs + * [TIZI/TICI] ui: Hide vEgo and True vEgo by @sunnyhaibin + * [TIZI/TICI] ui: Visuals panel by @nayan8teen + * Device: Retain QuickBoot state after op switch by @nayan8teen + * [TIZI/TICI] ui: Trips panel by @sunnyhaibin + * [TIZI/TICI] ui: dynamic ICBM status by @sunnyhaibin + * [TIZI/TICI] ui: Cruise panel by @sunnyhaibin + * ui: better wake mode support by @nayan8teen + * Pause Lateral Control with Blinker: Post-Blinker Delay by @CHaucke89 + * SCC-V: Use p97 for predicted lateral accel by @yasu-oh + * Controls: Support for Torque Lateral Control v0 Tune by @sunnyhaibin +* What's Changed (sunnypilot/opendbc) + * Honda: DBC for Accord 9th Generation by @mvl-boston + * FCA: update tire stiffness values for `RAM_HD` by @dparring + * Honda: Nidec hybrid baseline brake support by @mvl-boston + * Subaru Global Gen2: bump steering limits and update tuning by @sunnyhaibin + * Toyota: Enforce Stock Longitudinal Control by @rav4kumar + * Nissan: use MADS enabled status for LKAS HUD logic by @downquark7 + * Reapply "Lateral: lower friction threshold (#2915)" (#378) by @sunnyhaibin + * HKG: add KIA_FORTE_2019_NON_SCC fingerprint by @royjr + * Nissan: Parse cruise control buttons by @downquark7 + * Rivian: Add stalk down ACC behavior to match stock Rivian by @lukasloetkolben + * Tesla: remove `TESLA_MODEL_X` from `dashcamOnly` by @ssysm + * Hyundai Longitudinal: refactor tuning by @Discountchubbs + * Tesla: add fingerprint for Model 3 Performance HW4 by @sunnyhaibin + * Toyota: do not disable radar when smartDSU or CAN Filter detected by @sunnyhaibin + * Honda: add missing `GasInterceptor` messages to Taiwan Odyssey DBC by @mvl-boston + * GM: remove `CHEVROLET_EQUINOX_NON_ACC_3RD_GEN` from `dashcamOnly` by @sunnyhaibin + * GM: remove `CHEVROLET_BOLT_NON_ACC_2ND_GEN` from `dashcamOnly` by @sunnyhaibin +* New Contributors (sunnypilot/sunnypilot) + * @TheSecurityDev made their first contribution in "ui: fix sidebar scroll in UI screenshots" + * @zikeji made their first contribution in "sunnylink: block remote modification of SSH key parameters" + * @Candy0707 made their first contribution in "[TIZI/TICI] ui: Fix misaligned turn signals and blindspot indicators with sidebar" + * @CHaucke89 made their first contribution in "Pause Lateral Control with Blinker: Post-Blinker Delay" + * @yasu-oh made their first contribution in "SCC-V: Use p97 for predicted lateral accel" +* New Contributors (sunnypilot/opendbc) + * @AmyJeanes made their first contribution in "Tesla: Fix stock LKAS being blocked when MADS is enabled" + * @mvl-boston made their first contribution in "Honda: Update Clarity brake to renamed DBC message name" + * @dzid26 made their first contribution in "Tesla: Parse speed limit from CAN" + * @firestar5683 made their first contribution in "GM: Non-ACC platforms with steering only support" + * @downquark7 made their first contribution in "Nissan: use MADS enabled status for LKAS HUD logic" + * @royjr made their first contribution in "HKG: add KIA_FORTE_2019_NON_SCC fingerprint" + * @ssysm made their first contribution in "Tesla: remove `TESLA_MODEL_X` from `dashcamOnly`" +* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2025.002.000...v2026.001.000 sunnypilot Version 2025.002.000 (2025-11-06) ======================== diff --git a/Dockerfile.openpilot b/Dockerfile.openpilot index 106a06e3a2..72d874b022 100644 --- a/Dockerfile.openpilot +++ b/Dockerfile.openpilot @@ -1,14 +1,38 @@ -FROM ghcr.io/commaai/openpilot-base:latest +FROM ubuntu:24.04 ENV PYTHONUNBUFFERED=1 -ENV OPENPILOT_PATH=/home/batman/openpilot +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && \ + apt-get install -y --no-install-recommends sudo tzdata locales && \ + rm -rf /var/lib/apt/lists/* +RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen +ENV LANG=en_US.UTF-8 +ENV LANGUAGE=en_US:en +ENV LC_ALL=en_US.UTF-8 + +ENV NVIDIA_VISIBLE_DEVICES=all +ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute + +ARG USER=batman +ARG USER_UID=1001 +RUN useradd -m -s /bin/bash -u $USER_UID $USER +RUN usermod -aG sudo $USER +RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers +USER $USER + +ENV OPENPILOT_PATH=/home/$USER/openpilot RUN mkdir -p ${OPENPILOT_PATH} WORKDIR ${OPENPILOT_PATH} -COPY . ${OPENPILOT_PATH}/ +COPY --chown=$USER . ${OPENPILOT_PATH}/ -ENV UV_BIN="/home/batman/.local/bin/" -ENV PATH="$UV_BIN:$PATH" -RUN UV_PROJECT_ENVIRONMENT=$VIRTUAL_ENV uv run scons --cache-readonly -j$(nproc) +ENV UV_BIN="/home/$USER/.local/bin/" +ENV VIRTUAL_ENV=${OPENPILOT_PATH}/.venv +ENV PATH="$UV_BIN:$VIRTUAL_ENV/bin:$PATH" +RUN tools/setup_dependencies.sh && \ + sudo rm -rf /var/lib/apt/lists/* + +USER root +RUN git config --global --add safe.directory '*' diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base deleted file mode 100644 index 04fb589bf6..0000000000 --- a/Dockerfile.openpilot_base +++ /dev/null @@ -1,82 +0,0 @@ -FROM ubuntu:24.04 - -ENV PYTHONUNBUFFERED=1 - -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && \ - apt-get install -y --no-install-recommends sudo tzdata locales ssh pulseaudio xvfb x11-xserver-utils gnome-screenshot python3-tk python3-dev && \ - rm -rf /var/lib/apt/lists/* - -RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen -ENV LANG=en_US.UTF-8 -ENV LANGUAGE=en_US:en -ENV LC_ALL=en_US.UTF-8 - -COPY tools/install_ubuntu_dependencies.sh /tmp/tools/ -RUN /tmp/tools/install_ubuntu_dependencies.sh && \ - rm -rf /var/lib/apt/lists/* /tmp/* && \ - cd /usr/lib/gcc/arm-none-eabi/* && \ - 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 \ - apt-utils \ - alien \ - unzip \ - tar \ - curl \ - xz-utils \ - dbus \ - gcc-arm-none-eabi \ - tmux \ - vim \ - libx11-6 \ - wget \ - && rm -rf /var/lib/apt/lists/* - -RUN mkdir -p /tmp/opencl-driver-intel && \ - cd /tmp/opencl-driver-intel && \ - wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \ - mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - mkdir -p /etc/OpenCL/vendors && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \ - cd /opt/intel && \ - tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - mkdir -p /etc/ld.so.conf.d && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \ - ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \ - cd / && \ - rm -rf /tmp/opencl-driver-intel - -ENV NVIDIA_VISIBLE_DEVICES=all -ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute -ENV QTWEBENGINE_DISABLE_SANDBOX=1 - -RUN dbus-uuidgen > /etc/machine-id -RUN apt-get update && apt-get install -y fonts-noto-cjk fonts-noto-color-emoji - -ARG USER=batman -ARG USER_UID=1001 -RUN useradd -m -s /bin/bash -u $USER_UID $USER -RUN usermod -aG sudo $USER -RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers -USER $USER - -COPY --chown=$USER pyproject.toml uv.lock /home/$USER -COPY --chown=$USER tools/install_python_dependencies.sh /home/$USER/tools/ - -ENV VIRTUAL_ENV=/home/$USER/.venv -ENV PATH="$VIRTUAL_ENV/bin:$PATH" -RUN cd /home/$USER && \ - tools/install_python_dependencies.sh && \ - rm -rf tools/ pyproject.toml uv.lock .cache - -USER root -RUN sudo git config --global --add safe.directory /tmp/openpilot diff --git a/Dockerfile.sunnypilot b/Dockerfile.sunnypilot deleted file mode 100644 index 88a226ad08..0000000000 --- a/Dockerfile.sunnypilot +++ /dev/null @@ -1,12 +0,0 @@ -FROM ghcr.io/sunnypilot/sunnypilot-base:latest - -ENV PYTHONUNBUFFERED=1 - -ENV OPENPILOT_PATH=/home/batman/openpilot - -RUN mkdir -p ${OPENPILOT_PATH} -WORKDIR ${OPENPILOT_PATH} - -COPY . ${OPENPILOT_PATH}/ - -RUN scons --cache-readonly -j$(nproc) diff --git a/Dockerfile.sunnypilot_base b/Dockerfile.sunnypilot_base deleted file mode 100644 index ec1e3b6c06..0000000000 --- a/Dockerfile.sunnypilot_base +++ /dev/null @@ -1,83 +0,0 @@ -FROM ubuntu:24.04 - -ENV PYTHONUNBUFFERED=1 - -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && \ - apt-get install -y --no-install-recommends sudo tzdata locales ssh pulseaudio xvfb x11-xserver-utils gnome-screenshot python3-tk python3-dev && \ - rm -rf /var/lib/apt/lists/* - -RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen -ENV LANG=en_US.UTF-8 -ENV LANGUAGE=en_US:en -ENV LC_ALL=en_US.UTF-8 - -COPY tools/install_ubuntu_dependencies.sh /tmp/tools/ -RUN /tmp/tools/install_ubuntu_dependencies.sh && \ - rm -rf /var/lib/apt/lists/* /tmp/* && \ - cd /usr/lib/gcc/arm-none-eabi/* && \ - 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 \ - apt-utils \ - alien \ - unzip \ - tar \ - curl \ - xz-utils \ - dbus \ - gcc-arm-none-eabi \ - tmux \ - vim \ - libx11-6 \ - wget \ - rsync \ - && rm -rf /var/lib/apt/lists/* - -RUN mkdir -p /tmp/opencl-driver-intel && \ - cd /tmp/opencl-driver-intel && \ - wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \ - mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - mkdir -p /etc/OpenCL/vendors && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \ - cd /opt/intel && \ - tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - mkdir -p /etc/ld.so.conf.d && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \ - ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \ - cd / && \ - rm -rf /tmp/opencl-driver-intel - -ENV NVIDIA_VISIBLE_DEVICES=all -ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute -ENV QTWEBENGINE_DISABLE_SANDBOX=1 - -RUN dbus-uuidgen > /etc/machine-id -RUN apt-get update && apt-get install -y fonts-noto-cjk fonts-noto-color-emoji - -ARG USER=batman -ARG USER_UID=1001 -RUN useradd -m -s /bin/bash -u $USER_UID $USER -RUN usermod -aG sudo $USER -RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers -USER $USER - -COPY --chown=$USER pyproject.toml uv.lock /home/$USER -COPY --chown=$USER tools/install_python_dependencies.sh /home/$USER/tools/ - -ENV VIRTUAL_ENV=/home/$USER/.venv -ENV PATH="$VIRTUAL_ENV/bin:$PATH" -RUN cd /home/$USER && \ - tools/install_python_dependencies.sh && \ - rm -rf tools/ pyproject.toml uv.lock .cache - -USER root -RUN sudo git config --global --add safe.directory /tmp/openpilot diff --git a/Jenkinsfile b/Jenkinsfile index c095eda8a9..c5ebf6162b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -210,30 +210,23 @@ node { 'HW + Unit Tests': { deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"), step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]), step("test manager", "pytest system/manager/test/test_manager.py"), ]) }, - 'loopback': { - deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [ - step("build openpilot", "cd system/manager && ./build.py"), - step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"), - ]) - }, 'camerad OX03C10': { deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]), - step("test exposure", "pytest system/camerad/test/test_exposure.py"), + step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), + step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, 'camerad OS04C10': { deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]), - step("test exposure", "pytest system/camerad/test/test_exposure.py"), + step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), + step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, 'sensord': { @@ -251,11 +244,9 @@ node { 'tizi': { deviceStage("tizi", "tizi", ["UNSAFE=1"], [ step("build openpilot", "cd system/manager && ./build.py"), - step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"), + step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"), step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"), step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"), - // TODO: enable once new AGNOS is available - // step("test esim", "pytest system/hardware/tici/tests/test_esim.py"), step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]), ]) }, diff --git a/RELEASES.md b/RELEASES.md index 6191c6ba3d..895dcbba7a 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,8 @@ Version 0.10.4 (2026-02-17) ======================== +* Kia K7 2017 support thanks to royjr! * Lexus LS 2018 support thanks to Hacheoy! +* Reduce comma four standby power usage by 77% to 52 mW Version 0.10.3 (2025-12-17) ======================== diff --git a/SConstruct b/SConstruct index 9f54e2a560..7db9f7e3d2 100644 --- a/SConstruct +++ b/SConstruct @@ -18,6 +18,7 @@ AddOption('--asan', action='store_true', help='turn on ASAN') AddOption('--ubsan', action='store_true', help='turn on UBSan') AddOption('--mutation', action='store_true', help='generate mutation-ready code') AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line') +AddOption('--verbose', action='store_true', default=False, help='show full build commands') AddOption('--minimal', action='store_false', dest='extras', @@ -28,7 +29,6 @@ AddOption('--minimal', arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() if platform.system() == "Darwin": arch = "Darwin" - brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() elif arch == "aarch64" and os.path.isfile('/TICI'): arch = "larch64" assert arch in [ @@ -38,6 +38,25 @@ assert arch in [ "Darwin", # macOS arm64 (x86 not supported) ] +if arch != "larch64": + import bzip2 + import capnproto + import eigen + import ffmpeg as ffmpeg_pkg + import libjpeg + import libyuv + import ncurses + import openssl3 + import python3_dev + import zeromq + import zstd + pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, zeromq, zstd] + py_include = python3_dev.INCLUDE_DIR +else: + # TODO: remove when AGNOS has our new vendor pkgs + pkgs = [] + py_include = sysconfig.get_paths()['include'] + env = Environment( ENV={ "PATH": os.environ['PATH'], @@ -46,15 +65,13 @@ env = Environment( "ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath, "TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer" }, - CC='clang', - CXX='clang++', CCFLAGS=[ "-g", "-fPIC", "-O2", "-Wunused", "-Werror", - "-Wshadow", + "-Wshadow" if arch in ("Darwin", "larch64") else "-Wshadow=local", "-Wno-unknown-warning-option", "-Wno-inconsistent-missing-override", "-Wno-c99-designator", @@ -73,7 +90,7 @@ env = Environment( "#third_party/acados/include/blasfeo/include", "#third_party/acados/include/hpipm/include", "#third_party/catch2/include", - "#third_party/libyuv/include", + [x.INCLUDE_DIR for x in pkgs], ], LIBPATH=[ "#common", @@ -81,8 +98,8 @@ env = Environment( "#third_party", "#selfdrive/pandad", "#rednose/helpers", - f"#third_party/libyuv/{arch}/lib", f"#third_party/acados/{arch}/lib", + [x.LIB_DIR for x in pkgs], ], RPATH=[], CYTHONCFILESUFFIX=".cpp", @@ -94,7 +111,8 @@ env = Environment( # Arch-specific flags and paths if arch == "larch64": - env.Append(CPPPATH=["#third_party/opencl/include"]) + env["CC"] = "clang" + env["CXX"] = "clang++" env.Append(LIBPATH=[ "/usr/local/lib", "/system/vendor/lib64", @@ -105,17 +123,10 @@ if arch == "larch64": env.Append(CXXFLAGS=arch_flags) elif arch == "Darwin": env.Append(LIBPATH=[ - f"{brew_prefix}/lib", - f"{brew_prefix}/opt/openssl@3.0/lib", - f"{brew_prefix}/opt/llvm/lib/c++", "/System/Library/Frameworks/OpenGL.framework/Libraries", ]) env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"]) env.Append(CXXFLAGS=["-DGL_SILENCE_DEPRECATION"]) - env.Append(CPPPATH=[ - f"{brew_prefix}/include", - f"{brew_prefix}/opt/openssl@3.0/include", - ]) else: env.Append(LIBPATH=[ "/usr/lib", @@ -138,6 +149,22 @@ if _extra_cc: if arch != "Darwin": env.Append(LINKFLAGS=["-Wl,--as-needed", "-Wl,--no-undefined"]) +# Shorter build output: show brief descriptions instead of full commands. +# Full command lines are still printed on failure by scons. +if not GetOption('verbose'): + for action, short in ( + ("CC", "CC"), + ("CXX", "CXX"), + ("LINK", "LINK"), + ("SHCC", "CC"), + ("SHCXX", "CXX"), + ("SHLINK", "LINK"), + ("AR", "AR"), + ("RANLIB", "RANLIB"), + ("AS", "AS"), + ): + env[f"{action}COMSTR"] = f" [{short}] $TARGET" + # progress output node_interval = 5 node_count = 0 @@ -149,10 +176,9 @@ if os.environ.get('SCONS_PROGRESS'): Progress(progress_function, interval=node_interval) # ********** Cython build environment ********** -py_include = sysconfig.get_paths()['include'] envCython = env.Clone() envCython["CPPPATH"] += [py_include, np.get_include()] -envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-shadow", "-Wno-deprecated-declarations"] +envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-deprecated-declarations"] envCython["CCFLAGS"].remove("-Werror") envCython["LIBS"] = [] @@ -215,10 +241,8 @@ SConscript(['selfdrive/SConscript']) SConscript(['sunnypilot/SConscript']) -if Dir('#tools/cabana/').exists() and GetOption('extras'): - SConscript(['tools/replay/SConscript']) - if arch != "larch64": - SConscript(['tools/cabana/SConscript']) +if Dir('#tools/cabana/').exists() and arch != "larch64": + SConscript(['tools/cabana/SConscript']) env.CompilationDatabase('compile_commands.json') diff --git a/cereal/log.capnp b/cereal/log.capnp index 343b7a97fa..d14e149c14 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -499,7 +499,8 @@ struct DeviceState @0xa4d8b5af2aa492eb { pmicTempC @39 :List(Float32); intakeTempC @46 :Float32; exhaustTempC @47 :Float32; - caseTempC @48 :Float32; + gnssTempC @48 :Float32; + bottomSocTempC @50 :Float32; maxTempC @44 :Float32; # max of other temps, used to control fan thermalZones @38 :List(ThermalZone); thermalStatus @14 :ThermalStatus; @@ -592,6 +593,7 @@ struct PandaState @0xa7649e2575e4591e { harnessStatus @21 :HarnessStatus; sbu1Voltage @35 :Float32; sbu2Voltage @36 :Float32; + soundOutputLevel @37 :UInt16; # can health canState0 @29 :PandaCanState; @@ -2232,9 +2234,9 @@ struct DriverMonitoringState @0xb83cda094a1da284 { isActiveMode @16 :Bool; isRHD @4 :Bool; uncertainCount @19 :UInt32; - phoneProbOffset @20 :Float32; - phoneProbValidCount @21 :UInt32; + phoneProbOffsetDEPRECATED @20 :Float32; + phoneProbValidCountDEPRECATED @21 :UInt32; isPreviewDEPRECATED @15 :Bool; rhdCheckedDEPRECATED @5 :Bool; eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED); diff --git a/cereal/messaging/tests/test_messaging.py b/cereal/messaging/tests/test_messaging.py index 583eb8b0d8..afdab8a51f 100644 --- a/cereal/messaging/tests/test_messaging.py +++ b/cereal/messaging/tests/test_messaging.py @@ -5,7 +5,7 @@ import numbers import random import threading import time -from parameterized import parameterized +from openpilot.common.parameterized import parameterized import pytest from cereal import log, car diff --git a/cereal/messaging/tests/test_services.py b/cereal/messaging/tests/test_services.py index 8bfd2ea978..3320723fec 100644 --- a/cereal/messaging/tests/test_services.py +++ b/cereal/messaging/tests/test_services.py @@ -1,7 +1,7 @@ import os import tempfile from typing import Dict -from parameterized import parameterized +from openpilot.common.parameterized import parameterized import cereal.services as services from cereal.services import SERVICE_LIST diff --git a/common/SConscript b/common/SConscript index 1c68cf05c7..15a0e5eff1 100644 --- a/common/SConscript +++ b/common/SConscript @@ -5,7 +5,6 @@ common_libs = [ 'swaglog.cc', 'util.cc', 'ratekeeper.cc', - 'clutil.cc', ] _common = env.Library('common', common_libs, LIBS="json11") diff --git a/common/clutil.cc b/common/clutil.cc deleted file mode 100644 index f8381a7e09..0000000000 --- a/common/clutil.cc +++ /dev/null @@ -1,98 +0,0 @@ -#include "common/clutil.h" - -#include -#include -#include - -#include "common/util.h" -#include "common/swaglog.h" - -namespace { // helper functions - -template -std::string get_info(Func get_info_func, Id id, Name param_name) { - size_t size = 0; - CL_CHECK(get_info_func(id, param_name, 0, NULL, &size)); - std::string info(size, '\0'); - CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL)); - return info; -} -inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); } -inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); } - -void cl_print_info(cl_platform_id platform, cl_device_id device) { - size_t work_group_size = 0; - cl_device_type device_type = 0; - clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL); - clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL); - const char *type_str = "Other..."; - switch (device_type) { - case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break; - case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break; - case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; - } - - LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); - LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); - LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); - LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); - LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); - LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); - LOGD("max work group size: %zu", work_group_size); - LOGD("type = %d, %s", (int)device_type, type_str); -} - -void cl_print_build_errors(cl_program program, cl_device_id device) { - cl_build_status status; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL); - size_t log_size; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); - std::string log(log_size, '\0'); - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); - - LOGE("build failed; status=%d, log: %s", status, log.c_str()); -} - -} // namespace - -cl_device_id cl_get_device_id(cl_device_type device_type) { - cl_uint num_platforms = 0; - CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms)); - std::unique_ptr platform_ids = std::make_unique(num_platforms); - CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); - - for (size_t i = 0; i < num_platforms; ++i) { - LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); - - // Get first device - if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { - cl_print_info(platform_ids[i], device_id); - return device_id; - } - } - LOGE("No valid openCL platform found"); - assert(0); - return nullptr; -} - -cl_context cl_create_context(cl_device_id device_id) { - return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); -} - -void cl_release_context(cl_context context) { - clReleaseContext(context); -} - -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) { - return cl_program_from_source(ctx, device_id, util::read_file(path), args); -} - -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) { - const char *csrc = src.c_str(); - cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err)); - if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { - cl_print_build_errors(prg, device_id); - assert(0); - } - return prg; -} diff --git a/common/clutil.h b/common/clutil.h deleted file mode 100644 index b364e79d45..0000000000 --- a/common/clutil.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include - -#define CL_CHECK(_expr) \ - do { \ - assert(CL_SUCCESS == (_expr)); \ - } while (0) - -#define CL_CHECK_ERR(_expr) \ - ({ \ - cl_int err = CL_INVALID_VALUE; \ - __typeof__(_expr) _ret = _expr; \ - assert(_ret&& err == CL_SUCCESS); \ - _ret; \ - }) - -cl_device_id cl_get_device_id(cl_device_type device_type); -cl_context cl_create_context(cl_device_id device_id); -void cl_release_context(cl_context context); -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); diff --git a/common/file_chunker.py b/common/file_chunker.py new file mode 100644 index 0000000000..ac9ddbb384 --- /dev/null +++ b/common/file_chunker.py @@ -0,0 +1,37 @@ +import math +import os +from pathlib import Path + +CHUNK_SIZE = 45 * 1024 * 1024 # 45MB, under GitHub's 50MB limit + +def get_chunk_name(name, idx, num_chunks): + return f"{name}.chunk{idx+1:02d}of{num_chunks:02d}" + +def get_manifest_path(name): + return f"{name}.chunkmanifest" + +def get_chunk_paths(path, file_size): + num_chunks = math.ceil(file_size / CHUNK_SIZE) + return [get_manifest_path(path)] + [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)] + +def chunk_file(path, targets): + manifest_path, *chunk_paths = targets + with open(path, 'rb') as f: + data = f.read() + actual_num_chunks = max(1, math.ceil(len(data) / CHUNK_SIZE)) + assert len(chunk_paths) >= actual_num_chunks, f"Allowed {len(chunk_paths)} chunks but needs at least {actual_num_chunks}, for path {path}" + for i, chunk_path in enumerate(chunk_paths): + with open(chunk_path, 'wb') as f: + f.write(data[i * CHUNK_SIZE:(i + 1) * CHUNK_SIZE]) + Path(manifest_path).write_text(str(len(chunk_paths))) + os.remove(path) + + +def read_file_chunked(path): + manifest_path = get_manifest_path(path) + if os.path.isfile(manifest_path): + num_chunks = int(Path(manifest_path).read_text().strip()) + return b''.join(Path(get_chunk_name(path, i, num_chunks)).read_bytes() for i in range(num_chunks)) + if os.path.isfile(path): + return Path(path).read_bytes() + raise FileNotFoundError(path) diff --git a/common/mat.h b/common/mat.h deleted file mode 100644 index 8e10d61971..0000000000 --- a/common/mat.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -typedef struct vec3 { - float v[3]; -} vec3; - -typedef struct vec4 { - float v[4]; -} vec4; - -typedef struct mat3 { - float v[3*3]; -} mat3; - -typedef struct mat4 { - float v[4*4]; -} mat4; - -static inline mat3 matmul3(const mat3 &a, const mat3 &b) { - mat3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - float v = 0.0; - for (int k=0; k<3; k++) { - v += a.v[r*3+k] * b.v[k*3+c]; - } - ret.v[r*3+c] = v; - } - } - return ret; -} - -static inline vec3 matvecmul3(const mat3 &a, const vec3 &b) { - vec3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - ret.v[r] += a.v[r*3+c] * b.v[c]; - } - } - return ret; -} - -static inline mat4 matmul(const mat4 &a, const mat4 &b) { - mat4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - float v = 0.0; - for (int k=0; k<4; k++) { - v += a.v[r*4+k] * b.v[k*4+c]; - } - ret.v[r*4+c] = v; - } - } - return ret; -} - -static inline vec4 matvecmul(const mat4 &a, const vec4 &b) { - vec4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - ret.v[r] += a.v[r*4+c] * b.v[c]; - } - } - return ret; -} - -// scales the input and output space of a transformation matrix -// that assumes pixel-center origin. -static inline mat3 transform_scale_buffer(const mat3 &in, float s) { - // in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s - - mat3 transform_out = {{ - 1.0f/s, 0.0f, 0.5f, - 0.0f, 1.0f/s, 0.5f, - 0.0f, 0.0f, 1.0f, - }}; - - mat3 transform_in = {{ - s, 0.0f, -0.5f*s, - 0.0f, s, -0.5f*s, - 0.0f, 0.0f, 1.0f, - }}; - - return matmul3(transform_in, matmul3(in, transform_out)); -} diff --git a/common/parameterized.py b/common/parameterized.py new file mode 100644 index 0000000000..7cd21bb9c5 --- /dev/null +++ b/common/parameterized.py @@ -0,0 +1,47 @@ +import sys +import pytest +import inspect + + +class parameterized: + @staticmethod + def expand(cases): + cases = list(cases) + + if not cases: + return lambda func: pytest.mark.skip("no parameterized cases")(func) + + def decorator(func): + params = [p for p in inspect.signature(func).parameters if p != 'self'] + normalized = [c if isinstance(c, tuple) else (c,) for c in cases] + # Infer arg count from first case so extra params (e.g. from @given) are left untouched + expand_params = params[: len(normalized[0])] + if len(expand_params) == 1: + return pytest.mark.parametrize(expand_params[0], [c[0] for c in normalized])(func) + return pytest.mark.parametrize(', '.join(expand_params), normalized)(func) + + return decorator + + +def parameterized_class(attrs, input_list=None): + if isinstance(attrs, list) and (not attrs or isinstance(attrs[0], dict)): + params_list = attrs + else: + assert input_list is not None + attr_names = (attrs,) if isinstance(attrs, str) else tuple(attrs) + params_list = [dict(zip(attr_names, v if isinstance(v, (tuple, list)) else (v,), strict=False)) for v in input_list] + + def decorator(cls): + globs = sys._getframe(1).f_globals + for i, params in enumerate(params_list): + name = f"{cls.__name__}_{i}" + new_cls = type(name, (cls,), dict(params)) + new_cls.__module__ = cls.__module__ + new_cls.__test__ = True # override inherited False so pytest collects this subclass + globs[name] = new_cls + # Don't collect the un-parametrised base, but return it so outer decorators + # (e.g. @pytest.mark.skip) land on it and propagate to subclasses via MRO. + cls.__test__ = False + return cls + + return decorator diff --git a/common/params_keys.h b/common/params_keys.h index dba0742268..3164fe5365 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -170,6 +170,7 @@ inline static std::unordered_map keys = { {"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}}, {"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}}, {"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}}, + {"OnroadScreenOffBrightnessMigrated", {PERSISTENT | BACKUP, STRING, "0.0"}}, {"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}}, {"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}}, {"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}}, @@ -190,7 +191,7 @@ inline static std::unordered_map keys = { // Model Manager params {"ModelManager_ActiveBundle", {PERSISTENT, JSON}}, {"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}}, - {"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT, "0"}}, + {"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT}}, {"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}}, {"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}}, {"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}}, @@ -218,6 +219,7 @@ inline static std::unordered_map keys = { {"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}}, {"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}}, {"ToyotaEnforceStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}}, + {"ToyotaStopAndGoHack", {PERSISTENT | BACKUP, BOOL, "0"}}, {"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}}, {"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}}, @@ -268,6 +270,7 @@ inline static std::unordered_map keys = { {"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}}, {"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}}, {"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}}, + {"TorqueControlTune", {PERSISTENT | BACKUP, FLOAT}}, {"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}}, {"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}}, {"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}}, diff --git a/common/prefix.h b/common/prefix.h index 30ee18f637..de3a94d71f 100644 --- a/common/prefix.h +++ b/common/prefix.h @@ -27,14 +27,14 @@ public: auto param_path = Params().getParamPath(); if (util::file_exists(param_path)) { std::string real_path = util::readlink(param_path); - system(util::string_format("rm %s -rf", real_path.c_str()).c_str()); + util::check_system(util::string_format("rm %s -rf", real_path.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()); + util::check_system(util::string_format("rm %s -rf", Path::download_cache_root().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()); + util::check_system(util::string_format("rm %s -rf", Path::comma_home().c_str())); + util::check_system(util::string_format("rm %s -rf", msgq_path.c_str())); unsetenv("OPENPILOT_PREFIX"); } diff --git a/common/ratekeeper.cc b/common/ratekeeper.cc index 7e63815168..a79acd7d51 100644 --- a/common/ratekeeper.cc +++ b/common/ratekeeper.cc @@ -6,9 +6,9 @@ #include "common/timing.h" #include "common/util.h" -RateKeeper::RateKeeper(const std::string &name, float rate, float print_delay_threshold) - : name(name), - print_delay_threshold(std::max(0.f, print_delay_threshold)) { +RateKeeper::RateKeeper(const std::string &name_, float rate, float print_delay_threshold_) + : name(name_), + print_delay_threshold(std::max(0.f, print_delay_threshold_)) { interval = 1 / rate; last_monitor_time = seconds_since_boot(); next_frame_time = last_monitor_time + interval; diff --git a/common/tests/test_util.cc b/common/tests/test_util.cc index de87fa3e06..d927b98a4d 100644 --- a/common/tests/test_util.cc +++ b/common/tests/test_util.cc @@ -36,7 +36,7 @@ TEST_CASE("util::read_file") { REQUIRE(util::read_file(filename).empty()); std::string content = random_bytes(64 * 1024); - write(fd, content.c_str(), content.size()); + REQUIRE(write(fd, content.c_str(), content.size()) == (ssize_t)content.size()); std::string ret = util::read_file(filename); bool equal = (ret == content); REQUIRE(equal); @@ -114,12 +114,12 @@ TEST_CASE("util::safe_fwrite") { } TEST_CASE("util::create_directories") { - system("rm /tmp/test_create_directories -rf"); + REQUIRE(system("rm /tmp/test_create_directories -rf") == 0); std::string dir = "/tmp/test_create_directories/a/b/c/d/e/f"; - auto check_dir_permissions = [](const std::string &dir, mode_t mode) -> bool { + auto check_dir_permissions = [](const std::string &path, mode_t mode) -> bool { struct stat st = {}; - return stat(dir.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == mode; + return stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == mode; }; SECTION("create_directories") { @@ -132,7 +132,7 @@ TEST_CASE("util::create_directories") { } SECTION("a file exists with the same name") { REQUIRE(util::create_directories(dir, 0755)); - int f = open((dir + "/file").c_str(), O_RDWR | O_CREAT); + int f = open((dir + "/file").c_str(), O_RDWR | O_CREAT, 0644); REQUIRE(f != -1); close(f); REQUIRE(util::create_directories(dir + "/file", 0755) == false); diff --git a/common/util.cc b/common/util.cc index 26a2bd60bc..84b47e187e 100644 --- a/common/util.cc +++ b/common/util.cc @@ -181,9 +181,9 @@ bool file_exists(const std::string& fn) { } static bool createDirectory(std::string dir, mode_t mode) { - auto verify_dir = [](const std::string& dir) -> bool { + auto verify_dir = [](const std::string& path) -> bool { struct stat st = {}; - return (stat(dir.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR); + return (stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR); }; // remove trailing /'s while (dir.size() > 1 && dir.back() == '/') { @@ -288,7 +288,7 @@ std::string strip(const std::string &str) { std::string check_output(const std::string& command) { char buffer[128]; std::string result; - std::unique_ptr pipe(popen(command.c_str(), "r"), pclose); + std::unique_ptr pipe(popen(command.c_str(), "r"), pclose); if (!pipe) { return ""; @@ -303,7 +303,7 @@ std::string check_output(const std::string& command) { bool system_time_valid() { // Default to August 26, 2024 - tm min_tm = {.tm_year = 2024 - 1900, .tm_mon = 7, .tm_mday = 26}; + tm min_tm = {.tm_mday = 26, .tm_mon = 7, .tm_year = 2024 - 1900}; time_t min_date = mktime(&min_tm); struct stat st; diff --git a/common/util.h b/common/util.h index 6d28e3ccbc..98c2883538 100644 --- a/common/util.h +++ b/common/util.h @@ -97,6 +97,13 @@ bool create_directories(const std::string &dir, mode_t mode); std::string check_output(const std::string& command); +inline void check_system(const std::string& cmd) { + int ret = std::system(cmd.c_str()); + if (ret != 0) { + fprintf(stderr, "system command failed (%d): %s\n", ret, cmd.c_str()); + } +} + bool system_time_valid(); inline void sleep_for(const int milliseconds) { diff --git a/docs/CARS.md b/docs/CARS.md index ab1eabeb13..31f6f2d32b 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 335 Supported Cars +# 336 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -166,6 +166,7 @@ A supported vehicle is one that just works when you install a comma device. All |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 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
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 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
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 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|K7 2017|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 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|K8 Hybrid (with HDA II) 2023|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 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
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 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
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 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -345,7 +346,7 @@ A supported vehicle is one that just works when you install a comma device. All |Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
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 `nightly-dev`.
+1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `nightly-dev`.
2Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
3See more setup details for GM.
42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 7583095eaf..62468c7448 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -13,13 +13,13 @@ Development is coordinated through [Discord](https://discord.comma.ai) and GitHu ## What contributions are we looking for? **openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** -openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. +openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. ### What gets merged? The probability of a pull request being merged is a function of its value to the project and the effort it will take us to get it merged. If a PR offers *some* value but will take lots of time to get merged, it will be closed. -Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. +Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. All of these are examples of good PRs: * typo fix: https://github.com/commaai/openpilot/pull/30678 @@ -29,17 +29,17 @@ All of these are examples of good PRs: ### What doesn't get merged? -* **style changes**: code is art, and it's up to the author to make it beautiful +* **style changes**: code is art, and it's up to the author to make it beautiful * **500+ line PRs**: clean it up, break it up into smaller PRs, or both * **PRs without a clear goal**: every PR must have a singular and clear goal * **UI design**: we do not have a good review process for this yet * **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't. -* **Negative expected value**: This a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged. +* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged. ### First contribution [Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty. -There's lot of bounties that don't require a comma 3X or a car. +There are a lot of bounties that don't require a comma 3X or a car. ## Pull Requests diff --git a/docs/README.md b/docs/README.md index 08dd4fa8bc..12d0b6f5dd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ NOTE: Those commands must be run in the root directory of openpilot, **not /docs **1. Install the docs dependencies** ``` bash -pip install .[docs] +uv pip install .[docs] ``` **2. Build the new site** diff --git a/docs/car-porting/what-is-a-car-port.md b/docs/car-porting/what-is-a-car-port.md index 55cce94da1..3480e4e5d5 100644 --- a/docs/car-porting/what-is-a-car-port.md +++ b/docs/car-porting/what-is-a-car-port.md @@ -21,10 +21,10 @@ Each car brand is supported by a standard interface structure in `opendbc/car/[b * `values.py`: Limits for actuation, general constants for cars, and supported car documentation * `radar_interface.py`: Interface for parsing radar points from the car, if applicable -## panda +## safety -* `board/safety/safety_[brand].h`: Brand-specific safety logic -* `tests/safety/test_[brand].py`: Brand-specific safety CI tests +* `opendbc_repo/opendbc/safety/modes/[brand].h`: Brand-specific safety logic +* `opendbc_repo/opendbc/safety/tests/test_[brand].py`: Brand-specific safety CI tests ## openpilot diff --git a/msgq_repo b/msgq_repo index 4c4e814ed5..ed2777747d 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 4c4e814ed592db52431bb53d37f0bb93299e960c +Subproject commit ed2777747d60de5a399b74ef1d4be4c1fb406ae1 diff --git a/opendbc_repo b/opendbc_repo index f07b9b3f38..9918ec656f 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit f07b9b3f38ab94a367c4d576b8cdc0f73a81ee06 +Subproject commit 9918ec656ffa0d1a576f8ae159390408adcaf4cd diff --git a/panda b/panda index a95e060e85..f5f296c65c 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit a95e060e855dc59898dd3bf2274a598dacdd2571 +Subproject commit f5f296c65c756a9b81af845cd874ee054d9c598c diff --git a/pyproject.toml b/pyproject.toml index 3d6eb07475..56664b16b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,26 +20,34 @@ dependencies = [ # core "cffi", "scons", - "pycapnp==2.1.0", + "pycapnp", "Cython", "setuptools", "numpy >=2.0", + # vendored native dependencies + "bzip2 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=bzip2", + "capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto", + "eigen @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=eigen", + "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", + "libjpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libjpeg", + "libyuv @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libyuv", + "openssl3 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=openssl3", + "python3-dev @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=python3-dev", + "zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd", + "ncurses @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ncurses", + "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", + "git-lfs @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=git-lfs", + # body / webrtcd + "av", "aiohttp", "aiortc", - # aiortc does not put an upper bound on pyopenssl and is now incompatible - # with the latest release - "pyopenssl < 24.3.0", - "pyaudio", # panda "libusb1", "spidev; platform_system == 'Linux'", - # modeld - "onnx >= 1.14.0", - # logging "pyzmq", "sentry-sdk", @@ -67,7 +75,6 @@ dependencies = [ # ui "raylib > 5.5.0.3", "qrcode", - "mapbox-earcut", "jeepney", ] @@ -86,7 +93,6 @@ testing = [ "pytest-subtests", # https://github.com/pytest-dev/pytest-xdist/pull/1229 "pytest-xdist @ git+https://github.com/sshane/pytest-xdist@2b4372bd62699fb412c4fe2f95bf9f01bd2018da", - "pytest-timeout", "pytest-asyncio", "pytest-mock", "ruff", @@ -95,17 +101,13 @@ testing = [ ] dev = [ - "av", - "dictdiffer", "matplotlib", "opencv-python-headless", - "parameterized >=0.8, <0.9", - "pyautogui", - "pywinctl", + "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi", ] tools = [ - "metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl ; (platform_machine != 'aarch64')", + "metadrive-simulator @ git+https://github.com/commaai/metadrive.git@minimal ; (platform_machine != 'aarch64')", "dearpygui>=2.1.0; (sys_platform != 'linux' or platform_machine != 'aarch64')", # not vended for linux aarch64 ] @@ -129,7 +131,6 @@ cpp_files = "test_*" cpp_harness = "selfdrive/test/cpp_harness.py" python_files = "test_*.py" asyncio_default_fixture_loop_scope = "function" -#timeout = "30" # you get this long by default markers = [ "slow: tests that take awhile to run and can be skipped with -m 'not slow'", "tici: tests that are only meant to run on the C3/C3X", @@ -147,7 +148,7 @@ testpaths = [ [tool.codespell] quiet-level = 3 # if you've got a short variable name that's getting flagged, add it here -ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite" +ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite,ser" builtin = "clear,rare,informal,code,names,en-GB_to_en-US" skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html" diff --git a/rednose_repo b/rednose_repo index 7fddc8e6d4..6ccb8d0556 160000 --- a/rednose_repo +++ b/rednose_repo @@ -1 +1 @@ -Subproject commit 7fddc8e6d49def83c952a78673179bdc62789214 +Subproject commit 6ccb8d055652cd9769b5e418edf116272fde4e09 diff --git a/release/build_release.sh b/release/build_release.sh index 9bd4c81f6d..22d880991f 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -72,9 +72,8 @@ find . -name '*.pyc' -delete find . -name 'moc_*' -delete find . -name '__pycache__' -delete rm -rf .sconsign.dblite Jenkinsfile release/ -rm selfdrive/modeld/models/driving_vision.onnx -rm selfdrive/modeld/models/driving_policy.onnx -rm sunnypilot/modeld*/models/supercombo.onnx +rm -f selfdrive/modeld/models/*.onnx +rm -f sunnypilot/modeld*/models/*.onnx find third_party/ -name '*x86*' -exec rm -r {} + find third_party/ -name '*Darwin*' -exec rm -r {} + diff --git a/release/ci/docker_build_sp.sh b/release/ci/docker_build_sp.sh index 9a8d390c61..369daf5233 100755 --- a/release/ci/docker_build_sp.sh +++ b/release/ci/docker_build_sp.sh @@ -1,12 +1,14 @@ #!/usr/bin/env bash set -e -# To build sim and docs, you can run the following to mount the scons cache to the same place as in CI: -# mkdir -p .ci_cache/scons_cache -# sudo mount --bind /tmp/scons_cache/ .ci_cache/scons_cache - SCRIPT_DIR=$(dirname "$0") OPENPILOT_DIR=$SCRIPT_DIR/../../ + +DOCKER_IMAGE=sunnypilot +DOCKER_FILE=Dockerfile.openpilot +DOCKER_REGISTRY=ghcr.io/sunnypilot +COMMIT_SHA=$(git rev-parse HEAD) + if [ -n "$TARGET_ARCHITECTURE" ]; then PLATFORM="linux/$TARGET_ARCHITECTURE" TAG_SUFFIX="-$TARGET_ARCHITECTURE" @@ -15,9 +17,11 @@ else TAG_SUFFIX="" fi -source $SCRIPT_DIR/docker_common_sp.sh $1 "$TAG_SUFFIX" +LOCAL_TAG=$DOCKER_IMAGE$TAG_SUFFIX +REMOTE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG +REMOTE_SHA_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:$COMMIT_SHA -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 +DOCKER_BUILDKIT=1 docker buildx build --provenance false --pull --platform $PLATFORM --load -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/release/ci/docker_common_sp.sh b/release/ci/docker_common_sp.sh deleted file mode 100755 index e8f515762a..0000000000 --- a/release/ci/docker_common_sp.sh +++ /dev/null @@ -1,18 +0,0 @@ -if [ "$1" = "base" ]; then - export DOCKER_IMAGE=sunnypilot-base - export DOCKER_FILE=Dockerfile.sunnypilot_base -elif [ "$1" = "prebuilt" ]; then - export DOCKER_IMAGE=sunnypilot-prebuilt - export DOCKER_FILE=Dockerfile.sunnypilot -else - echo "Invalid docker build image: '$1'" - exit 1 -fi - -export DOCKER_REGISTRY=ghcr.io/sunnypilot -export COMMIT_SHA=$(git rev-parse HEAD) - -TAG_SUFFIX=$2 -LOCAL_TAG=$DOCKER_IMAGE$TAG_SUFFIX -REMOTE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG -REMOTE_SHA_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:$COMMIT_SHA diff --git a/release/ci/model_generator.py b/release/ci/model_generator.py index 96352254b6..da6b933030 100755 --- a/release/ci/model_generator.py +++ b/release/ci/model_generator.py @@ -1,4 +1,5 @@ import os +import pickle import sys import hashlib import json @@ -6,6 +7,41 @@ import re from pathlib import Path from datetime import datetime, UTC +REQUIRED_OUTPUT_KEYS = frozenset({ + "plan", + "lane_lines", + "road_edges", + "lead", + "desire_state", + "desire_pred", + "meta", + "lead_prob", + "lane_lines_prob", + "pose", + "wide_from_device_euler", + "road_transform", + "hidden_state", +}) +OPTIONAL_OUTPUT_KEYS = frozenset({ + "planplus", + "sim_pose", + "desired_curvature", +}) + + +def validate_model_outputs(metadata_paths: list[Path]) -> None: + combined_keys: set[str] = set() + for path in metadata_paths: + with open(path, "rb") as f: + metadata = pickle.load(f) + combined_keys.update(metadata.get("output_slices", {}).keys()) + missing = REQUIRED_OUTPUT_KEYS - combined_keys + if missing: + raise ValueError(f"Combined model metadata is missing required output keys: {sorted(missing)}") + detected_optional = sorted(OPTIONAL_OUTPUT_KEYS & combined_keys) + if detected_optional: + print(f"Optional output keys detected: {detected_optional}") + def create_short_name(full_name): # Remove parentheses and extract alphanumeric words @@ -124,9 +160,19 @@ if __name__ == "__main__": parser.add_argument("--output-dir", default="./output", help="Output directory for metadata") parser.add_argument("--custom-name", help="Custom display name for the model") parser.add_argument("--is-20hz", action="store_true", help="Whether this is a 20Hz model") + parser.add_argument("--validate-only", action="store_true") parser.add_argument("--upstream-branch", default="unknown", help="Upstream branch name") args = parser.parse_args() + if args.validate_only: + metadata_paths = glob.glob(os.path.join(args.model_dir, "*_metadata.pkl")) + if not metadata_paths: + print(f"No metadata files found in {args.model_dir}", file=sys.stderr) + sys.exit(1) + validate_model_outputs([Path(p) for p in metadata_paths]) + print(f"Validated {len(metadata_paths)} metadata files successfully.") + sys.exit(0) + # Find all ONNX files in the given directory model_paths = glob.glob(os.path.join(args.model_dir, "*.onnx")) if not model_paths: diff --git a/scripts/reporter.py b/scripts/reporter.py index 903fcc8911..d894b8af48 100755 --- a/scripts/reporter.py +++ b/scripts/reporter.py @@ -1,17 +1,33 @@ #!/usr/bin/env python3 import os import glob -import onnx + +from tinygrad.nn.onnx import OnnxPBParser BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) + MASTER_PATH = os.getenv("MASTER_PATH", BASEDIR) MODEL_PATH = "/selfdrive/modeld/models/" + +class MetadataOnnxPBParser(OnnxPBParser): + def _parse_ModelProto(self) -> dict: + obj = {"metadata_props": []} + for fid, wire_type in self._parse_message(self.reader.len): + match fid: + case 14: + obj["metadata_props"].append(self._parse_StringStringEntryProto()) + case _: + self.reader.skip_field(wire_type) + return obj + + def get_checkpoint(f): - model = onnx.load(f) - metadata = {prop.key: prop.value for prop in model.metadata_props} + model = MetadataOnnxPBParser(f).parse() + metadata = {prop["key"]: prop["value"] for prop in model["metadata_props"]} return metadata['model_checkpoint'].split('/')[0] + if __name__ == "__main__": print("| | master | PR branch |") print("|-| ----- | --------- |") @@ -24,8 +40,4 @@ if __name__ == "__main__": fn = os.path.basename(f) master = get_checkpoint(MASTER_PATH + MODEL_PATH + fn) pr = get_checkpoint(BASEDIR + MODEL_PATH + fn) - print( - "|", fn, "|", - f"[{master}](https://reporter.comma.life/experiment/{master})", "|", - f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|" - ) + print("|", fn, "|", f"[{master}](https://reporter.comma.life/experiment/{master})", "|", f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|") diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_hover.png b/selfdrive/assets/icons_mici/buttons/button_circle_hover.png deleted file mode 100644 index 5cae152106..0000000000 --- a/selfdrive/assets/icons_mici/buttons/button_circle_hover.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:20024203288f144633014422e16119278477099f24fba5c155a804a1864a26b4 -size 7511 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png b/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png new file mode 100644 index 0000000000..9db0c2cd81 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70d4236bcfd3aa8f100b81179c1e0f193c6ffbd84769c4a516be4381e62b270a +size 18666 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_red_hover.png b/selfdrive/assets/icons_mici/buttons/button_circle_red_hover.png deleted file mode 100644 index 3696334d5e..0000000000 --- a/selfdrive/assets/icons_mici/buttons/button_circle_red_hover.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:279c1d8f95eb9f4a3058dff76b0f316ce9eef7bc8f4296936ad25fd08703ce13 -size 10380 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png b/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png new file mode 100644 index 0000000000..e61a678c1c --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed07f72339cf1c3926a2cb7314f9baa099bcdb3f8bc89a9084661b71334b0526 +size 32599 diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle.png b/selfdrive/assets/icons_mici/buttons/button_rectangle.png index 230c537d6d..4ccf6995b1 100644 --- a/selfdrive/assets/icons_mici/buttons/button_rectangle.png +++ b/selfdrive/assets/icons_mici/buttons/button_rectangle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffb293236f5f8f7da44b5a3c4c0b72e86c4e1fdb04f89c94507af008ff7de139 -size 8210 +oid sha256:5dedb4139a7ddeafcdaf050144769e490643820db726201a15250e1042eb6d15 +size 7982 diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png b/selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png index 76e75d5421..5e891588f5 100644 --- a/selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png +++ b/selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bda53863c9a46c50a1e2920a76c2d2f1fe4df8a94b8d2e26f5d83eef3a9c3bd3 -size 3627 +oid sha256:d527dcff61fa66902681706b4916586244b8cf0520086ac980ff782ab2d99ce7 +size 4778 diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle_hover.png b/selfdrive/assets/icons_mici/buttons/button_rectangle_hover.png deleted file mode 100644 index a9fd28cc35..0000000000 --- a/selfdrive/assets/icons_mici/buttons/button_rectangle_hover.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b55e43c50e805ac5e8357e5943374ed02d756cefa3aaffb58c568a0b125c30b -size 7750 diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png b/selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png index 779c219fcb..2cbf1cedb8 100644 --- a/selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png +++ b/selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5528e9c041b824f005bf1ef6e49b2dbbc4ba10f994b0726d2a17a4fbf8c80f55 -size 21379 +oid sha256:c6b1b0f1270a596b5ac150dee8ade54794de55b2033a529d4a17176f688aa6f0 +size 56738 diff --git a/selfdrive/assets/icons_mici/onroad/blind_spot_right.png b/selfdrive/assets/icons_mici/onroad/blind_spot_right.png deleted file mode 100644 index b6cd7834ef..0000000000 --- a/selfdrive/assets/icons_mici/onroad/blind_spot_right.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:584cea202afff6dd20d67ae1a9cd6d2b8cc07598bccb91a8d1bac0142567308e -size 45489 diff --git a/selfdrive/assets/icons_mici/onroad/turn_signal_right.png b/selfdrive/assets/icons_mici/onroad/turn_signal_right.png deleted file mode 100644 index 6bcb68dac5..0000000000 --- a/selfdrive/assets/icons_mici/onroad/turn_signal_right.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7fae4872ab3c24d5e4c2be6150127a844f89bbdcadfccdff2dfed180e125d577 -size 45699 diff --git a/selfdrive/assets/icons_mici/settings/network/new/connect_button.png b/selfdrive/assets/icons_mici/settings/network/new/connect_button.png deleted file mode 100644 index eae5af77f0..0000000000 --- a/selfdrive/assets/icons_mici/settings/network/new/connect_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04236fa0f2759a01c6e321ac7b1c86c7a039215a7953b1a23d250ecf2ef1fa87 -size 8563 diff --git a/selfdrive/assets/icons_mici/settings/network/new/connect_button_pressed.png b/selfdrive/assets/icons_mici/settings/network/new/connect_button_pressed.png deleted file mode 100644 index 0da6c384d9..0000000000 --- a/selfdrive/assets/icons_mici/settings/network/new/connect_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4337098554af30c98ebd512e17ab08207db868ff34acca5f865fcbfc940286d3 -size 21123 diff --git a/selfdrive/assets/icons_mici/settings/network/new/full_connect_button.png b/selfdrive/assets/icons_mici/settings/network/new/full_connect_button.png deleted file mode 100644 index 905170fd10..0000000000 --- a/selfdrive/assets/icons_mici/settings/network/new/full_connect_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffd37d5e5d5980efa98fee1cd0e8ebbf4139149b41c099e7dc3d5bd402cffb92 -size 9072 diff --git a/selfdrive/assets/icons_mici/settings/network/new/full_connect_button_pressed.png b/selfdrive/assets/icons_mici/settings/network/new/full_connect_button_pressed.png deleted file mode 100644 index 88eb4ac2a3..0000000000 --- a/selfdrive/assets/icons_mici/settings/network/new/full_connect_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1b1d58704f8808dcb5a7ce9d86bc4212477759e96ac2419475f16f9184ee6a42 -size 21892 diff --git a/selfdrive/assets/icons_mici/settings/network/new/lock.png b/selfdrive/assets/icons_mici/settings/network/new/lock.png index 9fc152d3db..65bd71f654 100644 --- a/selfdrive/assets/icons_mici/settings/network/new/lock.png +++ b/selfdrive/assets/icons_mici/settings/network/new/lock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:782161f35b4925c7063c441b0c341331c814614cf241f21b4e70134280c630f0 -size 1182 +oid sha256:7488c1aa69b728387b2cf300a614cc64e3c2305d2b509c14cf44cad65d20d85c +size 2509 diff --git a/selfdrive/assets/icons_mici/settings/network/new/wifi_selected.png b/selfdrive/assets/icons_mici/settings/network/new/wifi_selected.png deleted file mode 100644 index 2a3e837138..0000000000 --- a/selfdrive/assets/icons_mici/settings/network/new/wifi_selected.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:160f67162e075436200d6719e614ddf96caaa2b7c0a3943f728c2afef10aa4ad -size 2489 diff --git a/selfdrive/assets/icons_mici/setup/green_button.png b/selfdrive/assets/icons_mici/setup/green_button.png deleted file mode 100644 index 9708cfe284..0000000000 --- a/selfdrive/assets/icons_mici/setup/green_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:163ac31cb990bdddfe552efef9a68870404caadb1c40fa8a5042b5ae956e6b4c -size 24687 diff --git a/selfdrive/assets/icons_mici/setup/green_button_pressed.png b/selfdrive/assets/icons_mici/setup/green_button_pressed.png deleted file mode 100644 index 030ce61d5b..0000000000 --- a/selfdrive/assets/icons_mici/setup/green_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6e4614adb2d3d0e44c64a855c221ec462a7aee22fff26132ad551035141c1a53 -size 62056 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_black_rounded_rectangle_pressed.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_black_rounded_rectangle_pressed.png new file mode 100644 index 0000000000..470bfc50c0 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_black_rounded_rectangle_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1dd642ae4708cc7a837e8ef8b4c75f578654d241f8c854249c2b1ade640ceca +size 14058 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png index 9ebff76b50..88e6985f12 100644 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcd08444c77b3e559876eeb88d17808f72496adc26e27c3c21c00ff410879447 -size 10966 +oid sha256:5ba98ab2b75f0c1f8fdffb9eab0a742645b80ef4ca404c007f374a5e0fd48d8c +size 10254 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle_pressed.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle_pressed.png new file mode 100644 index 0000000000..999cdafcc7 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4545fdbaf67402b28b18644a7353a0620250ece6416c1b0ce0e27c758817b042 +size 26729 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png new file mode 100644 index 0000000000..eea6eded86 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a804da77b268f0a625f93949642ae74cdfe5b5caa5baea1c52c4605ae25c80e4 +size 12916 diff --git a/selfdrive/assets/icons_mici/setup/start_button.png b/selfdrive/assets/icons_mici/setup/start_button.png new file mode 100644 index 0000000000..58d8a8b748 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/start_button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e993247160edcbc9c3cba3efa93169028568d484bcfd0bf64f3e3a7ec7556c0 +size 18608 diff --git a/selfdrive/assets/icons_mici/setup/start_button_pressed.png b/selfdrive/assets/icons_mici/setup/start_button_pressed.png new file mode 100644 index 0000000000..564de0bef7 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/start_button_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c4f1002ecde9a2b33779c2e784a39b492b4c8d76abc063e935ce0aa971925dd +size 65513 diff --git a/selfdrive/assets/icons_mici/turn_intent_right.png b/selfdrive/assets/icons_mici/turn_intent_right.png deleted file mode 100644 index e342778731..0000000000 --- a/selfdrive/assets/icons_mici/turn_intent_right.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7b7e0194a8b9009e493cdce35cd15711596a54227c740e9d6419a3891c6c4037 -size 912 diff --git a/selfdrive/car/cruise.py b/selfdrive/car/cruise.py index 9973862b85..572dfabc02 100644 --- a/selfdrive/car/cruise.py +++ b/selfdrive/car/cruise.py @@ -48,14 +48,14 @@ class VCruiseHelper(VCruiseHelperSP): self.get_minimum_set_speed(is_metric) + _enabled = self.update_enabled_state(CS, enabled) + if CS.cruiseState.available: - _enabled = self.update_enabled_state(CS, enabled) if not self.CP.pcmCruise or (not self.CP_SP.pcmCruiseSpeed and _enabled): # if stock cruise is completely disabled, then we can use our own set speed logic self._update_v_cruise_non_pcm(CS, _enabled, is_metric) self.update_speed_limit_assist_v_cruise_non_pcm() self.v_cruise_cluster_kph = self.v_cruise_kph - self.update_button_timers(CS, enabled) else: self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH @@ -69,6 +69,9 @@ class VCruiseHelper(VCruiseHelperSP): self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_cluster_kph = V_CRUISE_UNSET + if not self.CP.pcmCruise or not self.CP_SP.pcmCruiseSpeed: + self.update_button_timers(CS, enabled) + def _update_v_cruise_non_pcm(self, CS, enabled, is_metric): # handle button presses. TODO: this should be in state_control, but a decelCruise press # would have the effect of both enabling and changing speed is checked after the state transition diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 61a7906652..cd44759cbf 100644 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -1,7 +1,7 @@ import os import hypothesis.strategies as st from hypothesis import Phase, given, settings -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from cereal import car, custom from opendbc.car import DT_CTRL diff --git a/selfdrive/car/tests/test_cruise_speed.py b/selfdrive/car/tests/test_cruise_speed.py index 61bae68931..d3ca6eed4f 100644 --- a/selfdrive/car/tests/test_cruise_speed.py +++ b/selfdrive/car/tests/test_cruise_speed.py @@ -2,7 +2,7 @@ import pytest import itertools import numpy as np -from parameterized import parameterized_class +from openpilot.common.parameterized import parameterized_class from cereal import log from openpilot.selfdrive.car.cruise import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT from cereal import car, custom diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index a5a0b1ce6a..209df3b159 100644 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -7,7 +7,7 @@ import unittest # noqa: TID251 from collections import defaultdict, Counter import hypothesis.strategies as st from hypothesis import Phase, given, settings -from parameterized import parameterized_class +from openpilot.common.parameterized import parameterized_class from opendbc.car import DT_CTRL, gen_empty_fingerprint, structs from opendbc.car.can_definitions import CanData diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 9d0e5c9f15..1c8e9f76b1 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -64,6 +64,8 @@ class Controls(ControlsExt): elif self.CP.lateralTuning.which() == 'torque': self.LaC = LatControlTorque(self.CP, self.CP_SP, self.CI, DT_CTRL) + self.LaC = ControlsExt.initialize_lateral_control(self, self.LaC, self.CI, DT_CTRL) + def update(self): self.sm.update(15) if self.sm.updated["liveCalibration"]: @@ -183,7 +185,7 @@ class Controls(ControlsExt): hudControl.leftLaneDepart = self.sm['driverAssistance'].leftLaneDeparture hudControl.rightLaneDepart = self.sm['driverAssistance'].rightLaneDeparture - if self.sm['selfdriveState'].active: + if self.get_lat_active(self.sm): CO = self.sm['carOutput'] if self.CP.steerControlType == car.CarParams.SteerControlType.angle: self.steer_limited_by_safety = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \ diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py index 8f66d89bf8..1eb88d7206 100644 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -1,6 +1,6 @@ import pytest import itertools -from parameterized import parameterized_class +from openpilot.common.parameterized import parameterized_class from cereal import log @@ -42,4 +42,5 @@ class TestFollowingDistance: 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 - assert simulation_steady_state == pytest.approx(correct_steady_state, abs=err_ratio * correct_steady_state + .5) + abs_err_margin = 0.5 if v_lead > 0.0 else 1.15 + assert simulation_steady_state == pytest.approx(correct_steady_state, abs=err_ratio * correct_steady_state + abs_err_margin) diff --git a/selfdrive/controls/tests/test_latcontrol.py b/selfdrive/controls/tests/test_latcontrol.py index 4180047617..d36ed52192 100644 --- a/selfdrive/controls/tests/test_latcontrol.py +++ b/selfdrive/controls/tests/test_latcontrol.py @@ -1,4 +1,4 @@ -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from cereal import car, log from opendbc.car.car_helpers import interfaces diff --git a/selfdrive/controls/tests/test_latcontrol_torque_buffer.py b/selfdrive/controls/tests/test_latcontrol_torque_buffer.py index 52810ff55b..b13576a6cf 100644 --- a/selfdrive/controls/tests/test_latcontrol_torque_buffer.py +++ b/selfdrive/controls/tests/test_latcontrol_torque_buffer.py @@ -1,4 +1,4 @@ -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from cereal import car, log from opendbc.car.car_helpers import interfaces diff --git a/selfdrive/debug/mem_usage.py b/selfdrive/debug/mem_usage.py index 9bed566e19..bc0e97e7ca 100755 --- a/selfdrive/debug/mem_usage.py +++ b/selfdrive/debug/mem_usage.py @@ -4,11 +4,11 @@ import os from collections import defaultdict import numpy as np -from tabulate import tabulate +from openpilot.common.utils import tabulate from openpilot.tools.lib.logreader import LogReader -DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" +DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" MB = 1024 * 1024 TABULATE_OPTS = dict(tablefmt="simple_grid", stralign="center", numalign="center") diff --git a/selfdrive/debug/uiview.py b/selfdrive/debug/uiview.py index ad3ccea036..eac1f8fbf4 100755 --- a/selfdrive/debug/uiview.py +++ b/selfdrive/debug/uiview.py @@ -3,7 +3,7 @@ import time from cereal import car, log, messaging from openpilot.common.params import Params -from openpilot.system.manager.process_config import managed_processes, is_snpe_model, is_tinygrad_model, is_stock_model +from openpilot.system.manager.process_config import managed_processes, is_tinygrad_model, is_stock_model from openpilot.system.hardware import HARDWARE if __name__ == "__main__": @@ -11,8 +11,6 @@ if __name__ == "__main__": params = Params() params.put("CarParams", CP.to_bytes()) - if use_snpe_modeld := is_snpe_model(False, params, CP): - print("Using SNPE modeld") if use_tinygrad_modeld := is_tinygrad_model(False, params, CP): print("Using TinyGrad modeld") if use_stock_modeld := is_stock_model(False, params, CP): @@ -21,7 +19,7 @@ if __name__ == "__main__": HARDWARE.set_power_save(False) procs = ['camerad', 'ui', 'calibrationd', 'plannerd', 'dmonitoringmodeld', 'dmonitoringd'] - procs += ["modeld_snpe" if use_snpe_modeld else "modeld_tinygrad" if use_tinygrad_modeld else "modeld"] + procs += ["modeld_tinygrad" if use_tinygrad_modeld else "modeld"] for p in procs: managed_processes[p].start() diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py index 7f1d5fd032..f432eb88bb 100755 --- a/selfdrive/locationd/lagd.py +++ b/selfdrive/locationd/lagd.py @@ -25,6 +25,7 @@ MIN_ABS_YAW_RATE = 0.0 MAX_YAW_RATE_SANITY_CHECK = 1.0 MIN_NCC = 0.95 MAX_LAG = 1.0 +MIN_LAG = 0.15 MAX_LAG_STD = 0.1 MAX_LAT_ACCEL = 2.0 MAX_LAT_ACCEL_DIFF = 0.6 @@ -216,7 +217,7 @@ class LateralLagEstimator: liveDelay.status = log.LiveDelayData.Status.unestimated if liveDelay.status == log.LiveDelayData.Status.estimated: - liveDelay.lateralDelay = valid_mean_lag + liveDelay.lateralDelay = min(MAX_LAG, max(MIN_LAG, valid_mean_lag)) else: liveDelay.lateralDelay = self.initial_lag @@ -299,7 +300,7 @@ class LateralLagEstimator: new_values_start_idx = next(-i for i, t in enumerate(reversed(times)) if t <= self.last_estimate_t) is_valid = is_valid and not (new_values_start_idx == 0 or not np.any(okay[new_values_start_idx:])) - delay, corr, confidence = self.actuator_delay(desired, actual, okay, self.dt, MAX_LAG) + delay, corr, confidence = self.actuator_delay(desired, actual, okay, self.dt, MIN_LAG, MAX_LAG) if corr < self.min_ncc or confidence < self.min_confidence or not is_valid: return @@ -307,22 +308,23 @@ class LateralLagEstimator: self.last_estimate_t = self.t @staticmethod - def actuator_delay(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, dt: float, max_lag: float) -> tuple[float, float, float]: + def actuator_delay(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, + dt: float, min_lag: float, max_lag: float) -> tuple[float, float, float]: assert len(expected_sig) == len(actual_sig) - max_lag_samples = int(max_lag / dt) + min_lag_samples, max_lag_samples = int(round(min_lag / dt)), int(round(max_lag / dt)) padded_size = fft_next_good_size(len(expected_sig) + max_lag_samples) ncc = masked_normalized_cross_correlation(expected_sig, actual_sig, mask, padded_size) - # only consider lags from 0 to max_lag - roi = np.s_[len(expected_sig) - 1: len(expected_sig) - 1 + max_lag_samples] + # only consider lags from min_lag to max_lag + roi = np.s_[len(expected_sig) - 1 + min_lag_samples: len(expected_sig) - 1 + max_lag_samples] extended_roi = np.s_[roi.start - CORR_BORDER_OFFSET: roi.stop + CORR_BORDER_OFFSET] roi_ncc = ncc[roi] extended_roi_ncc = ncc[extended_roi] max_corr_index = np.argmax(roi_ncc) corr = roi_ncc[max_corr_index] - lag = parabolic_peak_interp(roi_ncc, max_corr_index) * dt + lag = parabolic_peak_interp(roi_ncc, max_corr_index) * dt + min_lag # to estimate lag confidence, gather all high-correlation candidates and see how spread they are # if e.g. 0.8 and 0.4 are both viable, this is an ambiguous case diff --git a/selfdrive/locationd/test/test_lagd.py b/selfdrive/locationd/test/test_lagd.py index a3dfce9c29..4728413d9d 100644 --- a/selfdrive/locationd/test/test_lagd.py +++ b/selfdrive/locationd/test/test_lagd.py @@ -97,7 +97,7 @@ class TestLagd: assert msg.liveDelay.calPerc == 0 def test_estimator_basics(self, subtests): - for lag_frames in range(5): + for lag_frames in range(3, 10): with subtests.test(msg=f"lag_frames={lag_frames}"): mocked_CP = car.CarParams(steerActuatorDelay=0.8) estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0) @@ -111,7 +111,7 @@ class TestLagd: assert msg.liveDelay.calPerc == 100 def test_estimator_masking(self): - mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.8), random.randint(1, 19) + mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.8), random.randint(3, 19) estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0, min_valid_block_count=1) process_messages(estimator, lag_frames, (int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_SIZE) * 2, rejection_threshold=0.4) msg = estimator.get_msg(True) @@ -120,7 +120,6 @@ class TestLagd: assert msg.liveDelay.calPerc == 100 @pytest.mark.skipif(PC, reason="only on device") - @pytest.mark.timeout(60) def test_estimator_performance(self): mocked_CP = car.CarParams(steerActuatorDelay=0.8) estimator = LateralLagEstimator(mocked_CP, DT) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 903cb5d389..ff63b644d5 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,59 +1,62 @@ import os import glob +from openpilot.common.file_chunker import chunk_file, get_chunk_paths -Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') +Import('env', 'arch') +chunker_file = File("#common/file_chunker.py") lenv = env.Clone() -lenvCython = envCython.Clone() -libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] -frameworks = [] +tinygrad_root = env.Dir("#").abspath +tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) + if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] -common_src = [ - "models/commonmodel.cc", - "transforms/loadyuv.cc", - "transforms/transform.cc", -] +def estimate_pickle_max_size(onnx_size): + return 1.2 * onnx_size + 10 * 1024 * 1024 # 20% + 10MB is plenty -# OpenCL is a framework on Mac -if arch == "Darwin": - frameworks += ['OpenCL'] -else: - libs += ['OpenCL'] - -# Set path definitions -for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): - for xenv in (lenv, lenvCython): - xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') - -# Compile cython -cython_libs = envCython["LIBS"] + libs -commonmodel_lib = lenv.Library('commonmodel', common_src) -lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) -tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) +# compile warp +# THREADS=0 is need to prevent bug: https://github.com/tinygrad/tinygrad/issues/14689 +tg_flags = { + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU THREADS=0 HOME={os.path.expanduser("~")} IMAGE=0', # tinygrad calls brew which needs a $HOME in the env +}.get(arch, 'DEV=CPU CPU_LLVM=1 THREADS=0 IMAGE=0') # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath script_files = [File(Dir("#selfdrive/modeld").File("get_model_metadata.py").abspath)] - cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' + cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) +image_flag = { + 'larch64': 'IMAGE=2', +}.get(arch, 'IMAGE=0') +script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] +compile_warp_cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye +warp_targets = [] +for cam in [_ar_ox_fisheye, _os_fisheye]: + w, h = cam.width, cam.height + warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] +lenv.Command(warp_targets, tinygrad_files + script_files, compile_warp_cmd) + def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath + pkl = fn + "_tinygrad.pkl" + onnx_path = fn + ".onnx" + chunk_targets = get_chunk_paths(pkl, estimate_pickle_max_size(os.path.getsize(onnx_path))) + def do_chunk(target, source, env): + chunk_file(pkl, chunk_targets) return lenv.Command( - fn + "_tinygrad.pkl", - [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + chunk_targets, + [onnx_path] + tinygrad_files + [chunker_file], + [f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl}', + do_chunk] ) # Compile small models for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: - flags = { - 'larch64': 'DEV=QCOM', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")} IMAGE=0', # tinygrad calls brew which needs a $HOME in the env - }.get(arch, 'DEV=CPU CPU_LLVM=1 IMAGE=0') - tg_compile(flags, model_name) + tg_compile(tg_flags, model_name) # Compile BIG model if USB GPU is available if "USBGPU" in os.environ: diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py new file mode 100755 index 0000000000..75cc65f84c --- /dev/null +++ b/selfdrive/modeld/compile_warp.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +import time +import pickle +import numpy as np +from pathlib import Path +from tinygrad.tensor import Tensor +from tinygrad.helpers import Context +from tinygrad.device import Device +from tinygrad.engine.jit import TinyJit + +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye + +MODELS_DIR = Path(__file__).parent / 'models' + +CAMERA_CONFIGS = [ + (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 + (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 +] + +UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) +UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) + +IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2) + + +def warp_pkl_path(w, h): + return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + + +def dm_warp_pkl_path(w, h): + return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl' + + +def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad): + w_dst, h_dst = dst_shape + h_src, w_src = src_shape + + x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst).reshape(-1) + y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst).reshape(-1) + + # inline 3x3 matmul as elementwise to avoid reduce op (enables fusion with gather) + src_x = M_inv[0, 0] * x + M_inv[0, 1] * y + M_inv[0, 2] + src_y = M_inv[1, 0] * x + M_inv[1, 1] * y + M_inv[1, 2] + src_w = M_inv[2, 0] * x + M_inv[2, 1] * y + M_inv[2, 2] + + src_x = src_x / src_w + src_y = src_y / src_w + + x_nn_clipped = Tensor.round(src_x).clip(0, w_src - 1).cast('int') + y_nn_clipped = Tensor.round(src_y).clip(0, h_src - 1).cast('int') + idx = y_nn_clipped * (w_src + stride_pad) + x_nn_clipped + + return src_flat[idx] + + +def frames_to_tensor(frames, model_w, model_h): + H = (frames.shape[0] * 2) // 3 + W = frames.shape[1] + in_img1 = Tensor.cat(frames[0:H:2, 0::2], + frames[1:H:2, 0::2], + frames[0:H:2, 1::2], + frames[1:H:2, 1::2], + frames[H:H+H//4].reshape((H//2, W//2)), + frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) + return in_img1 + + +def make_frame_prepare(cam_w, cam_h, model_w, model_h): + stride, y_height, uv_height, _ = get_nv12_info(cam_w, cam_h) + uv_offset = stride * y_height + stride_pad = stride - cam_w + + def frame_prepare_tinygrad(input_frame, M_inv): + # UV_SCALE @ M_inv @ UV_SCALE_INV simplifies to elementwise scaling + M_inv_uv = M_inv * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]]) + # deinterleave NV12 UV plane (UVUV... -> separate U, V) + uv = input_frame[uv_offset:uv_offset + uv_height * stride].reshape(uv_height, stride) + with Context(SPLIT_REDUCEOP=0): + y = warp_perspective_tinygrad(input_frame[:cam_h*stride], + M_inv, (model_w, model_h), + (cam_h, cam_w), stride_pad).realize() + u = warp_perspective_tinygrad(uv[:cam_h//2, :cam_w:2].flatten(), + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), 0).realize() + v = warp_perspective_tinygrad(uv[:cam_h//2, 1:cam_w:2].flatten(), + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), 0).realize() + yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) + tensor = frames_to_tensor(yuv, model_w, model_h) + return tensor + return frame_prepare_tinygrad + + +def make_update_img_input(frame_prepare, model_w, model_h): + def update_img_input_tinygrad(tensor, frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + new_img = frame_prepare(frame, M_inv) + full_buffer = tensor[6:].cat(new_img, dim=0).contiguous() + return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) + return update_img_input_tinygrad + + +def make_update_both_imgs(frame_prepare, model_w, model_h): + update_img = make_update_img_input(frame_prepare, model_w, model_h) + + def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, + calib_big_img_buffer, new_big_img, M_inv_big): + calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) + calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) + return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair + return update_both_imgs_tinygrad + + +def make_warp_dm(cam_w, cam_h, dm_w, dm_h): + stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) + stride_pad = stride - cam_w + + def warp_dm(input_frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad).reshape(-1, dm_h * dm_w) + return result + return warp_dm + + +def compile_modeld_warp(cam_w, cam_h): + model_w, model_h = MEDMODEL_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling modeld warp for {cam_w}x{cam_h}...") + + frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) + update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h) + update_img_jit = TinyJit(update_both_imgs, prune=True) + + full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + + for i in range(10): + new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + img_inputs = [full_buffer, + Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + big_img_inputs = [big_full_buffer, + Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + inputs = img_inputs + big_img_inputs + Device.default.synchronize() + + inputs_np = [x.numpy() for x in inputs] + inputs_np[0] = full_buffer_np + inputs_np[3] = big_full_buffer_np + + st = time.perf_counter() + out = update_img_jit(*inputs) + full_buffer = out[0].contiguous().realize().clone() + big_full_buffer = out[2].contiguous().realize().clone() + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(update_img_jit, f) + print(f" Saved to {pkl_path}") + + jit = pickle.load(open(pkl_path, "rb")) + jit(*inputs) + + +def compile_dm_warp(cam_w, cam_h): + dm_w, dm_h = DM_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling DM warp for {cam_w}x{cam_h}...") + + warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) + warp_dm_jit = TinyJit(warp_dm, prune=True) + + for i in range(10): + inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + Device.default.synchronize() + st = time.perf_counter() + warp_dm_jit(*inputs) + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = dm_warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(warp_dm_jit, f) + print(f" Saved to {pkl_path}") + + +def run_and_save_pickle(): + for cam_w, cam_h in CAMERA_CONFIGS: + compile_modeld_warp(cam_w, cam_h) + compile_dm_warp(cam_w, cam_h) + + +if __name__ == "__main__": + run_and_save_pickle() diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 7177998571..28190db3e6 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -3,7 +3,6 @@ import os from openpilot.system.hardware import TICI os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -16,50 +15,57 @@ from openpilot.common.swaglog import cloudlog from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' - +MODELS_DIR = Path(__file__).parent / 'models' class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self, cl_ctx): + def __init__(self): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] - self.frame = MonitoringModelFrame(cl_ctx) self.numpy_inputs = { 'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32), } + self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)} + self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()} + self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} - with open(MODEL_PKL_PATH, "rb") as f: - self.model_run = pickle.load(f) + self._blob_cache : dict[int, Tensor] = {} + self.image_warp = None + self.model_run = pickle.loads(read_file_chunked(str(MODEL_PKL_PATH))) def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]: self.numpy_inputs['calib'][0,:] = calib t1 = time.perf_counter() - input_img_cl = self.frame.prepare(buf, transform.flatten()) - if TICI: - # The imgs tensors are backed by opencl memory, only need init once - if 'input_img' not in self.tensor_inputs: - self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8) - else: - self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize() + if self.image_warp is None: + self.frame_buf_params = get_nv12_info(buf.width, buf.height) + warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.image_warp = pickle.load(f) + ptr = buf.data.ctypes.data + # There is a ringbuffer of imgs, just cache tensors pointing to all of them + if ptr not in self._blob_cache: + self._blob_cache[ptr] = Tensor.from_blob(ptr, (self.frame_buf_params[3],), dtype='uint8') + self.warp_inputs_np['transform'][:] = transform[:] + self.tensor_inputs['input_img'] = self.image_warp(self._blob_cache[ptr], self.warp_inputs['transform']).realize() - output = self.model_run(**self.tensor_inputs).numpy().flatten() + output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy().flatten() t2 = time.perf_counter() return output, t2 - t1 @@ -107,12 +113,11 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - cl_context = CLContext() - model = ModelState(cl_context) + model = ModelState() cloudlog.warning("models loaded, dmonitoringmodeld starting") cloudlog.warning("connecting to driver stream") - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context) + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) while not vipc_client.connect(False): time.sleep(0.1) assert vipc_client.is_connected() diff --git a/selfdrive/modeld/get_model_metadata.py b/selfdrive/modeld/get_model_metadata.py index 2001d23d75..838b1e9f40 100755 --- a/selfdrive/modeld/get_model_metadata.py +++ b/selfdrive/modeld/get_model_metadata.py @@ -1,33 +1,51 @@ #!/usr/bin/env python3 import sys import pathlib -import onnx import codecs import pickle from typing import Any -def get_name_and_shape(value_info:onnx.ValueInfoProto) -> tuple[str, tuple[int,...]]: - shape = tuple([int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim]) - name = value_info.name +from tinygrad.nn.onnx import OnnxPBParser + + +class MetadataOnnxPBParser(OnnxPBParser): + def _parse_ModelProto(self) -> dict: + obj: dict[str, Any] = {"graph": {"input": [], "output": []}, "metadata_props": []} + for fid, wire_type in self._parse_message(self.reader.len): + match fid: + case 7: + obj["graph"] = self._parse_GraphProto() + case 14: + obj["metadata_props"].append(self._parse_StringStringEntryProto()) + case _: + self.reader.skip_field(wire_type) + return obj + + +def get_name_and_shape(value_info: dict[str, Any]) -> tuple[str, tuple[int, ...]]: + shape = tuple(int(dim) if isinstance(dim, int) else 0 for dim in value_info["parsed_type"].shape) + name = value_info["name"] return name, shape -def get_metadata_value_by_name(model:onnx.ModelProto, name:str) -> str | Any: - for prop in model.metadata_props: - if prop.key == name: - return prop.value + +def get_metadata_value_by_name(model: dict[str, Any], name: str) -> str | Any: + for prop in model["metadata_props"]: + if prop["key"] == name: + return prop["value"] return None + if __name__ == "__main__": model_path = pathlib.Path(sys.argv[1]) - model = onnx.load(str(model_path)) + model = MetadataOnnxPBParser(model_path).parse() output_slices = get_metadata_value_by_name(model, 'output_slices') assert output_slices is not None, 'output_slices not found in metadata' metadata = { 'model_checkpoint': get_metadata_value_by_name(model, 'model_checkpoint'), 'output_slices': pickle.loads(codecs.decode(output_slices.encode(), "base64")), - 'input_shapes': dict([get_name_and_shape(x) for x in model.graph.input]), - 'output_shapes': dict([get_name_and_shape(x) for x in model.graph.output]) + 'input_shapes': dict(get_name_and_shape(x) for x in model["graph"]["input"]), + 'output_shapes': dict(get_name_and_shape(x) for x in model["graph"]["output"]), } metadata_path = model_path.parent / (model_path.stem + '_metadata.pkl') diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 216c5da443..494feb99c1 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -7,7 +7,6 @@ if USBGPU: os.environ['DEV'] = 'AMD' os.environ['AMD_IFACE'] = 'USB' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -22,17 +21,17 @@ from openpilot.common.params import Params from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.transformations.camera import DEVICE_CAMERAS +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.common.transformations.model import get_warp_matrix from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState +from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.constants import ModelConstants, Plan -from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address from openpilot.sunnypilot.livedelay.helpers import get_lat_delay -from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase +from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase PROCESS_NAME = "selfdrive.modeld.modeld" @@ -42,11 +41,15 @@ VISION_PKL_PATH = Path(__file__).parent / 'models/driving_vision_tinygrad.pkl' POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl' VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl' POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl' +MODELS_DIR = Path(__file__).parent / 'models' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 +IMG_QUEUE_SHAPE = (6*(ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ + 1), 128, 256) +assert IMG_QUEUE_SHAPE[0] == 30 + def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: @@ -139,12 +142,11 @@ class InputQueues: return out class ModelState(ModelStateBase): - frames: dict[str, DrivingModelFrame] inputs: dict[str, np.ndarray] output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse - def __init__(self, context: CLContext): + def __init__(self): ModelStateBase.__init__(self) self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS with open(VISION_METADATA_PATH, 'rb') as f: @@ -160,7 +162,6 @@ class ModelState(ModelStateBase): self.policy_output_slices = policy_metadata['output_slices'] policy_output_size = policy_metadata['output_shapes']['outputs'][1] - self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names} self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) # policy inputs @@ -170,18 +171,20 @@ class ModelState(ModelStateBase): self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) self.full_input_queues.reset() - # img buffers are managed in openCL transform code - self.vision_inputs: dict[str, Tensor] = {} + self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), + 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize()} + self.full_frames : dict[str, Tensor] = {} + self._blob_cache : dict[int, Tensor] = {} + self.transforms_np = {k: np.zeros((3,3), dtype=np.float32) for k in self.img_queues} + self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() - - with open(VISION_PKL_PATH, "rb") as f: - self.vision_run = pickle.load(f) - - with open(POLICY_PKL_PATH, "rb") as f: - self.policy_run = pickle.load(f) + self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} + self.update_imgs = None + self.vision_run = pickle.loads(read_file_chunked(str(VISION_PKL_PATH))) + self.policy_run = pickle.loads(read_file_chunked(str(POLICY_PKL_PATH))) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} @@ -193,23 +196,34 @@ class ModelState(ModelStateBase): inputs['desire_pulse'][0] = 0 new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) self.prev_desire[:] = inputs['desire_pulse'] + if self.update_imgs is None: + for key in bufs.keys(): + w, h = bufs[key].width, bufs[key].height + self.frame_buf_params[key] = get_nv12_info(w, h) + warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.update_imgs = pickle.load(f) - imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names} + for key in bufs.keys(): + ptr = bufs[key].data.ctypes.data + yuv_size = self.frame_buf_params[key][3] + # There is a ringbuffer of imgs, just cache tensors pointing to all of them + cache_key = (key, ptr) + if cache_key not in self._blob_cache: + self._blob_cache[cache_key] = Tensor.from_blob(ptr, (yuv_size,), dtype='uint8') + self.full_frames[key] = self._blob_cache[cache_key] + for key in bufs.keys(): + self.transforms_np[key][:,:] = transforms[key][:,:] - if TICI and not USBGPU: - # The imgs tensors are backed by opencl memory, only need init once - for key in imgs_cl: - if key not in self.vision_inputs: - self.vision_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.vision_input_shapes[key], dtype=dtypes.uint8) - else: - for key in imgs_cl: - frame_input = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.vision_input_shapes[key]) - self.vision_inputs[key] = Tensor(frame_input, dtype=dtypes.uint8).realize() + out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], + self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) + self.img_queues['img'], self.img_queues['big_img'] = out[0].realize(), out[2].realize() + vision_inputs = {'img': out[1], 'big_img': out[3]} if prepare_only: return None - self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy() + self.vision_output = self.vision_run(**vision_inputs).contiguous().realize().uop.base.buffer.numpy().flatten() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -217,9 +231,8 @@ class ModelState(ModelStateBase): self.numpy_inputs[k][:] = self.full_input_queues.get(k)[k] self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention'] - self.policy_output = self.policy_run(**self.policy_inputs).numpy().flatten() + self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy().flatten() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) - combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) @@ -236,10 +249,8 @@ def main(demo=False): config_realtime_process(7, 54) st = time.monotonic() - cloudlog.warning("setting up CL context") - cl_context = CLContext() - cloudlog.warning("CL context ready; loading model") - model = ModelState(cl_context) + cloudlog.warning("loading model") + model = ModelState() cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # visionipc clients @@ -252,8 +263,8 @@ def main(demo=False): time.sleep(.1) vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") while not vipc_client_main.connect(False): diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc deleted file mode 100644 index d3341e76ec..0000000000 --- a/selfdrive/modeld/models/commonmodel.cc +++ /dev/null @@ -1,64 +0,0 @@ -#include "selfdrive/modeld/models/commonmodel.h" - -#include -#include - -#include "common/clutil.h" - -DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - temporal_skip = _temporal_skip; - input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err)); - region.origin = temporal_skip * frame_size_bytes; - region.size = frame_size_bytes; - last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); - - loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - - for (int i = 0; i < temporal_skip; i++) { - CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); - } - loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); - - copy_queue(&loadyuv, q, img_buffer_20hz_cl, input_frames_cl, 0, 0, frame_size_bytes); - copy_queue(&loadyuv, q, last_img_cl, input_frames_cl, 0, frame_size_bytes, frame_size_bytes); - - // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. - clFinish(q); - return &input_frames_cl; -} - -DrivingModelFrame::~DrivingModelFrame() { - deinit_transform(); - loadyuv_destroy(&loadyuv); - CL_CHECK(clReleaseMemObject(input_frames_cl)); - CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl)); - CL_CHECK(clReleaseMemObject(last_img_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} - - -MonitoringModelFrame::MonitoringModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - input_frame_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - clFinish(q); - return &y_cl; -} - -MonitoringModelFrame::~MonitoringModelFrame() { - deinit_transform(); - CL_CHECK(clReleaseMemObject(input_frame_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h deleted file mode 100644 index 176d7eb6dc..0000000000 --- a/selfdrive/modeld/models/commonmodel.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" -#include "selfdrive/modeld/transforms/loadyuv.h" -#include "selfdrive/modeld/transforms/transform.h" - -class ModelFrame { -public: - ModelFrame(cl_device_id device_id, cl_context context) { - q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); - } - virtual ~ModelFrame() {} - virtual cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { return NULL; } - uint8_t* buffer_from_cl(cl_mem *in_frames, int buffer_size) { - CL_CHECK(clEnqueueReadBuffer(q, *in_frames, CL_TRUE, 0, buffer_size, input_frames.get(), 0, nullptr, nullptr)); - clFinish(q); - return &input_frames[0]; - } - - int MODEL_WIDTH; - int MODEL_HEIGHT; - int MODEL_FRAME_SIZE; - int buf_size; - -protected: - cl_mem y_cl, u_cl, v_cl; - Transform transform; - cl_command_queue q; - std::unique_ptr input_frames; - - void init_transform(cl_device_id device_id, cl_context context, int model_width, int model_height) { - y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, model_width * model_height, NULL, &err)); - u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - transform_init(&transform, context, device_id); - } - - void deinit_transform() { - transform_destroy(&transform); - CL_CHECK(clReleaseMemObject(v_cl)); - CL_CHECK(clReleaseMemObject(u_cl)); - CL_CHECK(clReleaseMemObject(y_cl)); - } - - void run_transform(cl_mem yuv_cl, int model_width, int model_height, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - transform_queue(&transform, q, - yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, - y_cl, u_cl, v_cl, model_width, model_height, projection); - } -}; - -class DrivingModelFrame : public ModelFrame { -public: - DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip); - ~DrivingModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 512; - const int MODEL_HEIGHT = 256; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart - const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); - -private: - LoadYUVState loadyuv; - cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; - cl_buffer_region region; - int temporal_skip; -}; - -class MonitoringModelFrame : public ModelFrame { -public: - MonitoringModelFrame(cl_device_id device_id, cl_context context); - ~MonitoringModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 1440; - const int MODEL_HEIGHT = 960; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT; - const int buf_size = MODEL_FRAME_SIZE; - -private: - cl_mem input_frame_cl; -}; diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd deleted file mode 100644 index 4ac64d9172..0000000000 --- a/selfdrive/modeld/models/commonmodel.pxd +++ /dev/null @@ -1,27 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem - -cdef extern from "common/mat.h": - cdef struct mat3: - float v[9] - -cdef extern from "common/clutil.h": - cdef unsigned long CL_DEVICE_TYPE_DEFAULT - cl_device_id cl_get_device_id(unsigned long) - cl_context cl_create_context(cl_device_id) - void cl_release_context(cl_context) - -cdef extern from "selfdrive/modeld/models/commonmodel.h": - cppclass ModelFrame: - int buf_size - unsigned char * buffer_from_cl(cl_mem*, int); - cl_mem * prepare(cl_mem, int, int, int, int, mat3) - - cppclass DrivingModelFrame: - int buf_size - DrivingModelFrame(cl_device_id, cl_context, int) - - cppclass MonitoringModelFrame: - int buf_size - MonitoringModelFrame(cl_device_id, cl_context) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd deleted file mode 100644 index 0bb798625b..0000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pxd +++ /dev/null @@ -1,13 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext - -cdef class CLContext(BaseCLContext): - pass - -cdef class CLMem: - cdef cl_mem * mem - - @staticmethod - cdef create(void*) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx deleted file mode 100644 index 5b7d11bc71..0000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pyx +++ /dev/null @@ -1,74 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii, language_level=3 - -import numpy as np -cimport numpy as cnp -from libc.string cimport memcpy -from libc.stdint cimport uintptr_t - -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, cl_release_context -from .commonmodel cimport mat3, ModelFrame as cppModelFrame, DrivingModelFrame as cppDrivingModelFrame, MonitoringModelFrame as cppMonitoringModelFrame - - -cdef class CLContext(BaseCLContext): - def __cinit__(self): - self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) - self.context = cl_create_context(self.device_id) - - def __dealloc__(self): - if self.context: - cl_release_context(self.context) - -cdef class CLMem: - @staticmethod - cdef create(void * cmem): - mem = CLMem() - mem.mem = cmem - return mem - - @property - def mem_address(self): - return (self.mem) - -def cl_from_visionbuf(VisionBuf buf): - return CLMem.create(&buf.buf.buf_cl) - - -cdef class ModelFrame: - cdef cppModelFrame * frame - cdef int buf_size - - def __dealloc__(self): - del self.frame - - def prepare(self, VisionBuf buf, float[:] projection): - cdef mat3 cprojection - memcpy(cprojection.v, &projection[0], 9*sizeof(float)) - cdef cl_mem * data - data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection) - return CLMem.create(data) - - def buffer_from_cl(self, CLMem in_frames): - cdef unsigned char * data2 - data2 = self.frame.buffer_from_cl(in_frames.mem, self.buf_size) - return np.asarray( data2) - - -cdef class DrivingModelFrame(ModelFrame): - cdef cppDrivingModelFrame * _frame - - def __cinit__(self, CLContext context, int temporal_skip): - self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - -cdef class MonitoringModelFrame(ModelFrame): - cdef cppMonitoringModelFrame * _frame - - def __cinit__(self, CLContext context): - self._frame = new cppMonitoringModelFrame(context.device_id, context.context) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 9b1c4a1834..dc621bed03 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:3446bf8b22e50e47669a25bf32460ae8baf8547037f346753e19ecbfcf6d4e59 -size 6954368 +oid sha256:7aff7ff1dc08bbaf562a8f77380ab5e5914f8557dba2f760d87e4d953f5604b0 +size 7307246 diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py index 038f51ca9c..5c11e8ca18 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -113,6 +113,8 @@ class Parser: plan_mhp = self.is_mhp(outs, 'plan', ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH) plan_in_N, plan_out_N = (ModelConstants.PLAN_MHP_N, ModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0) self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)) + if 'planplus' in outs: + self.parse_mdn('planplus', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)) self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) return outs diff --git a/selfdrive/modeld/runners/tinygrad_helpers.py b/selfdrive/modeld/runners/tinygrad_helpers.py deleted file mode 100644 index 776381341c..0000000000 --- a/selfdrive/modeld/runners/tinygrad_helpers.py +++ /dev/null @@ -1,8 +0,0 @@ - -from tinygrad.tensor import Tensor -from tinygrad.helpers import to_mv - -def qcom_tensor_from_opencl_address(opencl_address, shape, dtype): - cl_buf_desc_ptr = to_mv(opencl_address, 8).cast('Q')[0] - rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer. - return Tensor.from_blob(rawbuf_ptr, shape, dtype=dtype, device='QCOM') diff --git a/selfdrive/modeld/transforms/loadyuv.cc b/selfdrive/modeld/transforms/loadyuv.cc deleted file mode 100644 index c93f5cd038..0000000000 --- a/selfdrive/modeld/transforms/loadyuv.cc +++ /dev/null @@ -1,76 +0,0 @@ -#include "selfdrive/modeld/transforms/loadyuv.h" - -#include -#include -#include - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { - memset(s, 0, sizeof(*s)); - - s->width = width; - s->height = height; - - char args[1024]; - snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " - "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", - width, height); - cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); - - s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); - s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); - s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); - - // done with this - CL_CHECK(clReleaseProgram(prg)); -} - -void loadyuv_destroy(LoadYUVState* s) { - CL_CHECK(clReleaseKernel(s->loadys_krnl)); - CL_CHECK(clReleaseKernel(s->loaduv_krnl)); - CL_CHECK(clReleaseKernel(s->copy_krnl)); -} - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl) { - cl_int global_out_off = 0; - - CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); - - const size_t loadys_work_size = (s->width*s->height)/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, - &loadys_work_size, NULL, 0, 0, NULL)); - - const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; - global_out_off += (s->width*s->height); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); - - global_out_off += (s->width/2)*(s->height/2); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); -} - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size) { - CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset)); - const size_t copy_work_size = size/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, - ©_work_size, NULL, 0, 0, NULL)); -} \ No newline at end of file diff --git a/selfdrive/modeld/transforms/loadyuv.cl b/selfdrive/modeld/transforms/loadyuv.cl deleted file mode 100644 index 970187a6d7..0000000000 --- a/selfdrive/modeld/transforms/loadyuv.cl +++ /dev/null @@ -1,47 +0,0 @@ -#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) - -__kernel void loadys(__global uchar8 const * const Y, - __global uchar * out, - int out_offset) -{ - const int gid = get_global_id(0); - const int ois = gid * 8; - const int oy = ois / TRANSFORMED_WIDTH; - const int ox = ois % TRANSFORMED_WIDTH; - - const uchar8 ys = Y[gid]; - - // 02 - // 13 - - __global uchar* outy0; - __global uchar* outy1; - if ((oy & 1) == 0) { - outy0 = out + out_offset; //y0 - outy1 = out + out_offset + UV_SIZE*2; //y2 - } else { - outy0 = out + out_offset + UV_SIZE; //y1 - outy1 = out + out_offset + UV_SIZE*3; //y3 - } - - vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); - vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); -} - -__kernel void loaduv(__global uchar8 const * const in, - __global uchar8 * out, - int out_offset) -{ - const int gid = get_global_id(0); - const uchar8 inv = in[gid]; - out[gid + out_offset / 8] = inv; -} - -__kernel void copy(__global uchar8 * in, - __global uchar8 * out, - int in_offset, - int out_offset) -{ - const int gid = get_global_id(0); - out[gid + out_offset / 8] = in[gid + in_offset / 8]; -} diff --git a/selfdrive/modeld/transforms/loadyuv.h b/selfdrive/modeld/transforms/loadyuv.h deleted file mode 100644 index 659059cd25..0000000000 --- a/selfdrive/modeld/transforms/loadyuv.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "common/clutil.h" - -typedef struct { - int width, height; - cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; -} LoadYUVState; - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); - -void loadyuv_destroy(LoadYUVState* s); - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl); - - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size); \ No newline at end of file diff --git a/selfdrive/modeld/transforms/transform.cc b/selfdrive/modeld/transforms/transform.cc deleted file mode 100644 index 305643cf42..0000000000 --- a/selfdrive/modeld/transforms/transform.cc +++ /dev/null @@ -1,97 +0,0 @@ -#include "selfdrive/modeld/transforms/transform.h" - -#include -#include - -#include "common/clutil.h" - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { - memset(s, 0, sizeof(*s)); - - cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); - s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); - // done with this - CL_CHECK(clReleaseProgram(prg)); - - s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); - s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); -} - -void transform_destroy(Transform* s) { - CL_CHECK(clReleaseMemObject(s->m_y_cl)); - CL_CHECK(clReleaseMemObject(s->m_uv_cl)); - CL_CHECK(clReleaseKernel(s->krnl)); -} - -void transform_queue(Transform* s, - cl_command_queue q, - cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection) { - const int zero = 0; - - // sampled using pixel center origin - // (because that's how fastcv and opencv does it) - - mat3 projection_y = projection; - - // in and out uv is half the size of y. - mat3 projection_uv = transform_scale_buffer(projection, 0.5); - - CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); - CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); - - const int in_y_width = in_width; - const int in_y_height = in_height; - const int in_y_px_stride = 1; - const int in_uv_width = in_width/2; - const int in_uv_height = in_height/2; - const int in_uv_px_stride = 2; - const int in_u_offset = in_uv_offset; - const int in_v_offset = in_uv_offset + 1; - - const int out_y_width = out_width; - const int out_y_height = out_height; - const int out_uv_width = out_width/2; - const int out_uv_height = out_height/2; - - CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src - CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M - - const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_y, NULL, 0, 0, NULL)); - - const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; - - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); -} diff --git a/selfdrive/modeld/transforms/transform.cl b/selfdrive/modeld/transforms/transform.cl deleted file mode 100644 index 2ca25920cd..0000000000 --- a/selfdrive/modeld/transforms/transform.cl +++ /dev/null @@ -1,54 +0,0 @@ -#define INTER_BITS 5 -#define INTER_TAB_SIZE (1 << INTER_BITS) -#define INTER_SCALE 1.f / INTER_TAB_SIZE - -#define INTER_REMAP_COEF_BITS 15 -#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) - -__kernel void warpPerspective(__global const uchar * src, - int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, - __global uchar * dst, - int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, - __constant float * M) -{ - int dx = get_global_id(0); - int dy = get_global_id(1); - - if (dx < dst_cols && dy < dst_rows) - { - float X0 = M[0] * dx + M[1] * dy + M[2]; - float Y0 = M[3] * dx + M[4] * dy + M[5]; - float W = M[6] * dx + M[7] * dy + M[8]; - W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; - int X = rint(X0 * W), Y = rint(Y0 * W); - - int sx = convert_short_sat(X >> INTER_BITS); - int sy = convert_short_sat(Y >> INTER_BITS); - - short sx_clamp = clamp(sx, 0, src_cols - 1); - short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); - short sy_clamp = clamp(sy, 0, src_rows - 1); - short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); - int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - - short ay = (short)(Y & (INTER_TAB_SIZE - 1)); - short ax = (short)(X & (INTER_TAB_SIZE - 1)); - float taby = 1.f/INTER_TAB_SIZE*ay; - float tabx = 1.f/INTER_TAB_SIZE*ax; - - int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); - - int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); - int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); - - int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; - - uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); - dst[dst_index] = pix; - } -} diff --git a/selfdrive/modeld/transforms/transform.h b/selfdrive/modeld/transforms/transform.h deleted file mode 100644 index 771a7054b3..0000000000 --- a/selfdrive/modeld/transforms/transform.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" - -typedef struct { - cl_kernel krnl; - cl_mem m_y_cl, m_uv_cl; -} Transform; - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); - -void transform_destroy(Transform* transform); - -void transform_queue(Transform* s, cl_command_queue q, - cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection); diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 4f068c4f5a..d6d4566884 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -35,14 +35,7 @@ class DRIVER_MONITOR_SETTINGS: self._EYE_THRESHOLD = 0.65 self._SG_THRESHOLD = 0.9 self._BLINK_THRESHOLD = 0.865 - - self._PHONE_THRESH = 0.75 if device_type == 'mici' else 0.4 - self._PHONE_THRESH2 = 15.0 - self._PHONE_MAX_OFFSET = 0.06 - self._PHONE_MIN_OFFSET = 0.025 - self._PHONE_DATA_AVG = 0.05 - self._PHONE_DATA_VAR = 3*0.005 - self._PHONE_MAX_COUNT = int(360 / self._DT_DMON) + self._PHONE_THRESH = 0.5 self._POSE_PITCH_THRESHOLD = 0.3133 self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 @@ -152,11 +145,10 @@ class DriverMonitoring: # init driver status wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2) - phone_filter_raw_priors = (self.settings._PHONE_DATA_AVG, self.settings._PHONE_DATA_VAR, 2) self.wheelpos = DriverProb(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT) - self.phone = DriverProb(raw_priors=phone_filter_raw_priors, max_trackable=self.settings._PHONE_MAX_COUNT) self.pose = DriverPose(settings=self.settings) self.blink = DriverBlink() + self.phone_prob = 0. self.always_on = always_on self.distracted_types = [] @@ -257,12 +249,7 @@ class DriverMonitoring: if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD: distracted_types.append(DistractedType.DISTRACTED_BLINK) - if self.phone.prob_calibrated: - using_phone = self.phone.prob > max(min(self.phone.prob_offseter.filtered_stat.M, self.settings._PHONE_MAX_OFFSET), self.settings._PHONE_MIN_OFFSET) \ - * self.settings._PHONE_THRESH2 - else: - using_phone = self.phone.prob > self.settings._PHONE_THRESH - if using_phone: + if self.phone_prob > self.settings._PHONE_THRESH: distracted_types.append(DistractedType.DISTRACTED_PHONE) return distracted_types @@ -301,7 +288,7 @@ class DriverMonitoring: * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) - self.phone.prob = driver_data.phoneProb + self.phone_prob = driver_data.phoneProb self.distracted_types = self._get_distracted_types() self.driver_distracted = (DistractedType.DISTRACTED_PHONE in self.distracted_types @@ -315,11 +302,9 @@ class DriverMonitoring: if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): self.pose.pitch_offseter.push_and_update(self.pose.pitch) self.pose.yaw_offseter.push_and_update(self.pose.yaw) - self.phone.prob_offseter.push_and_update(self.phone.prob) 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.phone.prob_calibrated = self.phone.prob_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT if self.face_detected and not self.driver_distracted: if model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD: @@ -425,8 +410,6 @@ class DriverMonitoring: "posePitchValidCount": self.pose.pitch_offseter.filtered_stat.n, "poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(), "poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n, - "phoneProbOffset": self.phone.prob_offseter.filtered_stat.mean(), - "phoneProbValidCount": self.phone.prob_offseter.filtered_stat.n, "stepChange": self.step_change, "awarenessActive": self.awareness_active, "awarenessPassive": self.awareness_passive, diff --git a/selfdrive/pandad/.gitignore b/selfdrive/pandad/.gitignore index f7226cdb87..cb292405c9 100644 --- a/selfdrive/pandad/.gitignore +++ b/selfdrive/pandad/.gitignore @@ -1,3 +1,3 @@ pandad pandad_api_impl.cpp -tests/test_pandad_usbprotocol +tests/test_pandad_canprotocol diff --git a/selfdrive/pandad/SConscript b/selfdrive/pandad/SConscript index 5e0b782c1e..fd59db9853 100644 --- a/selfdrive/pandad/SConscript +++ b/selfdrive/pandad/SConscript @@ -1,9 +1,10 @@ -Import('env', 'common', 'messaging') +Import('env', 'arch', 'common', 'messaging') -libs = ['usb-1.0', common, messaging, 'pthread'] -panda = env.Library('panda', ['panda.cc', 'panda_comms.cc', 'spi.cc']) +if arch != "Darwin": + libs = [common, messaging, 'pthread'] + panda = env.Library('panda', ['panda.cc', 'spi.cc']) -env.Program('pandad', ['main.cc', 'pandad.cc', 'panda_safety.cc'], LIBS=[panda] + libs) + env.Program('pandad', ['main.cc', 'pandad.cc', 'panda_safety.cc'], LIBS=[panda] + libs) -if GetOption('extras'): - env.Program('tests/test_pandad_usbprotocol', ['tests/test_pandad_usbprotocol.cc'], LIBS=[panda] + libs) + if GetOption('extras'): + env.Program('tests/test_pandad_canprotocol', ['tests/test_pandad_canprotocol.cc'], LIBS=[panda] + libs) diff --git a/selfdrive/pandad/main.cc b/selfdrive/pandad/main.cc index b63d884a45..ef30d6037c 100644 --- a/selfdrive/pandad/main.cc +++ b/selfdrive/pandad/main.cc @@ -16,7 +16,7 @@ int main(int argc, char *argv[]) { assert(err == 0); } - std::vector serials(argv + 1, argv + argc); - pandad_main_thread(serials); + std::string serial = (argc > 1) ? argv[1] : ""; + pandad_main_thread(serial); return 0; } diff --git a/selfdrive/pandad/panda.cc b/selfdrive/pandad/panda.cc index 72b8ddcf81..acb472ab57 100644 --- a/selfdrive/pandad/panda.cc +++ b/selfdrive/pandad/panda.cc @@ -12,19 +12,9 @@ const bool PANDAD_MAXOUT = getenv("PANDAD_MAXOUT") != nullptr; -Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { - // try USB first, then SPI - try { - handle = std::make_unique(serial); - LOGW("connected to %s over USB", serial.c_str()); - } catch (std::exception &e) { -#ifndef __APPLE__ - handle = std::make_unique(serial); - LOGW("connected to %s over SPI", serial.c_str()); -#else - throw e; -#endif - } +Panda::Panda(std::string serial) { + handle = std::make_unique(serial); + LOGW("connected to %s over SPI", serial.c_str()); hw_type = get_hw_type(); can_reset_communications(); @@ -42,20 +32,8 @@ std::string Panda::hw_serial() { return handle->hw_serial; } -std::vector Panda::list(bool usb_only) { - std::vector serials = PandaUsbHandle::list(); - -#ifndef __APPLE__ - if (!usb_only) { - for (const auto &s : PandaSpiHandle::list()) { - if (std::find(serials.begin(), serials.end(), s) == serials.end()) { - serials.push_back(s); - } - } - } -#endif - - return serials; +std::vector Panda::list() { + return PandaSpiHandle::list(); } void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param) { @@ -195,7 +173,7 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data for (const 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_OFFSET)) { + if (bus >= PANDA_BUS_OFFSET) { continue; } auto can_data = cmsg.getDat(); @@ -207,7 +185,7 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data header.addr = cmsg.getAddress(); header.extended = (cmsg.getAddress() >= 0x800) ? 1 : 0; header.data_len_code = data_len_code; - header.bus = bus - bus_offset; + header.bus = bus; header.checksum = 0; memcpy(&send_buf[pos], (uint8_t *)&header, sizeof(can_header)); @@ -283,7 +261,7 @@ bool Panda::unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector handle; + std::unique_ptr handle; public: - Panda(std::string serial="", uint32_t bus_offset=0); + Panda(std::string serial); cereal::PandaState::PandaType hw_type = cereal::PandaState::PandaType::UNKNOWN; - const uint32_t bus_offset; bool connected(); bool comms_healthy(); std::string hw_serial(); // Static functions - static std::vector list(bool usb_only=false); + static std::vector list(); // Panda functionality cereal::PandaState::PandaType get_hw_type(); @@ -91,7 +90,7 @@ protected: uint8_t receive_buffer[RECV_SIZE + sizeof(can_header) + 64]; uint32_t receive_buffer_size = 0; - Panda(uint32_t bus_offset) : bus_offset(bus_offset) {} + Panda() {} void pack_can_buffer(const capnp::List::Reader &can_data_list, std::function write_func); bool unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector &out_vec); diff --git a/selfdrive/pandad/panda_comms.cc b/selfdrive/pandad/panda_comms.cc deleted file mode 100644 index 8a20f397d3..0000000000 --- a/selfdrive/pandad/panda_comms.cc +++ /dev/null @@ -1,227 +0,0 @@ -#include "selfdrive/pandad/panda.h" - -#include -#include -#include - -#include "common/swaglog.h" - -static libusb_context *init_usb_ctx() { - libusb_context *context = nullptr; - int err = libusb_init(&context); - if (err != 0) { - LOGE("libusb initialization error"); - return nullptr; - } - -#if LIBUSB_API_VERSION >= 0x01000106 - libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); -#else - libusb_set_debug(context, 3); -#endif - return context; -} - -PandaUsbHandle::PandaUsbHandle(std::string serial) : PandaCommsHandle(serial) { - // init libusb - ssize_t num_devices; - libusb_device **dev_list = NULL; - int err = 0; - ctx = init_usb_ctx(); - if (!ctx) { goto fail; } - - // connect by serial - num_devices = libusb_get_device_list(ctx, &dev_list); - if (num_devices < 0) { goto fail; } - for (size_t i = 0; i < num_devices; ++i) { - libusb_device_descriptor desc; - libusb_get_device_descriptor(dev_list[i], &desc); - if (desc.idVendor == 0x3801 && desc.idProduct == 0xddcc) { - int ret = libusb_open(dev_list[i], &dev_handle); - if (dev_handle == NULL || ret < 0) { goto fail; } - - unsigned char desc_serial[26] = { 0 }; - ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); - if (ret < 0) { goto fail; } - - hw_serial = std::string((char *)desc_serial, ret); - if (serial.empty() || serial == hw_serial) { - break; - } - libusb_close(dev_handle); - dev_handle = NULL; - } - } - if (dev_handle == NULL) goto fail; - libusb_free_device_list(dev_list, 1); - dev_list = nullptr; - - if (libusb_kernel_driver_active(dev_handle, 0) == 1) { - libusb_detach_kernel_driver(dev_handle, 0); - } - - err = libusb_set_configuration(dev_handle, 1); - if (err != 0) { goto fail; } - - err = libusb_claim_interface(dev_handle, 0); - if (err != 0) { goto fail; } - - return; - -fail: - if (dev_list != NULL) { - libusb_free_device_list(dev_list, 1); - } - cleanup(); - throw std::runtime_error("Error connecting to panda"); -} - -PandaUsbHandle::~PandaUsbHandle() { - std::lock_guard lk(hw_lock); - cleanup(); - connected = false; -} - -void PandaUsbHandle::cleanup() { - if (dev_handle) { - libusb_release_interface(dev_handle, 0); - libusb_close(dev_handle); - } - - if (ctx) { - libusb_exit(ctx); - } -} - -std::vector PandaUsbHandle::list() { - static std::unique_ptr context(init_usb_ctx(), libusb_exit); - // init libusb - ssize_t num_devices; - libusb_device **dev_list = NULL; - std::vector serials; - if (!context) { return serials; } - - num_devices = libusb_get_device_list(context.get(), &dev_list); - if (num_devices < 0) { - LOGE("libusb can't get device list"); - goto finish; - } - for (size_t i = 0; i < num_devices; ++i) { - libusb_device *device = dev_list[i]; - libusb_device_descriptor desc; - libusb_get_device_descriptor(device, &desc); - if (desc.idVendor == 0x3801 && desc.idProduct == 0xddcc) { - libusb_device_handle *handle = NULL; - int ret = libusb_open(device, &handle); - if (ret < 0) { goto finish; } - - unsigned char desc_serial[26] = { 0 }; - ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); - libusb_close(handle); - if (ret < 0) { goto finish; } - - serials.push_back(std::string((char *)desc_serial, ret)); - } - } - -finish: - if (dev_list != NULL) { - libusb_free_device_list(dev_list, 1); - } - return serials; -} - -void PandaUsbHandle::handle_usb_issue(int err, const char func[]) { - LOGE_100("usb error %d \"%s\" in %s", err, libusb_strerror((enum libusb_error)err), func); - if (err == LIBUSB_ERROR_NO_DEVICE) { - LOGE("lost connection"); - connected = false; - } - // TODO: check other errors, is simply retrying okay? -} - -int PandaUsbHandle::control_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout) { - int err; - const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; - - if (!connected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - std::lock_guard lk(hw_lock); - do { - err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, NULL, 0, timeout); - if (err < 0) handle_usb_issue(err, __func__); - } while (err < 0 && connected); - - return err; -} - -int PandaUsbHandle::control_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) { - int err; - const uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; - - if (!connected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - std::lock_guard lk(hw_lock); - do { - err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout); - if (err < 0) handle_usb_issue(err, __func__); - } while (err < 0 && connected); - - return err; -} - -int PandaUsbHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { - int err; - int transferred = 0; - - if (!connected) { - return 0; - } - - std::lock_guard lk(hw_lock); - do { - // Try sending can messages. If the receive buffer on the panda is full it will NAK - // and libusb will try again. After 5ms, it will time out. We will drop the messages. - err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); - - if (err == LIBUSB_ERROR_TIMEOUT) { - LOGW("Transmit buffer full"); - break; - } else if (err != 0 || length != transferred) { - handle_usb_issue(err, __func__); - } - } while (err != 0 && connected); - - return transferred; -} - -int PandaUsbHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { - int err; - int transferred = 0; - - if (!connected) { - return 0; - } - - std::lock_guard lk(hw_lock); - - do { - err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); - - if (err == LIBUSB_ERROR_TIMEOUT) { - break; // timeout is okay to exit, recv still happened - } else if (err == LIBUSB_ERROR_OVERFLOW) { - comms_healthy = false; - LOGE_100("overflow got 0x%x", transferred); - } else if (err != 0) { - handle_usb_issue(err, __func__); - } - - } while (err != 0 && connected); - - return transferred; -} diff --git a/selfdrive/pandad/panda_comms.h b/selfdrive/pandad/panda_comms.h index 9c452faf6d..cdfb5019b6 100644 --- a/selfdrive/pandad/panda_comms.h +++ b/selfdrive/pandad/panda_comms.h @@ -6,67 +6,20 @@ #include #include -#ifndef __APPLE__ -#include -#endif - -#include - #define TIMEOUT 0 #define SPI_BUF_SIZE 2048 -// comms base class -class PandaCommsHandle { +class PandaSpiHandle { public: - PandaCommsHandle(std::string serial) {} - virtual ~PandaCommsHandle() {} - virtual void cleanup() = 0; - std::string hw_serial; std::atomic connected = true; std::atomic comms_healthy = true; - static std::vector list(); - // HW communication - virtual int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT) = 0; - virtual int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT) = 0; - virtual int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; - virtual int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; -}; - -class PandaUsbHandle : public PandaCommsHandle { -public: - PandaUsbHandle(std::string serial); - ~PandaUsbHandle(); - int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT); - int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT); - int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); - int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); - void cleanup(); - - static std::vector list(); - -private: - libusb_context *ctx = NULL; - libusb_device_handle *dev_handle = NULL; - std::recursive_mutex hw_lock; - void handle_usb_issue(int err, const char func[]); -}; - -#ifndef __APPLE__ -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); ~PandaSpiHandle(); + int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT); int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT); int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); @@ -81,13 +34,19 @@ private: uint8_t rx_buf[SPI_BUF_SIZE]; inline static std::recursive_mutex hw_lock; + struct __attribute__((packed)) spi_header { + uint8_t sync; + uint8_t endpoint; + uint16_t tx_len; + uint16_t max_rx_len; + }; + int wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout, unsigned int length); 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); + int lltransfer(struct spi_ioc_transfer &t); spi_header header; uint32_t xfer_count = 0; }; -#endif diff --git a/selfdrive/pandad/panda_safety.cc b/selfdrive/pandad/panda_safety.cc index 29e96032bf..8381256f46 100644 --- a/selfdrive/pandad/panda_safety.cc +++ b/selfdrive/pandad/panda_safety.cc @@ -24,19 +24,15 @@ void PandaSafety::updateMultiplexingMode() { // Initialize to ELM327 without OBD multiplexing for initial fingerprinting if (!initialized_) { prev_obd_multiplexing_ = false; - for (int i = 0; i < pandas_.size(); ++i) { - pandas_[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); - } + panda_->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); initialized_ = true; } // Switch between multiplexing modes based on the OBD multiplexing request bool obd_multiplexing_requested = params_.getBool("ObdMultiplexingEnabled"); if (obd_multiplexing_requested != prev_obd_multiplexing_) { - for (int i = 0; i < pandas_.size(); ++i) { - const uint16_t safety_param = (i > 0 || !obd_multiplexing_requested) ? 1U : 0U; - pandas_[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param); - } + const uint16_t safety_param = obd_multiplexing_requested ? 0U : 1U; + panda_->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param); prev_obd_multiplexing_ = obd_multiplexing_requested; params_.putBool("ObdMultiplexingChanged", true); } @@ -74,19 +70,12 @@ void PandaSafety::setSafetyMode(const std::vector ¶ms_string) { uint16_t alternative_experience = car_params.getAlternativeExperience(); uint16_t safety_param_sp = car_params_sp.getSafetyParam(); - for (int i = 0; i < pandas_.size(); ++i) { - // Default to SILENT safety model if not specified - cereal::CarParams::SafetyModel safety_model = cereal::CarParams::SafetyModel::SILENT; - uint16_t safety_param = 0U; - if (i < safety_configs.size()) { - safety_model = safety_configs[i].getSafetyModel(); - safety_param = safety_configs[i].getSafetyParam(); - } + cereal::CarParams::SafetyModel safety_model = safety_configs[0].getSafetyModel(); + uint16_t safety_param = safety_configs[0].getSafetyParam(); - LOGW("Panda %d: setting safety model: %d, param: %d, alternative experience: %d, param_sp: %d", i, (int)safety_model, safety_param, alternative_experience, safety_param_sp); - pandas_[i]->set_alternative_experience(alternative_experience, safety_param_sp); - pandas_[i]->set_safety_model(safety_model, safety_param); - } + LOGW("setting safety model: %d, param: %d, alternative experience: %d, param_sp: %d", (int)safety_model, safety_param, alternative_experience, safety_param_sp); + panda_->set_alternative_experience(alternative_experience, safety_param_sp); + panda_->set_safety_model(safety_model, safety_param); } bool PandaSafety::getOffroadMode() { diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc index 5d0ce4ecf3..3d0a551d80 100644 --- a/selfdrive/pandad/pandad.cc +++ b/selfdrive/pandad/pandad.cc @@ -1,6 +1,5 @@ #include "selfdrive/pandad/pandad.h" -#include #include #include #include @@ -18,25 +17,6 @@ #include "common/util.h" #include "system/hardware/hw.h" -// -- Multi-panda conventions -- -// Ordering: -// - 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, 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 its block of 4 buses. E.g.: the second panda will use -// bus numbers 4, 5, 6 and 7 -// - The internal panda will always be used for accessing the OBD2 port, -// and thus firmware queries -// Safety: -// - SafetyConfig is a list, which is mapped to the connected pandas -// - If there are more pandas connected than there are SafetyConfigs, -// the excess pandas will remain in "silent" or "noOutput" mode -// Ignition: -// - If any of the ignition sources in any panda is high, ignition is high - #define MAX_IR_PANDA_VAL 50 #define CUTOFF_IL 400 #define SATURATE_IL 1000 @@ -45,12 +25,10 @@ ExitHandler do_exit; -bool check_all_connected(const std::vector &pandas) { - for (const auto& panda : pandas) { - if (!panda->connected()) { - do_exit = true; - return false; - } +bool check_connected(Panda *panda) { + if (!panda->connected()) { + do_exit = true; + return false; } return true; } @@ -67,10 +45,10 @@ bool process_mads_heartbeat(SubMaster *sm) { return engaged; } -Panda *connect(std::string serial="", uint32_t index=0) { +Panda *connect(std::string serial) { std::unique_ptr panda; try { - panda = std::make_unique(serial, (index * PANDA_BUS_OFFSET)); + panda = std::make_unique(serial); } catch (std::exception &e) { return nullptr; } @@ -99,7 +77,7 @@ Panda *connect(std::string serial="", uint32_t index=0) { return panda.release(); } -void can_send_thread(std::vector pandas, bool fake_send) { +void can_send_thread(Panda *panda, bool fake_send) { util::set_thread_name("pandad_can_send"); AlignedBuffer aligned_buf; @@ -109,7 +87,7 @@ void can_send_thread(std::vector pandas, bool fake_send) { subscriber->setTimeout(100); // run as fast as messages come in - while (!do_exit && check_all_connected(pandas)) { + while (!do_exit && check_connected(panda)) { std::unique_ptr msg(subscriber->receive()); if (!msg) { continue; @@ -120,25 +98,20 @@ void can_send_thread(std::vector pandas, bool fake_send) { // Don't send if older than 1 second if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) { - for (const auto& panda : pandas) { - LOGT("sending sendcan to panda: %s", (panda->hw_serial()).c_str()); - panda->can_send(event.getSendcan()); - LOGT("sendcan sent to panda: %s", (panda->hw_serial()).c_str()); - } + LOGT("sending sendcan to panda: %s", (panda->hw_serial()).c_str()); + panda->can_send(event.getSendcan()); + LOGT("sendcan sent to panda: %s", (panda->hw_serial()).c_str()); } else { LOGE("sendcan too old to send: %" PRIu64 ", %" PRIu64, nanos_since_boot(), event.getLogMonoTime()); } } } -void can_recv(std::vector &pandas, PubMaster *pm) { +void can_recv(Panda *panda, PubMaster *pm) { static std::vector raw_can_data; { - bool comms_healthy = true; raw_can_data.clear(); - for (const auto& panda : pandas) { - comms_healthy &= panda->can_receive(raw_can_data); - } + bool comms_healthy = panda->can_receive(raw_can_data); MessageBuilder msg; auto evt = msg.initEvent(); @@ -178,6 +151,7 @@ void fill_panda_state(cereal::PandaState::Builder &ps, cereal::PandaState::Panda ps.setSpiErrorCount(health.spi_error_count_pkt); ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f); ps.setSbu2Voltage(health.sbu2_voltage_mV / 1000.0f); + ps.setSoundOutputLevel(health.sound_output_level_pkt); } void fill_panda_can_state(cereal::PandaState::PandaCanState::Builder &cs, const can_health_t &can_health) { @@ -208,102 +182,72 @@ void fill_panda_can_state(cereal::PandaState::PandaCanState::Builder &cs, const cs.setCanCoreResetCnt(can_health.can_core_reset_cnt); } -std::optional send_panda_states(PubMaster *pm, const std::vector &pandas, bool is_onroad, bool spoofing_started, bool always_offroad) { - bool ignition_local = false; - const uint32_t pandas_cnt = pandas.size(); - +std::optional send_panda_states(PubMaster *pm, Panda *panda, bool is_onroad, bool spoofing_started, bool always_offroad) { // build msg MessageBuilder msg; auto evt = msg.initEvent(); - auto pss = evt.initPandaStates(pandas_cnt); + auto pss = evt.initPandaStates(1); - std::vector pandaStates; - pandaStates.reserve(pandas_cnt); - - std::vector> pandaCanStates; - pandaCanStates.reserve(pandas_cnt); - - const bool red_panda_comma_three = (pandas.size() == 2) && - (pandas[0]->hw_type == cereal::PandaState::PandaType::DOS) && - (pandas[1]->hw_type == cereal::PandaState::PandaType::RED_PANDA); - - for (const auto& panda : pandas){ - auto health_opt = panda->get_state(); - if (!health_opt) { - return std::nullopt; - } - - health_t health = *health_opt; - - std::array can_health{}; - for (uint32_t i = 0; i < PANDA_CAN_CNT; i++) { - auto can_health_opt = panda->get_can_state(i); - if (!can_health_opt) { - return std::nullopt; - } - can_health[i] = *can_health_opt; - } - pandaCanStates.push_back(can_health); - - if (spoofing_started) { - health.ignition_line_pkt = 1; - } - - // on comma three setups with a red panda, the dos can - // get false positive ignitions due to the harness box - // without a harness connector, so ignore it - if (red_panda_comma_three && (panda->hw_type == cereal::PandaState::PandaType::DOS)) { - health.ignition_line_pkt = 0; - } - - ignition_local |= ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)) && !always_offroad; - - pandaStates.push_back(health); + auto health_opt = panda->get_state(); + if (!health_opt) { + return std::nullopt; } - for (uint32_t i = 0; i < pandas_cnt; i++) { - auto panda = pandas[i]; - const auto &health = pandaStates[i]; + health_t health = *health_opt; - // Make sure CAN buses are live: safety_setter_thread does not work if Panda CAN are silent and there is only one other CAN node - if (health.safety_mode_pkt == (uint8_t)(cereal::CarParams::SafetyModel::SILENT)) { - panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); + std::array can_health{}; + for (uint32_t i = 0; i < PANDA_CAN_CNT; i++) { + auto can_health_opt = panda->get_can_state(i); + if (!can_health_opt) { + return std::nullopt; } + can_health[i] = *can_health_opt; + } - bool power_save_desired = !ignition_local; - if (health.power_save_enabled_pkt != power_save_desired) { - panda->set_power_saving(power_save_desired); - } + if (spoofing_started) { + health.ignition_line_pkt = 1; + } - // set safety mode to NO_OUTPUT when car is off or we're not onroad. ELM327 is an alternative if we want to leverage athenad/connect - bool should_close_relay = !ignition_local || !is_onroad; - if (should_close_relay && (health.safety_mode_pkt != (uint8_t)(cereal::CarParams::SafetyModel::NO_OUTPUT))) { - panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); - } + bool ignition_local = ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)) && !always_offroad; - if (!panda->comms_healthy()) { - evt.setValid(false); - } + // Make sure CAN buses are live: safety_setter_thread does not work if Panda CAN are silent and there is only one other CAN node + if (health.safety_mode_pkt == (uint8_t)(cereal::CarParams::SafetyModel::SILENT)) { + panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); + } - auto ps = pss[i]; - fill_panda_state(ps, panda->hw_type, health); + bool power_save_desired = !ignition_local; + if (health.power_save_enabled_pkt != power_save_desired) { + panda->set_power_saving(power_save_desired); + } - auto cs = std::array{ps.initCanState0(), ps.initCanState1(), ps.initCanState2()}; - for (uint32_t j = 0; j < PANDA_CAN_CNT; j++) { - fill_panda_can_state(cs[j], pandaCanStates[i][j]); - } + // set safety mode to NO_OUTPUT when car is off or we're not onroad. ELM327 is an alternative if we want to leverage athenad/connect + bool should_close_relay = !ignition_local || !is_onroad; + if (should_close_relay && (health.safety_mode_pkt != (uint8_t)(cereal::CarParams::SafetyModel::NO_OUTPUT))) { + panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); + } - // Convert faults bitset to capnp list - std::bitset fault_bits(health.faults_pkt); - auto faults = ps.initFaults(fault_bits.count()); + if (!panda->comms_healthy()) { + evt.setValid(false); + } - size_t j = 0; - for (size_t f = size_t(cereal::PandaState::FaultType::RELAY_MALFUNCTION); - f <= size_t(cereal::PandaState::FaultType::HEARTBEAT_LOOP_WATCHDOG); f++) { - if (fault_bits.test(f)) { - faults.set(j, cereal::PandaState::FaultType(f)); - j++; - } + auto ps = pss[0]; + fill_panda_state(ps, panda->hw_type, health); + + auto cs = std::array{ps.initCanState0(), ps.initCanState1(), ps.initCanState2()}; + for (uint32_t j = 0; j < PANDA_CAN_CNT; j++) { + fill_panda_can_state(cs[j], can_health[j]); + } + + // Convert faults bitset to capnp list + std::bitset fault_bits(health.faults_pkt); + auto faults = ps.initFaults(fault_bits.count()); + + size_t j = 0; + for (size_t f = size_t(cereal::PandaState::FaultType::RELAY_MALFUNCTION); + f <= size_t(cereal::PandaState::FaultType::HEARTBEAT_LOOP_WATCHDOG); f++) { + if (fault_bits.test(f)) { + faults.set(j, cereal::PandaState::FaultType(f)); + j++; } } @@ -344,46 +288,22 @@ void send_peripheral_state(Panda *panda, PubMaster *pm) { pm->send("peripheralState", msg); } -void process_panda_state(std::vector &pandas, PubMaster *pm, bool engaged, bool engaged_mads, bool is_onroad, bool spoofing_started, bool always_offroad) { - std::vector connected_serials; - for (Panda *p : pandas) { - connected_serials.push_back(p->hw_serial()); +void process_panda_state(Panda *panda, PubMaster *pm, bool engaged, bool engaged_mads, bool is_onroad, bool spoofing_started, bool always_offroad) { + auto ignition_opt = send_panda_states(pm, panda, is_onroad, spoofing_started, always_offroad); + if (!ignition_opt) { + LOGE("Failed to get ignition_opt"); + return; } - { - auto ignition_opt = send_panda_states(pm, pandas, is_onroad, spoofing_started, always_offroad); - if (!ignition_opt) { - LOGE("Failed to get ignition_opt"); - return; - } - - // check if we should have pandad reconnect - if (!ignition_opt.value()) { - bool comms_healthy = true; - for (const auto &panda : pandas) { - comms_healthy &= panda->comms_healthy(); - } - - if (!comms_healthy) { - LOGE("Reconnecting, communication to pandas not healthy"); - do_exit = true; - - } else { - // check for new pandas - for (std::string &s : Panda::list(true)) { - if (!std::count(connected_serials.begin(), connected_serials.end(), s)) { - LOGW("Reconnecting to new panda: %s", s.c_str()); - do_exit = true; - break; - } - } - } - } - - for (const auto &panda : pandas) { - panda->send_heartbeat(engaged, engaged_mads); + // check if we should have pandad reconnect + if (!ignition_opt.value()) { + if (!panda->comms_healthy()) { + LOGE("Reconnecting, communication to panda not healthy"); + do_exit = true; } } + + panda->send_heartbeat(engaged, engaged_mads); } void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control) { @@ -450,32 +370,31 @@ void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control) } } -void pandad_run(std::vector &pandas) { +void pandad_run(Panda *panda) { const bool no_fan_control = getenv("NO_FAN_CONTROL") != nullptr; const bool spoofing_started = getenv("STARTED") != nullptr; const bool fake_send = getenv("FAKESEND") != nullptr; // Start the CAN send thread - std::thread send_thread(can_send_thread, pandas, fake_send); + std::thread send_thread(can_send_thread, panda, fake_send); Params params; RateKeeper rk("pandad", 100); SubMaster sm({"selfdriveState", "selfdriveStateSP", "carParams"}); PubMaster pm({"can", "pandaStates", "peripheralState"}); - PandaSafety panda_safety(pandas); - Panda *peripheral_panda = pandas[0]; + PandaSafety panda_safety(panda); bool engaged = false; bool engaged_mads = false; bool is_onroad = false; bool always_offroad = false; // Main loop: receive CAN data and process states - while (!do_exit && check_all_connected(pandas)) { - can_recv(pandas, &pm); + while (!do_exit && check_connected(panda)) { + can_recv(panda, &pm); // Process peripheral state at 20 Hz if (rk.frame() % 5 == 0) { - process_peripheral_state(peripheral_panda, &pm, no_fan_control); + process_peripheral_state(panda, &pm, no_fan_control); } // Process panda state at 10 Hz @@ -485,25 +404,23 @@ void pandad_run(std::vector &pandas) { engaged_mads = process_mads_heartbeat(&sm); is_onroad = params.getBool("IsOnroad"); always_offroad = panda_safety.getOffroadMode(); - process_panda_state(pandas, &pm, engaged, engaged_mads, is_onroad, spoofing_started, always_offroad); + process_panda_state(panda, &pm, engaged, engaged_mads, is_onroad, spoofing_started, always_offroad); panda_safety.configureSafetyMode(is_onroad); } // Send out peripheralState at 2Hz if (rk.frame() % 50 == 0) { - send_peripheral_state(peripheral_panda, &pm); + send_peripheral_state(panda, &pm); } - // Forward logs from pandas to cloudlog if available - for (auto *panda : pandas) { - std::string log = panda->serial_read(); - if (!log.empty()) { - if (log.find("Register 0x") != std::string::npos) { - // Log register divergent faults as errors - LOGE("%s", log.c_str()); - } else { - LOGD("%s", log.c_str()); - } + // Forward logs from panda to cloudlog if available + std::string log = panda->serial_read(); + if (!log.empty()) { + if (log.find("Register 0x") != std::string::npos) { + // Log register divergent faults as errors + LOGE("%s", log.c_str()); + } else { + LOGD("%s", log.c_str()); } } @@ -512,52 +429,38 @@ void pandad_run(std::vector &pandas) { // Close relay on exit to prevent a fault if (is_onroad && !engaged) { - for (auto &p : pandas) { - if (p->connected()) { - p->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); - } + if (panda->connected()) { + panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); } } send_thread.join(); } -void pandad_main_thread(std::vector serials) { - if (serials.size() == 0) { - serials = Panda::list(); +void pandad_main_thread(std::string serial) { + if (serial.empty()) { + auto serials = Panda::list(); - if (serials.size() == 0) { + if (serials.empty()) { LOGW("no pandas found, exiting"); return; } + serial = serials[0]; } - std::string serials_str; - for (int i = 0; i < serials.size(); i++) { - serials_str += serials[i]; - if (i < serials.size() - 1) serials_str += ", "; - } - LOGW("connecting to pandas: %s", serials_str.c_str()); + LOGW("connecting to panda: %s", serial.c_str()); - // connect to all provided serials - std::vector pandas; - for (int i = 0; i < serials.size() && !do_exit; /**/) { - Panda *p = connect(serials[i], i); - if (!p) { - util::sleep_for(100); - continue; - } - - pandas.push_back(p); - ++i; + Panda *panda = nullptr; + while (!do_exit) { + panda = connect(serial); + if (panda) break; + util::sleep_for(100); } if (!do_exit) { - LOGW("connected to all pandas"); - pandad_run(pandas); + LOGW("connected to panda"); + pandad_run(panda); } - for (Panda *panda : pandas) { - delete panda; - } + delete panda; } diff --git a/selfdrive/pandad/pandad.h b/selfdrive/pandad/pandad.h index 954851b613..1558d0469e 100644 --- a/selfdrive/pandad/pandad.h +++ b/selfdrive/pandad/pandad.h @@ -1,12 +1,11 @@ #pragma once #include -#include #include "common/params.h" #include "selfdrive/pandad/panda.h" -void pandad_main_thread(std::vector serials); +void pandad_main_thread(std::string serial); // deprecated devices static const std::vector SUPPORTED_PANDA_TYPES = { @@ -18,7 +17,7 @@ static const std::vector SUPPORTED_PANDA_TYPES = class PandaSafety { public: - PandaSafety(const std::vector &pandas) : pandas_(pandas) {} + PandaSafety(Panda *panda) : panda_(panda) {} void configureSafetyMode(bool is_onroad); bool getOffroadMode(); @@ -31,6 +30,6 @@ private: bool log_once_ = false; bool safety_configured_ = false; bool prev_obd_multiplexing_ = false; - std::vector pandas_; + Panda *panda_; Params params_; }; diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index d21e60f838..a0bca3f239 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -12,6 +12,8 @@ from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE from openpilot.common.swaglog import cloudlog +from openpilot.sunnypilot.selfdrive.pandad.rivian_long_flasher import flash_rivian_long + def get_expected_signature() -> bytes: try: @@ -124,52 +126,44 @@ def main() -> None: cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}") - # Flash pandas - pandas: list[Panda] = [] - for serial in panda_serials: - pandas.append(flash_panda(serial)) + # Flash the first panda + panda_serial = panda_serials[0] + panda = flash_panda(panda_serial) + + # flash Rivian longitudinal upgrade panda + flash_rivian_long(panda) # Ensure internal panda is present if expected - internal_pandas = [panda for panda in pandas if panda.is_internal()] - if HARDWARE.has_internal_panda() and len(internal_pandas) == 0: + if HARDWARE.has_internal_panda() and not panda.is_internal(): cloudlog.error("Internal panda is missing, trying again") no_internal_panda_count += 1 continue no_internal_panda_count = 0 - # sort pandas to have deterministic order - # * 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 version + params.put("PandaSignatures", panda.get_signature()) - # log panda fw versions - params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas)) + # skip health check if the detected panda is not supported + supported_panda = check_panda_support(panda) + if not supported_panda: + cloudlog.warning(f"Panda {panda.get_usb_serial()} is not supported (hw_type: {panda.get_type()}), skipping health check...") + continue - for panda in pandas: - # skip health check if the detected panda is not supported - supported_panda = check_panda_support(panda) - if not supported_panda: - cloudlog.warning(f"Panda {panda.get_usb_serial()} is not supported (hw_type: {panda.get_type()}), skipping health check...") - continue + # check health for lost heartbeat + health = panda.health() + if health["heartbeat_lost"]: + params.put_bool("PandaHeartbeatLost", True) + cloudlog.event("heartbeat lost", deviceState=health, serial=panda.get_usb_serial()) + if health["som_reset_triggered"]: + params.put_bool("PandaSomResetTriggered", True) + cloudlog.event("panda.som_reset_triggered", health=health, serial=panda.get_usb_serial()) - # check health for lost heartbeat - health = panda.health() - if health["heartbeat_lost"]: - params.put_bool("PandaHeartbeatLost", True) - cloudlog.event("heartbeat lost", deviceState=health, serial=panda.get_usb_serial()) - if health["som_reset_triggered"]: - params.put_bool("PandaSomResetTriggered", True) - cloudlog.event("panda.som_reset_triggered", health=health, serial=panda.get_usb_serial()) + if first_run: + # reset panda to ensure we're in a good state + cloudlog.info(f"Resetting panda {panda.get_usb_serial()}") + panda.reset(reconnect=True) - if first_run: - # reset panda to ensure we're in a good state - cloudlog.info(f"Resetting panda {panda.get_usb_serial()}") - panda.reset(reconnect=True) - - for p in pandas: - p.close() + panda.close() # TODO: wrap all panda exceptions in a base panda exception except (usb1.USBErrorNoDevice, usb1.USBErrorPipe): # a panda was disconnected while setting everything up. let's try again @@ -186,7 +180,7 @@ def main() -> None: # run pandad with all connected serials as arguments os.environ['MANAGER_DAEMON'] = 'pandad' - process = subprocess.Popen(["./pandad", *panda_serials], cwd=os.path.join(BASEDIR, "selfdrive/pandad")) + process = subprocess.Popen(["./pandad", panda_serial], cwd=os.path.join(BASEDIR, "selfdrive/pandad")) process.wait() diff --git a/selfdrive/pandad/spi.cc b/selfdrive/pandad/spi.cc index b6ee57801a..f54c26e506 100644 --- a/selfdrive/pandad/spi.cc +++ b/selfdrive/pandad/spi.cc @@ -1,4 +1,3 @@ -#ifndef __APPLE__ #include #include #include @@ -33,7 +32,7 @@ const std::string SPI_DEVICE = "/dev/spidev0.0"; class LockEx { public: - LockEx(int fd, std::recursive_mutex &m) : fd(fd), m(m) { + LockEx(int fd_, std::recursive_mutex &m_) : fd(fd_), m(m_) { m.lock(); flock(fd, LOCK_EX); } @@ -55,7 +54,7 @@ private: util::hexdump(tx_buf, std::min((int)header.tx_len, 8)).c_str()); \ } while (0) -PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) { +PandaSpiHandle::PandaSpiHandle(std::string serial) { int ret; const int uid_len = 12; uint8_t uid[uid_len] = {0}; @@ -407,4 +406,3 @@ fail: if (ret >= 0) ret = -1; return ret; } -#endif diff --git a/selfdrive/pandad/tests/test_pandad_usbprotocol.cc b/selfdrive/pandad/tests/test_pandad_canprotocol.cc similarity index 87% rename from selfdrive/pandad/tests/test_pandad_usbprotocol.cc rename to selfdrive/pandad/tests/test_pandad_canprotocol.cc index 11f7184efd..8499a1aab8 100644 --- a/selfdrive/pandad/tests/test_pandad_usbprotocol.cc +++ b/selfdrive/pandad/tests/test_pandad_canprotocol.cc @@ -1,13 +1,15 @@ #define CATCH_CONFIG_MAIN #define CATCH_CONFIG_ENABLE_BENCHMARKING +#include + #include "catch2/catch.hpp" #include "cereal/messaging/messaging.h" #include "common/util.h" #include "selfdrive/pandad/panda.h" struct PandaTest : public Panda { - PandaTest(uint32_t bus_offset, int can_list_size, cereal::PandaState::PandaType hw_type); + PandaTest(int can_list_size, cereal::PandaState::PandaType hw_type); void test_can_send(); void test_can_recv(uint32_t chunk_size = 0); void test_chunked_can_recv(); @@ -19,8 +21,8 @@ struct PandaTest : public Panda { capnp::List::Reader can_data_list; }; -PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState::PandaType hw_type) : can_list_size(can_list_size), Panda(bus_offset_) { - this->hw_type = hw_type; +PandaTest::PandaTest(int can_list_size_, cereal::PandaState::PandaType hw_type_) : can_list_size(can_list_size_), Panda() { + this->hw_type = hw_type_; int data_limit = ((hw_type == cereal::PandaState::PandaType::RED_PANDA) ? std::size(dlc_to_len) : 8); // prepare test data for (int i = 0; i < data_limit; ++i) { @@ -40,7 +42,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, 2) + bus_offset); + can.setSrc(util::random_int(0, 2)); can.setDat(kj::ArrayPtr((uint8_t *)dat.data(), dat.size())); total_pakets_size += sizeof(can_header) + dat.size(); } @@ -103,9 +105,8 @@ void PandaTest::test_can_recv(uint32_t rx_chunk_size) { } TEST_CASE("send/recv CAN 2.0 packets") { - auto bus_offset = GENERATE(0, 4); auto can_list_size = GENERATE(1, 3, 5, 10, 30, 60, 100, 200); - PandaTest test(bus_offset, can_list_size, cereal::PandaState::PandaType::DOS); + PandaTest test(can_list_size, cereal::PandaState::PandaType::DOS); SECTION("can_send") { test.test_can_send(); @@ -119,9 +120,8 @@ TEST_CASE("send/recv CAN 2.0 packets") { } TEST_CASE("send/recv CAN FD packets") { - auto bus_offset = GENERATE(0, 4); auto can_list_size = GENERATE(1, 3, 5, 10, 30, 60, 100, 200); - PandaTest test(bus_offset, can_list_size, cereal::PandaState::PandaType::RED_PANDA); + PandaTest test(can_list_size, cereal::PandaState::PandaType::RED_PANDA); SECTION("can_send") { test.test_can_send(); diff --git a/selfdrive/pandad/tests/test_pandad_loopback.py b/selfdrive/pandad/tests/test_pandad_loopback.py index eff70d2544..fd4a99be62 100644 --- a/selfdrive/pandad/tests/test_pandad_loopback.py +++ b/selfdrive/pandad/tests/test_pandad_loopback.py @@ -13,12 +13,11 @@ from openpilot.common.utils 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.system.hardware import TICI from openpilot.selfdrive.test.helpers import with_processes @retry(attempts=3) -def setup_pandad(num_pandas): +def setup_pandad(): params = Params() params.clear_all() params.put_bool("IsOnroad", False) @@ -29,16 +28,12 @@ def setup_pandad(num_pandas): 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 + cp.safetyConfigs = [safety_config] params.put_bool("IsOnroad", True) params.put_bool("FirmwareQueryDone", True) @@ -49,12 +44,12 @@ def setup_pandad(num_pandas): 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): +def send_random_can_messages(sendcan, count): 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]) + bus = random.choice(range(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]: @@ -74,8 +69,7 @@ class TestBoarddLoopback: @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) + setup_pandad() sendcan = messaging.pub_sock('sendcan') can = messaging.sub_sock('can', conflate=False, timeout=100) @@ -86,7 +80,7 @@ class TestBoarddLoopback: 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_msgs = send_random_can_messages(sendcan, random.randrange(20, 100)) sent_loopback = copy.deepcopy(sent_msgs) sent_loopback.update({k+128: copy.deepcopy(v) for k, v in sent_msgs.items()}) diff --git a/selfdrive/pandad/tests/test_pandad_spi.py b/selfdrive/pandad/tests/test_pandad_spi.py index da4b181993..69dfb67e93 100644 --- a/selfdrive/pandad/tests/test_pandad_spi.py +++ b/selfdrive/pandad/tests/test_pandad_spi.py @@ -22,7 +22,7 @@ class TestBoarddSpi: @with_processes(['pandad']) def test_spi_corruption(self, subtests): - setup_pandad(1) + setup_pandad() sendcan = messaging.pub_sock('sendcan') socks = {s: messaging.sub_sock(s, conflate=False, timeout=100) for s in ('can', 'pandaStates', 'peripheralState')} diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 62f4553bff..3e20a28023 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -933,7 +933,7 @@ if __name__ == '__main__': for i, alerts in EVENTS.items(): for et, alert in alerts.items(): - if callable(alert): + if not isinstance(alert, Alert): alert = alert(CP, CS, sm, False, 1, log.LongitudinalPersonality.standard) alerts_by_type[et][alert.priority].append(event_names[i]) diff --git a/selfdrive/test/docker_build.sh b/selfdrive/test/docker_build.sh index 4d58a1507c..8d1fa82249 100755 --- a/selfdrive/test/docker_build.sh +++ b/selfdrive/test/docker_build.sh @@ -1,12 +1,14 @@ #!/usr/bin/env bash set -e -# To build sim and docs, you can run the following to mount the scons cache to the same place as in CI: -# mkdir -p .ci_cache/scons_cache -# sudo mount --bind /tmp/scons_cache/ .ci_cache/scons_cache - SCRIPT_DIR=$(dirname "$0") OPENPILOT_DIR=$SCRIPT_DIR/../../ + +DOCKER_IMAGE=openpilot +DOCKER_FILE=Dockerfile.openpilot +DOCKER_REGISTRY=ghcr.io/commaai +COMMIT_SHA=$(git rev-parse HEAD) + if [ -n "$TARGET_ARCHITECTURE" ]; then PLATFORM="linux/$TARGET_ARCHITECTURE" TAG_SUFFIX="-$TARGET_ARCHITECTURE" @@ -15,9 +17,11 @@ else TAG_SUFFIX="" fi -source $SCRIPT_DIR/docker_common.sh $1 "$TAG_SUFFIX" +LOCAL_TAG=$DOCKER_IMAGE$TAG_SUFFIX +REMOTE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG +REMOTE_SHA_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:$COMMIT_SHA -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 +DOCKER_BUILDKIT=1 docker buildx build --provenance false --pull --platform $PLATFORM --load -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 deleted file mode 100644 index 2887fff74b..0000000000 --- a/selfdrive/test/docker_common.sh +++ /dev/null @@ -1,18 +0,0 @@ -if [ "$1" = "base" ]; then - export DOCKER_IMAGE=openpilot-base - export DOCKER_FILE=Dockerfile.openpilot_base -elif [ "$1" = "prebuilt" ]; then - export DOCKER_IMAGE=openpilot-prebuilt - export DOCKER_FILE=Dockerfile.openpilot -else - echo "Invalid docker build image: '$1'" - exit 1 -fi - -export DOCKER_REGISTRY=ghcr.io/commaai -export COMMIT_SHA=$(git rev-parse HEAD) - -TAG_SUFFIX=$2 -LOCAL_TAG=$DOCKER_IMAGE$TAG_SUFFIX -REMOTE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG -REMOTE_SHA_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:$COMMIT_SHA diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index 81635aa31f..5dfc1c3ec8 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -37,6 +37,43 @@ def release_only(f): return wrap +def collect_logs(services, duration): + socks = [messaging.sub_sock(s, conflate=False, timeout=100) for s in services] + logs = [] + start = time.monotonic() + while time.monotonic() - start < duration: + for s in socks: + logs.extend(messaging.drain_sock(s)) + return logs + + +@contextlib.contextmanager +def log_collector(services): + """Background thread that continuously drains messages from services. + Use when the main thread needs to do blocking work (e.g. capturing images).""" + socks = [messaging.sub_sock(s, conflate=False, timeout=100) for s in services] + raw_logs = [] + lock = threading.Lock() + stop_event = threading.Event() + + def _drain(): + while not stop_event.is_set(): + for s in socks: + msgs = messaging.drain_sock(s) + if msgs: + with lock: + raw_logs.extend(msgs) + time.sleep(0.01) + + thread = threading.Thread(target=_drain, daemon=True) + thread.start() + try: + yield raw_logs, lock + finally: + stop_event.set() + thread.join(timeout=2) + + @contextlib.contextmanager def processes_context(processes, init_time=0, ignore_stopped=None): ignore_stopped = [] if ignore_stopped is None else ignore_stopped diff --git a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index ab1800b4fb..90bc46b187 100644 --- a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -1,5 +1,5 @@ import itertools -from parameterized import parameterized_class +from openpilot.common.parameterized import parameterized_class from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index 13d51a636f..e2d912a833 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -3,13 +3,16 @@ import sys import math import capnp import numbers -import dictdiffer from collections import Counter from openpilot.tools.lib.logreader import LogReader EPSILON = sys.float_info.epsilon +_DynamicStructReader = capnp.lib.capnp._DynamicStructReader +_DynamicListReader = capnp.lib.capnp._DynamicListReader +_DynamicEnum = capnp.lib.capnp._DynamicEnum + def remove_ignored_fields(msg, ignore): msg = msg.as_builder() @@ -39,6 +42,61 @@ def remove_ignored_fields(msg, ignore): return msg +def _diff_capnp(r1, r2, path, tolerance): + """Walk two capnp struct readers and yield (action, dotted_path, value) diffs. + + Floats are compared with the given tolerance (combined absolute+relative). + """ + schema = r1.schema + + for fname in schema.non_union_fields: + child_path = path + (fname,) + v1 = getattr(r1, fname) + v2 = getattr(r2, fname) + yield from _diff_capnp_values(v1, v2, child_path, tolerance) + + if schema.union_fields: + w1, w2 = r1.which(), r2.which() + if w1 != w2: + yield 'change', '.'.join(path), (w1, w2) + else: + child_path = path + (w1,) + v1, v2 = getattr(r1, w1), getattr(r2, w2) + yield from _diff_capnp_values(v1, v2, child_path, tolerance) + + +def _diff_capnp_values(v1, v2, path, tolerance): + if isinstance(v1, _DynamicStructReader): + yield from _diff_capnp(v1, v2, path, tolerance) + + elif isinstance(v1, _DynamicListReader): + dot = '.'.join(path) + n1, n2 = len(v1), len(v2) + n = min(n1, n2) + for i in range(n): + yield from _diff_capnp_values(v1[i], v2[i], path + (str(i),), tolerance) + if n2 > n: + yield 'add', dot, list(enumerate(v2[n:], n)) + if n1 > n: + yield 'remove', dot, list(reversed([(i, v1[i]) for i in range(n, n1)])) + + elif isinstance(v1, _DynamicEnum): + s1, s2 = str(v1), str(v2) + if s1 != s2: + yield 'change', '.'.join(path), (s1, s2) + + elif isinstance(v1, float): + if not (v1 == v2 or ( + math.isfinite(v1) and math.isfinite(v2) and + abs(v1 - v2) <= max(tolerance, tolerance * max(abs(v1), abs(v2))) + )): + yield 'change', '.'.join(path), (v1, v2) + + else: + if v1 != v2: + yield 'change', '.'.join(path), (v1, v2) + + def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=None,): if ignore_fields is None: ignore_fields = [] @@ -65,26 +123,7 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non msg2 = remove_ignored_fields(msg2, ignore_fields) if msg1.to_bytes() != msg2.to_bytes(): - msg1_dict = msg1.as_reader().to_dict(verbose=True) - msg2_dict = msg2.as_reader().to_dict(verbose=True) - - dd = dictdiffer.diff(msg1_dict, msg2_dict, ignore=ignore_fields) - - # Dictdiffer only supports relative tolerance, we also want to check for absolute - # TODO: add this to dictdiffer - def outside_tolerance(diff): - try: - if diff[0] == "change": - a, b = diff[2] - finite = math.isfinite(a) and math.isfinite(b) - if finite and isinstance(a, numbers.Number) and isinstance(b, numbers.Number): - return abs(a - b) > max(tolerance, tolerance * max(abs(a), abs(b))) - except TypeError: - pass - return True - - dd = list(filter(outside_tolerance, dd)) - + dd = list(_diff_capnp(msg1.as_reader(), msg2.as_reader(), (), tolerance)) diff.extend(dd) return diff diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index d35474f373..a6ccaa1047 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -34,8 +34,8 @@ GITHUB = GithubUtils(API_TOKEN, DATA_TOKEN) EXEC_TIMINGS = [ # model, instant max, average max - ("modelV2", 0.035, 0.025), - ("driverStateV2", 0.02, 0.015), + ("modelV2", 0.05, 0.028), + ("driverStateV2", 0.05, 0.016), ] def get_log_fn(test_route, ref="master"): diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 1e59ee7afa..c2d0bbc921 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -4,6 +4,7 @@ import time import copy import heapq import signal +import numpy as np from collections import Counter from dataclasses import dataclass, field from itertools import islice @@ -23,8 +24,9 @@ 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 openpilot.selfdrive.car.card import convert_to_capnp +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.system.manager.process_config import managed_processes +from openpilot.selfdrive.car.card import convert_to_capnp 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 @@ -204,7 +206,8 @@ class ProcessContainer: if meta.camera_state in self.cfg.vision_pubs: assert frs[meta.camera_state].pix_fmt == 'nv12' frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h) - vipc_server.create_buffers(meta.stream, 2, *frame_size) + stride, y_height, _, yuv_size = get_nv12_info(frame_size[0], frame_size[1]) + vipc_server.create_buffers_with_sizes(meta.stream, 2, frame_size[0], frame_size[1], yuv_size, stride, stride * y_height) vipc_server.start_listener() self.vipc_server = vipc_server @@ -301,7 +304,17 @@ class ProcessContainer: camera_meta = meta_from_camera_state(m.which()) assert frs is not None img = frs[m.which()].get(camera_state.frameId) - self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(), + + h, w = frs[m.which()].h, frs[m.which()].w + stride, y_height, _, yuv_size = get_nv12_info(w, h) + uv_offset = stride * y_height + padded_img = np.zeros(((uv_offset //stride) + (h // 2), stride)) + padded_img[:h, :w] = img[:h * w].reshape((-1, w)) + padded_img[uv_offset // stride:uv_offset // stride + h // 2, :w] = img[h * w:].reshape((-1, w)) + img_bytes = np.zeros((yuv_size,), dtype=np.uint8) + img_bytes[:padded_img.size] = padded_img.flatten() + + self.vipc_server.send(camera_meta.stream, img_bytes.tobytes(), camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) self.msg_queue = [] diff --git a/selfdrive/test/process_replay/test_fuzzy.py b/selfdrive/test/process_replay/test_fuzzy.py index 723112163e..6989f8957f 100644 --- a/selfdrive/test/process_replay/test_fuzzy.py +++ b/selfdrive/test/process_replay/test_fuzzy.py @@ -2,7 +2,7 @@ import copy import os from hypothesis import given, HealthCheck, Phase, settings import hypothesis.strategies as st -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from cereal import log from opendbc.car.toyota.values import CAR as TOYOTA diff --git a/selfdrive/test/process_replay/test_regen.py b/selfdrive/test/process_replay/test_regen.py index 5f26daf786..f4942e486c 100644 --- a/selfdrive/test/process_replay/test_regen.py +++ b/selfdrive/test/process_replay/test_regen.py @@ -1,4 +1,4 @@ -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from openpilot.selfdrive.test.process_replay.regen import regen_segment from openpilot.selfdrive.test.process_replay.process_replay import check_openpilot_enabled diff --git a/selfdrive/test/setup_vsound.sh b/selfdrive/test/setup_vsound.sh deleted file mode 100755 index aab1499744..0000000000 --- a/selfdrive/test/setup_vsound.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -{ - #start pulseaudio daemon - sudo pulseaudio -D - - # create a virtual null audio and set it to default device - sudo pactl load-module module-null-sink sink_name=virtual_audio - sudo pactl set-default-sink virtual_audio -} > /dev/null 2>&1 diff --git a/selfdrive/test/setup_xvfb.sh b/selfdrive/test/setup_xvfb.sh index 692b84d65f..c1b74a850e 100755 --- a/selfdrive/test/setup_xvfb.sh +++ b/selfdrive/test/setup_xvfb.sh @@ -2,7 +2,11 @@ # Sets up a virtual display for running map renderer and simulator without an X11 display -DISP_ID=99 +if uname -r | grep -q "WSL2"; then + DISP_ID=0 # WSLg uses display :0 +else + DISP_ID=99 # Standard Xvfb display +fi export DISPLAY=:$DISP_ID sudo Xvfb $DISPLAY -screen 0 2160x1080x24 2>/dev/null & @@ -15,5 +19,4 @@ do done touch ~/.Xauthority -export XDG_SESSION_TYPE="x11" -xset -q \ No newline at end of file +export XDG_SESSION_TYPE="x11" \ No newline at end of file diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 0de3e13c01..4d7448c62f 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,6 +1,4 @@ -import os import re -import json from pathlib import Path Import('env', 'arch', 'common') @@ -8,7 +6,7 @@ Import('env', 'arch', 'common') generator = File("#selfdrive/assets/fonts/process.py") source_files = Glob("#selfdrive/assets/fonts/*.ttf") + Glob("#selfdrive/assets/fonts/*.otf") output_files = [ - (f.abspath.split('.')[0] + ".fnt", f.abspath.split('.')[0] + ".png") + (f"#{Path(f.path).with_suffix('.fnt')}", f"#{Path(f.path).with_suffix('.png')}") for f in source_files if "NotoColor" not in f.name ] @@ -18,20 +16,8 @@ env.Command( action=f"python3 {generator}", ) -# compile gettext .po -> .mo translations -with open(File("translations/languages.json").abspath) as f: - languages = json.loads(f.read()) -po_sources = [f"#selfdrive/ui/translations/app_{l}.po" for l in languages.values()] -po_sources = [src for src in po_sources if os.path.exists(File(src).abspath)] -mo_targets = [src.replace(".po", ".mo") for src in po_sources] -mo_build = [] -for src, tgt in zip(po_sources, mo_targets): - mo_build.append(env.Command(tgt, src, "msgfmt -o $TARGET $SOURCE")) -mo_alias = env.Alias('mo', mo_build) -env.AlwaysBuild(mo_alias) - -if GetOption('extras'): +if GetOption('extras') and arch == "larch64": # build installers if arch != "Darwin": raylib_env = env.Clone() @@ -68,4 +54,4 @@ if GetOption('extras'): obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter, inter_bold, inter_light], LIBS=raylib_libs) # keep installers small - assert f[0].get_size() < 1900*1e3, f[0].get_size() + assert f[0].get_size() < 2500*1e3, f[0].get_size() diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 072fa4e24b..7599454194 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -30,7 +30,7 @@ const std::string VALID_CACHE_PATH = "/data/.openpilot_cache"; #define TMP_INSTALL_PATH "/data/tmppilot" -const int FONT_SIZE = 120; +const int FONT_SIZE = 160; extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_start"); extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_end"); @@ -88,7 +88,7 @@ void finishInstall() { int text_width = MeasureText(m, FONT_SIZE); DrawTextEx(font_display, m, (Vector2){(float)(GetScreenWidth() - text_width)/2 + FONT_SIZE, (float)(GetScreenHeight() - FONT_SIZE)/2}, FONT_SIZE, 0, WHITE); } else { - DrawTextEx(font_display, "finishing setup", (Vector2){8, 10}, 82, 0, WHITE); + DrawTextEx(font_display, "finishing setup", (Vector2){12, 0}, 77, 0, (Color){255, 255, 255, (unsigned char)(255 * 0.9)}); } EndDrawing(); util::sleep_for(60 * 1000); @@ -106,10 +106,10 @@ void renderProgress(int progress) { DrawRectangleRec(bar, (Color){70, 91, 234, 255}); DrawTextEx(font_inter, (std::to_string(progress) + "%").c_str(), (Vector2){150, 670}, 85, 0, WHITE); } else { - DrawTextEx(font_display, "installing", (Vector2){8, 10}, 82, 0, WHITE); + DrawTextEx(font_display, "installing...", (Vector2){12, 0}, 77, 0, (Color){255, 255, 255, (unsigned char)(255 * 0.9)}); const std::string percent_str = std::to_string(progress) + "%"; - DrawTextEx(font_roman, percent_str.c_str(), (Vector2){6, (float)(GetScreenHeight() - 128 + 18)}, 128, 0, - (Color){255, 255, 255, (unsigned char)(255 * 0.9 * 0.35)}); + DrawTextEx(font_inter, percent_str.c_str(), (Vector2){12, (float)(GetScreenHeight() - 154 + 20)}, 154, 0, + (Color){255, 255, 255, (unsigned char)(255 * 0.9 * 0.65)}); } EndDrawing(); diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index a020a63b55..2adecfeaa8 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -39,10 +39,12 @@ class MainLayout(Widget): # Set callbacks self._setup_callbacks() - # Start onboarding if terms or training not completed + gui_app.push_widget(self) + + # Start onboarding if terms or training not completed, make sure to push after self self._onboarding_window = OnboardingWindow() if not self._onboarding_window.completed: - gui_app.set_modal_overlay(self._onboarding_window) + gui_app.push_widget(self._onboarding_window) def _render(self, _): self._handle_onroad_transition() diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index c480a3ed9d..c53db2231a 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -84,6 +84,9 @@ class TrainingGuide(Widget): if self._completed_callback: self._completed_callback() + # NOTE: this pops OnboardingWindow during real onboarding + gui_app.pop_widget() + def _update_state(self): if len(self._image_objs): self._textures.append(gui_app._load_texture_from_image(self._image_objs.pop(0))) @@ -213,11 +216,10 @@ class OnboardingWindow(Widget): elif not self._training_done: self._state = OnboardingState.ONBOARDING else: - gui_app.set_modal_overlay(None) + gui_app.pop_widget() def _on_completed_training(self): ui_state.params.put("CompletedTrainingVersion", training_version) - gui_app.set_modal_overlay(None) def _render(self, _): if self._training_guide is None: @@ -231,12 +233,12 @@ class OnboardingWindow(Widget): if not self._training_done: self._state = OnboardingState.ONBOARDING else: - gui_app.set_modal_overlay(None) + gui_app.pop_widget() elif self._state == OnboardingState.ONBOARDING: if not self._training_done: self._training_guide.render(self._rect) else: - gui_app.set_modal_overlay(None) + gui_app.pop_widget() elif self._state == OnboardingState.DECLINE: self._decline_page.render(self._rect) return -1 diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 2ff94aa52d..56c2951d0d 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -169,7 +169,7 @@ class DeveloperLayout(Widget): def _on_alpha_long_enabled(self, state: bool): if state: - def confirm_callback(result: int): + def confirm_callback(result: DialogResult): if result == DialogResult.CONFIRM: self._params.put_bool("AlphaLongitudinalEnabled", True) self._params.put_bool("OnroadCycleRequested", True) @@ -181,8 +181,8 @@ class DeveloperLayout(Widget): content = (f"

{self._alpha_long_toggle.title}


" + f"

{self._alpha_long_toggle.description}

") - dlg = ConfirmDialog(content, tr("Enable"), rich=True) - gui_app.set_modal_overlay(dlg, callback=confirm_callback) + dlg = ConfirmDialog(content, tr("Enable"), rich=True, callback=confirm_callback) + gui_app.push_widget(dlg) else: self._params.put_bool("AlphaLongitudinalEnabled", False) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 8830ef946f..45589af1f0 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -9,7 +9,6 @@ from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialo from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.layouts.onboarding import TrainingGuide from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog -from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.multilang import multilang, tr, tr_noop from openpilot.system.ui.widgets import Widget, DialogResult @@ -37,8 +36,6 @@ class DeviceLayout(Widget): self._params = Params() self._select_language_dialog: MultiOptionDialog | None = None - self._driver_camera: DriverCameraDialog | None = None - self._pair_device_dialog: PairingDialog | None = None self._fcc_dialog: HtmlModal | None = None self._training_guide: TrainingGuide | None = None @@ -48,7 +45,8 @@ class DeviceLayout(Widget): ui_state.add_offroad_transition_callback(self._offroad_transition) def _initialize_items(self): - self._pair_device_btn = button_item(lambda: tr("Pair Device"), lambda: tr("PAIR"), lambda: tr(DESCRIPTIONS['pair_device']), callback=self._pair_device) + self._pair_device_btn = button_item(lambda: tr("Pair Device"), lambda: tr("PAIR"), lambda: tr(DESCRIPTIONS['pair_device']), + callback=lambda: gui_app.push_widget(PairingDialog())) self._pair_device_btn.set_visible(lambda: not ui_state.prime_state.is_paired()) self._reset_calib_btn = button_item(lambda: tr("Reset Calibration"), lambda: tr("RESET"), lambda: tr(DESCRIPTIONS['reset_calibration']), @@ -63,15 +61,14 @@ class DeviceLayout(Widget): text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))), self._pair_device_btn, button_item(lambda: tr("Driver Camera"), lambda: tr("PREVIEW"), lambda: tr(DESCRIPTIONS['driver_camera']), - callback=self._show_driver_camera, enabled=ui_state.is_offroad), + callback=lambda: gui_app.push_widget(DriverCameraDialog()), enabled=ui_state.is_offroad), self._reset_calib_btn, button_item(lambda: tr("Review Training Guide"), lambda: tr("REVIEW"), lambda: tr(DESCRIPTIONS['review_guide']), self._on_review_training_guide, enabled=ui_state.is_offroad), - regulatory_btn := button_item(lambda: tr("Regulatory"), lambda: tr("VIEW"), callback=self._on_regulatory, enabled=ui_state.is_offroad), + button_item(lambda: tr("Regulatory"), lambda: tr("VIEW"), callback=self._on_regulatory, enabled=ui_state.is_offroad), button_item(lambda: tr("Change Language"), lambda: tr("CHANGE"), callback=self._show_language_dialog), self._power_off_btn, ] - regulatory_btn.set_visible(TICI) return items def _offroad_transition(self): @@ -84,29 +81,23 @@ class DeviceLayout(Widget): self._scroller.render(rect) def _show_language_dialog(self): - def handle_language_selection(result: int): - if result == 1 and self._select_language_dialog: + def handle_language_selection(result: DialogResult): + if result == DialogResult.CONFIRM and self._select_language_dialog: selected_language = multilang.languages[self._select_language_dialog.selection] multilang.change_language(selected_language) self._update_calib_description() self._select_language_dialog = None self._select_language_dialog = MultiOptionDialog(tr("Select a language"), multilang.languages, multilang.codes[multilang.language], - option_font_weight=FontWeight.UNIFONT) - gui_app.set_modal_overlay(self._select_language_dialog, callback=handle_language_selection) - - def _show_driver_camera(self): - if not self._driver_camera: - self._driver_camera = DriverCameraDialog() - - gui_app.set_modal_overlay(self._driver_camera, callback=lambda result: setattr(self, '_driver_camera', None)) + option_font_weight=FontWeight.UNIFONT, callback=handle_language_selection) + gui_app.push_widget(self._select_language_dialog) def _reset_calibration_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Reset Calibration"))) + gui_app.push_widget(alert_dialog(tr("Disengage to Reset Calibration"))) return - def reset_calibration(result: int): + def reset_calibration(result: DialogResult): # Check engaged again in case it changed while the dialog was open if ui_state.engaged or result != DialogResult.CONFIRM: return @@ -119,8 +110,8 @@ class DeviceLayout(Widget): self._params.put_bool("OnroadCycleRequested", True) self._update_calib_description() - dialog = ConfirmDialog(tr("Are you sure you want to reset calibration?"), tr("Reset")) - gui_app.set_modal_overlay(dialog, callback=reset_calibration) + dialog = ConfirmDialog(tr("Are you sure you want to reset calibration?"), tr("Reset"), callback=reset_calibration) + gui_app.push_widget(dialog) def _update_calib_description(self): desc = tr(DESCRIPTIONS['reset_calibration']) @@ -172,42 +163,34 @@ class DeviceLayout(Widget): def _reboot_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Reboot"))) + gui_app.push_widget(alert_dialog(tr("Disengage to Reboot"))) return - dialog = ConfirmDialog(tr("Are you sure you want to reboot?"), tr("Reboot")) - gui_app.set_modal_overlay(dialog, callback=self._perform_reboot) + def perform_reboot(result: DialogResult): + if not ui_state.engaged and result == DialogResult.CONFIRM: + self._params.put_bool_nonblocking("DoReboot", True) - def _perform_reboot(self, result: int): - if not ui_state.engaged and result == DialogResult.CONFIRM: - self._params.put_bool_nonblocking("DoReboot", True) + dialog = ConfirmDialog(tr("Are you sure you want to reboot?"), tr("Reboot"), callback=perform_reboot) + gui_app.push_widget(dialog) def _power_off_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Power Off"))) + gui_app.push_widget(alert_dialog(tr("Disengage to Power Off"))) return - dialog = ConfirmDialog(tr("Are you sure you want to power off?"), tr("Power Off")) - gui_app.set_modal_overlay(dialog, callback=self._perform_power_off) + def perform_power_off(result: DialogResult): + if not ui_state.engaged and result == DialogResult.CONFIRM: + self._params.put_bool_nonblocking("DoShutdown", True) - def _perform_power_off(self, result: int): - if not ui_state.engaged and result == DialogResult.CONFIRM: - self._params.put_bool_nonblocking("DoShutdown", True) - - def _pair_device(self): - if not self._pair_device_dialog: - self._pair_device_dialog = PairingDialog() - gui_app.set_modal_overlay(self._pair_device_dialog, callback=lambda result: setattr(self, '_pair_device_dialog', None)) + dialog = ConfirmDialog(tr("Are you sure you want to power off?"), tr("Power Off"), callback=perform_power_off) + gui_app.push_widget(dialog) def _on_regulatory(self): if not self._fcc_dialog: self._fcc_dialog = HtmlModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/fcc.html")) - gui_app.set_modal_overlay(self._fcc_dialog) + gui_app.push_widget(self._fcc_dialog) def _on_review_training_guide(self): if not self._training_guide: - def completed_callback(): - gui_app.set_modal_overlay(None) - - self._training_guide = TrainingGuide(completed_callback=completed_callback) - gui_app.set_modal_overlay(self._training_guide) + self._training_guide = TrainingGuide() + gui_app.push_widget(self._training_guide) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index e4c3098c2e..f7424b974d 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -168,12 +168,12 @@ class SoftwareLayout(Widget): os.system("pkill -SIGHUP -f system.updated.updated") def _on_uninstall(self): - def handle_uninstall_confirmation(result): + def handle_uninstall_confirmation(result: DialogResult): if result == DialogResult.CONFIRM: ui_state.params.put_bool("DoUninstall", True) - dialog = ConfirmDialog(tr("Are you sure you want to uninstall?"), tr("Uninstall")) - gui_app.set_modal_overlay(dialog, callback=handle_uninstall_confirmation) + dialog = ConfirmDialog(tr("Are you sure you want to uninstall?"), tr("Uninstall"), callback=handle_uninstall_confirmation) + gui_app.push_widget(dialog) def _on_install_update(self): # Trigger reboot to install update @@ -192,9 +192,8 @@ class SoftwareLayout(Widget): branches.insert(0, b) current_target = ui_state.params.get("UpdaterTargetBranch") or "" - self._branch_dialog = MultiOptionDialog(tr("Select a branch"), branches, current_target) - def handle_selection(result): + def handle_selection(result: DialogResult): # Confirmed selection if result == DialogResult.CONFIRM and self._branch_dialog is not None and self._branch_dialog.selection: selection = self._branch_dialog.selection @@ -203,4 +202,5 @@ class SoftwareLayout(Widget): os.system("pkill -SIGUSR1 -f system.updated.updated") self._branch_dialog = None - gui_app.set_modal_overlay(self._branch_dialog, callback=handle_selection) + self._branch_dialog = MultiOptionDialog(tr("Select a branch"), branches, current_target, callback=handle_selection) + gui_app.push_widget(self._branch_dialog) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index f5f3a4e9c5..9f704b1fb4 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -218,7 +218,7 @@ class TogglesLayout(Widget): def _handle_experimental_mode_toggle(self, state: bool): confirmed = self._params.get_bool("ExperimentalModeConfirmed") if state and not confirmed: - def confirm_callback(result: int): + def confirm_callback(result: DialogResult): if result == DialogResult.CONFIRM: self._params.put_bool("ExperimentalMode", True) self._params.put_bool("ExperimentalModeConfirmed", True) @@ -229,8 +229,8 @@ class TogglesLayout(Widget): # show confirmation dialog content = (f"

{self._toggles['ExperimentalMode'].title}


" + f"

{self._toggles['ExperimentalMode'].description}

") - dlg = ConfirmDialog(content, tr("Enable"), rich=True) - gui_app.set_modal_overlay(dlg, callback=confirm_callback) + dlg = ConfirmDialog(content, tr("Enable"), rich=True, callback=confirm_callback) + gui_app.push_widget(dlg) else: self._update_experimental_mode_icon() self._params.put_bool("ExperimentalMode", state) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 0da5445ed2..1d6d8dad2a 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -1,14 +1,16 @@ +import datetime import time from cereal import log import pyray as rl from collections.abc import Callable -from openpilot.system.ui.widgets.label import gui_label, MiciLabel, UnifiedLabel from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_COLOR, MousePos +from openpilot.system.ui.widgets.layouts import HBoxLayout +from openpilot.system.ui.widgets.icon_widget import IconWidget +from openpilot.system.ui.widgets.label import MiciLabel, UnifiedLabel +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.text import wrap_text -from openpilot.system.version import training_version, RELEASE_BRANCHES +from openpilot.system.version import RELEASE_BRANCHES HEAD_BUTTON_FONT_SIZE = 40 HOME_PADDING = 8 @@ -26,55 +28,56 @@ NETWORK_TYPES = { } -class DeviceStatus(Widget): +class NetworkIcon(Widget): def __init__(self): super().__init__() - self.set_rect(rl.Rectangle(0, 0, 300, 175)) - self._update_state() - self._version_text = self._get_version_text() + self.set_rect(rl.Rectangle(0, 0, 54, 44)) # max size of all icons + self._net_type = NetworkType.none + self._net_strength = 0 - self._do_welcome() + self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 50, 44) + self._wifi_none_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_none.png", 50, 37) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 50, 37) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 50, 37) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 50, 37) - def _do_welcome(self): - ui_state.params.put("CompletedTrainingVersion", training_version) - - def refresh(self): - self._update_state() - self._version_text = self._get_version_text() - - def _get_version_text(self) -> str: - brand = "sunnypilot" - description = ui_state.params.get("UpdaterCurrentDescription") - return f"{brand} {description}" if description else brand + self._cell_none_txt = gui_app.texture("icons_mici/settings/network/cell_strength_none.png", 54, 36) + self._cell_low_txt = gui_app.texture("icons_mici/settings/network/cell_strength_low.png", 54, 36) + self._cell_medium_txt = gui_app.texture("icons_mici/settings/network/cell_strength_medium.png", 54, 36) + self._cell_high_txt = gui_app.texture("icons_mici/settings/network/cell_strength_high.png", 54, 36) + self._cell_full_txt = gui_app.texture("icons_mici/settings/network/cell_strength_full.png", 54, 36) def _update_state(self): - # TODO: refresh function that can be called periodically, not at 60 fps, so we can update version - # update system status - self._system_status = "SYSTEM READY ✓" if ui_state.panda_type != log.PandaState.PandaType.unknown else "BOOTING UP..." - - # update network status - strength = ui_state.sm['deviceState'].networkStrength.raw - strength_text = "● " * strength + "○ " * (4 - strength) # ◌ also works - network_type = NETWORK_TYPES[ui_state.sm['deviceState'].networkType.raw] - self._network_status = f"{network_type} {strength_text}" + device_state = ui_state.sm['deviceState'] + self._net_type = device_state.networkType + strength = device_state.networkStrength + self._net_strength = max(0, min(5, strength.raw + 1)) if strength.raw > 0 else 0 def _render(self, _): - # draw status - status_rect = rl.Rectangle(self._rect.x, self._rect.y, self._rect.width, 40) - gui_label(status_rect, self._system_status, font_size=HEAD_BUTTON_FONT_SIZE, color=DEFAULT_TEXT_COLOR, - font_weight=FontWeight.BOLD, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + if self._net_type == NetworkType.wifi: + # There is no 1 + draw_net_txt = {0: self._wifi_none_txt, + 2: self._wifi_low_txt, + 3: self._wifi_medium_txt, + 4: self._wifi_full_txt, + 5: self._wifi_full_txt}.get(self._net_strength, self._wifi_low_txt) + elif self._net_type in (NetworkType.cell2G, NetworkType.cell3G, NetworkType.cell4G, NetworkType.cell5G): + draw_net_txt = {0: self._cell_none_txt, + 2: self._cell_low_txt, + 3: self._cell_medium_txt, + 4: self._cell_high_txt, + 5: self._cell_full_txt}.get(self._net_strength, self._cell_none_txt) + else: + draw_net_txt = self._wifi_slash_txt - # draw network status - network_rect = rl.Rectangle(self._rect.x, self._rect.y + 60, self._rect.width, 40) - gui_label(network_rect, self._network_status, font_size=40, color=DEFAULT_TEXT_COLOR, - font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + draw_x = self._rect.x + (self._rect.width - draw_net_txt.width) / 2 + draw_y = self._rect.y + (self._rect.height - draw_net_txt.height) / 2 - # draw version - version_font_size = 30 - version_rect = rl.Rectangle(self._rect.x, self._rect.y + 140, self._rect.width + 20, 40) - wrapped_text = '\n'.join(wrap_text(self._version_text, version_font_size, version_rect.width)) - gui_label(version_rect, wrapped_text, font_size=version_font_size, color=DEFAULT_TEXT_COLOR, - font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + if draw_net_txt == self._wifi_slash_txt: + # Offset by difference in height between slashless and slash icons to make center align match + draw_y -= (self._wifi_slash_txt.height - self._wifi_none_txt.height) / 2 + + rl.draw_texture(draw_net_txt, int(draw_x), int(draw_y), rl.Color(255, 255, 255, int(255 * 0.9))) class MiciHomeLayout(Widget): @@ -90,24 +93,15 @@ class MiciHomeLayout(Widget): self._version_text = None self._experimental_mode = False - self._settings_txt = gui_app.texture("icons_mici/settings.png", 48, 48) - self._experimental_txt = gui_app.texture("icons_mici/experimental_mode.png", 48, 48) - self._mic_txt = gui_app.texture("icons_mici/microphone.png", 32, 46) + self._experimental_icon = IconWidget("icons_mici/experimental_mode.png", (48, 48)) + self._mic_icon = IconWidget("icons_mici/microphone.png", (32, 46)) - self._net_type = NETWORK_TYPES.get(NetworkType.none) - self._net_strength = 0 - - self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 50, 44) - self._wifi_none_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_none.png", 50, 37) - self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 50, 37) - self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 50, 37) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 50, 37) - - self._cell_none_txt = gui_app.texture("icons_mici/settings/network/cell_strength_none.png", 54, 36) - self._cell_low_txt = gui_app.texture("icons_mici/settings/network/cell_strength_low.png", 54, 36) - self._cell_medium_txt = gui_app.texture("icons_mici/settings/network/cell_strength_medium.png", 54, 36) - self._cell_high_txt = gui_app.texture("icons_mici/settings/network/cell_strength_high.png", 54, 36) - self._cell_full_txt = gui_app.texture("icons_mici/settings/network/cell_strength_full.png", 54, 36) + self._status_bar_layout = HBoxLayout([ + IconWidget("icons_mici/settings.png", (48, 48), opacity=0.9), + NetworkIcon(), + self._experimental_icon, + self._mic_icon, + ], spacing=18) self._openpilot_label = MiciLabel("sunnypilot", font_size=90, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.AUDIOWIDE) self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) @@ -118,7 +112,6 @@ class MiciHomeLayout(Widget): def show_event(self): self._version_text = self._get_version_text() - self._update_network_status(ui_state.sm['deviceState']) self._update_params() def _update_params(self): @@ -142,19 +135,11 @@ class MiciHomeLayout(Widget): self._did_long_press = True if rl.get_time() - self._last_refresh > 5.0: - device_state = ui_state.sm['deviceState'] - self._update_network_status(device_state) - # Update version text self._version_text = self._get_version_text() self._last_refresh = rl.get_time() self._update_params() - def _update_network_status(self, device_state): - self._net_type = device_state.networkType - strength = device_state.networkStrength - self._net_strength = max(0, min(5, strength.raw + 1)) if strength.raw > 0 else 0 - def set_callbacks(self, on_settings: Callable | None = None): self._on_settings_click = on_settings @@ -165,17 +150,22 @@ class MiciHomeLayout(Widget): self._did_long_press = False def _get_version_text(self) -> tuple[str, str, str, str] | None: - description = ui_state.params.get("UpdaterCurrentDescription") + version = ui_state.params.get("Version") + branch = ui_state.params.get("GitBranch") + commit = ui_state.params.get("GitCommit") - if description is not None and len(description) > 0: - # Expect "version / branch / commit / date"; be tolerant of other formats - try: - version, branch, commit, date = description.split(" / ") - return version, branch, commit, date - except Exception: - return None + if not all((version, branch, commit)): + return None - return None + commit_date_raw = ui_state.params.get("GitCommitDate") + try: + # GitCommitDate format from get_commit_date(): '%ct %ci' e.g. "'1708012345 2024-02-15 ...'" + unix_ts = int(commit_date_raw.strip("'").split()[0]) + date_str = datetime.datetime.fromtimestamp(unix_ts).strftime("%b %d") + except (ValueError, IndexError, TypeError, AttributeError): + date_str = "" + + return version, branch, commit[:7], date_str def _render(self, _): # TODO: why is there extra space here to get it to be flush? @@ -206,60 +196,9 @@ class MiciHomeLayout(Widget): self._version_commit_label.set_position(version_pos.x, version_pos.y + self._date_label.font_size + 7) self._version_commit_label.render() - self._render_bottom_status_bar() - - def _render_bottom_status_bar(self): # ***** Center-aligned bottom section icons ***** + self._experimental_icon.set_visible(self._experimental_mode) + self._mic_icon.set_visible(ui_state.recording_audio) - # TODO: refactor repeated icon drawing into a small loop - ITEM_SPACING = 18 - Y_CENTER = 24 - - last_x = self.rect.x + HOME_PADDING - - # Draw settings icon in bottom left corner - rl.draw_texture(self._settings_txt, int(last_x), int(self._rect.y + self.rect.height - self._settings_txt.height / 2 - Y_CENTER), - rl.Color(255, 255, 255, int(255 * 0.9))) - last_x = last_x + self._settings_txt.width + ITEM_SPACING - - # draw network - if self._net_type == NetworkType.wifi: - # There is no 1 - draw_net_txt = {0: self._wifi_none_txt, - 2: self._wifi_low_txt, - 3: self._wifi_medium_txt, - 4: self._wifi_full_txt, - 5: self._wifi_full_txt}.get(self._net_strength, self._wifi_low_txt) - rl.draw_texture(draw_net_txt, int(last_x), - int(self._rect.y + self.rect.height - draw_net_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, int(255 * 0.9))) - last_x += draw_net_txt.width + ITEM_SPACING - - elif self._net_type in (NetworkType.cell2G, NetworkType.cell3G, NetworkType.cell4G, NetworkType.cell5G): - draw_net_txt = {0: self._cell_none_txt, - 2: self._cell_low_txt, - 3: self._cell_medium_txt, - 4: self._cell_high_txt, - 5: self._cell_full_txt}.get(self._net_strength, self._cell_none_txt) - rl.draw_texture(draw_net_txt, int(last_x), - int(self._rect.y + self.rect.height - draw_net_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, int(255 * 0.9))) - last_x += draw_net_txt.width + ITEM_SPACING - - else: - # No network - # Offset by difference in height between slashless and slash icons to make center align match - rl.draw_texture(self._wifi_slash_txt, int(last_x), int(self._rect.y + self.rect.height - self._wifi_slash_txt.height / 2 - - (self._wifi_slash_txt.height - self._wifi_none_txt.height) / 2 - Y_CENTER), - rl.Color(255, 255, 255, 255)) - last_x += self._wifi_slash_txt.width + ITEM_SPACING - - # draw experimental icon - if self._experimental_mode: - rl.draw_texture(self._experimental_txt, int(last_x), - int(self._rect.y + self.rect.height - self._experimental_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, 255)) - last_x += self._experimental_txt.width + ITEM_SPACING - - # draw microphone icon when recording audio is enabled - if ui_state.recording_audio: - rl.draw_texture(self._mic_txt, int(last_x), - int(self._rect.y + self.rect.height - self._mic_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, 255)) - last_x += self._mic_txt.width + ITEM_SPACING + footer_rect = rl.Rectangle(self.rect.x + HOME_PADDING, self.rect.y + self.rect.height - 48, self.rect.width - HOME_PADDING, 48) + self._status_bar_layout.render(footer_rect) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 8f24d0b9b9..860030a24e 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -1,5 +1,4 @@ import pyray as rl -from enum import IntEnum import cereal.messaging as messaging from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout @@ -18,18 +17,12 @@ if gui_app.sunnypilot_ui(): ONROAD_DELAY = 2.5 # seconds -class MainState(IntEnum): - MAIN = 0 - SETTINGS = 1 - - -class MiciMainLayout(Widget): +class MiciMainLayout(Scroller): def __init__(self): - super().__init__() + super().__init__(snap_items=True, spacing=0, pad=0, scroll_indicator=False, edge_shadows=False) self._pm = messaging.PubMaster(['bookmarkButton']) - self._current_mode: MainState | None = None self._prev_onroad = False self._prev_standstill = False self._onroad_time_delay: float | None = None @@ -46,44 +39,37 @@ class MiciMainLayout(Widget): # TODO: set parent rect and use it if never passed rect from render (like in Scroller) widget.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - self._scroller = Scroller([ + self._scroller.add_widgets([ self._alerts_layout, self._home_layout, self._onroad_layout, - ], spacing=0, pad_start=0, pad_end=0, scroll_indicator=False) + ]) self._scroller.set_reset_scroll_at_show(False) # Disable scrolling when onroad is interacting with bookmark self._scroller.set_scrolling_enabled(lambda: not self._onroad_layout.is_swiping_left()) - self._layouts = { - MainState.MAIN: self._scroller, - MainState.SETTINGS: self._settings_layout, - } - # Set callbacks self._setup_callbacks() - # Start onboarding if terms or training not completed + gui_app.add_nav_stack_tick(self._handle_transitions) + gui_app.push_widget(self) + + # Start onboarding if terms or training not completed, make sure to push after self self._onboarding_window = OnboardingWindow() if not self._onboarding_window.completed: - gui_app.set_modal_overlay(self._onboarding_window) + gui_app.push_widget(self._onboarding_window) def _setup_callbacks(self): - self._home_layout.set_callbacks(on_settings=self._on_settings_clicked) - self._settings_layout.set_callbacks(on_close=self._on_settings_closed) + self._home_layout.set_callbacks(on_settings=lambda: gui_app.push_widget(self._settings_layout)) self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) - device.add_interactive_timeout_callback(self._set_mode_for_started) + device.add_interactive_timeout_callback(self._on_interactive_timeout) def _scroll_to(self, layout: Widget): layout_x = int(layout.rect.x) self._scroller.scroll_to(layout_x, smooth=True) def _render(self, _): - # Initial show event - if self._current_mode is None: - self._set_mode(MainState.MAIN) - if not self._setup: if self._alerts_layout.active_alerts() > 0: self._scroller.scroll_to(self._alerts_layout.rect.x) @@ -92,59 +78,47 @@ class MiciMainLayout(Widget): self._setup = True # Render - if self._current_mode == MainState.MAIN: - self._scroller.render(self._rect) - - elif self._current_mode == MainState.SETTINGS: - self._settings_layout.render(self._rect) - - self._handle_transitions() - - def _set_mode(self, mode: MainState): - if mode != self._current_mode: - if self._current_mode is not None: - self._layouts[self._current_mode].hide_event() - self._layouts[mode].show_event() - self._current_mode = mode + super()._render(self._rect) def _handle_transitions(self): + # Don't pop if onboarding + if gui_app.get_active_widget() == self._onboarding_window: + return + if ui_state.started != self._prev_onroad: self._prev_onroad = ui_state.started + # onroad: after delay, pop nav stack and scroll to onroad + # offroad: immediately scroll to home, but don't pop nav stack (can stay in settings) if ui_state.started: self._onroad_time_delay = rl.get_time() else: - self._set_mode_for_started(True) - - # delay so we show home for a bit after starting - if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: - self._set_mode_for_started(True) - self._onroad_time_delay = None - - CS = ui_state.sm["carState"] - if not CS.standstill and self._prev_standstill: - self._set_mode(MainState.MAIN) - self._scroll_to(self._onroad_layout) - self._prev_standstill = CS.standstill - - def _set_mode_for_started(self, onroad_transition: bool = False): - if ui_state.started: - CS = ui_state.sm["carState"] - # Only go onroad if car starts or is not at a standstill - if not CS.standstill or onroad_transition: - self._set_mode(MainState.MAIN) - self._scroll_to(self._onroad_layout) - else: - # Stay in settings if car turns off while in settings - if not onroad_transition or self._current_mode != MainState.SETTINGS: - self._set_mode(MainState.MAIN) self._scroll_to(self._home_layout) - def _on_settings_clicked(self): - self._set_mode(MainState.SETTINGS) + # FIXME: these two pops can interrupt user interacting in the settings + if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: + gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) + self._onroad_time_delay = None - def _on_settings_closed(self): - self._set_mode(MainState.MAIN) + # When car leaves standstill, pop nav stack and scroll to onroad + CS = ui_state.sm["carState"] + if not CS.standstill and self._prev_standstill: + gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) + self._prev_standstill = CS.standstill + + def _on_interactive_timeout(self): + # Don't pop if onboarding + if gui_app.get_active_widget() == self._onboarding_window: + return + + if ui_state.started: + # Don't pop if at standstill + if not ui_state.sm["carState"].standstill: + gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) + else: + # Screen turns off on timeout offroad, so pop immediately without animation + gui_app.pop_widgets_to(self, instant=True) + self._scroll_to(self._home_layout) def _on_bookmark_clicked(self): user_bookmark = messaging.new_message('bookmarkButton') diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index b01dae6aeb..3aec5bbfed 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -186,19 +186,17 @@ class AlertItem(Widget): rl.draw_texture(icon_texture, int(icon_x), int(icon_y), rl.WHITE) -class MiciOffroadAlerts(Widget): +class MiciOffroadAlerts(Scroller): """Offroad alerts layout with vertical scrolling.""" def __init__(self): - super().__init__() + # Create vertical scroller + super().__init__(horizontal=False, spacing=12, pad=0) self.params = Params() self.sorted_alerts: list[AlertData] = [] self.alert_items: list[AlertItem] = [] self._last_refresh = 0.0 - # Create vertical scroller - self._scroller = Scroller([], horizontal=False, spacing=12, pad_start=0, pad_end=0, snap_items=False) - # Create empty state label self._empty_label = UnifiedLabel(tr("no alerts"), 65, FontWeight.DISPLAY, rl.WHITE, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, @@ -289,7 +287,7 @@ class MiciOffroadAlerts(Widget): def show_event(self): """Reset scroll position when shown and refresh alerts.""" - self._scroller.show_event() + super().show_event() self._last_refresh = time.monotonic() self.refresh() diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 9f9c7b83ab..7340360575 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -14,7 +14,7 @@ from openpilot.system.ui.widgets.slider import SmallSlider from openpilot.system.ui.mici_setup import TermsHeader, TermsPage as SetupTermsPage from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer -from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog +from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.lib.multilang import tr from openpilot.system.version import terms_version, training_version, terms_version_sp @@ -29,9 +29,9 @@ class OnboardingState(IntEnum): SUNNYLINK_CONSENT = 3 -class DriverCameraSetupDialog(DriverCameraDialog): +class DriverCameraSetupDialog(BaseDriverCameraDialog): def __init__(self): - super().__init__(no_escape=True) + super().__init__() self.driver_state_renderer = DriverStateRenderer(inset=True) self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 120, 120)) self.driver_state_renderer.load_icons() @@ -45,7 +45,7 @@ class DriverCameraSetupDialog(DriverCameraDialog): gui_label(rect, tr("camera starting"), font_size=64, font_weight=FontWeight.BOLD, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) rl.end_scissor_mode() - return -1 + return # Position dmoji on opposite side from driver is_rhd = self.driver_state_renderer.is_rhd @@ -58,7 +58,6 @@ class DriverCameraSetupDialog(DriverCameraDialog): self._draw_face_detection(rect) rl.end_scissor_mode() - return -1 class TrainingGuidePreDMTutorial(SetupTermsPage): @@ -127,8 +126,11 @@ class TrainingGuideDMTutorial(Widget): def __init__(self, continue_callback): super().__init__() + + self_ref = weakref.ref(self) + self._back_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_question.png", 28, 48)) - self._back_button.set_click_callback(self._show_bad_face_page) + self._back_button.set_click_callback(lambda: self_ref() and self_ref()._show_bad_face_page()) self._good_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 42, 42)) # Wrap the continue callback to restore settings @@ -141,7 +143,7 @@ class TrainingGuideDMTutorial(Widget): self._progress = FirstOrderFilter(0.0, 0.5, 1 / gui_app.target_fps) self._dialog = DriverCameraSetupDialog() - self._bad_face_page = DMBadFaceDetected(HARDWARE.shutdown, self._hide_bad_face_page) + self._bad_face_page = DMBadFaceDetected(HARDWARE.shutdown, lambda: self_ref() and self_ref()._hide_bad_face_page()) self._should_show_bad_face_page = False # Disable driver monitoring model when device times out for inactivity @@ -367,9 +369,9 @@ class TrainingGuide(Widget): self._completed_callback() def _render(self, _): + rl.draw_rectangle_rec(self._rect, rl.BLACK) if self._step < len(self._steps): self._steps[self._step].render(self._rect) - return -1 class DeclinePage(Widget): @@ -485,7 +487,7 @@ class OnboardingWindow(Widget): def close(self): ui_state.params.put_bool("IsDriverViewEnabled", False) - gui_app.set_modal_overlay(None) + gui_app.pop_widget() def _on_terms_accepted(self): ui_state.params.put("HasAcceptedTerms", terms_version) @@ -502,6 +504,7 @@ class OnboardingWindow(Widget): self.close() def _render(self, _): + rl.draw_rectangle_rec(self._rect, rl.BLACK) if self._state == OnboardingState.TERMS: self._terms.render(self._rect) elif self._state == OnboardingState.SUNNYLINK_CONSENT: @@ -518,4 +521,3 @@ class OnboardingWindow(Widget): self.close() elif self._state == OnboardingState.DECLINE: self._decline_page.render(self._rect) - return -1 diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index b6145e042e..4e7796814e 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -1,21 +1,16 @@ -import pyray as rl -from collections.abc import Callable - from openpilot.common.time_helpers import system_time_valid -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle, BigParamControl, BigCircleParamControl from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.widgets import NavWidget from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyAction -class DeveloperLayoutMici(NavWidget): - def __init__(self, back_callback: Callable): +class DeveloperLayoutMici(NavScroller): + def __init__(self): super().__init__() - self.set_back_callback(back_callback) def github_username_callback(username: str): if username: @@ -25,16 +20,20 @@ class DeveloperLayoutMici(NavWidget): self._ssh_keys_btn.set_value(username) else: dlg = BigDialog("", ssh_keys._error_message) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) + else: + ui_state.params.remove("GithubUsername") + ui_state.params.remove("GithubSshKeys") + self._ssh_keys_btn.set_value("Not set") def ssh_keys_callback(): github_username = ui_state.params.get("GithubUsername") or "" - dlg = BigInputDialog("enter GitHub username", github_username, confirm_callback=github_username_callback) + dlg = BigInputDialog("enter GitHub username...", github_username, minimum_length=0, confirm_callback=github_username_callback) if not system_time_valid(): dlg = BigDialog("Please connect to Wi-Fi to fetch your key", "") - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) return - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) txt_ssh = gui_app.texture("icons_mici/settings/developer/ssh.png", 56, 64) github_username = ui_state.params.get("GithubUsername") or "" @@ -58,7 +57,7 @@ class DeveloperLayoutMici(NavWidget): toggle_callback=lambda checked: (gui_app.set_show_touches(checked), gui_app.set_show_fps(checked))) - self._scroller = Scroller([ + self._scroller.add_widgets([ self._adb_toggle, self._ssh_toggle, self._ssh_keys_btn, @@ -66,7 +65,7 @@ class DeveloperLayoutMici(NavWidget): self._long_maneuver_toggle, self._alpha_long_toggle, self._debug_mode_toggle, - ], snap_items=False) + ]) # Toggle lists self._refresh_toggles = ( @@ -102,12 +101,8 @@ class DeveloperLayoutMici(NavWidget): def show_event(self): super().show_event() - self._scroller.show_event() self._update_toggles() - def _render(self, rect: rl.Rectangle): - self._scroller.render(rect) - def _update_toggles(self): ui_state.update_params() diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 64ce80e231..0d253cb26f 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -7,30 +7,25 @@ from collections.abc import Callable from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.time_helpers import system_time_valid -from openpilot.system.ui.widgets.scroller import Scroller -from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 +from openpilot.system.ui.widgets.scroller import NavRawScrollPanel, NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog -from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide +from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.widgets import Widget, NavWidget +from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.widgets.label import MiciLabel from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID -class MiciFccModal(NavWidget): - BACK_TOUCH_AREA_PERCENTAGE = 0.1 - +class MiciFccModal(NavRawScrollPanel): def __init__(self, file_path: str | None = None, text: str | None = None): super().__init__() - self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) self._content = HtmlRenderer(file_path=file_path, text=text) - self._scroll_panel = GuiScrollPanel2(horizontal=False) self._fcc_logo = gui_app.texture("icons_mici/settings/device/fcc_logo.png", 76, 64) def _render(self, rect: rl.Rectangle): @@ -47,8 +42,6 @@ class MiciFccModal(NavWidget): rl.draw_texture_ex(self._fcc_logo, fcc_pos, 0.0, 1.0, rl.WHITE) - return -1 - def _engaged_confirmation_callback(callback: Callable, action_text: str): if not ui_state.engaged: @@ -74,10 +67,10 @@ def _engaged_confirmation_callback(callback: Callable, action_text: str): dlg: BigConfirmationDialogV2 | BigDialog = BigConfirmationDialogV2(f"slide to\n{action_text.lower()}", icon, red=red, exit_on_confirm=action_text == "reset", confirm_callback=confirm_callback) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) else: dlg = BigDialog(f"Disengage to {action_text}", "") - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) class DeviceInfoLayoutMici(Widget): @@ -124,6 +117,8 @@ class PairBigButton(BigButton): return 64 def _update_state(self): + super()._update_state() + if ui_state.prime_state.is_paired(): self.set_text("paired") if ui_state.prime_state.is_prime(): @@ -147,7 +142,7 @@ class PairBigButton(BigButton): dlg = BigDialog(tr("Device must be registered with the comma.ai backend to pair"), "") else: dlg = PairingDialog() - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) UPDATER_TIMEOUT = 10.0 # seconds to wait for updater to respond @@ -171,9 +166,11 @@ class UpdateOpenpilotBigButton(BigButton): self.set_enabled(True) def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + if not system_time_valid(): dlg = BigDialog(tr("Please connect to Wi-Fi to update"), "") - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) return self.set_enabled(False) @@ -198,6 +195,8 @@ class UpdateOpenpilotBigButton(BigButton): self.set_text("update sunnypilot") def _update_state(self): + super()._update_state() + if ui_state.started: self.set_enabled(False) return @@ -267,13 +266,11 @@ class UpdateOpenpilotBigButton(BigButton): self._waiting_for_updater_t = None -class DeviceLayoutMici(NavWidget): - def __init__(self, back_callback: Callable): +class DeviceLayoutMici(NavScroller): + def __init__(self): super().__init__() self._fcc_dialog: HtmlModal | None = None - self._driver_camera: DriverCameraDialog | None = None - self._training_guide: TrainingGuide | None = None def power_off_callback(): ui_state.params.put_bool("DoShutdown", True) @@ -304,62 +301,38 @@ class DeviceLayoutMici(NavWidget): self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True, icon_size=(64, 66)) self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) + self._power_off_btn.set_visible(lambda: not ui_state.ignition) regulatory_btn = BigButton("regulatory info", "", "icons_mici/settings/device/info.png") regulatory_btn.set_click_callback(self._on_regulatory) driver_cam_btn = BigButton("driver\ncamera preview", "", "icons_mici/settings/device/cameras.png") - driver_cam_btn.set_click_callback(self._show_driver_camera) + driver_cam_btn.set_click_callback(lambda: gui_app.push_widget(DriverCameraDialog())) driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) review_training_guide_btn = BigButton("review\ntraining guide", "", "icons_mici/settings/device/info.png") - review_training_guide_btn.set_click_callback(self._on_review_training_guide) + review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(TrainingGuide(completed_callback=gui_app.pop_widget))) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) - self._scroller = Scroller([ + terms_btn = BigButton("terms &\nconditions", "", "icons_mici/settings/device/info.png") + terms_btn.set_click_callback(lambda: gui_app.push_widget(TermsPage(on_accept=gui_app.pop_widget))) + terms_btn.set_enabled(lambda: ui_state.is_offroad()) + + self._scroller.add_widgets([ DeviceInfoLayoutMici(), UpdateOpenpilotBigButton(), PairBigButton(), review_training_guide_btn, driver_cam_btn, - # lang_button, + terms_btn, + regulatory_btn, reset_calibration_btn, uninstall_openpilot_btn, - regulatory_btn, reboot_btn, self._power_off_btn, - ], snap_items=False) - - # Set up back navigation - self.set_back_callback(back_callback) - - # Hide power off button when onroad - ui_state.add_offroad_transition_callback(self._offroad_transition) + ]) def _on_regulatory(self): if not self._fcc_dialog: self._fcc_dialog = MiciFccModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/mici_fcc.html")) - gui_app.set_modal_overlay(self._fcc_dialog) - - def _offroad_transition(self): - self._power_off_btn.set_visible(ui_state.is_offroad()) - - def _show_driver_camera(self): - if not self._driver_camera: - self._driver_camera = DriverCameraDialog() - gui_app.set_modal_overlay(self._driver_camera, callback=lambda result: setattr(self, '_driver_camera', None)) - - def _on_review_training_guide(self): - if not self._training_guide: - def completed_callback(): - gui_app.set_modal_overlay(None) - - self._training_guide = TrainingGuide(completed_callback=completed_callback) - gui_app.set_modal_overlay(self._training_guide, callback=lambda result: setattr(self, '_training_guide', None)) - - def show_event(self): - super().show_event() - self._scroller.show_event() - - def _render(self, rect: rl.Rectangle): - self._scroller.render(rect) + gui_app.push_widget(self._fcc_dialog) diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index 78e5169856..741ea9655a 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -13,7 +13,8 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.lib.multilang import tr, trn, tr_noop -from openpilot.system.ui.widgets import Widget, NavWidget +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.scroller import NavRawScrollPanel TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( @@ -132,9 +133,6 @@ class FirehoseLayoutBase(Widget): y = self._draw_wrapped_text(x, y, w, tr(answer), gui_app.font(FontWeight.ROMAN), 32, self.LIGHT_GRAY) y += 20 - # return value not used by NavWidget - return -1 - def _draw_wrapped_text(self, x, y, width, text, font, font_size, color): wrapped = wrap_text(font, text, font_size, width) for line in wrapped: @@ -220,9 +218,5 @@ class FirehoseLayoutBase(Widget): time.sleep(self.UPDATE_INTERVAL) -class FirehoseLayout(FirehoseLayoutBase, NavWidget): - BACK_TOUCH_AREA_PERCENTAGE = 0.1 - - def __init__(self, back_callback): - super().__init__() - self.set_back_callback(back_callback) +class FirehoseLayout(NavRawScrollPanel, FirehoseLayoutBase): + pass diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index fb1d56a1f6..553a74fc60 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -1,33 +1,76 @@ import pyray as rl -from enum import IntEnum -from collections.abc import Callable -from openpilot.system.ui.widgets.scroller import Scroller -from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon, normalize_ssid +from openpilot.system.ui.widgets.scroller import NavScroller +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.widgets import NavWidget -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus, SecurityType, normalize_ssid -class NetworkPanelType(IntEnum): - NONE = 0 - WIFI = 1 +class WifiNetworkButton(BigButton): + def __init__(self, wifi_manager: WifiManager): + self._wifi_manager = wifi_manager + self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 28, 36) + self._draw_lock = False + + self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 64, 56) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 64, 47) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 64, 47) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 64, 47) + + super().__init__("wi-fi", "not connected", self._wifi_slash_txt, scroll=True) + + def _update_state(self): + super()._update_state() + + # Update wi-fi button with ssid and ip address + # TODO: make sure we handle hidden ssids + wifi_state = self._wifi_manager.wifi_state + display_network = next((n for n in self._wifi_manager.networks if n.ssid == wifi_state.ssid), None) + if wifi_state.status == ConnectStatus.CONNECTING: + self.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) + self.set_value("starting" if self._wifi_manager.is_tethering_active() else "connecting...") + elif wifi_state.status == ConnectStatus.CONNECTED: + self.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) + self.set_value(self._wifi_manager.ipv4_address or "obtaining IP...") + else: + display_network = None + self.set_text("wi-fi") + self.set_value("not connected") + + if display_network is not None: + strength = WifiIcon.get_strength_icon_idx(display_network.strength) + self.set_icon(self._wifi_full_txt if strength == 2 else self._wifi_medium_txt if strength == 1 else self._wifi_low_txt) + self._draw_lock = display_network.security_type not in (SecurityType.OPEN, SecurityType.UNSUPPORTED) + elif self._wifi_manager.is_tethering_active(): + # takes a while to get Network + self.set_icon(self._wifi_full_txt) + self._draw_lock = True + else: + self.set_icon(self._wifi_slash_txt) + self._draw_lock = False + + def _draw_content(self, btn_y: float): + super()._draw_content(btn_y) + # Render lock icon at lower right of wifi icon if secured + if self._draw_lock: + icon_x = self._rect.x + self._rect.width - 30 - self._txt_icon.width + icon_y = btn_y + 30 + lock_x = icon_x + self._txt_icon.width - self._lock_txt.width + 7 + lock_y = icon_y + self._txt_icon.height - self._lock_txt.height + 8 + rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, 1.0, rl.WHITE) -class NetworkLayoutMici(NavWidget): - def __init__(self, back_callback: Callable): +class NetworkLayoutMici(NavScroller): + def __init__(self): super().__init__() - self._current_panel = NetworkPanelType.WIFI - self.set_back_enabled(lambda: self._current_panel == NetworkPanelType.NONE) - self._wifi_manager = WifiManager() self._wifi_manager.set_active(False) - self._wifi_ui = WifiUIMici(self._wifi_manager, back_callback=lambda: self._switch_to_panel(NetworkPanelType.NONE)) + self._wifi_ui = WifiUIMici(self._wifi_manager) self._wifi_manager.add_callbacks( networks_updated=self._on_network_updated, @@ -36,6 +79,7 @@ class NetworkLayoutMici(NavWidget): # ******** Tethering ******** def tethering_toggle_callback(checked: bool): self._tethering_toggle_btn.set_enabled(False) + self._tethering_password_btn.set_enabled(False) self._network_metered_btn.set_enabled(False) self._wifi_manager.set_tethering_active(checked) @@ -43,13 +87,15 @@ class NetworkLayoutMici(NavWidget): def tethering_password_callback(password: str): if password: + self._tethering_toggle_btn.set_enabled(False) + self._tethering_password_btn.set_enabled(False) self._wifi_manager.set_tethering_password(password) def tethering_password_clicked(): tethering_password = self._wifi_manager.tethering_password dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8, confirm_callback=tethering_password_callback) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) txt_tethering = gui_app.texture("icons_mici/settings/network/tethering.png", 64, 54) self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) @@ -70,13 +116,8 @@ class NetworkLayoutMici(NavWidget): self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) self._network_metered_btn.set_enabled(False) - self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 64, 56) - self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 64, 47) - self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 64, 47) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 64, 47) - - self._wifi_button = BigButton("wi-fi", "not connected", self._wifi_slash_txt, scroll=True) - self._wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI)) + self._wifi_button = WifiNetworkButton(self._wifi_manager) + self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) # ******** Advanced settings ******** # ******** Roaming toggle ******** @@ -90,7 +131,7 @@ class NetworkLayoutMici(NavWidget): self._cellular_metered_btn = BigParamControl("cellular metered", "GsmMetered", toggle_callback=self._toggle_cellular_metered) # Main scroller ---------------------------------- - self._scroller = Scroller([ + self._scroller.add_widgets([ self._wifi_button, self._network_metered_btn, self._tethering_toggle_btn, @@ -100,16 +141,13 @@ class NetworkLayoutMici(NavWidget): self._apn_btn, self._cellular_metered_btn, # */ - ], snap_items=False) + ]) # Set initial config roaming_enabled = ui_state.params.get_bool("GsmRoaming") metered = ui_state.params.get_bool("GsmMetered") self._wifi_manager.update_gsm_settings(roaming_enabled, ui_state.params.get("GsmApn") or "", metered) - # Set up back navigation - self.set_back_callback(back_callback) - def _update_state(self): super()._update_state() @@ -122,13 +160,16 @@ class NetworkLayoutMici(NavWidget): def show_event(self): super().show_event() - self._current_panel = NetworkPanelType.NONE - self._wifi_ui.show_event() - self._scroller.show_event() + self._wifi_manager.set_active(True) + + # Process wifi callbacks while at any point in the nav stack + gui_app.add_nav_stack_tick(self._wifi_manager.process_callbacks) def hide_event(self): super().hide_event() - self._wifi_ui.hide_event() + self._wifi_manager.set_active(False) + + gui_app.remove_nav_stack_tick(self._wifi_manager.process_callbacks) def _toggle_roaming(self, checked: bool): self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered")) @@ -144,8 +185,8 @@ class NetworkLayoutMici(NavWidget): self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), apn, ui_state.params.get_bool("GsmMetered")) current_apn = ui_state.params.get("GsmApn") or "" - dlg = BigInputDialog("enter APN", current_apn, minimum_length=0, confirm_callback=update_apn) - gui_app.set_modal_overlay(dlg) + dlg = BigInputDialog("enter APN...", current_apn, minimum_length=0, confirm_callback=update_apn) + gui_app.push_widget(dlg) def _toggle_cellular_metered(self, checked: bool): self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), ui_state.params.get("GsmApn") or "", checked) @@ -155,26 +196,10 @@ class NetworkLayoutMici(NavWidget): tethering_active = self._wifi_manager.is_tethering_active() # TODO: use real signals (like activated/settings changed, etc.) to speed up re-enabling buttons self._tethering_toggle_btn.set_enabled(True) + self._tethering_password_btn.set_enabled(True) self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) self._tethering_toggle_btn.set_checked(tethering_active) - # Update wi-fi button with ssid and ip address - # TODO: make sure we handle hidden ssids - connected_network = next((network for network in networks if network.is_connected), None) - self._wifi_button.set_text(normalize_ssid(connected_network.ssid) if connected_network is not None else "wi-fi") - self._wifi_button.set_value(self._wifi_manager.ipv4_address or "not connected") - if connected_network is not None: - strength = WifiIcon.get_strength_icon_idx(connected_network.strength) - if strength == 2: - strength_icon = self._wifi_full_txt - elif strength == 1: - strength_icon = self._wifi_medium_txt - else: - strength_icon = self._wifi_low_txt - self._wifi_button.set_icon(strength_icon) - else: - self._wifi_button.set_icon(self._wifi_slash_txt) - # Update network metered self._network_metered_btn.set_value( { @@ -182,16 +207,3 @@ class NetworkLayoutMici(NavWidget): MeteredType.YES: 'metered', MeteredType.NO: 'unmetered' }.get(self._wifi_manager.current_network_metered, 'default')) - - def _switch_to_panel(self, panel_type: NetworkPanelType): - if panel_type == NetworkPanelType.WIFI: - self._wifi_ui.show_event() - self._current_panel = panel_type - - def _render(self, rect: rl.Rectangle): - self._wifi_manager.process_callbacks() - - if self._current_panel == NetworkPanelType.WIFI: - self._wifi_ui.render(rect) - else: - self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index db68632c0e..22d3d1d0da 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -3,409 +3,321 @@ import numpy as np import pyray as rl from collections.abc import Callable +from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.swaglog import cloudlog -from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, BigInputDialog, BigDialogOptionButton, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, LABEL_COLOR from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight -from openpilot.system.ui.widgets import Widget, NavWidget -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType - - -def normalize_ssid(ssid: str) -> str: - return ssid.replace("’", "'") # for iPhone hotspots +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.scroller import NavScroller +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType, normalize_ssid class LoadingAnimation(Widget): - def _render(self, _): - cx = int(self._rect.x + 70) - cy = int(self._rect.y + self._rect.height / 2 - 50) + HIDE_TIME = 4 - y_mag = 20 - anim_scale = 5 - spacing = 28 + def __init__(self): + super().__init__() + self._opacity_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + self._opacity_target = 1.0 + self._hide_time = 0.0 + + def show_event(self): + self._opacity_target = 1.0 + self._hide_time = rl.get_time() + + def _render(self, _): + if rl.get_time() - self._hide_time > self.HIDE_TIME: + self._opacity_target = 0.0 + + self._opacity_filter.update(self._opacity_target) + + if self._opacity_filter.x < 0.01: + return + + cx = int(self._rect.x + self._rect.width / 2) + cy = int(self._rect.y + self._rect.height / 2) + + y_mag = 7 + anim_scale = 4 + spacing = 14 for i in range(3): x = cx - spacing + i * spacing y = int(cy + min(math.sin((rl.get_time() - i * 0.2) * anim_scale) * y_mag, 0)) - alpha = int(np.interp(cy - y, [0, y_mag], [255 * 0.45, 255 * 0.9])) - rl.draw_circle(x, y, 10, rl.Color(255, 255, 255, alpha)) + alpha = int(np.interp(cy - y, [0, y_mag], [255 * 0.45, 255 * 0.9]) * self._opacity_filter.x) + rl.draw_circle(x, y, 5, rl.Color(255, 255, 255, alpha)) class WifiIcon(Widget): - def __init__(self): + def __init__(self, network: Network): super().__init__() - self.set_rect(rl.Rectangle(0, 0, 86, 64)) + self.set_rect(rl.Rectangle(0, 0, 48 + 5, 36 + 5)) - self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 86, 64) - self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 86, 64) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 86, 64) - self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 22, 32) + self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 48, 42) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 48, 36) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 48, 36) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 48, 36) + self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 21, 27) - self._network: Network | None = None - self._scale = 1.0 + self._network: Network = network + self._network_missing = False # if network disappeared from scan results - def set_current_network(self, network: Network): + def update_network(self, network: Network): self._network = network - def set_scale(self, scale: float): - self._scale = scale + def set_network_missing(self, missing: bool): + self._network_missing = missing @staticmethod def get_strength_icon_idx(strength: int) -> int: return round(strength / 100 * 2) def _render(self, _): - if self._network is None: - return - # Determine which wifi strength icon to use strength = self.get_strength_icon_idx(self._network.strength) - if strength == 2: + if self._network_missing: + strength_icon = self._wifi_slash_txt + elif strength == 2: strength_icon = self._wifi_full_txt elif strength == 1: strength_icon = self._wifi_medium_txt else: strength_icon = self._wifi_low_txt - icon_x = int(self._rect.x + (self._rect.width - strength_icon.width * self._scale) // 2) - icon_y = int(self._rect.y + (self._rect.height - strength_icon.height * self._scale) // 2) - rl.draw_texture_ex(strength_icon, (icon_x, icon_y), 0.0, self._scale, rl.WHITE) + rl.draw_texture_ex(strength_icon, (self._rect.x, self._rect.y + self._rect.height - strength_icon.height), 0.0, 1.0, rl.WHITE) # Render lock icon at lower right of wifi icon if secured if self._network.security_type not in (SecurityType.OPEN, SecurityType.UNSUPPORTED): - lock_scale = self._scale * 1.1 - lock_x = int(icon_x + 1 + strength_icon.width * self._scale - self._lock_txt.width * lock_scale / 2) - lock_y = int(icon_y + 1 + strength_icon.height * self._scale - self._lock_txt.height * lock_scale / 2) - rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, lock_scale, rl.WHITE) + lock_x = self._rect.x + self._rect.width - self._lock_txt.width + lock_y = self._rect.y + self._rect.height - self._lock_txt.height + 6 + rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, 1.0, rl.WHITE) -class WifiItem(BigDialogOptionButton): - LEFT_MARGIN = 20 +class WifiButton(BigButton): + LABEL_PADDING = 98 + LABEL_WIDTH = 402 - 98 - 28 # button width - left padding - right padding + SUB_LABEL_WIDTH = 402 - BigButton.LABEL_HORIZONTAL_PADDING * 2 - def __init__(self, network: Network): - super().__init__(network.ssid) - - self.set_rect(rl.Rectangle(0, 0, gui_app.width, self.HEIGHT)) - - self._selected_txt = gui_app.texture("icons_mici/settings/network/new/wifi_selected.png", 48, 96) + def __init__(self, network: Network, wifi_manager: WifiManager): + super().__init__(normalize_ssid(network.ssid), scroll=True) self._network = network - self._wifi_icon = WifiIcon() - self._wifi_icon.set_current_network(network) + self._wifi_manager = wifi_manager - def set_current_network(self, network: Network): + self._wifi_icon = WifiIcon(network) + self._forget_btn = ForgetButton(self._forget_network) + self._check_txt = gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 32, 32) + + # Eager state (not sourced from Network) + self._network_missing = False + self._network_forgetting = False + self._wrong_password = False + + def update_network(self, network: Network): self._network = network - self._wifi_icon.set_current_network(network) + self._wifi_icon.update_network(network) - def _render(self, _): - if self._network.is_connected: - selected_x = int(self._rect.x - self._selected_txt.width / 2) - selected_y = int(self._rect.y + (self._rect.height - self._selected_txt.height) / 2) - rl.draw_texture(self._selected_txt, selected_x, selected_y, rl.WHITE) + # We can assume network is not missing if got new Network + self._network_missing = False + self._wifi_icon.set_network_missing(False) + if self._is_connected or self._is_connecting: + self._wrong_password = False - self._wifi_icon.set_scale((1.0 if self._selected else 0.65) * 0.7) - self._wifi_icon.render(rl.Rectangle( - self._rect.x + self.LEFT_MARGIN, - self._rect.y, - self.SELECTED_HEIGHT, - self._rect.height - )) + def _forget_network(self): + if self._network_forgetting: + return - if self._selected: - self._label.set_font_size(self.SELECTED_HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) - self._label.set_font_weight(FontWeight.DISPLAY) - else: - self._label.set_font_size(self.HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58))) - self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) + self._network_forgetting = True + self._wifi_manager.forget_connection(self._network.ssid) - label_offset = self.LEFT_MARGIN + self._wifi_icon.rect.width + 20 - label_rect = rl.Rectangle(self._rect.x + label_offset, self._rect.y, self._rect.width - label_offset, self._rect.height) - self._label.set_text(normalize_ssid(self._network.ssid)) - self._label.render(label_rect) + def on_forgotten(self): + self._network_forgetting = False + def set_network_missing(self, missing: bool): + self._network_missing = missing + self._wifi_icon.set_network_missing(missing) -class ConnectButton(Widget): - def __init__(self): - super().__init__() - self._bg_txt = gui_app.texture("icons_mici/settings/network/new/connect_button.png", 410, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/connect_button_pressed.png", 410, 100) - self._bg_full_txt = gui_app.texture("icons_mici/settings/network/new/full_connect_button.png", 520, 100) - self._bg_full_pressed_txt = gui_app.texture("icons_mici/settings/network/new/full_connect_button_pressed.png", 520, 100) - - self._full: bool = False - - self._label = UnifiedLabel("", 36, FontWeight.MEDIUM, rl.Color(255, 255, 255, int(255 * 0.9)), - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + def set_wrong_password(self): + self._wrong_password = True + self.trigger_shake() @property - def full(self) -> bool: - return self._full + def network(self) -> Network: + return self._network - def set_full(self, full: bool): - self._full = full - self.set_rect(rl.Rectangle(0, 0, 520 if self._full else 410, 100)) + @property + def _show_forget_btn(self): + if self._network.is_tethering or self._network_forgetting: + return False - def set_label(self, text: str): - self._label.set_text(text) + return (self._is_saved and not self._wrong_password) or self._is_connecting - def _render(self, _): - if self._full: - bg_txt = self._bg_full_pressed_txt if self.is_pressed and self.enabled else self._bg_full_txt - else: - bg_txt = self._bg_pressed_txt if self.is_pressed and self.enabled else self._bg_txt + def _handle_mouse_release(self, mouse_pos: MousePos): + if self._show_forget_btn and rl.check_collision_point_rec(mouse_pos, self._forget_btn.rect): + return + super()._handle_mouse_release(mouse_pos) - rl.draw_texture(bg_txt, int(self._rect.x), int(self._rect.y), rl.WHITE) + def _get_label_font_size(self): + return 48 - self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9) if self.enabled else int(255 * 0.9 * 0.65))) - self._label.render(self._rect) + def _draw_content(self, btn_y: float): + self._label.set_color(LABEL_COLOR) + label_rect = rl.Rectangle(self._rect.x + self.LABEL_PADDING, btn_y + self.LABEL_VERTICAL_PADDING, + self.LABEL_WIDTH, self._rect.height - self.LABEL_VERTICAL_PADDING * 2) + self._label.render(label_rect) + + if self.value: + sub_label_x = self._rect.x + self.LABEL_HORIZONTAL_PADDING + label_y = btn_y + self._rect.height - self.LABEL_VERTICAL_PADDING + sub_label_w = self.SUB_LABEL_WIDTH - (self._forget_btn.rect.width if self._show_forget_btn else 0) + sub_label_height = self._sub_label.get_content_height(sub_label_w) + + if self._is_connected and not self._network_forgetting: + check_y = int(label_y - sub_label_height + (sub_label_height - self._check_txt.height) / 2) + rl.draw_texture(self._check_txt, int(sub_label_x), check_y, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65))) + sub_label_x += self._check_txt.width + 14 + + sub_label_rect = rl.Rectangle(sub_label_x, label_y - sub_label_height, sub_label_w, sub_label_height) + self._sub_label.render(sub_label_rect) + + # Wifi icon + self._wifi_icon.render(rl.Rectangle( + self._rect.x + 30, + btn_y + 30, + self._wifi_icon.rect.width, + self._wifi_icon.rect.height, + )) + + # Forget button + if self._show_forget_btn: + self._forget_btn.render(rl.Rectangle( + self._rect.x + self._rect.width - self._forget_btn.rect.width, + btn_y + self._rect.height - self._forget_btn.rect.height, + self._forget_btn.rect.width, + self._forget_btn.rect.height, + )) + + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: + super().set_touch_valid_callback(lambda: touch_callback() and not self._forget_btn.is_pressed) + self._forget_btn.set_touch_valid_callback(touch_callback) + + @property + def _is_saved(self): + return self._wifi_manager.is_connection_saved(self._network.ssid) + + @property + def _is_connecting(self): + return self._wifi_manager.connecting_to_ssid == self._network.ssid + + @property + def _is_connected(self): + return self._wifi_manager.connected_ssid == self._network.ssid + + def _update_state(self): + super()._update_state() + + if any((self._network_missing, self._is_connecting, self._is_connected, self._network_forgetting, + self._network.security_type == SecurityType.UNSUPPORTED)): + self.set_enabled(False) + self._sub_label.set_color(rl.Color(255, 255, 255, int(255 * 0.585))) + self._sub_label.set_font_weight(FontWeight.ROMAN) + + if self._network_forgetting: + self.set_value("forgetting...") + elif self._is_connecting: + self.set_value("starting..." if self._network.is_tethering else "connecting...") + elif self._is_connected: + self.set_value("tethering" if self._network.is_tethering else "connected") + elif self._network_missing: + # after connecting/connected since NM will still attempt to connect/stay connected for a while + self.set_value("not in range") + else: + self.set_value("unsupported") + + else: # saved, wrong password, or unknown + self.set_value("wrong password" if self._wrong_password else "connect") + self.set_enabled(True) + self._sub_label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) + self._sub_label.set_font_weight(FontWeight.SEMI_BOLD) class ForgetButton(Widget): - HORIZONTAL_MARGIN = 8 + MARGIN = 12 # bottom and right - def __init__(self, forget_network: Callable, open_network_manage_page): + def __init__(self, forget_network: Callable): super().__init__() self._forget_network = forget_network - self._open_network_manage_page = open_network_manage_page - self._bg_txt = gui_app.texture("icons_mici/settings/network/new/forget_button.png", 100, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/forget_button_pressed.png", 100, 100) - self._trash_txt = gui_app.texture("icons_mici/settings/network/new/trash.png", 35, 42) - self.set_rect(rl.Rectangle(0, 0, 100 + self.HORIZONTAL_MARGIN * 2, 100)) + self._bg_txt = gui_app.texture("icons_mici/settings/network/new/forget_button.png", 84, 84) + self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/forget_button_pressed.png", 84, 84) + self._trash_txt = gui_app.texture("icons_mici/settings/network/new/trash.png", 29, 35) + self.set_rect(rl.Rectangle(0, 0, 84 + self.MARGIN * 2, 84 + self.MARGIN * 2)) def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) dlg = BigConfirmationDialogV2("slide to forget", "icons_mici/settings/network/new/trash.png", red=True, confirm_callback=self._forget_network) - gui_app.set_modal_overlay(dlg, callback=self._open_network_manage_page) + gui_app.push_widget(dlg) def _render(self, _): bg_txt = self._bg_pressed_txt if self.is_pressed else self._bg_txt - rl.draw_texture(bg_txt, int(self._rect.x + self.HORIZONTAL_MARGIN), int(self._rect.y), rl.WHITE) + rl.draw_texture_ex(bg_txt, (self._rect.x + (self._rect.width - self._bg_txt.width) / 2, + self._rect.y + (self._rect.height - self._bg_txt.height) / 2), 0, 1.0, rl.WHITE) - trash_x = int(self._rect.x + (self._rect.width - self._trash_txt.width) // 2) - trash_y = int(self._rect.y + (self._rect.height - self._trash_txt.height) // 2) - rl.draw_texture(self._trash_txt, trash_x, trash_y, rl.WHITE) + trash_x = self._rect.x + (self._rect.width - self._trash_txt.width) / 2 + trash_y = self._rect.y + (self._rect.height - self._trash_txt.height) / 2 + rl.draw_texture_ex(self._trash_txt, (trash_x, trash_y), 0, 1.0, rl.WHITE) -class NetworkInfoPage(NavWidget): - def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Callable, open_network_manage_page: Callable): +class WifiUIMici(NavScroller): + def __init__(self, wifi_manager: WifiManager): super().__init__() - self._wifi_manager = wifi_manager - - self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - - self._wifi_icon = WifiIcon() - self._forget_btn = ForgetButton(lambda: forget_callback(self._network.ssid) if self._network is not None else None, - open_network_manage_page) - self._connect_btn = ConnectButton() - self._connect_btn.set_click_callback(lambda: connect_callback(self._network.ssid) if self._network is not None else None) - - self._title = UnifiedLabel("", 64, FontWeight.DISPLAY, rl.Color(255, 255, 255, int(255 * 0.9)), - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, scroll=True) - self._subtitle = UnifiedLabel("", 36, FontWeight.ROMAN, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) - - self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) - - # State - self._network: Network | None = None - self._connecting: Callable[[], str | None] | None = None - - def show_event(self): - super().show_event() - self._title.reset_scroll() - - def update_networks(self, networks: dict[str, Network]): - # update current network from latest scan results - for ssid, network in networks.items(): - if self._network is not None and ssid == self._network.ssid: - self.set_current_network(network) - break - else: - # network disappeared, close page - gui_app.set_modal_overlay(None) - - def _update_state(self): - super()._update_state() - # Modal overlays stop main UI rendering, so we need to call here - self._wifi_manager.process_callbacks() - - if self._network is None: - return - - self._connect_btn.set_full(not self._network.is_saved and not self._is_connecting) - if self._is_connecting: - self._connect_btn.set_label("connecting...") - self._connect_btn.set_enabled(False) - elif self._network.is_connected: - self._connect_btn.set_label("connected") - self._connect_btn.set_enabled(False) - elif self._network.security_type == SecurityType.UNSUPPORTED: - self._connect_btn.set_label("connect") - self._connect_btn.set_enabled(False) - else: # saved or unknown - self._connect_btn.set_label("connect") - self._connect_btn.set_enabled(True) - - self._title.set_text(normalize_ssid(self._network.ssid)) - if self._network.security_type == SecurityType.OPEN: - self._subtitle.set_text("open") - elif self._network.security_type == SecurityType.UNSUPPORTED: - self._subtitle.set_text("unsupported") - else: - self._subtitle.set_text("secured") - - def set_current_network(self, network: Network): - self._network = network - self._wifi_icon.set_current_network(network) - - def set_connecting(self, is_connecting: Callable[[], str | None]): - self._connecting = is_connecting - - @property - def _is_connecting(self): - if self._connecting is None or self._network is None: - return False - is_connecting = self._connecting() == self._network.ssid - return is_connecting - - def _render(self, _): - self._wifi_icon.render(rl.Rectangle( - self._rect.x + 32, - self._rect.y + (self._rect.height - self._connect_btn.rect.height - self._wifi_icon.rect.height) / 2, - self._wifi_icon.rect.width, - self._wifi_icon.rect.height, - )) - - self._title.render(rl.Rectangle( - self._rect.x + self._wifi_icon.rect.width + 32 + 32, - self._rect.y + 32 - 16, - self._rect.width - (self._wifi_icon.rect.width + 32 + 32), - 64, - )) - - self._subtitle.render(rl.Rectangle( - self._rect.x + self._wifi_icon.rect.width + 32 + 32, - self._rect.y + 32 + 64 - 16, - self._rect.width - (self._wifi_icon.rect.width + 32 + 32), - 48, - )) - - self._connect_btn.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._connect_btn.rect.height, - self._connect_btn.rect.width, - self._connect_btn.rect.height, - )) - - if not self._connect_btn.full: - self._forget_btn.render(rl.Rectangle( - self._rect.x + self._rect.width - self._forget_btn.rect.width, - self._rect.y + self._rect.height - self._forget_btn.rect.height, - self._forget_btn.rect.width, - self._forget_btn.rect.height, - )) - - return -1 - - -class WifiUIMici(BigMultiOptionDialog): - # Wait this long after user interacts with widget to update network list - INACTIVITY_TIMEOUT = 1 - - def __init__(self, wifi_manager: WifiManager, back_callback: Callable): - super().__init__([], None) - - # Set up back navigation - self.set_back_callback(back_callback) - - self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, self._forget_network, self._open_network_manage_page) - self._network_info_page.set_connecting(lambda: self._connecting) self._loading_animation = LoadingAnimation() self._wifi_manager = wifi_manager - self._connecting: str | None = None self._networks: dict[str, Network] = {} - # widget state - self._last_interaction_time = -float('inf') - self._restore_selection = False - self._wifi_manager.add_callbacks( need_auth=self._on_need_auth, - activated=self._on_activated, forgotten=self._on_forgotten, networks_updated=self._on_network_updated, - disconnected=self._on_disconnected, ) def show_event(self): - # Call super to prepare scroller; selection scroll is handled dynamically + # Clear scroller items and update from latest scan results super().show_event() + self._loading_animation.show_event() self._wifi_manager.set_active(True) - self._last_interaction_time = -float('inf') - - def hide_event(self): - super().hide_event() - self._wifi_manager.set_active(False) - - def _open_network_manage_page(self, result=None): - self._network_info_page.update_networks(self._networks) - gui_app.set_modal_overlay(self._network_info_page) - - def _forget_network(self, ssid: str): - network = self._networks.get(ssid) - if network is None: - cloudlog.warning(f"Trying to forget unknown network: {ssid}") - return - - self._wifi_manager.forget_connection(network.ssid) + self._scroller.items.clear() + # trigger button update on latest sorted networks + self._on_network_updated(self._wifi_manager.networks) def _on_network_updated(self, networks: list[Network]): self._networks = {network.ssid: network for network in networks} self._update_buttons() - self._network_info_page.update_networks(self._networks) def _update_buttons(self): - # Don't update buttons while user is actively interacting - if rl.get_time() - self._last_interaction_time < self.INACTIVITY_TIMEOUT: - return + # Update existing buttons, add new ones to the end + existing = {btn.network.ssid: btn for btn in self._scroller.items if isinstance(btn, WifiButton)} for network in self._networks.values(): - # pop and re-insert to eliminate stuttering on update (prevents position lost for a frame) - network_button_idx = next((i for i, btn in enumerate(self._scroller._items) if btn.option == network.ssid), None) - if network_button_idx is not None: - network_button = self._scroller._items.pop(network_button_idx) - # Update network on existing button - network_button.set_current_network(network) + if network.ssid in existing: + existing[network.ssid].update_network(network) else: - network_button = WifiItem(network) + btn = WifiButton(network, self._wifi_manager) + btn.set_click_callback(lambda ssid=network.ssid: self._connect_to_network(ssid)) + self._scroller.add_widget(btn) - self._scroller.add_widget(network_button) - - # remove networks no longer present - self._scroller._items[:] = [btn for btn in self._scroller._items if btn.option in self._networks] - - # try to restore previous selection to prevent jumping from adding/removing/reordering buttons - self._restore_selection = True + # Mark networks no longer in scan results (display handled by _update_state) + for btn in self._scroller.items: + if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: + btn.set_network_missing(True) def _connect_with_password(self, ssid: str, password: str): - if password: - self._connecting = ssid - self._wifi_manager.connect_to_network(ssid, password) - self._update_buttons() - - def _on_option_selected(self, option: str): - super()._on_option_selected(option) - - if option in self._networks: - self._network_info_page.set_current_network(self._networks[option]) - self._open_network_manage_page() + self._wifi_manager.connect_to_network(ssid, password) + self._move_network_to_front(ssid, scroll=True) def _connect_to_network(self, ssid: str): network = self._networks.get(ssid) @@ -413,47 +325,62 @@ class WifiUIMici(BigMultiOptionDialog): cloudlog.warning(f"Trying to connect to unknown network: {ssid}") return - if network.is_saved: - self._connecting = network.ssid + if self._wifi_manager.is_connection_saved(network.ssid): self._wifi_manager.activate_connection(network.ssid) - self._update_buttons() elif network.security_type == SecurityType.OPEN: - self._connecting = network.ssid self._wifi_manager.connect_to_network(network.ssid, "") - self._update_buttons() else: self._on_need_auth(network.ssid, False) + return + + self._move_network_to_front(ssid, scroll=True) def _on_need_auth(self, ssid, incorrect_password=True): - hint = "incorrect password..." if incorrect_password else "enter password..." - dlg = BigInputDialog(hint, "", minimum_length=8, + if incorrect_password: + for btn in self._scroller.items: + if isinstance(btn, WifiButton) and btn.network.ssid == ssid: + btn.set_wrong_password() + break + return + + dlg = BigInputDialog("enter password...", "", minimum_length=8, confirm_callback=lambda _password: self._connect_with_password(ssid, _password)) - # go back to the manage network page - gui_app.set_modal_overlay(dlg, self._open_network_manage_page) + gui_app.push_widget(dlg) - def _on_activated(self): - self._connecting = None + def _on_forgotten(self, ssid): + # For eager UI forget + for btn in self._scroller.items: + if isinstance(btn, WifiButton) and btn.network.ssid == ssid: + btn.on_forgotten() - def _on_forgotten(self): - self._connecting = None + def _move_network_to_front(self, ssid: str | None, scroll: bool = False): + # Move connecting/connected network to the front with animation + front_btn_idx = next((i for i, btn in enumerate(self._scroller.items) + if isinstance(btn, WifiButton) and + btn.network.ssid == ssid), None) if ssid else None - def _on_disconnected(self): - self._connecting = None + if front_btn_idx is not None and front_btn_idx > 0: + self._scroller.move_item(front_btn_idx, 0) + + if scroll: + # Scroll to the new position of the network + self._scroller.scroll_to(self._scroller.scroll_panel.get_offset(), smooth=True) def _update_state(self): super()._update_state() - if self.is_pressed: - self._last_interaction_time = rl.get_time() + + self._move_network_to_front(self._wifi_manager.wifi_state.ssid) + + # Show loading animation near end + max_scroll = max(self._scroller.content_size - self._scroller.rect.width, 1) + progress = -self._scroller.scroll_panel.get_offset() / max_scroll + if progress > 0.8 or len(self._scroller.items) <= 1: + self._loading_animation.show_event() def _render(self, _): - # Update Scroller layout and restore current selection whenever buttons are updated, before first render - current_selection = self.get_selected_option() - if self._restore_selection and current_selection in self._networks: - self._scroller._layout() - BigMultiOptionDialog._on_option_selected(self, current_selection) - self._restore_selection = None + super()._render(self._rect) - super()._render(_) - - if not self._networks: - self._loading_animation.render(self._rect) + anim_w = 90 + anim_x = self._rect.x + self._rect.width - anim_w + anim_y = self._rect.y + self._rect.height - 25 + 2 + self._loading_animation.render(rl.Rectangle(anim_x, anim_y, anim_w, 20)) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index a6f59a0389..c7fb3201f5 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -1,10 +1,5 @@ -import pyray as rl -from dataclasses import dataclass -from enum import IntEnum -from collections.abc import Callable - from openpilot.common.params import Params -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.selfdrive.ui.mici.layouts.settings.toggles import TogglesLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.network import NetworkLayoutMici @@ -12,22 +7,6 @@ from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayout from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.widgets import Widget, NavWidget - - -class PanelType(IntEnum): - TOGGLES = 0 - NETWORK = 1 - DEVICE = 2 - DEVELOPER = 3 - USER_MANUAL = 4 - FIREHOSE = 5 - - -@dataclass -class PanelInfo: - name: str - instance: Widget class SettingsBigButton(BigButton): @@ -35,25 +14,32 @@ class SettingsBigButton(BigButton): return 64 -class SettingsLayout(NavWidget): +class SettingsLayout(NavScroller): def __init__(self): super().__init__() self._params = Params() - self._current_panel = None # PanelType.DEVICE + toggles_panel = TogglesLayoutMici() toggles_btn = SettingsBigButton("toggles", "", "icons_mici/settings.png") - toggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.TOGGLES)) + toggles_btn.set_click_callback(lambda: gui_app.push_widget(toggles_panel)) + + network_panel = NetworkLayoutMici() network_btn = SettingsBigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) - network_btn.set_click_callback(lambda: self._set_current_panel(PanelType.NETWORK)) + network_btn.set_click_callback(lambda: gui_app.push_widget(network_panel)) + + device_panel = DeviceLayoutMici() device_btn = SettingsBigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60)) - device_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVICE)) + device_btn.set_click_callback(lambda: gui_app.push_widget(device_panel)) + + developer_panel = DeveloperLayoutMici() developer_btn = SettingsBigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60)) - developer_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVELOPER)) + developer_btn.set_click_callback(lambda: gui_app.push_widget(developer_panel)) + firehose_panel = FirehoseLayout() firehose_btn = SettingsBigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) - firehose_btn.set_click_callback(lambda: self._set_current_panel(PanelType.FIREHOSE)) + firehose_btn.set_click_callback(lambda: gui_app.push_widget(firehose_panel)) - self._scroller = Scroller([ + self._scroller.add_widgets([ toggles_btn, network_btn, device_btn, @@ -61,58 +47,6 @@ class SettingsLayout(NavWidget): #BigDialogButton("manual", "", "icons_mici/settings/manual_icon.png", "Check out the mici user\nmanual at comma.ai/setup"), firehose_btn, developer_btn, - ], snap_items=False) - - # Set up back navigation - self.set_back_callback(self.close_settings) - self.set_back_enabled(lambda: self._current_panel is None) - - self._panels = { - PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayoutMici(back_callback=lambda: self._set_current_panel(None))), - PanelType.NETWORK: PanelInfo("Network", NetworkLayoutMici(back_callback=lambda: self._set_current_panel(None))), - PanelType.DEVICE: PanelInfo("Device", DeviceLayoutMici(back_callback=lambda: self._set_current_panel(None))), - PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayoutMici(back_callback=lambda: self._set_current_panel(None))), - PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout(back_callback=lambda: self._set_current_panel(None))), - } + ]) self._font_medium = gui_app.font(FontWeight.MEDIUM) - - # Callbacks - self._close_callback: Callable | None = None - - def show_event(self): - super().show_event() - self._set_current_panel(None) - self._scroller.show_event() - if self._current_panel is not None: - self._panels[self._current_panel].instance.show_event() - - def hide_event(self): - super().hide_event() - if self._current_panel is not None: - self._panels[self._current_panel].instance.hide_event() - - def set_callbacks(self, on_close: Callable): - self._close_callback = on_close - - def _render(self, rect: rl.Rectangle): - if self._current_panel is not None: - self._draw_current_panel() - else: - self._scroller.render(rect) - - def _draw_current_panel(self): - panel = self._panels[self._current_panel] - panel.instance.render(self._rect) - - def _set_current_panel(self, panel_type: PanelType | None): - if panel_type != self._current_panel: - if self._current_panel is not None: - self._panels[self._current_panel].instance.hide_event() - self._current_panel = panel_type - if self._current_panel is not None: - self._panels[self._current_panel].instance.show_event() - - def close_settings(self): - if self._close_callback: - self._close_callback() diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py index 1266311a1b..8635336f97 100644 --- a/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -1,21 +1,17 @@ -import pyray as rl -from collections.abc import Callable from cereal import log -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigParamControl, BigMultiParamToggle from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.widgets import NavWidget from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants -class TogglesLayoutMici(NavWidget): - def __init__(self, back_callback: Callable): +class TogglesLayoutMici(NavScroller): + def __init__(self): super().__init__() - self.set_back_callback(back_callback) self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"]) self._experimental_btn = BigParamControl("experimental mode", "ExperimentalMode") @@ -26,7 +22,7 @@ class TogglesLayoutMici(NavWidget): record_mic = BigParamControl("record & upload mic audio", "RecordAudio", toggle_callback=restart_needed_callback) enable_openpilot = BigParamControl("enable sunnypilot", "OpenpilotEnabledToggle", toggle_callback=restart_needed_callback) - self._scroller = Scroller([ + self._scroller.add_widgets([ self._personality_toggle, self._experimental_btn, is_metric_toggle, @@ -35,7 +31,7 @@ class TogglesLayoutMici(NavWidget): record_front, record_mic, enable_openpilot, - ], snap_items=False) + ]) # Toggle lists self._refresh_toggles = ( @@ -69,7 +65,6 @@ class TogglesLayoutMici(NavWidget): def show_event(self): super().show_event() - self._scroller.show_event() self._update_toggles() def _update_toggles(self): @@ -90,6 +85,3 @@ class TogglesLayoutMici(NavWidget): # Refresh toggles from params to mirror external changes for key, item in self._refresh_toggles: item.set_checked(ui_state.params.get_bool(key)) - - def _render(self, rect: rl.Rectangle): - self._scroller.render(rect) diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index b7a336a35a..68b2aef7a5 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -13,6 +13,8 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.selfdrive.ui.sunnypilot.onroad.speed_limit import SpeedLimitAlertRenderer + AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus @@ -46,6 +48,7 @@ class IconLayout(NamedTuple): side: IconSide margin_x: int margin_y: int + alpha: float = 255.0 class AlertLayout(NamedTuple): @@ -86,9 +89,10 @@ ALERT_CRITICAL_REBOOT = Alert( ) -class AlertRenderer(Widget): +class AlertRenderer(Widget, SpeedLimitAlertRenderer): def __init__(self): - super().__init__() + Widget.__init__(self) + SpeedLimitAlertRenderer.__init__(self) self._alert_text1_label = UnifiedLabel(text="", font_size=ALERT_FONT_BIG, font_weight=FontWeight.DISPLAY, line_height=0.86, letter_spacing=-0.02) @@ -112,9 +116,9 @@ class AlertRenderer(Widget): def _load_icons(self): self._txt_turn_signal_left = gui_app.texture('icons_mici/onroad/turn_signal_left.png', 104, 96) - self._txt_turn_signal_right = gui_app.texture('icons_mici/onroad/turn_signal_right.png', 104, 96) + self._txt_turn_signal_right = gui_app.texture('icons_mici/onroad/turn_signal_left.png', 104, 96, flip_x=True) self._txt_blind_spot_left = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 134, 150) - self._txt_blind_spot_right = gui_app.texture('icons_mici/onroad/blind_spot_right.png', 134, 150) + self._txt_blind_spot_right = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 134, 150, flip_x=True) def get_alert(self, sm: messaging.SubMaster) -> Alert | None: """Generate the current alert based on selfdrive state.""" @@ -155,6 +159,7 @@ class AlertRenderer(Widget): def _icon_helper(self, alert: Alert) -> AlertLayout: icon_side = None txt_icon = None + icon_alpha = 255.0 icon_margin_x = 20 icon_margin_y = 18 @@ -191,6 +196,9 @@ class AlertRenderer(Widget): icon_margin_x = 8 icon_margin_y = 0 + elif event_name == 'speedLimitPreActive': + icon_side, txt_icon, icon_alpha, icon_margin_x, icon_margin_y = SpeedLimitAlertRenderer.speed_limit_pre_active_icon_helper(self) + else: self._turn_signal_timer = 0.0 @@ -212,7 +220,7 @@ class AlertRenderer(Widget): text_width, self._rect.height, ) - icon_layout = IconLayout(txt_icon, icon_side, icon_margin_x, icon_margin_y) if txt_icon is not None and icon_side is not None else None + icon_layout = IconLayout(txt_icon, icon_side, icon_margin_x, icon_margin_y, icon_alpha) if txt_icon is not None and icon_side is not None else None return AlertLayout(text_rect, icon_layout) def _render(self, rect: rl.Rectangle) -> bool: @@ -235,6 +243,9 @@ class AlertRenderer(Widget): self._draw_background(alert) + # update speed limit UI states + SpeedLimitAlertRenderer.update(self) + alert_layout = self._icon_helper(alert) self._draw_text(alert, alert_layout) self._draw_icons(alert_layout) @@ -257,7 +268,7 @@ class AlertRenderer(Widget): pos_x = int(self._rect.x + self._rect.width - alert_layout.icon.margin_x - alert_layout.icon.texture.width) if alert_layout.icon.texture not in (self._txt_turn_signal_left, self._txt_turn_signal_right): - icon_alpha = 255 + icon_alpha = alert_layout.icon.alpha else: icon_alpha = int(min(self._turn_signal_alpha_filter.x, 255)) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index f8aee7969f..70cc249d2a 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -14,7 +14,7 @@ from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView from openpilot.system.ui.lib.application import FontWeight, gui_app, MousePos, MouseEvent from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets import Widget -from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter +from openpilot.common.filter_simple import BounceFilter from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCameraConfig, view_frame_from_device_frame from openpilot.common.transformations.orientation import rot_from_euler from enum import IntEnum @@ -50,8 +50,6 @@ class BookmarkIcon(Widget): super().__init__() self._bookmark_callback = bookmark_callback self._icon = gui_app.texture("icons_mici/onroad/bookmark.png", 180, 180) - self._icon_fill = gui_app.texture("icons_mici/onroad/bookmark_fill.png", 180, 180) - self._active_icon = self._icon self._offset_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps) # State @@ -90,7 +88,6 @@ class BookmarkIcon(Widget): if self._offset_filter.x < 1e-3: self._interacting = False - self._active_icon = self._icon def _handle_mouse_event(self, mouse_event: MouseEvent): if not ui_state.started: @@ -103,7 +100,6 @@ class BookmarkIcon(Widget): self._is_swiping = True self._is_swiping_left = False self._state = BookmarkState.DRAGGING - self._active_icon = self._icon elif mouse_event.left_down and self._is_swiping: self._swipe_current_x = mouse_event.pos.x @@ -120,7 +116,6 @@ class BookmarkIcon(Widget): if swipe_distance > self.PEEK_THRESHOLD: self._state = BookmarkState.TRIGGERED self._triggered_time = rl.get_time() - self._active_icon = self._icon_fill self._bookmark_callback() else: # Otherwise, transition back to hidden @@ -134,8 +129,8 @@ class BookmarkIcon(Widget): """Render the bookmark icon.""" if self._offset_filter.x > 0: icon_x = self.rect.x + self.rect.width - round(self._offset_filter.x) - icon_y = self.rect.y + (self.rect.height - self._active_icon.height) / 2 # Vertically centered - rl.draw_texture(self._active_icon, int(icon_x), int(icon_y), rl.WHITE) + icon_y = self.rect.y + (self.rect.height - self._icon.height) / 2 # Vertically centered + rl.draw_texture(self._icon, int(icon_x), int(icon_y), rl.WHITE) class AugmentedRoadView(CameraView): @@ -169,7 +164,6 @@ class AugmentedRoadView(CameraView): alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png") - self._fade_alpha_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) # debug self._pm = messaging.PubMaster(['uiDebug']) @@ -222,11 +216,8 @@ class AugmentedRoadView(CameraView): # Draw all UI overlays self._model_renderer.render(self._content_rect) - # Fade out bottom of overlays for looks (only when engaged) - fade_alpha = self._fade_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED) - if fade_alpha > 1e-2: - rl.draw_texture_ex(self._fade_texture, rl.Vector2(self._content_rect.x, self._content_rect.y), 0.0, 1.0, - rl.Color(255, 255, 255, int(255 * fade_alpha))) + # Fade out bottom of overlays for looks + rl.draw_texture_ex(self._fade_texture, rl.Vector2(self._content_rect.x, self._content_rect.y), 0.0, 1.0, rl.WHITE) alert_to_render, not_animating_out = self._alert_renderer.will_render() @@ -375,7 +366,7 @@ class AugmentedRoadView(CameraView): if __name__ == "__main__": gui_app.init_window("OnRoad Camera View") - road_camera_view = AugmentedRoadView(ROAD_CAM) + road_camera_view = AugmentedRoadView(lambda: None, stream_type=ROAD_CAM) print("***press space to switch camera view***") try: for _ in gui_app.render(): diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index bab3d6e6f1..e8321b099c 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -7,7 +7,8 @@ from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.selfdrive.selfdrived.events import EVENTS, ET from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import gui_label EventName = log.OnroadEvent.EventName @@ -24,19 +25,15 @@ class DriverCameraView(CameraView): return base -class DriverCameraDialog(NavWidget): - def __init__(self, no_escape=False): +class BaseDriverCameraDialog(Widget): + # Not a NavWidget so training guide can use this without back navigation + def __init__(self): super().__init__() self._camera_view = DriverCameraView("camerad", VisionStreamType.VISION_STREAM_DRIVER) self.driver_state_renderer = DriverStateRenderer(lines=True) self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 200, 200)) self.driver_state_renderer.load_icons() self._pm: messaging.PubMaster | None = None - if not no_escape: - # TODO: this can grow unbounded, should be given some thought - device.add_interactive_timeout_callback(lambda: gui_app.set_modal_overlay(None)) - self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) - self.set_back_enabled(not no_escape) # Load eye icons self._eye_fill_texture = None @@ -87,7 +84,7 @@ class DriverCameraDialog(NavWidget): alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) rl.end_scissor_mode() self._publish_alert_sound(None) - return -1 + return driver_data = self._draw_face_detection(rect) if driver_data is not None: @@ -105,7 +102,7 @@ class DriverCameraDialog(NavWidget): self._render_dm_alerts(rect) rl.end_scissor_mode() - return -1 + return def _publish_alert_sound(self, dm_state): """Publish selfdriveState with only alertSound field set""" @@ -231,13 +228,20 @@ class DriverCameraDialog(NavWidget): rl.draw_texture_v(self._glasses_texture, glasses_pos, rl.Color(70, 80, 161, int(255 * glasses_prob))) +class DriverCameraDialog(NavWidget, BaseDriverCameraDialog): + def __init__(self): + super().__init__() + # TODO: this can grow unbounded, should be given some thought + device.add_interactive_timeout_callback(gui_app.pop_widget) + + if __name__ == "__main__": gui_app.init_window("Driver Camera View (mici)") driver_camera_view = DriverCameraDialog() + gui_app.push_widget(driver_camera_view) try: for _ in gui_app.render(): ui_state.update() - driver_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) finally: driver_camera_view.close() diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 659d13fad7..ccf32023ed 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -50,7 +50,7 @@ class TurnIntent(Widget): self._turn_intent_rotation_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) self._txt_turn_intent_left: rl.Texture = gui_app.texture('icons_mici/turn_intent_left.png', 50, 20) - self._txt_turn_intent_right: rl.Texture = gui_app.texture('icons_mici/turn_intent_right.png', 50, 20) + self._txt_turn_intent_right: rl.Texture = gui_app.texture('icons_mici/turn_intent_left.png', 50, 20, flip_x=True) def _render(self, _): if self._turn_intent_alpha_filter.x > 1e-2: diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py new file mode 100755 index 0000000000..be12839cd7 --- /dev/null +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -0,0 +1,117 @@ +import pyray as rl +rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) +import gc +import weakref +import pytest +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import Widget + +# mici dialogs +from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow +from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog +from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2, BigInputDialog +from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal + +# tici dialogs +from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog as TiciDriverCameraDialog +from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow as TiciOnboardingWindow +from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog as TiciPairingDialog +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog +from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog +from openpilot.system.ui.widgets.html_render import HtmlModal +from openpilot.system.ui.widgets.keyboard import Keyboard + +# FIXME: known small leaks not worth worrying about at the moment +KNOWN_LEAKS = { + "openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog.DriverCameraView", + "openpilot.selfdrive.ui.mici.layouts.onboarding.TermsPage", + "openpilot.selfdrive.ui.mici.layouts.onboarding.TrainingGuide", + "openpilot.selfdrive.ui.mici.layouts.onboarding.DeclinePage", + "openpilot.selfdrive.ui.mici.layouts.onboarding.OnboardingWindow", + "openpilot.selfdrive.ui.onroad.driver_state.DriverStateRenderer", + "openpilot.selfdrive.ui.onroad.driver_camera_dialog.DriverCameraDialog", + "openpilot.selfdrive.ui.layouts.onboarding.TermsPage", + "openpilot.selfdrive.ui.layouts.onboarding.DeclinePage", + "openpilot.selfdrive.ui.layouts.onboarding.OnboardingWindow", + "openpilot.system.ui.widgets.confirm_dialog.ConfirmDialog", + "openpilot.system.ui.widgets.label.Label", + "openpilot.system.ui.widgets.button.Button", + "openpilot.system.ui.widgets.html_render.HtmlRenderer", + "openpilot.system.ui.widgets.nav_widget.NavBar", + "openpilot.selfdrive.ui.mici.layouts.settings.device.MiciFccModal", + "openpilot.system.ui.widgets.inputbox.InputBox", + "openpilot.system.ui.widgets.scroller_tici.Scroller", + "openpilot.system.ui.widgets.label.UnifiedLabel", + "openpilot.system.ui.widgets.mici_keyboard.MiciKeyboard", + "openpilot.selfdrive.ui.mici.widgets.dialog.BigConfirmationDialogV2", + "openpilot.system.ui.widgets.keyboard.Keyboard", + "openpilot.system.ui.widgets.slider.BigSlider", + "openpilot.selfdrive.ui.mici.widgets.dialog.BigInputDialog", + "openpilot.system.ui.widgets.option_dialog.MultiOptionDialog", +} + + +def get_child_widgets(widget: Widget) -> list[Widget]: + children = [] + for val in widget.__dict__.values(): + items = val if isinstance(val, (list, tuple)) else (val,) + children.extend(w for w in items if isinstance(w, Widget)) + return children + + +@pytest.mark.skip(reason="segfaults") +def test_dialogs_do_not_leak(): + gui_app.init_window("ref-test") + + leaked_widgets = set() + + for ctor in ( + # mici + MiciDriverCameraDialog, MiciTrainingGuide, MiciOnboardingWindow, MiciPairingDialog, + lambda: BigDialog("test", "test"), + lambda: BigConfirmationDialogV2("test", "icons_mici/settings/network/new/trash.png"), + lambda: BigInputDialog("test"), + lambda: MiciFccModal(text="test"), + # tici + TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard, + lambda: ConfirmDialog("test", "ok"), + lambda: MultiOptionDialog("test", ["a", "b"]), + lambda: HtmlModal(text="test"), + ): + widget = ctor() + all_refs = [weakref.ref(w) for w in get_child_widgets(widget) + [widget]] + + del widget + + for ref in all_refs: + if ref() is not None: + obj = ref() + name = f"{type(obj).__module__}.{type(obj).__qualname__}" + leaked_widgets.add(name) + + print(f"\n=== Widget {name} alive after del") + print(" Referrers:") + for r in gc.get_referrers(obj): + if r is obj: + continue + + if hasattr(r, '__self__') and r.__self__ is not obj: + print(f" bound method: {type(r.__self__).__qualname__}.{r.__name__}") + elif hasattr(r, '__func__'): + print(f" method: {r.__name__}") + else: + print(f" {type(r).__module__}.{type(r).__qualname__}") + del obj + + gui_app.close() + + unexpected = leaked_widgets - KNOWN_LEAKS + assert not unexpected, f"New leaked widgets: {unexpected}" + + fixed = KNOWN_LEAKS - leaked_widgets + assert not fixed, f"These leaks are fixed, remove from KNOWN_LEAKS: {fixed}" + + +if __name__ == "__main__": + test_dialogs_do_not_leak() diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 29844ccda1..b5bd65e2de 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -1,3 +1,4 @@ +import math import pyray as rl from typing import Union from enum import Enum @@ -16,8 +17,6 @@ except ImportError: SCROLLING_SPEED_PX_S = 50 COMPLICATION_SIZE = 36 LABEL_COLOR = rl.Color(255, 255, 255, int(255 * 0.9)) -LABEL_HORIZONTAL_PADDING = 40 -LABEL_VERTICAL_PADDING = 23 # visually matches 30 in figma COMPLICATION_GREY = rl.Color(0xAA, 0xAA, 0xAA, 255) PRESSED_SCALE = 1.15 if DO_ZOOM else 1.07 @@ -36,25 +35,22 @@ class BigCircleButton(Widget): # State self.set_rect(rl.Rectangle(0, 0, 180, 180)) - self._press_state_enabled = True self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._click_delay = 0.075 # Icons self._txt_icon = gui_app.texture(icon, *icon_size) self._txt_btn_disabled_bg = gui_app.texture("icons_mici/buttons/button_circle_disabled.png", 180, 180) self._txt_btn_bg = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180) - self._txt_btn_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_hover.png", 180, 180) + self._txt_btn_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_pressed.png", 180, 180) self._txt_btn_red_bg = gui_app.texture("icons_mici/buttons/button_circle_red.png", 180, 180) - self._txt_btn_red_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_red_hover.png", 180, 180) - - def set_enable_pressed_state(self, pressed: bool): - self._press_state_enabled = pressed + self._txt_btn_red_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_red_pressed.png", 180, 180) def _draw_content(self, btn_y: float): # draw icon - icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) + icon_color = rl.Color(255, 255, 255, int(255 * 0.9)) if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) rl.draw_texture_ex(self._txt_icon, (self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0], btn_y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), 0, 1.0, icon_color) @@ -63,10 +59,10 @@ class BigCircleButton(Widget): txt_bg = self._txt_btn_bg if not self._red else self._txt_btn_red_bg if not self.enabled: txt_bg = self._txt_btn_disabled_bg - elif self.is_pressed and self._press_state_enabled: + elif self.is_pressed: txt_bg = self._txt_btn_pressed_bg if not self._red else self._txt_btn_red_pressed_bg - scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed and self._press_state_enabled else 1.0) + scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) @@ -106,6 +102,9 @@ class BigCircleToggle(BigCircleButton): class BigButton(Widget): + LABEL_HORIZONTAL_PADDING = 40 + LABEL_VERTICAL_PADDING = 23 # visually matches 30 in figma + """A lightweight stand-in for the Qt BigButton, drawn & updated each frame.""" def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", icon_size: tuple[int, int] = (64, 64), @@ -119,6 +118,8 @@ class BigButton(Widget): self.set_icon(icon) self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._click_delay = 0.075 + self._shake_start: float | None = None self._rotate_icon_t: float | None = None @@ -143,12 +144,11 @@ class BigButton(Widget): self._txt_default_bg = gui_app.texture("icons_mici/buttons/button_rectangle.png", 402, 180) self._txt_pressed_bg = gui_app.texture("icons_mici/buttons/button_rectangle_pressed.png", 402, 180) self._txt_disabled_bg = gui_app.texture("icons_mici/buttons/button_rectangle_disabled.png", 402, 180) - self._txt_hover_bg = gui_app.texture("icons_mici/buttons/button_rectangle_hover.png", 402, 180) def _width_hint(self) -> int: # Single line if scrolling, so hide behind icon if exists icon_size = self._icon_size[0] if self._txt_icon and self._scroll and self.value else 0 - return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - icon_size) + return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - icon_size) def _get_label_font_size(self): if len(self.text) <= 18: @@ -179,20 +179,50 @@ class BigButton(Widget): def get_text(self): return self.text + def trigger_shake(self): + self._shake_start = rl.get_time() + + @property + def _shake_offset(self) -> float: + SHAKE_DURATION = 0.5 + SHAKE_AMPLITUDE = 24.0 + SHAKE_FREQUENCY = 32.0 + t = rl.get_time() - (self._shake_start or 0.0) + if t > SHAKE_DURATION: + return 0.0 + decay = 1.0 - t / SHAKE_DURATION + return decay * SHAKE_AMPLITUDE * math.sin(t * SHAKE_FREQUENCY) + + def set_position(self, x: float, y: float) -> None: + super().set_position(x + self._shake_offset, y) + + def _handle_background(self) -> tuple[rl.Texture, float, float, float]: + # draw _txt_default_bg + txt_bg = self._txt_default_bg + if not self.enabled: + txt_bg = self._txt_disabled_bg + elif self.is_pressed: + txt_bg = self._txt_pressed_bg + + scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) + btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 + btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 + return txt_bg, btn_x, btn_y, scale + def _draw_content(self, btn_y: float): # LABEL ------------------------------------------------------------------ - label_x = self._rect.x + LABEL_HORIZONTAL_PADDING + label_x = self._rect.x + self.LABEL_HORIZONTAL_PADDING label_color = LABEL_COLOR if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) self._label.set_color(label_color) - label_rect = rl.Rectangle(label_x, btn_y + LABEL_VERTICAL_PADDING, self._width_hint(), - self._rect.height - LABEL_VERTICAL_PADDING * 2) + label_rect = rl.Rectangle(label_x, btn_y + self.LABEL_VERTICAL_PADDING, self._width_hint(), + self._rect.height - self.LABEL_VERTICAL_PADDING * 2) self._label.render(label_rect) if self.value: - label_y = btn_y + self._rect.height - LABEL_VERTICAL_PADDING - sub_label_height = self._sub_label.get_content_height(self._width_hint()) - sub_label_rect = rl.Rectangle(label_x, label_y - sub_label_height, self._width_hint(), sub_label_height) + label_y = btn_y + self.LABEL_VERTICAL_PADDING + self._label.get_content_height(self._width_hint()) + sub_label_height = btn_y + self._rect.height - self.LABEL_VERTICAL_PADDING - label_y + sub_label_rect = rl.Rectangle(label_x, label_y, self._width_hint(), sub_label_height) self._sub_label.render(sub_label_rect) # ICON ------------------------------------------------------------------- @@ -207,22 +237,21 @@ class BigButton(Widget): source_rec = rl.Rectangle(0, 0, self._txt_icon.width, self._txt_icon.height) dest_rec = rl.Rectangle(x, y, self._txt_icon.width, self._txt_icon.height) origin = rl.Vector2(self._txt_icon.width / 2, self._txt_icon.height / 2) - rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.WHITE) + rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.Color(255, 255, 255, int(255 * 0.9))) def _render(self, _): - # draw _txt_default_bg - txt_bg = self._txt_default_bg - if not self.enabled: - txt_bg = self._txt_disabled_bg - elif self.is_pressed: - txt_bg = self._txt_hover_bg + txt_bg, btn_x, btn_y, scale = self._handle_background() - scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) - btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 - btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 - rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) + if self._scroll: + # draw black background since images are transparent + scaled_rect = rl.Rectangle(btn_x, btn_y, self._rect.width * scale, self._rect.height * scale) + rl.draw_rectangle_rounded(scaled_rect, 0.4, 7, rl.Color(0, 0, 0, int(255 * 0.5))) - self._draw_content(btn_y) + self._draw_content(btn_y) + rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) + else: + rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) + self._draw_content(btn_y) class BigToggle(BigButton): @@ -271,7 +300,7 @@ class BigMultiToggle(BigToggle): self.set_value(self._options[0]) def _width_hint(self) -> int: - return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width) + return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width) def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index b88ac20494..619c1ca28f 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -3,14 +3,12 @@ import math import pyray as rl from typing import Union from collections.abc import Callable -from typing import cast -from openpilot.system.ui.widgets import Widget, NavWidget, DialogResult +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, MouseEvent -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter from openpilot.selfdrive.ui.mici.widgets.button import BigButton @@ -23,17 +21,7 @@ PADDING = 20 class BigDialogBase(NavWidget, abc.ABC): def __init__(self): super().__init__() - self._ret = DialogResult.NO_ACTION self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - self.set_back_callback(lambda: setattr(self, '_ret', DialogResult.CANCEL)) - - def _render(self, _) -> DialogResult: - """ - Allows `gui_app.set_modal_overlay(BigDialog(...))`. - The overlay runner keeps calling until result != NO_ACTION. - """ - - return self._ret class BigDialog(BigDialogBase): @@ -44,7 +32,7 @@ class BigDialog(BigDialogBase): self._title = title self._description = description - def _render(self, _) -> DialogResult: + def _render(self, _): super()._render(_) # draw title @@ -74,8 +62,6 @@ class BigDialog(BigDialogBase): gui_label(desc_rect, desc_wrapped, 30, font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) - return self._ret - class BigConfirmationDialogV2(BigDialogBase): def __init__(self, title: str, icon: str, red: bool = False, @@ -91,22 +77,21 @@ class BigConfirmationDialogV2(BigDialogBase): self._slider = RedBigSlider(title, icon_txt, confirm_callback=self._on_confirm) else: self._slider = BigSlider(title, icon_txt, confirm_callback=self._on_confirm) - self._slider.set_enabled(lambda: not self._swiping_away) + self._slider.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget def _on_confirm(self): - if self._confirm_callback: - self._confirm_callback() if self._exit_on_confirm: - self._ret = DialogResult.CONFIRM + self.dismiss(self._confirm_callback) + elif self._confirm_callback: + self._confirm_callback() def _update_state(self): super()._update_state() - if self._swiping_away and not self._slider.confirmed: + if self.is_dismissing and not self._slider.confirmed: self._slider.reset() - def _render(self, _) -> DialogResult: + def _render(self, _): self._slider.render(self._rect) - return self._ret class BigInputDialog(BigDialogBase): @@ -124,6 +109,7 @@ class BigInputDialog(BigDialogBase): font_weight=FontWeight.MEDIUM) self._keyboard = MiciKeyboard() self._keyboard.set_text(default_text) + self._keyboard.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget self._minimum_length = minimum_length self._backspace_held_time: float | None = None @@ -140,14 +126,17 @@ class BigInputDialog(BigDialogBase): self._top_right_button_rect = rl.Rectangle(0, 0, 0, 0) def confirm_callback_wrapper(): - self._ret = DialogResult.CONFIRM - if confirm_callback: - confirm_callback(self._keyboard.text()) + text = self._keyboard.text() + self.dismiss((lambda: confirm_callback(text)) if confirm_callback else None) self._confirm_callback = confirm_callback_wrapper def _update_state(self): super()._update_state() + if self.is_dismissing: + self._backspace_held_time = None + return + last_mouse_event = gui_app.last_mouse_event if last_mouse_event.left_down and rl.check_collision_point_rec(last_mouse_event.pos, self._top_right_button_rect) and self._backspace_img_alpha.x > 1: if self._backspace_held_time is None: @@ -195,11 +184,13 @@ class BigInputDialog(BigDialogBase): rl.BLACK, rl.BLANK) # draw cursor + blink_alpha = (math.sin(rl.get_time() * 6) + 1) / 2 if text: - blink_alpha = (math.sin(rl.get_time() * 6) + 1) / 2 cursor_x = min(text_x + text_size.x + 3, text_field_rect.x + text_field_rect.width) - rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_field_rect.y), 4, int(text_size.y)), - 1, 4, rl.Color(255, 255, 255, int(255 * blink_alpha))) + else: + cursor_x = text_field_rect.x - 6 + rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_field_rect.y), 4, int(text_size.y)), + 1, 4, rl.Color(255, 255, 255, int(255 * blink_alpha))) # draw backspace icon with nice fade self._backspace_img_alpha.update(255 * bool(text)) @@ -209,7 +200,10 @@ class BigInputDialog(BigDialogBase): if not text and self._hint_label.text and not candidate_char: # draw description if no text entered yet and not drawing candidate char - self._hint_label.render(text_field_rect) + hint_rect = rl.Rectangle(text_field_rect.x, text_field_rect.y, + self._rect.width - text_field_rect.x - PADDING, + text_field_rect.height) + self._hint_label.render(hint_rect) # TODO: move to update state # make rect take up entire area so it's easier to click @@ -233,12 +227,14 @@ class BigInputDialog(BigDialogBase): rl.draw_rectangle_lines_ex(self._top_right_button_rect, 1, rl.Color(0, 255, 0, 255)) rl.draw_rectangle_lines_ex(self._top_left_button_rect, 1, rl.Color(0, 255, 0, 255)) - return self._ret - def _handle_mouse_press(self, mouse_pos: MousePos): super()._handle_mouse_press(mouse_pos) # TODO: need to track where press was so enter and back can activate on release rather than press # or turn into icon widgets :eyes_open: + + if self.is_dismissing: + return + # handle backspace icon click if rl.check_collision_point_rec(mouse_pos, self._top_right_button_rect) and self._backspace_img_alpha.x > 254: self._keyboard.backspace() @@ -247,149 +243,6 @@ class BigInputDialog(BigDialogBase): self._confirm_callback() -class BigDialogOptionButton(Widget): - HEIGHT = 64 - SELECTED_HEIGHT = 74 - - def __init__(self, option: str): - super().__init__() - self.option = option - self.set_rect(rl.Rectangle(0, 0, int(gui_app.width / 2 + 220), self.HEIGHT)) - - self._selected = False - - self._label = UnifiedLabel(option, font_size=70, text_color=rl.Color(255, 255, 255, int(255 * 0.58)), - font_weight=FontWeight.DISPLAY_REGULAR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, - scroll=True) - - def show_event(self): - super().show_event() - self._label.reset_scroll() - - def set_selected(self, selected: bool): - self._selected = selected - self._rect.height = self.SELECTED_HEIGHT if selected else self.HEIGHT - - def _render(self, _): - if DEBUG: - rl.draw_rectangle_lines_ex(self._rect, 1, rl.Color(0, 255, 0, 255)) - - # FIXME: offset x by -45 because scroller centers horizontally - if self._selected: - self._label.set_font_size(self.SELECTED_HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) - self._label.set_font_weight(FontWeight.DISPLAY) - else: - self._label.set_font_size(self.HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58))) - self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) - - self._label.render(self._rect) - - -class BigMultiOptionDialog(BigDialogBase): - BACK_TOUCH_AREA_PERCENTAGE = 0.1 - - def __init__(self, options: list[str], default: str | None): - super().__init__() - self._options = options - if default is not None: - assert default in options - - self._default_option: str | None = default - self._selected_option: str = self._default_option or (options[0] if len(options) > 0 else "") - self._last_selected_option: str = self._selected_option - - # Widget doesn't differentiate between click and drag - self._can_click = True - - self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True) - - for option in options: - self._scroller.add_widget(BigDialogOptionButton(option)) - - def show_event(self): - super().show_event() - self._scroller.show_event() - if self._default_option is not None: - self._on_option_selected(self._default_option) - - def get_selected_option(self) -> str: - return self._selected_option - - def _on_option_selected(self, option: str): - y_pos = 0.0 - for btn in self._scroller._items: - btn = cast(BigDialogOptionButton, btn) - if btn.option == option: - rect_center_y = self._rect.y + self._rect.height / 2 - if btn._selected: - height = btn.rect.height - else: - # when selecting an option under current, account for changing heights - btn_center_y = btn.rect.y + btn.rect.height / 2 # not accurate, just to determine direction - height_offset = BigDialogOptionButton.SELECTED_HEIGHT - BigDialogOptionButton.HEIGHT - height = (BigDialogOptionButton.HEIGHT - height_offset) if rect_center_y < btn_center_y else BigDialogOptionButton.SELECTED_HEIGHT - y_pos = rect_center_y - (btn.rect.y + height / 2) - break - - self._scroller.scroll_to(-y_pos) - - def _selected_option_changed(self): - pass - - def _handle_mouse_press(self, mouse_pos: MousePos): - super()._handle_mouse_press(mouse_pos) - self._can_click = True - - def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: - super()._handle_mouse_event(mouse_event) - - # # TODO: add generic _handle_mouse_click handler to Widget - if not self._scroller.scroll_panel.is_touch_valid(): - self._can_click = False - - def _handle_mouse_release(self, mouse_pos: MousePos): - super()._handle_mouse_release(mouse_pos) - - if not self._can_click: - return - - # select current option - for btn in self._scroller._items: - btn = cast(BigDialogOptionButton, btn) - if btn.option == self._selected_option: - self._on_option_selected(btn.option) - break - - def _update_state(self): - super()._update_state() - - # get selection by whichever button is closest to center - center_y = self._rect.y + self._rect.height / 2 - closest_btn = (None, float('inf')) - for btn in self._scroller._items: - dist_y = abs((btn.rect.y + btn.rect.height / 2) - center_y) - if dist_y < closest_btn[1]: - closest_btn = (btn, dist_y) - - if closest_btn[0]: - for btn in self._scroller._items: - btn.set_selected(btn.option == closest_btn[0].option) - self._selected_option = closest_btn[0].option - - # Signal to subclasses if selection changed - if self._selected_option != self._last_selected_option: - self._selected_option_changed() - self._last_selected_option = self._selected_option - - def _render(self, _): - super()._render(_) - self._scroller.render(self._rect) - - return self._ret - - class BigDialogButton(BigButton): def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", description: str = ""): super().__init__(text, value, icon) @@ -399,4 +252,4 @@ class BigDialogButton(BigButton): super()._handle_mouse_release(mouse_pos) dlg = BigDialog(self.text, self._description) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 88bab2d001..991cb05a8c 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -7,7 +7,7 @@ from openpilot.common.api import Api from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets.label import MiciLabel @@ -19,7 +19,6 @@ class PairingDialog(NavWidget): def __init__(self): super().__init__() - self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) self._params = Params() self._qr_texture: rl.Texture | None = None self._last_qr_generation = float("-inf") @@ -69,10 +68,10 @@ class PairingDialog(NavWidget): def _update_state(self): super()._update_state() - if ui_state.prime_state.is_paired(): - self._playing_dismiss_animation = True + if ui_state.prime_state.is_paired() and not self.is_dismissing: + self.dismiss() - def _render(self, rect: rl.Rectangle) -> int: + def _render(self, rect: rl.Rectangle): self._check_qr_refresh() self._render_qr_code() @@ -85,8 +84,6 @@ class PairingDialog(NavWidget): rl.draw_texture_ex(self._txt_pair, rl.Vector2(label_x, self._rect.y + self._rect.height - self._txt_pair.height - 16), 0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.35))) - return -1 - def _render_qr_code(self) -> None: if not self._qr_texture: error_font = gui_app.font(FontWeight.BOLD) @@ -107,10 +104,9 @@ class PairingDialog(NavWidget): if __name__ == "__main__": gui_app.init_window("pairing device") pairing = PairingDialog() + gui_app.push_widget(pairing) try: for _ in gui_app.render(): - result = pairing.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - if result != -1: - break + pass finally: del pairing diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 76e7b078d1..8abc8fbb52 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -15,6 +15,7 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCamera from openpilot.common.transformations.orientation import rot_from_euler if gui_app.sunnypilot_ui(): + from openpilot.selfdrive.ui.sunnypilot.onroad.alert_renderer import AlertRendererSP as AlertRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP, AugmentedRoadViewSP from openpilot.selfdrive.ui.sunnypilot.onroad.driver_state import DriverStateRendererSP as DriverStateRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer @@ -238,6 +239,7 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP): if __name__ == "__main__": gui_app.init_window("OnRoad Camera View") road_camera_view = AugmentedRoadView(ROAD_CAM) + gui_app.push_widget(road_camera_view) print("***press space to switch camera view***") try: for _ in gui_app.render(): @@ -246,6 +248,5 @@ if __name__ == "__main__": if WIDE_CAM in road_camera_view.available_streams: stream = ROAD_CAM if road_camera_view.stream_type == WIDE_CAM else WIDE_CAM road_camera_view.switch_stream(stream) - road_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) finally: road_camera_view.close() diff --git a/selfdrive/ui/onroad/driver_camera_dialog.py b/selfdrive/ui/onroad/driver_camera_dialog.py index f69ad8c49c..e66e04b824 100644 --- a/selfdrive/ui/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/onroad/driver_camera_dialog.py @@ -14,7 +14,7 @@ class DriverCameraDialog(CameraView): super().__init__("camerad", VisionStreamType.VISION_STREAM_DRIVER) self.driver_state_renderer = DriverStateRenderer() # TODO: this can grow unbounded, should be given some thought - device.add_interactive_timeout_callback(lambda: gui_app.set_modal_overlay(None)) + device.add_interactive_timeout_callback(gui_app.pop_widget) ui_state.params.put_bool("IsDriverViewEnabled", True) def hide_event(self): @@ -24,7 +24,7 @@ class DriverCameraDialog(CameraView): def _handle_mouse_release(self, _): super()._handle_mouse_release(_) - gui_app.set_modal_overlay(None) + gui_app.pop_widget() def __del__(self): self.close() @@ -103,9 +103,9 @@ if __name__ == "__main__": gui_app.init_window("Driver Camera View") driver_camera_view = DriverCameraDialog() + gui_app.push_widget(driver_camera_view) try: for _ in gui_app.render(): ui_state.update() - driver_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) finally: driver_camera_view.close() diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index c41fec2676..cea22655b1 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -23,11 +23,12 @@ MIN_VOLUME = 0.1 SELFDRIVE_STATE_TIMEOUT = 5 # 5 seconds FILTER_DT = 1. / (micd.SAMPLE_RATE / micd.FFT_SAMPLES) -AMBIENT_DB = 30 # DB where MIN_VOLUME is applied +AMBIENT_DB = 24 # DB where MIN_VOLUME is applied DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied VOLUME_BASE = 20 if HARDWARE.get_device_type() == "tizi": + AMBIENT_DB = 30 VOLUME_BASE = 10 AudibleAlert = car.CarControl.HUDControl.AudibleAlert @@ -65,7 +66,7 @@ def check_selfdrive_timeout_alert(sm): ss_missing = time.monotonic() - sm.recv_time['selfdriveState'] if ss_missing > SELFDRIVE_STATE_TIMEOUT: - if sm['selfdriveState'].enabled and (ss_missing - SELFDRIVE_STATE_TIMEOUT) < 10: + if (sm['selfdriveState'].enabled or sm['selfdriveStateSP'].mads.enabled) and (ss_missing - SELFDRIVE_STATE_TIMEOUT) < 10: return True return False @@ -158,7 +159,7 @@ class Soundd(QuietMode): # sounddevice must be imported after forking processes import sounddevice as sd - sm = messaging.SubMaster(['selfdriveState', 'soundPressure']) + sm = messaging.SubMaster(['selfdriveState', 'selfdriveStateSP', 'soundPressure']) with self.get_stream(sd) as stream: rk = Ratekeeper(20) diff --git a/selfdrive/ui/sunnypilot/layouts/onboarding.py b/selfdrive/ui/sunnypilot/layouts/onboarding.py index 9ba9f07494..eed3cfd6b3 100644 --- a/selfdrive/ui/sunnypilot/layouts/onboarding.py +++ b/selfdrive/ui/sunnypilot/layouts/onboarding.py @@ -96,8 +96,6 @@ class SunnylinkConsentPage(Widget): self._primary_btn.set_text(step_data["primary_btn"]) self._primary_btn.render(rl.Rectangle(self._rect.x + 45 * 2 + btn_width, btn_y, btn_width, 160)) - return -1 - class SunnylinkOnboarding: def __init__(self): diff --git a/selfdrive/ui/sunnypilot/layouts/settings/developer.py b/selfdrive/ui/sunnypilot/layouts/settings/developer.py index f3a2433373..4cac66e316 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/developer.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/developer.py @@ -69,8 +69,8 @@ class DeveloperLayoutSP(DeveloperLayout): def _on_error_log_closed(self, result, log_exists): if result == DialogResult.CONFIRM and log_exists: - dialog2 = ConfirmDialog(tr("Would you like to delete this log?"), tr("Yes"), tr("No"), rich=False) - gui_app.set_modal_overlay(dialog2, callback=self._on_delete_confirm) + dialog2 = ConfirmDialog(tr("Would you like to delete this log?"), tr("Yes"), tr("No"), rich=False, callback=self._on_delete_confirm) + gui_app.push_widget(dialog2) def _on_error_log_clicked(self): text = "" @@ -82,7 +82,7 @@ class DeveloperLayoutSP(DeveloperLayout): except Exception: pass dialog = HtmlModalSP(text=text, callback=lambda result: self._on_error_log_closed(result, os.path.exists(self.error_log_path))) - gui_app.set_modal_overlay(dialog) + gui_app.push_widget(dialog) def _update_state(self): disable_updates = ui_state.params.get_bool("DisableUpdates") diff --git a/selfdrive/ui/sunnypilot/layouts/settings/device.py b/selfdrive/ui/sunnypilot/layouts/settings/device.py index 8f9d0ae22b..1fb9314739 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/device.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/device.py @@ -5,6 +5,7 @@ This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout +from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app @@ -81,7 +82,7 @@ class DeviceLayoutSP(DeviceLayout): left_text=lambda: tr("Quiet Mode"), right_text=lambda: tr("Driver Camera Preview"), left_callback=lambda: ui_state.params.put_bool("QuietMode", not ui_state.params.get_bool("QuietMode")), - right_callback=self._show_driver_camera + right_callback=lambda: gui_app.push_widget(DriverCameraDialog()) ) self._quiet_mode_and_dcam.action_item.right_button.set_button_style(ButtonStyle.NORMAL) @@ -153,20 +154,20 @@ class DeviceLayoutSP(DeviceLayout): def _second_confirm(result: int): if result == DialogResult.CONFIRM: - gui_app.set_modal_overlay(ConfirmDialog( + gui_app.push_widget(ConfirmDialog( text=tr("The reset cannot be undone. You have been warned."), - confirm_text=tr("Confirm") - ), callback=_do_reset) + confirm_text=tr("Confirm"), callback=_do_reset + )) - gui_app.set_modal_overlay(ConfirmDialog( + gui_app.push_widget(ConfirmDialog( text=tr("Are you sure you want to reset all sunnypilot settings to default? Once the settings are reset, there is no going back."), - confirm_text=tr("Reset") - ), callback=_second_confirm) + confirm_text=tr("Reset"), callback=_second_confirm + )) @staticmethod def _handle_always_offroad(): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Enter Always Offroad Mode"))) + gui_app.push_widget(alert_dialog(tr("Disengage to Enter Always Offroad Mode"))) return _offroad_mode_state = ui_state.params.get_bool("OffroadMode") @@ -177,7 +178,7 @@ class DeviceLayoutSP(DeviceLayout): if result == DialogResult.CONFIRM and not ui_state.engaged: ui_state.params.put_bool("OffroadMode", not _offroad_mode_state) - gui_app.set_modal_overlay(ConfirmDialog(_offroad_mode_str, tr("Confirm")), callback=lambda result: _set_always_offroad(result)) + gui_app.push_widget(ConfirmDialog(_offroad_mode_str, tr("Confirm"), callback=lambda result: _set_always_offroad(result))) @staticmethod def _update_max_time_offroad_label(value: int) -> str: diff --git a/selfdrive/ui/sunnypilot/layouts/settings/display.py b/selfdrive/ui/sunnypilot/layouts/settings/display.py index e5f1eba181..d44118d8f4 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/display.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/display.py @@ -19,6 +19,7 @@ ONROAD_BRIGHTNESS_TIMER_VALUES = {0: 15, 1: 30, **{i: (i - 1) * 60 for i in rang class OnroadBrightness(IntEnum): AUTO = 0 AUTO_DARK = 1 + SCREEN_OFF = 2 class DisplayLayout(Widget): @@ -35,7 +36,7 @@ class DisplayLayout(Widget): title=lambda: tr("Onroad Brightness"), description="", min_value=0, - max_value=21, + max_value=22, value_change_step=1, label_callback=lambda value: self.update_onroad_brightness(value), inline=True @@ -79,7 +80,10 @@ class DisplayLayout(Widget): if val == OnroadBrightness.AUTO_DARK: return tr("Auto (Dark)") - return f"{(val - 1) * 5} %" + if val == OnroadBrightness.SCREEN_OFF: + return tr("Screen Off") + + return f"{(val - 2) * 5} %" def _update_state(self): super()._update_state() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/models.py b/selfdrive/ui/sunnypilot/layouts/settings/models.py index 5820b34cc0..bf6af56941 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/models.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/models.py @@ -61,7 +61,7 @@ class ModelsLayout(Widget): self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "", lambda: (ui_state.params.put("ModelManager_LastSyncTime", 0), - gui_app.set_modal_overlay(alert_dialog(tr("Fetching Latest Models"))))) + gui_app.push_widget(alert_dialog(tr("Fetching Latest Models"))))) self.clear_cache_item = ListItemSP( title=tr("Clear Model Cache"), @@ -99,7 +99,7 @@ class ModelsLayout(Widget): "Keeping this on provides the stock openpilot experience.") if lagd_toggle: desc += f"
{tr('Live Steer Delay:')} {ui_state.sm['liveDelay'].lateralDelay:.3f} s" - elif ui_state.CP: + elif ui_state.CP is not None: sw = float(ui_state.params.get("LagdToggleDelay", "0.2")) cp = ui_state.CP.steerActuatorDelay desc += f"
{tr('Actuator Delay:')} {cp:.2f} s + {tr('Software Delay:')} {sw:.2f} s = {tr('Total Delay:')} {cp + sw:.2f} s" @@ -122,8 +122,9 @@ class ModelsLayout(Widget): ui_state.params.put_bool("ModelManager_ClearCache", True) self.clear_cache_item.action_item.set_value(f"{self._calculate_cache_size():.2f} MB") - gui_app.set_modal_overlay(ConfirmDialog(tr("This will delete ALL downloaded models from the cache except the currently active model. Are you sure?"), - tr("Clear Cache")), callback=_callback) + dialog = ConfirmDialog(tr("This will delete ALL downloaded models from the cache except the currently active model. Are you sure?"), + tr("Clear Cache"), callback=_callback) + gui_app.push_widget(dialog) def _handle_bundle_download_progress(self): labels = {custom.ModelManagerSP.Model.Type.supercombo: self.supercombo_label, @@ -176,7 +177,8 @@ class ModelsLayout(Widget): ui_state.params.remove("CalibrationParams") ui_state.params.remove("LiveTorqueParameters") msg = tr("Model download has started in the background. We suggest resetting calibration. Would you like to do that now?") - gui_app.set_modal_overlay(ConfirmDialog(msg, tr("Reset Calibration")), callback=_callback) + dialog = ConfirmDialog(msg, tr("Reset Calibration"), callback=_callback) + gui_app.push_widget(dialog) def _on_model_selected(self, result): if result != DialogResult.CONFIRM: @@ -219,7 +221,7 @@ class ModelsLayout(Widget): active_ref = self.model_manager.activeBundle.ref if self.model_manager.activeBundle else "Default" self.model_dialog = TreeOptionDialog(tr("Select a Model"), folders_list, active_ref, "ModelManager_Favs", get_folders_fn=self._get_folders, on_exit=self._on_model_selected) - gui_app.set_modal_overlay(self.model_dialog, callback=self._on_model_selected) + gui_app.push_widget(self.model_dialog) def _update_state(self): advanced_controls: bool = ui_state.params.get_bool("ShowAdvancedControls") diff --git a/selfdrive/ui/sunnypilot/layouts/settings/osm.py b/selfdrive/ui/sunnypilot/layouts/settings/osm.py index f8a3a85042..58236961b9 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/osm.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/osm.py @@ -56,7 +56,7 @@ class OSMLayout(Widget): self.items = [self._mapd_version, self._delete_maps_btn, self._progress, self._update_btn, self._country_btn, self._state_btn] def _show_confirm(self, msg, confirm_text, func): - gui_app.set_modal_overlay(ConfirmDialog(msg, confirm_text), lambda res: func() if res == DialogResult.CONFIRM else None) + gui_app.push_widget(ConfirmDialog(msg, confirm_text, callback=lambda res: func() if res == DialogResult.CONFIRM else None)) def calculate_size(self): total_size = 0 @@ -150,7 +150,7 @@ class OSMLayout(Widget): dialog = TreeOptionDialog(tr(f"Select {region_type}"), [TreeFolder(folder="", nodes=locations)], current_ref=current, search_prompt="Perform a search") dialog.on_exit = lambda res: self._handle_region_selection(region_type, locations, key, res, dialog.selection_ref) - gui_app.set_modal_overlay(dialog, callback=lambda res: self._handle_region_selection(region_type, locations, key, res, dialog.selection_ref)) + gui_app.push_widget(dialog) def _update_labels(self): downloading = bool(self._mem_params.get("OSMDownloadLocations")) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/software.py b/selfdrive/ui/sunnypilot/layouts/settings/software.py index b890dd5b5b..7765a58b65 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/software.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/software.py @@ -47,8 +47,8 @@ class SoftwareLayoutSP(SoftwareLayout): self.disable_updates_toggle.action_item.set_state(ui_state.params.get_bool("DisableUpdates")) def _on_disable_updates_toggled(self, enabled): - dialog = ConfirmDialog(tr("System reboot required for changes to take effect. Reboot now?"), tr("Reboot")) - gui_app.set_modal_overlay(dialog, callback=self._handle_reboot) + dialog = ConfirmDialog(tr("System reboot required for changes to take effect. Reboot now?"), tr("Reboot"), callback=self._handle_reboot) + gui_app.push_widget(dialog) def _on_select_branch(self): current_git_branch = ui_state.params.get("GitBranch") or "" @@ -84,7 +84,7 @@ class SoftwareLayoutSP(SoftwareLayout): self._branch_dialog = TreeOptionDialog(tr("Select a branch"), folders, current_target, "", on_exit=_on_branch_selected) - gui_app.set_modal_overlay(self._branch_dialog, callback=_on_branch_selected) + gui_app.push_widget(self._branch_dialog) def _update_state(self): super()._update_state() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/lane_change_settings.py b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/lane_change_settings.py index 9a5d5202a5..fbb9ce7cf7 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/lane_change_settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/lane_change_settings.py @@ -75,7 +75,7 @@ class LaneChangeSettingsLayout(Widget): self._scroller.show_event() def _update_toggles(self): - enable_bsm = ui_state.CP and ui_state.CP.enableBsm + enable_bsm = ui_state.CP is not None and ui_state.CP.enableBsm if not enable_bsm and ui_state.params.get_bool("AutoLaneChangeBsmDelay"): ui_state.params.remove("AutoLaneChangeBsmDelay") self._bsm_delay.action_item.set_enabled(enable_bsm and ui_state.params.get("AutoLaneChangeTimer", return_default=True) > AutoLaneChangeMode.NUDGE) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py index 3542adf285..098fcf8ce8 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py @@ -91,12 +91,12 @@ class MadsSettingsLayout(Widget): if bundle: brand = bundle.get("brand", "") if not brand: - brand = ui_state.CP.brand if ui_state.CP else "" + brand = ui_state.CP.brand if ui_state.CP is not None else "" if brand == "rivian": return True elif brand == "tesla": - return not (ui_state.CP_SP and ui_state.CP_SP.flags & TeslaFlagsSP.HAS_VEHICLE_BUS) + return not (ui_state.CP_SP is not None and ui_state.CP_SP.flags & TeslaFlagsSP.HAS_VEHICLE_BUS) return False def _update_steering_mode_description(self, button_index: int): diff --git a/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py index dceee8b518..d4976b1295 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py @@ -4,15 +4,24 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ +import json +import math +import os from collections.abc import Callable import pyray as rl +from openpilot.common.basedir import BASEDIR from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, option_item_sp +from openpilot.system.ui.sunnypilot.lib.utils import NoElideButtonAction +from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP, toggle_item_sp, option_item_sp +from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeFolder, TreeNode +from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.network import NavButton from openpilot.system.ui.widgets.scroller_tici import Scroller -from openpilot.system.ui.widgets import Widget + +TORQUE_VERSIONS_PATH = os.path.join(BASEDIR, "sunnypilot", "selfdrive", "controls", "lib", "latcontrol_torque_versions.json") class TorqueSettingsLayout(Widget): @@ -20,10 +29,23 @@ class TorqueSettingsLayout(Widget): super().__init__() self._back_button = NavButton(tr("Back")) self._back_button.set_click_callback(back_btn_callback) + self._torque_version_dialog: TreeOptionDialog | None = None + self.cached_torque_versions = {} + self._load_versions() items = self._initialize_items() self._scroller = Scroller(items, line_separator=True, spacing=0) + def _load_versions(self): + with open(TORQUE_VERSIONS_PATH) as f: + self.cached_torque_versions = json.load(f) + def _initialize_items(self): + self._torque_control_versions = ListItemSP( + title=tr("Torque Control Tune Version"), + description="Select the version of Torque Control Tune to use.", + action_item=NoElideButtonAction(tr("SELECT")), + callback=self._show_torque_version_dialog, + ) self._self_tune_toggle = toggle_item_sp( param="LiveTorqueParamsToggle", title=lambda: tr("Self-Tune"), @@ -73,6 +95,7 @@ class TorqueSettingsLayout(Widget): ) items = [ + self._torque_control_versions, self._self_tune_toggle, self._relaxed_tune_toggle, self._custom_tune_toggle, @@ -103,6 +126,7 @@ class TorqueSettingsLayout(Widget): title_text = tr("Real-Time & Offline") if ui_state.params.get("TorqueParamsOverrideEnabled") else tr("Offline Only") self._torque_lat_accel_factor.set_title(lambda: tr("Lateral Acceleration Factor") + " (" + title_text + ")") self._torque_friction.set_title(lambda: tr("Friction") + " (" + title_text + ")") + self._torque_control_versions.action_item.set_value(self._get_current_torque_version_label()) def _render(self, rect): self._back_button.set_position(self._rect.x, self._rect.y + 20) @@ -113,3 +137,55 @@ class TorqueSettingsLayout(Widget): def show_event(self): self._scroller.show_event() + + def _get_current_torque_version_label(self): + current_val_bytes = ui_state.params.get("TorqueControlTune") + if current_val_bytes is None: + return tr("Default") + + try: + current_val = float(current_val_bytes) + for label, info in self.cached_torque_versions.items(): + if math.isclose(float(info["version"]), current_val, rel_tol=1e-5): + return label + except (ValueError, KeyError): + pass + + return tr("Default") + + def _show_torque_version_dialog(self): + options_map = {} + for label, info in self.cached_torque_versions.items(): + try: + options_map[label] = float(info["version"]) + except (ValueError, KeyError): + pass + + # Sort options by label in descending order + sorted_labels = sorted(options_map.keys(), key=lambda k: options_map[k], reverse=True) + + nodes = [TreeNode(tr("Default"))] + for label in sorted_labels: + nodes.append(TreeNode(label)) + + folders = [TreeFolder("", nodes)] + + current_label = self._get_current_torque_version_label() + + def handle_selection(result: int): + if result == DialogResult.CONFIRM and self._torque_version_dialog: + selected_ref = self._torque_version_dialog.selection_ref + if selected_ref == tr("Default"): + ui_state.params.remove("TorqueControlTune") + elif selected_ref in options_map: + ui_state.params.put("TorqueControlTune", options_map[selected_ref]) + self._torque_version_dialog = None + + self._torque_version_dialog = TreeOptionDialog( + tr("Select Torque Control Tune Version"), + folders, + current_ref=current_label, + option_font_weight=FontWeight.UNIFONT, + on_exit=handle_selection, + ) + gui_app.push_widget(self._torque_version_dialog) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py index 00baf0cccf..5edd2e5b89 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py @@ -215,21 +215,23 @@ class SunnylinkLayout(Widget): def _handle_pair_btn(self, sponsor_pairing: bool = False): sunnylink_dongle_id = self._get_sunnylink_dongle_id() if sunnylink_dongle_id == UNREGISTERED_SUNNYLINK_DONGLE_ID: - gui_app.set_modal_overlay(alert_dialog(message=tr("sunnylink Dongle ID not found. ") + + gui_app.push_widget(alert_dialog(message=tr("sunnylink Dongle ID not found. ") + tr("This may be due to weak internet connection or sunnylink registration issue. ") + tr("Please reboot and try again."))) elif not self._sunnylink_pairing_dialog: self._sunnylink_pairing_dialog = SunnylinkPairingDialog(sponsor_pairing) - gui_app.set_modal_overlay(self._sunnylink_pairing_dialog, callback=lambda result: setattr(self, '_sunnylink_pairing_dialog', None)) + gui_app.push_widget(self._sunnylink_pairing_dialog) def _handle_backup_btn(self): - backup_dialog = ConfirmDialog(text=tr("Are you sure you want to backup your current sunnypilot settings?"), confirm_text="Backup") - gui_app.set_modal_overlay(backup_dialog, callback=self._backup_handler) + backup_dialog = ConfirmDialog(text=tr("Are you sure you want to backup your current sunnypilot settings?"), confirm_text="Backup", + callback=self._backup_handler) + gui_app.push_widget(backup_dialog) def _handle_restore_btn(self): self._restore_btn.set_enabled(False) - restore_dialog = ConfirmDialog(text=tr("Are you sure you want to restore the last backed up sunnypilot settings?"), confirm_text="Restore") - gui_app.set_modal_overlay(restore_dialog, callback=self._restore_handler) + restore_dialog = ConfirmDialog(text=tr("Are you sure you want to restore the last backed up sunnypilot settings?"), + confirm_text="Restore", callback=self._restore_handler) + gui_app.push_widget(restore_dialog) def _backup_handler(self, dialog_result: int): if dialog_result == DialogResult.CONFIRM: @@ -269,7 +271,7 @@ class SunnylinkLayout(Widget): (backup_status == custom.BackupManagerSP.Status.idle and backup_progress == 100.0)): self._backup_in_progress = False dialog = alert_dialog(tr("Settings backup completed.")) - gui_app.set_modal_overlay(dialog) + gui_app.push_widget(dialog) self._backup_btn.set_enabled(not ui_state.is_onroad()) elif self._restore_in_progress: @@ -286,13 +288,13 @@ class SunnylinkLayout(Widget): self._restore_btn.set_enabled(not ui_state.is_onroad()) self._restore_btn.set_text(tr("Restore Failed")) dialog = alert_dialog(tr("Unable to restore the settings, try again later.")) - gui_app.set_modal_overlay(dialog) + gui_app.push_widget(dialog) elif (restore_status == custom.BackupManagerSP.Status.completed or (restore_status == custom.BackupManagerSP.Status.idle and restore_progress == 100.0)): self._restore_in_progress = False - dialog = alert_dialog(tr("Settings restored. Confirm to restart the interface.")) - gui_app.set_modal_overlay(dialog, callback=lambda: gui_app.request_close()) + dialog = ConfirmDialog(tr("Settings restored. Confirm to restart the interface."), tr("OK"), cancel_text="", callback=lambda _: gui_app.request_close()) + gui_app.push_widget(dialog) else: can_enable = self._sunnylink_enabled and not ui_state.is_onroad() @@ -309,10 +311,10 @@ class SunnylinkLayout(Widget): def on_consent_done(): enabled = ui_state.params.get_bool("SunnylinkEnabled") self._update_description(enabled) - gui_app.set_modal_overlay(None) + gui_app.pop_widget() sl_terms_dlg = SunnylinkConsentPage(done_callback=on_consent_done) - gui_app.set_modal_overlay(sl_terms_dlg) + gui_app.push_widget(sl_terms_dlg) else: ui_state.params.put_bool("SunnylinkEnabled", state) self._update_description(state) @@ -355,5 +357,10 @@ class SunnylinkLayout(Widget): def show_event(self): super().show_event() + ui_state.sunnylink_state.set_settings_open(True) self._scroller.show_event() self._sunnylink_description.set_visible(False) + + def hide_event(self): + super().hide_event() + ui_state.sunnylink_state.set_settings_open(False) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/trips.py b/selfdrive/ui/sunnypilot/layouts/settings/trips.py index b389892d5d..da9eb42d29 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/trips.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/trips.py @@ -147,5 +147,3 @@ class TripsLayout(Widget): y = self._render_stat_group(x, y, w, card_height, tr("ALL TIME"), all_time, is_metric) y += spacing y = self._render_stat_group(x, y, w, card_height, tr("PAST WEEK"), week, is_metric) - - return -1 diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py index 0dd12d76f4..d8fdd61e72 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py @@ -35,7 +35,7 @@ class VehicleLayout(Widget): def get_brand(): if bundle := ui_state.params.get("CarPlatformBundle"): return bundle.get("brand", "") - elif ui_state.CP and ui_state.CP.carFingerprint != "MOCK": + elif ui_state.CP is not None and ui_state.CP.carFingerprint != "MOCK": return ui_state.CP.brand return "" diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/hyundai.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/hyundai.py index f6849eb201..351dacbbc2 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/hyundai.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/hyundai.py @@ -32,7 +32,7 @@ class HyundaiSettings(BrandSettings): if bundle: platform = bundle.get("platform") self.alpha_long_available = CAR[platform] not in (UNSUPPORTED_LONGITUDINAL_CAR | CANFD_UNSUPPORTED_LONGITUDINAL_CAR) - elif ui_state.CP: + elif ui_state.CP is not None: self.alpha_long_available = ui_state.CP.alphaLongitudinalAvailable tuning_param = int(ui_state.params.get("HyundaiLongitudinalTuning") or "0") diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/subaru.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/subaru.py index 66e7ec1d5a..5ef53a0e43 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/subaru.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/subaru.py @@ -39,7 +39,7 @@ class SubaruSettings(BrandSettings): platform = bundle.get("platform") config = CAR[platform].config self.has_stop_and_go = not (config.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID)) - elif ui_state.CP: + elif ui_state.CP is not None: self.has_stop_and_go = not (ui_state.CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID)) disabled_msg = self.stop_and_go_disabled_msg() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py index ac3d04f367..5a696466b1 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py @@ -13,10 +13,17 @@ from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp +ONROAD_ONLY_DESCRIPTION = tr_noop("Start the vehicle to check vehicle compatibility.") +SNG_HACK_UNAVAILABLE = tr_noop("sunnypilot Longitudinal Control must be available and enabled for your vehicle to use this feature.") + DESCRIPTIONS = { 'enforce_stock_longitudinal': tr_noop( 'sunnypilot will not take over control of gas and brakes. Factory Toyota longitudinal control will be used.' ), + 'stop_and_go_hack': tr_noop( + 'sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. ' + + 'This feature is only applicable to certain models that are able to use longitudinal control. This is an alpha feature. Use at your own risk.' + ) } @@ -32,7 +39,18 @@ class ToyotaSettings(BrandSettings): enabled=lambda: not ui_state.engaged, ) - self.items = [self.enforce_stock_longitudinal, ] + self.stop_and_go_hack = toggle_item_sp( + lambda: tr("Stop and Go Hack (Alpha)"), + description=lambda: tr(DESCRIPTIONS["stop_and_go_hack"]), + initial_state=ui_state.params.get_bool("ToyotaStopAndGoHack"), + callback=self._on_enable_stop_and_go_hack, + enabled=lambda: not ui_state.engaged, + ) + + self.items = [ + self.enforce_stock_longitudinal, + self.stop_and_go_hack, + ] def _on_enable_enforce_stock_longitudinal(self, state: bool): if state: @@ -41,6 +59,8 @@ class ToyotaSettings(BrandSettings): ui_state.params.put_bool("ToyotaEnforceStockLongitudinal", True) if ui_state.params.get_bool("AlphaLongitudinalEnabled"): ui_state.params.put_bool("AlphaLongitudinalEnabled", False) + ui_state.params.put_bool("ToyotaStopAndGoHack", False) + self.stop_and_go_hack.action_item.set_state(False) ui_state.params.put_bool("OnroadCycleRequested", True) else: self.enforce_stock_longitudinal.action_item.set_state(False) @@ -48,12 +68,54 @@ class ToyotaSettings(BrandSettings): content = (f"

{self.enforce_stock_longitudinal.title}


" + f"

{self.enforce_stock_longitudinal.description}

") - dlg = ConfirmDialog(content, tr("Enable"), rich=True) - gui_app.set_modal_overlay(dlg, callback=confirm_callback) + dlg = ConfirmDialog(content, tr("Enable"), rich=True, callback=confirm_callback) + gui_app.push_widget(dlg) else: ui_state.params.put_bool("ToyotaEnforceStockLongitudinal", False) ui_state.params.put_bool("OnroadCycleRequested", True) + def _on_enable_stop_and_go_hack(self, state: bool): + if state: + def confirm_callback(result: int): + if result == DialogResult.CONFIRM: + ui_state.params.put_bool("ToyotaStopAndGoHack", True) + ui_state.params.put_bool("OnroadCycleRequested", True) + else: + self.stop_and_go_hack.action_item.set_state(False) + + content = (f"

{self.stop_and_go_hack.title}


" + + f"

{self.stop_and_go_hack.description}

") + + dlg = ConfirmDialog(content, tr("Enable"), rich=True, callback=confirm_callback) + gui_app.push_widget(dlg) + + else: + ui_state.params.put_bool("ToyotaStopAndGoHack", False) + ui_state.params.put_bool("OnroadCycleRequested", True) + def update_settings(self): - pass + if ui_state.CP is not None: + longitudinal = ui_state.CP.openpilotLongitudinalControl + enforce_stock = self.enforce_stock_longitudinal.action_item.get_state() + + if longitudinal and not enforce_stock: + self.stop_and_go_hack.action_item.set_enabled(not ui_state.engaged) + new_desc = tr(DESCRIPTIONS["stop_and_go_hack"]) + show_desc = False + else: + self.stop_and_go_hack.action_item.set_enabled(False) + self.stop_and_go_hack.action_item.set_state(False) + new_desc = "" + tr(SNG_HACK_UNAVAILABLE) + "\n\n" + tr(DESCRIPTIONS["stop_and_go_hack"]) + show_desc = True + + if self.stop_and_go_hack.description != new_desc: + self.stop_and_go_hack.set_description(new_desc) + if show_desc: + self.stop_and_go_hack.show_description(True) + else: + self.stop_and_go_hack.action_item.set_enabled(False) + new_desc = "" + tr(ONROAD_ONLY_DESCRIPTION) + "\n\n" + tr(DESCRIPTIONS["stop_and_go_hack"]) + if self.stop_and_go_hack.description != new_desc: + self.stop_and_go_hack.set_description(new_desc) + self.stop_and_go_hack.show_description(True) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py index db90595274..6102e8e9b7 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py @@ -95,10 +95,9 @@ class PlatformSelector(Button): offroad_msg = tr("This setting will take effect immediately.") if ui_state.is_offroad else \ tr("This setting will take effect once the device enters offroad state.") - confirm_dialog = ConfirmDialog(offroad_msg, tr("Confirm")) - callback = partial(self._confirm_platform, dialog.selection_ref) - gui_app.set_modal_overlay(confirm_dialog, callback=callback) + confirm_dialog = ConfirmDialog(offroad_msg, tr("Confirm"), callback=callback) + gui_app.push_widget(confirm_dialog) def _confirm_platform(self, platform_name, res): if res == DialogResult.CONFIRM: @@ -120,7 +119,7 @@ class PlatformSelector(Button): ) callback = partial(self._on_platform_selected, dialog) dialog.on_exit = callback - gui_app.set_modal_overlay(dialog, callback=callback) + gui_app.push_widget(dialog) def refresh(self): self.color = style.YELLOW @@ -131,7 +130,7 @@ class PlatformSelector(Button): self._platform = bundle.get("name", "") self.set_text(self._platform) self.color = style.BLUE - elif ui_state.CP and ui_state.CP.carFingerprint != "MOCK": + elif ui_state.CP is not None and ui_state.CP.carFingerprint != "MOCK": self._platform = ui_state.CP.carFingerprint self.set_text(self._platform) self.color = style.GREEN diff --git a/selfdrive/ui/sunnypilot/mici/layouts/models.py b/selfdrive/ui/sunnypilot/mici/layouts/models.py index d5964ea964..5d8de4381a 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/models.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/models.py @@ -10,11 +10,10 @@ from cereal import custom from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.widgets import NavWidget, Widget -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller import NavScroller -class ModelsLayoutMici(NavWidget): +class ModelsLayoutMici(NavScroller): def __init__(self, back_callback: Callable): super().__init__() self.set_back_callback(back_callback) @@ -27,8 +26,8 @@ class ModelsLayoutMici(NavWidget): self.cancel_download_btn = BigButton(tr("cancel download"), "", "") self.cancel_download_btn.set_click_callback(lambda: ui_state.params.remove("ModelManager_DownloadIndex")) - self.main_items: list[Widget] = [self.current_model_btn, self.cancel_download_btn] - self._scroller = Scroller(self.main_items, snap_items=False) + self.main_items = [self.current_model_btn, self.cancel_download_btn] + self._scroller.add_widgets(self.main_items) @property def model_manager(self): @@ -42,7 +41,7 @@ class ModelsLayoutMici(NavWidget): folders.setdefault(folder, []).append(bundle) return folders - def _show_selection_view(self, items: list[Widget], back_callback: Callable): + def _show_selection_view(self, items, back_callback: Callable): self._scroller._items = items for item in items: item.set_touch_valid_callback(lambda: self._scroller.scroll_panel.is_touch_valid() and self._scroller.enabled) @@ -88,7 +87,7 @@ class ModelsLayoutMici(NavWidget): self._scroller._items = self.main_items self.set_back_callback(self.original_back_callback) if self.focused_widget and self.focused_widget in self.main_items: - x = self._scroller._pad_start + x = self._scroller._pad for item in self.main_items: if not item.is_visible: continue @@ -113,10 +112,3 @@ class ModelsLayoutMici(NavWidget): self.cancel_download_btn.set_visible(False) self.current_model_btn.set_enabled(ui_state.is_offroad()) self.current_model_btn.set_text(tr("current model")) - - def _render(self, rect): - self._scroller.render(rect) - - def show_event(self): - super().show_event() - self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/mici/layouts/onboarding.py b/selfdrive/ui/sunnypilot/mici/layouts/onboarding.py index 930853e55c..03729f0f2f 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/onboarding.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/onboarding.py @@ -28,10 +28,6 @@ class SunnylinkConsentPage(SetupTermsPage): def _content_height(self): return self._terms_label.rect.y + self._terms_label.rect.height - self._scroll_panel.get_offset() - def _render(self, _): - super()._render(_) - return -1 - def _render_content(self, scroll_offset): self._title_header.set_position(self._rect.x + 16, self._rect.y + 12 + scroll_offset) self._title_header.render() diff --git a/selfdrive/ui/sunnypilot/mici/layouts/settings.py b/selfdrive/ui/sunnypilot/mici/layouts/settings.py index 69982e2298..43d0bd2cef 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/settings.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/settings.py @@ -4,39 +4,26 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ -from enum import IntEnum - from openpilot.selfdrive.ui.mici.layouts.settings import settings as OP from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.selfdrive.ui.sunnypilot.mici.layouts.sunnylink import SunnylinkLayoutMici from openpilot.selfdrive.ui.sunnypilot.mici.layouts.models import ModelsLayoutMici +from openpilot.system.ui.lib.application import gui_app ICON_SIZE = 70 -OP.PanelType = IntEnum( - "PanelType", - [es.name for es in OP.PanelType] + [ - "SUNNYLINK", - "MODELS", - ], - start=0, -) - class SettingsLayoutSP(OP.SettingsLayout): def __init__(self): OP.SettingsLayout.__init__(self) + sunnylink_panel = SunnylinkLayoutMici(back_callback=gui_app.pop_widget) sunnylink_btn = BigButton("sunnylink", "", "icons_mici/settings/developer/ssh.png") - sunnylink_btn.set_click_callback(lambda: self._set_current_panel(OP.PanelType.SUNNYLINK)) + sunnylink_btn.set_click_callback(lambda: gui_app.push_widget(sunnylink_panel)) + models_panel = ModelsLayoutMici(back_callback=gui_app.pop_widget) models_btn = BigButton("models", "", "../../sunnypilot/selfdrive/assets/offroad/icon_models.png") - models_btn.set_click_callback(lambda: self._set_current_panel(OP.PanelType.MODELS)) - - self._panels.update({ - OP.PanelType.SUNNYLINK: OP.PanelInfo("sunnylink", SunnylinkLayoutMici(back_callback=lambda: self._set_current_panel(None))), - OP.PanelType.MODELS: OP.PanelInfo("models", ModelsLayoutMici(back_callback=lambda: self._set_current_panel(None))), - }) + models_btn.set_click_callback(lambda: gui_app.push_widget(models_panel)) items = self._scroller._items.copy() diff --git a/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py b/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py index 172ef7d2f8..c892db9960 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py @@ -6,7 +6,6 @@ See the LICENSE.md file in the root directory for more details. """ from collections.abc import Callable -import pyray as rl from cereal import custom from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 @@ -16,12 +15,11 @@ from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.system.ui.lib.application import gui_app, MousePos from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.widgets import NavWidget -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.system.version import sunnylink_consent_version, sunnylink_consent_declined -class SunnylinkLayoutMici(NavWidget): +class SunnylinkLayoutMici(NavScroller): def __init__(self, back_callback: Callable): super().__init__() self.set_back_callback(back_callback) @@ -41,14 +39,14 @@ class SunnylinkLayoutMici(NavWidget): self._sunnylink_uploader_toggle = BigToggle(text=tr("sunnylink uploader"), initial_state=False, toggle_callback=self._sunnylink_uploader_callback) - self._scroller = Scroller([ + self._scroller.add_widgets([ self._sunnylink_toggle, self._sunnylink_sponsor_button, self._sunnylink_pair_button, self._backup_btn, self._restore_btn, self._sunnylink_uploader_toggle - ], snap_items=False) + ]) def _update_state(self): super()._update_state() @@ -76,12 +74,8 @@ class SunnylinkLayoutMici(NavWidget): def show_event(self): super().show_event() - self._scroller.show_event() ui_state.update_params() - def _render(self, rect: rl.Rectangle): - self._scroller.render(rect) - @staticmethod def _sunnylink_toggle_callback(state: bool): sl_consent: bool = ui_state.params.get("CompletedSunnylinkConsentVersion") == sunnylink_consent_version @@ -90,16 +84,16 @@ class SunnylinkLayoutMici(NavWidget): def sl_terms_accepted(): ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version) ui_state.params.put_bool("SunnylinkEnabled", True) - gui_app.set_modal_overlay(None) + gui_app.pop_widget() def sl_terms_declined(): ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_declined) ui_state.params.put_bool("SunnylinkEnabled", False) - gui_app.set_modal_overlay(None) + gui_app.pop_widget() if state and not sl_consent and not sl_enabled: sl_terms_dlg = SunnylinkConsentPage(on_accept=sl_terms_accepted, on_decline=sl_terms_declined) - gui_app.set_modal_overlay(sl_terms_dlg) + gui_app.push_widget(sl_terms_dlg) else: ui_state.params.put_bool("SunnylinkEnabled", state) @@ -113,7 +107,7 @@ class SunnylinkLayoutMici(NavWidget): lbl = tr("slide to restore") if restore else tr("slide to backup") icon = "icons_mici/settings/device/update.png" dlg = BigConfirmationDialogV2(lbl, icon, confirm_callback=self._restore_handler if restore else self._backup_handler) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) def _backup_handler(self): self._backup_in_progress = True @@ -152,7 +146,7 @@ class SunnylinkLayoutMici(NavWidget): elif (backup_status == custom.BackupManagerSP.Status.completed or (backup_status == custom.BackupManagerSP.Status.idle and backup_progress == 100.0)): self._backup_in_progress = False - gui_app.set_modal_overlay(BigDialog(title=tr("settings backed up"), description="")) + gui_app.push_widget(BigDialog(title=tr("settings backed up"), description="")) self._backup_btn.set_enabled(not ui_state.is_onroad()) elif self._restore_in_progress: @@ -170,12 +164,12 @@ class SunnylinkLayoutMici(NavWidget): self._restore_btn.set_enabled(not ui_state.is_onroad()) self._restore_btn.set_text(tr("restore")) self._restore_btn.set_value(tr("failed")) - gui_app.set_modal_overlay(BigDialog(title=tr("unable to restore"), description="try again later.")) + gui_app.push_widget(BigDialog(title=tr("unable to restore"), description="try again later.")) elif (restore_status == custom.BackupManagerSP.Status.completed or (restore_status == custom.BackupManagerSP.Status.idle and restore_progress == 100.0)): self._restore_in_progress = False - gui_app.set_modal_overlay(BigConfirmationDialogV2( + gui_app.push_widget(BigConfirmationDialogV2( title="slide to restart", icon="icons_mici/settings/device/reboot.png", confirm_callback=lambda: gui_app.request_close())) @@ -208,4 +202,4 @@ class SunnylinkPairBigButton(BigButton): elif not self.sponsor_pairing: dlg = SunnylinkPairingDialog(sponsor_pairing=False) if dlg: - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) diff --git a/selfdrive/ui/sunnypilot/mici/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/mici/onroad/hud_renderer.py index de52bb4622..9d39d01727 100644 --- a/selfdrive/ui/sunnypilot/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/mici/onroad/hud_renderer.py @@ -24,4 +24,5 @@ class HudRendererSP(HudRenderer): self.blind_spot_indicators.render(rect) def _has_blind_spot_detected(self) -> bool: + return self.blind_spot_indicators.detected diff --git a/selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py b/selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py index e2cef2fa07..eb3c2bddfe 100644 --- a/selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py +++ b/selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py @@ -12,7 +12,7 @@ from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.sunnypilot.sunnylink.api import SunnylinkApi, UNREGISTERED_SUNNYLINK_DONGLE_ID, API_HOST from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import MiciLabel diff --git a/selfdrive/ui/sunnypilot/onroad/alert_renderer.py b/selfdrive/ui/sunnypilot/onroad/alert_renderer.py new file mode 100644 index 0000000000..9f48287a70 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/alert_renderer.py @@ -0,0 +1,103 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl +from openpilot.selfdrive.ui.onroad.alert_renderer import AlertRenderer, AlertSize, ALERT_FONT_MEDIUM, ALERT_FONT_BIG, \ + ALERT_FONT_SMALL, ALERT_MARGIN, ALERT_HEIGHTS, ALERT_PADDING, Alert +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.lib.wrap_text import wrap_text + +ALERT_LINE_SPACING = 15 + + +class AlertRendererSP(AlertRenderer): + def __init__(self): + super().__init__() + + def _draw_text(self, rect: rl.Rectangle, alert: Alert) -> None: + if alert.size == AlertSize.small: + self._draw_multiline_centered(alert.text1, rect, self.font_bold, ALERT_FONT_MEDIUM) + + elif alert.size == AlertSize.mid: + wrap_width = int(rect.width) + lines1 = wrap_text(self.font_bold, alert.text1, ALERT_FONT_BIG, wrap_width) + lines2 = wrap_text(self.font_regular, alert.text2, ALERT_FONT_SMALL, wrap_width) if alert.text2 else [] + + total_text_height = len(lines1) * measure_text_cached(self.font_bold, "A", ALERT_FONT_BIG).y + if lines2: + total_text_height += ALERT_LINE_SPACING + len(lines2) * measure_text_cached(self.font_regular, "A", ALERT_FONT_SMALL).y + + curr_y = rect.y + (rect.height - total_text_height) / 2 + + for line in lines1: + line_height = measure_text_cached(self.font_bold, alert.text1, ALERT_FONT_BIG).y + self._draw_line_centered(line, rl.Rectangle(rect.x, curr_y, rect.width, line_height), self.font_bold, ALERT_FONT_BIG) + curr_y += line_height + + if lines2: + curr_y += ALERT_LINE_SPACING + for line in lines2: + line_height = measure_text_cached(self.font_regular, alert.text2, ALERT_FONT_SMALL).y + self._draw_line_centered(line, rl.Rectangle(rect.x, curr_y, rect.width, line_height), self.font_regular, ALERT_FONT_SMALL) + curr_y += line_height + + else: + super()._draw_text(rect, alert) + + def _draw_multiline_centered(self, text, rect, font, font_size, color=rl.WHITE) -> None: + lines = wrap_text(font, text, font_size, rect.width) + line_height = measure_text_cached(font, text, font_size).y + total_height = len(lines) * line_height + curr_y = rect.y + (rect.height - total_height) / 2 + for line in lines: + self._draw_line_centered(line, rl.Rectangle(rect.x, curr_y, rect.width, line_height), font, font_size, color) + curr_y += line_height + + def _draw_line_centered(self, text, rect, font, font_size, color=rl.WHITE) -> None: + text_size = measure_text_cached(font, text, font_size) + x = rect.x + (rect.width - text_size.x) / 2 + y = rect.y + rl.draw_text_ex(font, text, rl.Vector2(x, y), font_size, 0, color) + + def _get_alert_rect(self, rect: rl.Rectangle, size: int) -> rl.Rectangle: + if size == AlertSize.full: + return rect + + dev_ui_info = ui_state.developer_ui + v_adjustment = 40 if dev_ui_info in {2, 3} and size != AlertSize.full else 0 + h_adjustment = 230 if dev_ui_info in {1, 3} and size != AlertSize.full else 0 + + w = int(rect.width - ALERT_MARGIN * 2 - h_adjustment) + h = self._calculate_dynamic_height(size, w) + return rl.Rectangle(rect.x + ALERT_MARGIN, rect.y + rect.height - h + ALERT_MARGIN - v_adjustment, w, + h - ALERT_MARGIN * 2) + + def _calculate_dynamic_height(self, size: int, width: int) -> int: + alert = self.get_alert(ui_state.sm) + if not alert: + return ALERT_HEIGHTS.get(size, 271) + + height = 2 * ALERT_PADDING + wrap_width = width - 2 * ALERT_PADDING + + if size == AlertSize.small: + lines = wrap_text(self.font_bold, alert.text1, ALERT_FONT_MEDIUM, wrap_width) + line_height = measure_text_cached(self.font_bold, alert.text1, ALERT_FONT_MEDIUM).y + height += int(len(lines) * line_height) + elif size == AlertSize.mid: + lines1 = wrap_text(self.font_bold, alert.text1, ALERT_FONT_BIG, wrap_width) + line_height1 = measure_text_cached(self.font_bold, alert.text1, ALERT_FONT_BIG).y + height += int(len(lines1) * line_height1) + + if alert.text2: + lines2 = wrap_text(self.font_regular, alert.text2, ALERT_FONT_SMALL, wrap_width) + line_height2 = measure_text_cached(self.font_regular, alert.text2, ALERT_FONT_SMALL).y + height += int(ALERT_LINE_SPACING + len(lines2) * line_height2) + else: + height = ALERT_HEIGHTS.get(size, 271) + + return int(height) diff --git a/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py b/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py index 61aa52537b..4e1d748117 100644 --- a/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py +++ b/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py @@ -14,7 +14,7 @@ from openpilot.common.filter_simple import FirstOrderFilter class BlindSpotIndicators: def __init__(self): self._txt_blind_spot_left: rl.Texture = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 108, 128) - self._txt_blind_spot_right: rl.Texture = gui_app.texture('icons_mici/onroad/blind_spot_right.png', 108, 128) + self._txt_blind_spot_right: rl.Texture = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 108, 128, flip_x=True) self._blind_spot_left_alpha_filter = FirstOrderFilter(0, 0.15, 1 / gui_app.target_fps) self._blind_spot_right_alpha_filter = FirstOrderFilter(0, 0.15, 1 / gui_app.target_fps) @@ -28,7 +28,7 @@ class BlindSpotIndicators: @property def detected(self) -> bool: - return self._blind_spot_left_alpha_filter.x > 0.01 or self._blind_spot_right_alpha_filter.x > 0.01 + return ui_state.blindspot and (self._blind_spot_left_alpha_filter.x > 0.01 or self._blind_spot_right_alpha_filter.x > 0.01) def render(self, rect: rl.Rectangle) -> None: if not ui_state.blindspot: diff --git a/selfdrive/ui/sunnypilot/onroad/circular_alerts.py b/selfdrive/ui/sunnypilot/onroad/circular_alerts.py index 8aa4c71d0d..9c9ab7ac84 100644 --- a/selfdrive/ui/sunnypilot/onroad/circular_alerts.py +++ b/selfdrive/ui/sunnypilot/onroad/circular_alerts.py @@ -9,7 +9,7 @@ import pyray as rl from cereal import log from openpilot.selfdrive.ui import UI_BORDER_SIZE from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiState from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.text_measure import measure_text_cached @@ -77,7 +77,7 @@ class CircularAlertsRenderer: return e2e_alert_size = 250 - dev_ui_width_adjustment = 180 if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_RIGHT, DeveloperUiRenderer.DEV_UI_BOTH) else 100 + dev_ui_width_adjustment = 180 if ui_state.developer_ui in (DeveloperUiState.RIGHT, DeveloperUiState.BOTH) else 100 x = rect.x + rect.width - e2e_alert_size - dev_ui_width_adjustment - (UI_BORDER_SIZE * 3) y = rect.y + rect.height / 2 + 20 diff --git a/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py b/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py index 8e19f876a2..8204253d32 100644 --- a/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py +++ b/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py @@ -4,6 +4,8 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ +from enum import IntEnum + import pyray as rl from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui.elements import ( @@ -17,18 +19,25 @@ from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget -class DeveloperUiRenderer(Widget): - DEV_UI_OFF = 0 - DEV_UI_BOTTOM = 1 - DEV_UI_RIGHT = 2 - DEV_UI_BOTH = 3 - BOTTOM_BAR_HEIGHT = 61 +def get_bottom_dev_ui_offset(): + if ui_state.developer_ui in (DeveloperUiState.BOTTOM, DeveloperUiState.BOTH): + return 60 + return 0 + +class DeveloperUiState(IntEnum): + OFF = 0 + BOTTOM = 1 + RIGHT = 2 + BOTH = 3 + + +class DeveloperUiRenderer(Widget): def __init__(self): super().__init__() self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD) self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD) - self.dev_ui_mode = self.DEV_UI_OFF + self.dev_ui_mode = DeveloperUiState.OFF self.rel_dist_elem = RelDistElement() self.rel_speed_elem = RelSpeedElement() @@ -45,28 +54,22 @@ class DeveloperUiRenderer(Widget): self.bearing_elem = BearingDegElement() self.altitude_elem = AltitudeElement() - @staticmethod - def get_bottom_dev_ui_offset(): - if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_BOTTOM, DeveloperUiRenderer.DEV_UI_BOTH): - return DeveloperUiRenderer.BOTTOM_BAR_HEIGHT - return 0 - def _update_state(self) -> None: self.dev_ui_mode = ui_state.developer_ui def _render(self, rect: rl.Rectangle) -> None: - if self.dev_ui_mode == self.DEV_UI_OFF: + if self.dev_ui_mode == DeveloperUiState.OFF: return sm = ui_state.sm if sm.recv_frame["carState"] < ui_state.started_frame: return - if self.dev_ui_mode == self.DEV_UI_BOTTOM: + if self.dev_ui_mode == DeveloperUiState.BOTTOM: self._draw_bottom_dev_ui(rect) - elif self.dev_ui_mode == self.DEV_UI_RIGHT: + elif self.dev_ui_mode == DeveloperUiState.RIGHT: self._draw_right_dev_ui(rect) - elif self.dev_ui_mode == self.DEV_UI_BOTH: + elif self.dev_ui_mode == DeveloperUiState.BOTH: self._draw_right_dev_ui(rect) self._draw_bottom_dev_ui(rect) diff --git a/selfdrive/ui/sunnypilot/onroad/driver_state.py b/selfdrive/ui/sunnypilot/onroad/driver_state.py index 4f2d264ee2..d3239b9e3d 100644 --- a/selfdrive/ui/sunnypilot/onroad/driver_state.py +++ b/selfdrive/ui/sunnypilot/onroad/driver_state.py @@ -8,13 +8,12 @@ import numpy as np from openpilot.selfdrive.ui import UI_BORDER_SIZE from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer, BTN_SIZE, ARC_LENGTH -from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import get_bottom_dev_ui_offset class DriverStateRendererSP(DriverStateRenderer): def __init__(self): super().__init__() - self.dev_ui_offset = DeveloperUiRenderer.get_bottom_dev_ui_offset() def _pre_calculate_drawing_elements(self): """Pre-calculate all drawing elements based on the current rectangle""" @@ -22,7 +21,7 @@ class DriverStateRendererSP(DriverStateRenderer): width, height = self._rect.width, self._rect.height offset = UI_BORDER_SIZE + BTN_SIZE // 2 self.position_x = self._rect.x + (width - offset if self.is_rhd else offset) - self.position_y = self._rect.y + height - offset - self.dev_ui_offset + self.position_y = self._rect.y + height - offset - get_bottom_dev_ui_offset() # Pre-calculate the face lines positions positioned_keypoints = self.face_keypoints_transformed + np.array([self.position_x, self.position_y]) diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 86fcf3c693..2a66b3664b 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -8,7 +8,7 @@ import pyray as rl from openpilot.common.constants import CV from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar -from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer, DeveloperUiState, get_bottom_dev_ui_offset from openpilot.selfdrive.ui.sunnypilot.onroad.road_name import RoadNameRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel from openpilot.selfdrive.ui.sunnypilot.onroad.speed_limit import SpeedLimitRenderer @@ -22,6 +22,8 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached +SLA_ACTIVE_COLOR = rl.Color(0x91, 0x9b, 0x95, 0xff) + class HudRendererSP(HudRenderer): def __init__(self): @@ -71,6 +73,8 @@ class HudRendererSP(HudRenderer): self.show_icbm_status = self.icbm_active_counter > 0 def _draw_set_speed(self, rect: rl.Rectangle) -> None: + long_plan_sp = ui_state.sm['longitudinalPlanSP'] + long_override = ui_state.sm['carControl'].cruiseControl.override self._get_icbm_status() set_speed_width = UI_CONFIG.set_speed_width_metric if ui_state.is_metric else UI_CONFIG.set_speed_width_imperial @@ -85,12 +89,16 @@ class HudRendererSP(HudRenderer): set_speed_color = COLORS.DARK_GREY if self.is_cruise_set: set_speed_color = COLORS.WHITE - if ui_state.status == UIStatus.ENGAGED: - max_color = COLORS.ENGAGED - elif ui_state.status == UIStatus.DISENGAGED: - max_color = COLORS.DISENGAGED - elif ui_state.status == UIStatus.OVERRIDE: - max_color = COLORS.OVERRIDE + if long_plan_sp.speedLimit.assist.active: + set_speed_color = SLA_ACTIVE_COLOR if long_override else rl.Color(0, 0xff, 0, 0xff) + max_color = SLA_ACTIVE_COLOR if long_override else rl.Color(0x80, 0xd8, 0xa6, 0xff) + else: + if ui_state.status == UIStatus.ENGAGED: + max_color = COLORS.ENGAGED + elif ui_state.status == UIStatus.DISENGAGED: + max_color = COLORS.DISENGAGED + elif ui_state.status == UIStatus.OVERRIDE: + max_color = COLORS.OVERRIDE max_str_size = 60 if self.show_icbm_status else 40 max_str_y = 15 if self.show_icbm_status else 27 @@ -125,8 +133,8 @@ class HudRendererSP(HudRenderer): if ui_state.torque_bar and ui_state.sm['controlsState'].lateralControlState.which() != 'angleState': torque_rect = rect - if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_BOTTOM, DeveloperUiRenderer.DEV_UI_BOTH): - torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - DeveloperUiRenderer.BOTTOM_BAR_HEIGHT) + if ui_state.developer_ui in (DeveloperUiState.BOTTOM, DeveloperUiState.BOTH): + torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - get_bottom_dev_ui_offset()) self._torque_bar.render(torque_rect) self.developer_ui.render(rect) diff --git a/selfdrive/ui/sunnypilot/onroad/smart_cruise_control.py b/selfdrive/ui/sunnypilot/onroad/smart_cruise_control.py index ca71fcac4a..c89bd914be 100644 --- a/selfdrive/ui/sunnypilot/onroad/smart_cruise_control.py +++ b/selfdrive/ui/sunnypilot/onroad/smart_cruise_control.py @@ -10,6 +10,7 @@ from openpilot.selfdrive.ui.onroad.hud_renderer import COLORS from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.sunnypilot.lib.utils import AlertFadeAnimator from openpilot.system.ui.widgets import Widget @@ -18,14 +19,14 @@ class SmartCruiseControlRenderer(Widget): super().__init__() self.vision_enabled = False self.vision_active = False - self.vision_frame = 0 self.map_enabled = False self.map_active = False - self.map_frame = 0 self.long_override = False + self._vision_fade = AlertFadeAnimator(gui_app.target_fps) + self._map_fade = AlertFadeAnimator(gui_app.target_fps) + self.font = gui_app.font(FontWeight.BOLD) - self.scc_tex = rl.load_render_texture(256, 128) def update(self): sm = ui_state.sm @@ -42,21 +43,10 @@ class SmartCruiseControlRenderer(Widget): if sm.updated["carControl"]: self.long_override = sm["carControl"].cruiseControl.override - if self.vision_active: - self.vision_frame += 1 - else: - self.vision_frame = 0 + self._vision_fade.update(self.vision_active) + self._map_fade.update(self.map_active) - if self.map_active: - self.map_frame += 1 - else: - self.map_frame = 0 - - @staticmethod - def _pulse_element(frame): - return not (frame % gui_app.target_fps < (gui_app.target_fps / 2.5)) - - def _draw_icon(self, rect_center_x, rect_height, x_offset, y_offset, name): + def _draw_icon(self, rect_center_x, rect_height, x_offset, y_offset, name, alpha=1.0): text = name font_size = 36 padding_v = 5 @@ -65,44 +55,28 @@ class SmartCruiseControlRenderer(Widget): sz = measure_text_cached(self.font, text, font_size) box_height = int(sz.y + padding_v * 2) - texture_width = 256 - texture_height = 128 - - rl.begin_texture_mode(self.scc_tex) - rl.clear_background(rl.Color(0, 0, 0, 0)) - if self.long_override: - box_color = COLORS.OVERRIDE + color = COLORS.OVERRIDE + box_color = rl.Color(color.r, color.g, color.b, int(alpha * 255)) else: - box_color = rl.Color(0, 255, 0, 255) + box_color = rl.Color(0, 255, 0, int(alpha * 255)) - # Center box in texture - box_x = (texture_width - box_width) // 2 - box_y = (texture_height - box_height) // 2 - - rl.draw_rectangle_rounded(rl.Rectangle(box_x, box_y, box_width, box_height), 0.2, 10, box_color) - - # Draw text with custom blend mode to punch hole - rl.rl_set_blend_factors(rl.RL_ZERO, rl.RL_ONE_MINUS_SRC_ALPHA, 0x8006) - rl.rl_set_blend_mode(rl.BLEND_CUSTOM) - - text_pos_x = box_x + (box_width - sz.x) / 2 - text_pos_y = box_y + (box_height - sz.y) / 2 - - rl.draw_text_ex(self.font, text, rl.Vector2(text_pos_x, text_pos_y), font_size, 0, rl.WHITE) - - rl.rl_set_blend_mode(rl.BLEND_ALPHA) # Reset - rl.end_texture_mode() + text_color = rl.Color(0, 0, 0, int(alpha * 255)) screen_y = rect_height / 4 + y_offset - dest_x = rect_center_x + x_offset - texture_width / 2 - dest_y = screen_y - texture_height / 2 + box_x = rect_center_x + x_offset - box_width / 2 + box_y = screen_y - box_height / 2 - src_rect = rl.Rectangle(0, 0, texture_width, -texture_height) - dst_rect = rl.Rectangle(dest_x, dest_y, texture_width, texture_height) + # Draw rounded background box + if alpha > 0.01: + rl.draw_rectangle_rounded(rl.Rectangle(box_x, box_y, box_width, box_height), 0.2, 10, box_color) - rl.draw_texture_pro(self.scc_tex.texture, src_rect, dst_rect, rl.Vector2(0, 0), 0, rl.WHITE) + # Draw text centered in the box (black color for contrast against bright green/grey) + text_pos_x = box_x + (box_width - sz.x) / 2 + text_pos_y = box_y + (box_height - sz.y) / 2 + + rl.draw_text_ex(self.font, text, rl.Vector2(text_pos_x, text_pos_y), font_size, 0, text_color) def _render(self, rect: rl.Rectangle): x_offset = -260 @@ -122,10 +96,10 @@ class SmartCruiseControlRenderer(Widget): y_scc_m = orders[idx] idx += 1 - scc_vision_pulse = self._pulse_element(self.vision_frame) - if (self.vision_enabled and not self.vision_active) or (self.vision_active and scc_vision_pulse): - self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_v, "SCC-V") + if self.vision_enabled: + alpha = self._vision_fade.alpha if self.vision_active else 1.0 + self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_v, "SCC-V", alpha) - scc_map_pulse = self._pulse_element(self.map_frame) - if (self.map_enabled and not self.map_active) or (self.map_active and scc_map_pulse): - self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_m, "SCC-M") + if self.map_enabled: + alpha = self._map_fade.alpha if self.map_active else 1.0 + self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_m, "SCC-M", alpha) diff --git a/selfdrive/ui/sunnypilot/onroad/speed_limit.py b/selfdrive/ui/sunnypilot/onroad/speed_limit.py index a0b5ea3935..1b03e9a204 100644 --- a/selfdrive/ui/sunnypilot/onroad/speed_limit.py +++ b/selfdrive/ui/sunnypilot/onroad/speed_limit.py @@ -4,24 +4,28 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ + from dataclasses import dataclass -import math +from enum import StrEnum import pyray as rl from cereal import custom from openpilot.common.constants import CV -from openpilot.common.filter_simple import FirstOrderFilter from openpilot.selfdrive.ui.onroad.hud_renderer import UI_CONFIG from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode +from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.sunnypilot.lib.utils import AlertFadeAnimator from openpilot.system.ui.widgets import Widget METER_TO_FOOT = 3.28084 METER_TO_MILE = 0.000621371 AHEAD_THRESHOLD = 5 +SET_SPEED_NA = 255 +KM_TO_MILE = 0.621371 AssistState = custom.LongitudinalPlanSP.SpeedLimit.AssistState SpeedLimitSource = custom.LongitudinalPlanSP.SpeedLimit.Source @@ -31,16 +35,65 @@ SpeedLimitSource = custom.LongitudinalPlanSP.SpeedLimit.Source class Colors: WHITE = rl.WHITE BLACK = rl.BLACK - RED = rl.RED + RED = rl.Color(235, 32, 32, 255) GREY = rl.Color(145, 155, 149, 255) DARK_GREY = rl.Color(77, 77, 77, 255) SUB_BG = rl.Color(0, 0, 0, 180) MUTCD_LINES = rl.Color(255, 255, 255, 100) -class SpeedLimitRenderer(Widget): +class IconSide(StrEnum): + left = 'left' + right = 'right' + + +class SpeedLimitAlertRenderer: + ARROW_SIZE = 90 if HARDWARE.get_device_type() == 'mici' else 200 + def __init__(self): - super().__init__() + self.arrow_up = gui_app.texture("../../sunnypilot/selfdrive/assets/img_plus_arrow_up.png", self.ARROW_SIZE, self.ARROW_SIZE) + self.arrow_down = gui_app.texture("../../sunnypilot/selfdrive/assets/img_minus_arrow_down.png", self.ARROW_SIZE, self.ARROW_SIZE) + + blank_image = rl.gen_image_color(self.ARROW_SIZE, self.ARROW_SIZE, rl.Color(0, 0, 0, 0)) + self.arrow_blank = rl.load_texture_from_image(blank_image) + rl.unload_image(blank_image) + + self._pre_active_fade = AlertFadeAnimator(gui_app.target_fps, duration_on=0.75, rc=0.05) + + def update(self): + assist_state = ui_state.sm['longitudinalPlanSP'].speedLimit.assist.state + self._pre_active_fade.update(assist_state == AssistState.preActive) + + def speed_limit_pre_active_icon_helper(self): + icon_alpha = max(0.0, min(self._pre_active_fade.alpha * 255.0, 255.0)) + txt_icon = self.arrow_blank + icon_margin_x = 10 + icon_margin_y = 18 + + if icon_alpha > 0: + speed_conv = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH + speed_limit_final_last = ui_state.sm['longitudinalPlanSP'].speedLimit.resolver.speedLimitFinalLast + + v_cruise_cluster = ui_state.sm['carState'].vCruiseCluster + set_speed = ui_state.sm['controlsState'].vCruiseDEPRECATED if v_cruise_cluster == 0.0 else v_cruise_cluster + if not ui_state.is_metric: + set_speed *= KM_TO_MILE + + set_speed_round = round(set_speed) + speed_limit_round = round(speed_limit_final_last * speed_conv) + + if set_speed_round < speed_limit_round: + txt_icon = self.arrow_up + elif set_speed_round > speed_limit_round: + txt_icon = self.arrow_down + + return IconSide.right, txt_icon, icon_alpha, icon_margin_x, icon_margin_y + + +class SpeedLimitRenderer(Widget, SpeedLimitAlertRenderer): + def __init__(self): + Widget.__init__(self) + SpeedLimitAlertRenderer.__init__(self) self.speed_limit = 0.0 self.speed_limit_last = 0.0 @@ -57,26 +110,26 @@ class SpeedLimitRenderer(Widget): self.speed_limit_ahead_valid = False self.speed_limit_ahead_frame = 0 - self.assist_frame = 0 - self.speed = 0.0 - self.set_speed = 0.0 + self.is_cruise_set: bool = False + self.is_cruise_available: bool = True + self.set_speed: float = SET_SPEED_NA + self.speed: float = 0.0 + self.v_ego_cluster_seen: bool = False self.font_bold = gui_app.font(FontWeight.BOLD) self.font_demi = gui_app.font(FontWeight.SEMI_BOLD) self.font_norm = gui_app.font(FontWeight.NORMAL) - self._sign_alpha_filter = FirstOrderFilter(1.0, 0.5, 1 / gui_app.target_fps) - - arrow_size = 90 - self._arrow_up = gui_app.texture("../../sunnypilot/selfdrive/assets/img_plus_arrow_up.png", arrow_size, arrow_size) - self._arrow_down = gui_app.texture("../../sunnypilot/selfdrive/assets/img_minus_arrow_down.png", arrow_size, arrow_size) @property def speed_conv(self): return CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH def update(self): + SpeedLimitAlertRenderer.update(self) sm = ui_state.sm if sm.recv_frame["carState"] < ui_state.started_frame: + self.set_speed = SET_SPEED_NA + self.speed = 0.0 return if sm.updated["longitudinalPlanSP"]: @@ -106,9 +159,21 @@ class SpeedLimitRenderer(Widget): self.speed_limit_ahead_dist_prev = self.speed_limit_ahead_dist - cs = sm["carState"] - self.set_speed = cs.cruiseState.speed * self.speed_conv - v_ego = cs.vEgoCluster if cs.vEgoCluster != 0.0 else cs.vEgo + controls_state = sm['controlsState'] + car_state = sm["carState"] + + v_cruise_cluster = car_state.vCruiseCluster + self.set_speed = ( + controls_state.vCruiseDEPRECATED if v_cruise_cluster == 0.0 else v_cruise_cluster + ) + self.is_cruise_set = 0 < self.set_speed < SET_SPEED_NA + self.is_cruise_available = self.set_speed != -1 + + if self.is_cruise_set and not ui_state.is_metric: + self.set_speed *= KM_TO_MILE + + self.v_ego_cluster_seen = self.v_ego_cluster_seen or car_state.vEgoCluster != 0.0 + v_ego = car_state.vEgoCluster if self.v_ego_cluster_seen else car_state.vEgo self.speed = max(0.0, v_ego * self.speed_conv) @staticmethod @@ -123,13 +188,7 @@ class SpeedLimitRenderer(Widget): sign_rect = rl.Rectangle(x, y, width, UI_CONFIG.set_speed_height + 6 * 2) - if self.speed_limit_assist_state == AssistState.preActive: - self.assist_frame += 1 - pulse_value = 0.65 + 0.35 * math.sin(self.assist_frame * math.pi / gui_app.target_fps) - alpha = self._sign_alpha_filter.update(pulse_value) - else: - self.assist_frame = 0 - alpha = self._sign_alpha_filter.update(1.0) + alpha = self._pre_active_fade.alpha if ui_state.speed_limit_mode != SpeedLimitMode.off: self._draw_sign_main(sign_rect, alpha) @@ -161,43 +220,30 @@ class SpeedLimitRenderer(Widget): self._render_mutcd(rect, limit_str, sub_text, txt_color, has_limit, alpha) def _draw_pre_active_arrow(self, sign_rect): - set_speed_rounded = round(self.set_speed) - limit_rounded = round(self.speed_limit_final_last) - - bounce_frequency = 2.0 * math.pi / (gui_app.target_fps * 2.5) - bounce_offset = int(20 * math.sin(self.assist_frame * bounce_frequency)) - - sign_margin = 12 - arrow_spacing = int(sign_margin * 1.4) - arrow_x = sign_rect.x + sign_rect.width + arrow_spacing - - if set_speed_rounded < limit_rounded: - arrow_y = sign_rect.y + (sign_rect.height - self._arrow_up.height) / 2 + bounce_offset - rl.draw_texture(self._arrow_up, int(arrow_x), int(arrow_y), rl.WHITE) - elif set_speed_rounded > limit_rounded: - arrow_y = sign_rect.y + (sign_rect.height - self._arrow_down.height) / 2 - bounce_offset - rl.draw_texture(self._arrow_down, int(arrow_x), int(arrow_y), rl.WHITE) + _, txt_icon, icon_alpha, _, _ = SpeedLimitAlertRenderer.speed_limit_pre_active_icon_helper(self) + if icon_alpha > 0 and txt_icon != self.arrow_blank: + sign_margin = 12 + arrow_spacing = int(sign_margin * 1.4) + arrow_x = sign_rect.x + sign_rect.width + arrow_spacing + arrow_y = sign_rect.y + (sign_rect.height - txt_icon.height) / 2 + color = rl.Color(255, 255, 255, int(icon_alpha)) + rl.draw_texture(txt_icon, int(arrow_x), int(arrow_y), color) def _render_vienna(self, rect, val, sub, color, has_limit, alpha=1.0): center = rl.Vector2(rect.x + rect.width / 2, rect.y + rect.height / 2) radius = (rect.width + 18) / 2 - white = rl.Color(255, 255, 255, int(255 * alpha)) - red = rl.Color(255, 0, 0, int(255 * alpha)) - - if hasattr(color, 'r'): - text_color = rl.Color(color.r, color.g, color.b, int(255 * alpha)) - else: - text_color = rl.Color(color[0], color[1], color[2], int(255 * alpha)) - - black = rl.Color(0, 0, 0, int(255 * alpha)) - dark_grey = rl.Color(77, 77, 77, int(255 * alpha)) + white = rl.color_alpha(Colors.WHITE, alpha) + red = rl.color_alpha(Colors.RED, alpha) + black = rl.color_alpha(Colors.BLACK, alpha) + dark_grey = rl.color_alpha(Colors.DARK_GREY, alpha) + text_color = rl.color_alpha(color, alpha) rl.draw_circle_v(center, radius, white) - rl.draw_ring(center, radius * 0.80, radius, 0, 360, 36, red) + rl.draw_ring(center, radius * 0.75, radius, 0, 360, 36, red) - f_size = 70 if len(val) >= 3 else 85 - self._draw_text_centered(self.font_bold, val, f_size, center, text_color) + font_size = 70 if len(val) >= 3 else 85 + self._draw_text_centered(self.font_bold, val, font_size, center, text_color) if sub and has_limit: s_radius = radius * 0.4 @@ -206,22 +252,23 @@ class SpeedLimitRenderer(Widget): rl.draw_circle_v(s_center, s_radius, black) rl.draw_ring(s_center, s_radius - 3, s_radius, 0, 360, 36, dark_grey) - f_scale = 0.5 if len(sub) < 3 else 0.45 - self._draw_text_centered(self.font_bold, sub, int(s_radius * 2 * f_scale), s_center, white) + font_scale = 0.5 if len(sub) < 3 else 0.45 + self._draw_text_centered(self.font_bold, sub, int(s_radius * 2 * font_scale), s_center, white) def _render_mutcd(self, rect, val, sub, color, has_limit, alpha=1.0): - white = rl.Color(255, 255, 255, int(255 * alpha)) - black = rl.Color(0, 0, 0, int(255 * alpha)) - dark_grey = rl.Color(77, 77, 77, int(255 * alpha)) - - if hasattr(color, 'r'): - text_color = rl.Color(color.r, color.g, color.b, int(255 * alpha)) - else: - text_color = rl.Color(color[0], color[1], color[2], int(255 * alpha)) + white = rl.color_alpha(Colors.WHITE, alpha) + black = rl.color_alpha(Colors.BLACK, alpha) + dark_grey = rl.color_alpha(Colors.DARK_GREY, alpha) + text_color = rl.color_alpha(color, alpha) rl.draw_rectangle_rounded(rect, 0.35, 10, white) + inner = rl.Rectangle(rect.x + 10, rect.y + 10, rect.width - 20, rect.height - 20) - rl.draw_rectangle_rounded_lines_ex(inner, 0.35, 10, 4, black) + outer_radius = 0.35 * rect.width / 2.0 + inner_radius = outer_radius - 10.0 + inner_roundness = inner_radius / (inner.width / 2.0) + + rl.draw_rectangle_rounded_lines_ex(inner, inner_roundness, 10, 4, black) self._draw_text_centered(self.font_demi, "SPEED", 40, rl.Vector2(rect.x + rect.width / 2, rect.y + 40), black) self._draw_text_centered(self.font_demi, "LIMIT", 40, rl.Vector2(rect.x + rect.width / 2, rect.y + 80), black) diff --git a/selfdrive/ui/sunnypilot/onroad/turn_signal.py b/selfdrive/ui/sunnypilot/onroad/turn_signal.py index bd1aa7ee10..e285009364 100644 --- a/selfdrive/ui/sunnypilot/onroad/turn_signal.py +++ b/selfdrive/ui/sunnypilot/onroad/turn_signal.py @@ -34,8 +34,8 @@ class TurnSignalWidget(Widget): self._turn_signal_timer = 0.0 self._turn_signal_alpha_filter = FirstOrderFilter(0.0, 0.3, 1 / gui_app.target_fps) - self._signal_texture = gui_app.texture(f'icons_mici/onroad/turn_signal_{direction}.png', 120, 109) - self._blind_spot_texture = gui_app.texture(f'icons_mici/onroad/blind_spot_{direction}.png', 120, 109) + self._signal_texture = gui_app.texture('icons_mici/onroad/turn_signal_left.png', 120, 109, flip_x=(direction == IconSide.right)) + self._blind_spot_texture = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 120, 109, flip_x=(direction == IconSide.right)) self._texture = self._signal_texture def _render(self, _): diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 4d26e138b1..050df6d26d 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -162,14 +162,16 @@ class DeviceSP: # For AUTO (Default) and Manual modes (while timer running), use standard brightness return cur_brightness - # 0: Auto (Default), 1: Auto (Dark) + # 0: Auto (Default), 1: Auto (Dark), 2: Screen Off if _ui_state.onroad_brightness == OnroadBrightness.AUTO: return cur_brightness - elif _ui_state.onroad_brightness == OnroadBrightness.AUTO_DARK: + if _ui_state.onroad_brightness == OnroadBrightness.AUTO_DARK: return cur_brightness + if _ui_state.onroad_brightness == OnroadBrightness.SCREEN_OFF: + return 0.0 - # 2-21: 5% - 100% - return float((_ui_state.onroad_brightness - 1) * 5) + # 3-22: 5% - 100% + return float((_ui_state.onroad_brightness - 2) * 5) @staticmethod def set_min_onroad_brightness(_ui_state, min_brightness: int) -> int: diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore index 98f2a5e8ce..74ab2675db 100644 --- a/selfdrive/ui/tests/.gitignore +++ b/selfdrive/ui/tests/.gitignore @@ -1,9 +1,2 @@ test test_translations -test_ui/report_1 -test_ui/raylib_report - -diff/*.mp4 -diff/*.html -diff/.coverage -diff/htmlcov/ diff --git a/selfdrive/ui/tests/diff/.gitignore b/selfdrive/ui/tests/diff/.gitignore new file mode 100644 index 0000000000..e21a8d896e --- /dev/null +++ b/selfdrive/ui/tests/diff/.gitignore @@ -0,0 +1,2 @@ +report +.coverage diff --git a/selfdrive/ui/tests/diff/diff.py b/selfdrive/ui/tests/diff/diff.py index a581f68747..974edb42a3 100755 --- a/selfdrive/ui/tests/diff/diff.py +++ b/selfdrive/ui/tests/diff/diff.py @@ -2,26 +2,27 @@ import os import sys import subprocess -import tempfile import webbrowser import argparse from pathlib import Path from openpilot.common.basedir import BASEDIR DIFF_OUT_DIR = Path(BASEDIR) / "selfdrive" / "ui" / "tests" / "diff" / "report" +HTML_TEMPLATE_PATH = Path(__file__).with_name("diff_template.html") -def extract_frames(video_path, output_dir): - output_pattern = str(output_dir / "frame_%04d.png") - cmd = ['ffmpeg', '-i', video_path, '-vsync', '0', output_pattern, '-y'] - subprocess.run(cmd, capture_output=True, check=True) - frames = sorted(output_dir.glob("frame_*.png")) - return frames - - -def compare_frames(frame1_path, frame2_path): - result = subprocess.run(['cmp', '-s', frame1_path, frame2_path]) - return result.returncode == 0 +def extract_framehashes(video_path): + cmd = ['ffmpeg', '-i', video_path, '-map', '0:v:0', '-vsync', '0', '-f', 'framehash', '-hash', 'md5', '-'] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + hashes = [] + for line in result.stdout.splitlines(): + if not line or line.startswith('#'): + continue + parts = line.split(',') + if len(parts) < 4: + continue + hashes.append(parts[-1].strip()) + return hashes def create_diff_video(video1, video2, output_path): @@ -31,38 +32,24 @@ def create_diff_video(video1, video2, output_path): subprocess.run(cmd, capture_output=True, check=True) -def find_differences(video1, video2): - with tempfile.TemporaryDirectory() as tmpdir: - tmpdir = Path(tmpdir) +def find_differences(video1, video2) -> tuple[list[int], tuple[int, int]]: + print(f"Hashing frames from {video1}...") + hashes1 = extract_framehashes(video1) - print(f"Extracting frames from {video1}...") - frames1_dir = tmpdir / "frames1" - frames1_dir.mkdir() - frames1 = extract_frames(video1, frames1_dir) + print(f"Hashing frames from {video2}...") + hashes2 = extract_framehashes(video2) - print(f"Extracting frames from {video2}...") - frames2_dir = tmpdir / "frames2" - frames2_dir.mkdir() - frames2 = extract_frames(video2, frames2_dir) + print(f"Comparing {len(hashes1)} frames...") + different_frames = [] - if len(frames1) != len(frames2): - print(f"WARNING: Frame count mismatch: {len(frames1)} vs {len(frames2)}") - min_frames = min(len(frames1), len(frames2)) - frames1 = frames1[:min_frames] - frames2 = frames2[:min_frames] + for i, (h1, h2) in enumerate(zip(hashes1, hashes2, strict=False)): + if h1 != h2: + different_frames.append(i) - print(f"Comparing {len(frames1)} frames...") - different_frames = [] - - for i, (f1, f2) in enumerate(zip(frames1, frames2, strict=False)): - is_different = not compare_frames(f1, f2) - if is_different: - different_frames.append(i) - - return different_frames, len(frames1) + return different_frames, (len(hashes1), len(hashes2)) -def generate_html_report(video1, video2, basedir, different_frames, total_frames): +def generate_html_report(videos: tuple[str, str], basedir: str, different_frames: list[int], frame_counts: tuple[int, int], diff_video_name): chunks = [] if different_frames: current_chunk = [different_frames[0]] @@ -74,71 +61,28 @@ def generate_html_report(video1, video2, basedir, different_frames, total_frames current_chunk = [different_frames[i]] chunks.append(current_chunk) + total_frames = max(frame_counts) + frame_delta = frame_counts[1] - frame_counts[0] + different_total = len(different_frames) + abs(frame_delta) + result_text = ( f"✅ Videos are identical! ({total_frames} frames)" - if len(different_frames) == 0 - else f"❌ Found {len(different_frames)} different frames out of {total_frames} total ({(len(different_frames) / total_frames * 100):.1f}%)" + if different_total == 0 + else f"❌ Found {different_total} different frames out of {total_frames} total ({different_total / total_frames * 100:.1f}%)." + + (f" Video {'2' if frame_delta > 0 else '1'} is longer by {abs(frame_delta)} frames." if frame_delta != 0 else "") ) - html = f"""

UI Diff

- - - - - - -
-

Video 1

- -
-

Video 2

- -
-

Pixel Diff

- -
- -
-

Results: {result_text}

-""" + # Load HTML template and replace placeholders + html = HTML_TEMPLATE_PATH.read_text() + placeholders = { + "VIDEO1_SRC": os.path.join(basedir, os.path.basename(videos[0])), + "VIDEO2_SRC": os.path.join(basedir, os.path.basename(videos[1])), + "DIFF_SRC": os.path.join(basedir, diff_video_name), + "RESULT_TEXT": result_text, + } + for key, value in placeholders.items(): + html = html.replace(f"${key}", value) + return html @@ -152,6 +96,9 @@ def main(): args = parser.parse_args() + if not args.output.lower().endswith('.html'): + args.output += '.html' + os.makedirs(DIFF_OUT_DIR, exist_ok=True) print("=" * 60) @@ -162,18 +109,19 @@ def main(): print(f"Output: {args.output}") print() - # Create diff video - diff_video_path = os.path.join(os.path.dirname(args.output), DIFF_OUT_DIR / "diff.mp4") + # Create diff video with name derived from output HTML + diff_video_name = Path(args.output).stem + '.mp4' + diff_video_path = str(DIFF_OUT_DIR / diff_video_name) create_diff_video(args.video1, args.video2, diff_video_path) - different_frames, total_frames = find_differences(args.video1, args.video2) + different_frames, frame_counts = find_differences(args.video1, args.video2) if different_frames is None: sys.exit(1) print() print("Generating HTML report...") - html = generate_html_report(args.video1, args.video2, args.basedir, different_frames, total_frames) + html = generate_html_report((args.video1, args.video2), args.basedir, different_frames, frame_counts, diff_video_name) with open(DIFF_OUT_DIR / args.output, 'w') as f: f.write(html) @@ -183,7 +131,8 @@ def main(): print(f"Opening {args.output} in browser...") webbrowser.open(f'file://{os.path.abspath(DIFF_OUT_DIR / args.output)}') - return 0 if len(different_frames) == 0 else 1 + extra_frames = abs(frame_counts[0] - frame_counts[1]) + return 0 if (len(different_frames) + extra_frames) == 0 else 1 if __name__ == "__main__": diff --git a/selfdrive/ui/tests/diff/diff_template.html b/selfdrive/ui/tests/diff/diff_template.html new file mode 100644 index 0000000000..3f1de10512 --- /dev/null +++ b/selfdrive/ui/tests/diff/diff_template.html @@ -0,0 +1,80 @@ + + + + + + UI Diff Report + + + +

UI Diff

+
+
+

Results: $RESULT_TEXT

+ + + diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index ce24bf9190..08c45b7b7f 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -1,42 +1,21 @@ #!/usr/bin/env python3 import os -import time +import argparse import coverage import pyray as rl -from dataclasses import dataclass -from openpilot.selfdrive.ui.tests.diff.diff import DIFF_OUT_DIR - -os.environ["RECORD"] = "1" -if "RECORD_OUTPUT" not in os.environ: - os.environ["RECORD_OUTPUT"] = "mici_ui_replay.mp4" - -os.environ["RECORD_OUTPUT"] = os.path.join(DIFF_OUT_DIR, os.environ["RECORD_OUTPUT"]) +from typing import Literal +from collections.abc import Callable +from cereal.messaging import PubMaster from openpilot.common.params import Params +from openpilot.common.prefix import OpenpilotPrefix +from openpilot.selfdrive.ui.tests.diff.diff import DIFF_OUT_DIR from openpilot.system.version import terms_version, training_version, terms_version_sp, sunnylink_consent_version -from openpilot.system.ui.lib.application import gui_app, MousePos, MouseEvent -from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout + +LayoutVariant = Literal["mici", "tizi"] FPS = 60 -HEADLESS = os.getenv("WINDOWED", "0") == "1" - - -@dataclass -class DummyEvent: - click: bool = False - # TODO: add some kind of intensity - swipe_left: bool = False - swipe_right: bool = False - swipe_down: bool = False - - -SCRIPT = [ - (0, DummyEvent()), - (FPS * 1, DummyEvent(click=True)), - (FPS * 2, DummyEvent(click=True)), - (FPS * 3, DummyEvent()), -] +HEADLESS = os.getenv("WINDOWED", "0") != "1" def setup_state(): @@ -44,69 +23,76 @@ def setup_state(): params.put("HasAcceptedTerms", terms_version) params.put("CompletedTrainingVersion", training_version) params.put("DongleId", "test123456789") + # Combined description for layouts that still use it (BIG home, settings/software) params.put("UpdaterCurrentDescription", "0.10.1 / test-branch / abc1234 / Nov 30") params.put("HasAcceptedTermsSP", terms_version_sp) params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version) - return None + + # Params for mici home + params.put("Version", "0.10.1") + params.put("GitBranch", "test-branch") + params.put("GitCommit", "abc12340ff9131237ba23a1d0fbd8edf9c80e87") + params.put("GitCommitDate", "'1732924800 2024-11-30 00:00:00 +0000'") -def inject_click(coords): - events = [] - x, y = coords[0] - events.append(MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=True, left_released=False, left_down=False, t=time.monotonic())) - for x, y in coords[1:]: - events.append(MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=False, left_down=True, t=time.monotonic())) - x, y = coords[-1] - events.append(MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=True, left_down=False, t=time.monotonic())) +def run_replay(variant: LayoutVariant) -> None: + if HEADLESS: + rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) + os.environ["OFFSCREEN"] = "1" # Run UI without FPS limit (set before importing gui_app) - with gui_app._mouse._lock: - gui_app._mouse._events.extend(events) - - -def handle_event(event: DummyEvent): - if event.click: - inject_click([(gui_app.width // 2, gui_app.height // 2)]) - if event.swipe_left: - inject_click([(gui_app.width * 3 // 4, gui_app.height // 2), - (gui_app.width // 4, gui_app.height // 2), - (0, gui_app.height // 2)]) - if event.swipe_right: - inject_click([(gui_app.width // 4, gui_app.height // 2), - (gui_app.width * 3 // 4, gui_app.height // 2), - (gui_app.width, gui_app.height // 2)]) - if event.swipe_down: - inject_click([(gui_app.width // 2, gui_app.height // 4), - (gui_app.width // 2, gui_app.height * 3 // 4), - (gui_app.width // 2, gui_app.height)]) - - -def run_replay(): setup_state() os.makedirs(DIFF_OUT_DIR, exist_ok=True) - if not HEADLESS: - rl.set_config_flags(rl.FLAG_WINDOW_HIDDEN) - gui_app.init_window("ui diff test", fps=FPS) - main_layout = MiciMainLayout() - main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + from openpilot.selfdrive.ui.ui_state import ui_state # Import within OpenpilotPrefix context so param values are setup correctly + from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage + from openpilot.selfdrive.ui.tests.diff.replay_script import build_script - frame = 0 + gui_app.init_window("ui diff test", fps=FPS) + + # Dynamically import main layout based on variant + if variant == "mici": + from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout as MainLayout + else: + from openpilot.selfdrive.ui.layouts.main import MainLayout + main_layout = MainLayout() + + pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"]) + script = build_script(pm, main_layout, variant) script_index = 0 - for should_render in gui_app.render(): - while script_index < len(SCRIPT) and SCRIPT[script_index][0] == frame: - _, event = SCRIPT[script_index] - handle_event(event) + send_fn: Callable | None = None + frame = 0 + # Override raylib timing functions to return deterministic values based on frame count instead of real time + rl.get_frame_time = lambda: 1.0 / FPS + rl.get_time = lambda: frame / FPS + + # Main loop to replay events and render frames + for _ in gui_app.render(): + # Handle all events for the current frame + while script_index < len(script) and script[script_index][0] == frame: + _, event = script[script_index] + # Call setup function, if any + if event.setup: + event.setup() + # Send mouse events to the application + if event.mouse_events: + with gui_app._mouse._lock: + gui_app._mouse._events.extend(event.mouse_events) + # Update persistent send function + if event.send_fn is not None: + send_fn = event.send_fn + # Move to next script event script_index += 1 + # Keep sending cereal messages for persistent states (onroad, alerts) + if send_fn: + send_fn() + ui_state.update() - if should_render: - main_layout.render() - frame += 1 - if script_index >= len(SCRIPT): + if script_index >= len(script): break gui_app.close() @@ -116,14 +102,35 @@ def run_replay(): def main(): - cov = coverage.coverage(source=['openpilot.selfdrive.ui.mici']) - with cov.collect(): - run_replay() - cov.stop() - cov.save() - cov.report() - cov.html_report(directory=os.path.join(DIFF_OUT_DIR, 'htmlcov')) - print("HTML report: htmlcov/index.html") + parser = argparse.ArgumentParser() + parser.add_argument('--big', action='store_true', help='Use big UI layout (tizi/tici) instead of mici layout') + args = parser.parse_args() + + variant: LayoutVariant = 'tizi' if args.big else 'mici' + + if args.big: + os.environ["BIG"] = "1" + os.environ["RECORD"] = "1" + os.environ["RECORD_QUALITY"] = "0" # Use CRF 0 ("lossless" encode) for deterministic output across different machines + os.environ["RECORD_OUTPUT"] = os.path.join(DIFF_OUT_DIR, os.environ.get("RECORD_OUTPUT", f"{variant}_ui_replay.mp4")) + + print(f"Running {variant} UI replay...") + with OpenpilotPrefix(): + sources = ["openpilot.system.ui"] + if variant == "mici": + sources.append("openpilot.selfdrive.ui.mici") + omit = ["**/*tizi*", "**/*tici*"] # exclude files containing "tizi" or "tici" + else: + sources.extend(["openpilot.selfdrive.ui.layouts", "openpilot.selfdrive.ui.onroad", "openpilot.selfdrive.ui.widgets"]) + omit = ["**/*mici*"] # exclude files containing "mici" + cov = coverage.Coverage(source=sources, omit=omit) + with cov.collect(): + run_replay(variant) + cov.save() + cov.report() + directory = os.path.join(DIFF_OUT_DIR, f"htmlcov-{variant}") + cov.html_report(directory=directory) + print(f"HTML report: {directory}/index.html") if __name__ == "__main__": diff --git a/selfdrive/ui/tests/diff/replay_script.py b/selfdrive/ui/tests/diff/replay_script.py new file mode 100644 index 0000000000..9f2104ec49 --- /dev/null +++ b/selfdrive/ui/tests/diff/replay_script.py @@ -0,0 +1,249 @@ +from __future__ import annotations +from typing import TYPE_CHECKING +from collections.abc import Callable +from dataclasses import dataclass + +from cereal import car, log, messaging +from cereal.messaging import PubMaster +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert +from openpilot.selfdrive.ui.tests.diff.replay import FPS, LayoutVariant +from openpilot.system.updated.updated import parse_release_notes + +WAIT = int(FPS * 0.5) # Default frames to wait after events + +AlertSize = log.SelfdriveState.AlertSize +AlertStatus = log.SelfdriveState.AlertStatus + +BRANCH_NAME = "this-is-a-really-super-mega-ultra-max-extreme-ultimate-long-branch-name" + + +@dataclass +class ScriptEvent: + if TYPE_CHECKING: + # Only import for type checking to avoid excluding the application code from coverage + from openpilot.system.ui.lib.application import MouseEvent + + setup: Callable | None = None # Setup function to run prior to adding mouse events + mouse_events: list[MouseEvent] | None = None # Mouse events to send to the application on this event's frame + send_fn: Callable | None = None # When set, the main loop uses this as the new persistent sender + + +ScriptEntry = tuple[int, ScriptEvent] # (frame, event) + + +class Script: + def __init__(self, fps: int) -> None: + self.fps = fps + self.frame = 0 + self.entries: list[ScriptEntry] = [] + + def get_frame_time(self) -> float: + return self.frame / self.fps + + def add(self, event: ScriptEvent, before: int = 0, after: int = 0) -> None: + """Add event to the script, optionally with the given number of frames to wait before or after the event.""" + self.frame += before + self.entries.append((self.frame, event)) + self.frame += after + + def end(self) -> None: + """Add a final empty event to mark the end of the script.""" + self.add(ScriptEvent()) # Without this, it will just end on the last event without waiting for any specified delay after it + + def wait(self, frames: int) -> None: + """Add a delay for the given number of frames followed by an empty event.""" + self.add(ScriptEvent(), before=frames) + + def setup(self, fn: Callable, wait_after: int = WAIT) -> None: + """Add a setup function to be called immediately followed by a delay of the given number of frames.""" + self.add(ScriptEvent(setup=fn), after=wait_after) + + def set_send(self, fn: Callable, wait_after: int = WAIT) -> None: + """Set a new persistent send function to be called every frame.""" + self.add(ScriptEvent(send_fn=fn), after=wait_after) + + # TODO: Also add more complex gestures, like swipe or drag + def click(self, x: int, y: int, wait_after: int = WAIT, wait_between: int = 2) -> None: + """Add a click event to the script for the given position and specify frames to wait between mouse events or after the click.""" + # NOTE: By default we wait a couple frames between mouse events so pressed states will be rendered + from openpilot.system.ui.lib.application import MouseEvent, MousePos + + # TODO: Add support for long press (left_down=True) + mouse_down = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=True, left_released=False, left_down=False, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_down]), after=wait_between) + mouse_up = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=True, left_down=False, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_up]), after=wait_after) + + +# --- Setup functions --- + +def put_update_params(params: Params | None = None) -> None: + if params is None: + params = Params() + params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) + params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) + params.put("UpdaterTargetBranch", BRANCH_NAME) + + +def setup_offroad_alerts() -> None: + put_update_params(Params()) + set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C') + set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal') + set_offroad_alert("Offroad_IsTakingSnapshot", True) + + +def setup_update_available() -> None: + params = Params() + params.put_bool("UpdateAvailable", True) + params.put("UpdaterNewDescription", f"0.10.2 / {BRANCH_NAME} / 0a1b2c3 / Jan 01") + put_update_params(params) + + +def setup_developer_params() -> None: + CP = car.CarParams() + CP.alphaLongitudinalAvailable = True + Params().put("CarParamsPersistent", CP.to_bytes()) + + +# --- Send functions --- + +def send_onroad(pm: PubMaster) -> None: + ds = messaging.new_message('deviceState') + ds.deviceState.started = True + ds.deviceState.networkType = log.DeviceState.NetworkType.wifi + + ps = messaging.new_message('pandaStates', 1) + ps.pandaStates[0].pandaType = log.PandaState.PandaType.dos + ps.pandaStates[0].ignitionLine = True + + pm.send('deviceState', ds) + pm.send('pandaStates', ps) + + +def make_network_state_setup(pm: PubMaster, network_type) -> Callable: + def _send() -> None: + ds = messaging.new_message('deviceState') + ds.deviceState.networkType = network_type + pm.send('deviceState', ds) + return _send + + +def make_alert_setup(pm: PubMaster, size, text1, text2, status) -> Callable: + def _send() -> None: + send_onroad(pm) + alert = messaging.new_message('selfdriveState') + ss = alert.selfdriveState + ss.alertSize = size + ss.alertText1 = text1 + ss.alertText2 = text2 + ss.alertStatus = status + pm.send('selfdriveState', alert) + return _send + + +# --- Script builders --- + +def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None: + """Build the replay script for the mici layout.""" + from openpilot.system.ui.lib.application import gui_app + + center = (gui_app.width // 2, gui_app.height // 2) + + # TODO: Explore more + script.wait(FPS) + script.click(*center, FPS) # Open settings + script.click(*center, FPS) # Open toggles + script.end() + + +def build_tizi_script(pm: PubMaster, main_layout, script: Script) -> None: + """Build the replay script for the tizi layout.""" + + def make_home_refresh_setup(fn: Callable) -> Callable: + """Return setup function that calls the given function to modify state and forces an immediate refresh on the home layout.""" + from openpilot.selfdrive.ui.layouts.main import MainState + + def setup(): + fn() + main_layout._layouts[MainState.HOME].last_refresh = 0 + + return setup + + # TODO: Better way of organizing the events + + # === Homescreen === + script.set_send(make_network_state_setup(pm, log.DeviceState.NetworkType.wifi)) + + # === Offroad Alerts (auto-transitions via HomeLayout refresh) === + script.setup(make_home_refresh_setup(setup_offroad_alerts)) + + # === Update Available (auto-transitions via HomeLayout refresh) === + script.setup(make_home_refresh_setup(setup_update_available)) + + # === Settings - Device (click sidebar settings button) === + script.click(150, 90) + script.click(1985, 790) # reset calibration confirmation + script.click(650, 750) # cancel + + # === Settings - Network === + script.click(278, 450) + script.click(1880, 100) # advanced network settings + script.click(630, 80) # back + + # === Settings - Toggles === + script.click(278, 600) + script.click(1200, 280) # experimental mode description + + # === Settings - Software === + script.setup(put_update_params, wait_after=0) + script.click(278, 720) + + # === Settings - Firehose === + script.click(278, 845) + + # === Settings - Developer (set CarParamsPersistent first) === + script.setup(setup_developer_params, wait_after=0) + script.click(278, 950) + script.click(2000, 960) # toggle alpha long + script.click(1500, 875) # confirm + + # === Keyboard modal (SSH keys button in developer panel) === + script.click(1930, 470) # click SSH keys + script.click(1930, 115) # click cancel on keyboard + + # === Close settings === + script.click(250, 160) + + # === Onroad === + script.set_send(lambda: send_onroad(pm)) + script.click(1000, 500) # click onroad to toggle sidebar + + # === Onroad alerts === + # Small alert (normal) + script.set_send(make_alert_setup(pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal)) + # Medium alert (userPrompt) + script.set_send(make_alert_setup(pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt)) + # Full alert (critical) + script.set_send(make_alert_setup(pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical)) + # Full alert multiline + script.set_send(make_alert_setup(pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal)) + # Full alert long text + script.set_send(make_alert_setup(pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt)) + + # End + script.end() + + +def build_script(pm: PubMaster, main_layout, variant: LayoutVariant) -> list[ScriptEntry]: + """Build the replay script for the appropriate layout variant and return list of script entries.""" + print(f"Building {variant} replay script...") + + script = Script(FPS) + builder = build_tizi_script if variant == 'tizi' else build_mici_script + builder(pm, main_layout, script) + + print(f"Built replay script with {len(script.entries)} events and {script.frame} frames ({script.get_frame_time():.2f} seconds)") + + return script.entries diff --git a/selfdrive/ui/tests/profile_onroad.py b/selfdrive/ui/tests/profile_onroad.py index fde4f25ffe..18194d7363 100755 --- a/selfdrive/ui/tests/profile_onroad.py +++ b/selfdrive/ui/tests/profile_onroad.py @@ -83,7 +83,6 @@ if __name__ == "__main__": gui_app.init_window("UI Profiling", fps=600) main_layout = MiciMainLayout() - main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) print("Running...") patch_submaster(message_chunks) @@ -95,15 +94,13 @@ if __name__ == "__main__": yuv_buffer_size = W * H + (W // 2) * (H // 2) * 2 yuv_data = np.random.randint(0, 256, yuv_buffer_size, dtype=np.uint8).tobytes() with cProfile.Profile() as pr: - for should_render in gui_app.render(): + for _ in gui_app.render(): if ui_state.sm.frame >= len(message_chunks): break if ui_state.sm.frame % 3 == 0: eof = int((ui_state.sm.frame % 3) * 0.05 * 1e9) vipc.send(VisionStreamType.VISION_STREAM_ROAD, yuv_data, ui_state.sm.frame % 3, eof, eof) ui_state.update() - if should_render: - main_layout.render() pr.dump_stats(f'{args.output}_deterministic.stats') rl.close_window() diff --git a/selfdrive/ui/tests/test_soundd.py b/selfdrive/ui/tests/test_soundd.py index a9da8455eb..226117ae8f 100644 --- a/selfdrive/ui/tests/test_soundd.py +++ b/selfdrive/ui/tests/test_soundd.py @@ -10,8 +10,8 @@ AudibleAlert = car.CarControl.HUDControl.AudibleAlert class TestSoundd: def test_check_selfdrive_timeout_alert(self): - sm = SubMaster(['selfdriveState']) - pm = PubMaster(['selfdriveState']) + sm = SubMaster(['selfdriveState', 'selfdriveStateSP']) + pm = PubMaster(['selfdriveState', 'selfdriveStateSP']) for _ in range(100): cs = messaging.new_message('selfdriveState') @@ -31,5 +31,31 @@ class TestSoundd: assert check_selfdrive_timeout_alert(sm) + def test_check_selfdrive_timeout_alert_mads_lateral_only(self): + sm = SubMaster(['selfdriveState', 'selfdriveStateSP']) + pm = PubMaster(['selfdriveState', 'selfdriveStateSP']) + + for _ in range(100): + cs = messaging.new_message('selfdriveState') + cs.selfdriveState.enabled = False + + ss_sp = messaging.new_message('selfdriveStateSP') + ss_sp.selfdriveStateSP.mads.enabled = True + + pm.send("selfdriveState", cs) + pm.send("selfdriveStateSP", ss_sp) + + time.sleep(0.01) + + sm.update(0) + + assert not check_selfdrive_timeout_alert(sm) + + for _ in range(SELFDRIVE_STATE_TIMEOUT * 110): + sm.update(0) + time.sleep(0.01) + + assert check_selfdrive_timeout_alert(sm) + # TODO: add test with micd for checking that soundd actually outputs sounds diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 3177814f9f..599c99013c 100644 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -5,7 +5,7 @@ import re import xml.etree.ElementTree as ET import string import requests -from parameterized import parameterized_class +from openpilot.common.parameterized import parameterized_class from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, LANGUAGES_FILE with open(str(LANGUAGES_FILE)) as f: diff --git a/selfdrive/ui/tests/test_ui/print_mouse_coords.py b/selfdrive/ui/tests/test_ui/print_mouse_coords.py deleted file mode 100755 index 1e88ce57d3..0000000000 --- a/selfdrive/ui/tests/test_ui/print_mouse_coords.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple script to print mouse coordinates on Ubuntu. -Run with: python print_mouse_coords.py -Press Ctrl+C to exit. -""" - -from pynput import mouse - -print("Mouse coordinate printer - Press Ctrl+C to exit") -print("Click to set the top left origin") - -origin: tuple[int, int] | None = None -clicks: list[tuple[int, int]] = [] - - -def on_click(x, y, button, pressed): - global origin, clicks - if pressed: # Only on mouse down, not up - if origin is None: - origin = (x, y) - print(f"Origin set to: {x},{y}") - else: - rel_x = x - origin[0] - rel_y = y - origin[1] - clicks.append((rel_x, rel_y)) - print(f"Clicks: {clicks}") - - -if __name__ == "__main__": - try: - # Start mouse listener - with mouse.Listener(on_click=on_click) as listener: - listener.join() - except KeyboardInterrupt: - print("\nExiting...") diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py deleted file mode 100755 index cd27b1c12f..0000000000 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ /dev/null @@ -1,395 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import shutil -import time -import pathlib -from collections import namedtuple - -import pyautogui -import pywinctl - -from cereal import car, log -from cereal import messaging -from cereal.messaging import PubMaster -from openpilot.common.basedir import BASEDIR -from openpilot.common.params import Params -from openpilot.common.prefix import OpenpilotPrefix -from openpilot.selfdrive.test.helpers import with_processes -from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert -from openpilot.system.updated.updated import parse_release_notes -from openpilot.system.version import terms_version, training_version, terms_version_sp, sunnylink_consent_version - -AlertSize = log.SelfdriveState.AlertSize -AlertStatus = log.SelfdriveState.AlertStatus - -TEST_DIR = pathlib.Path(__file__).parent -TEST_OUTPUT_DIR = TEST_DIR / "raylib_report" -SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots" -UI_DELAY = 0.5 - -BRANCH_NAME = "this-is-a-really-super-mega-ultra-max-extreme-ultimate-long-branch-name" -VERSION = f"0.10.1 / {BRANCH_NAME} / 7864838 / Oct 03" - -# Offroad alerts to test -OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot'] - - -def put_update_params(params: Params): - params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) - params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) - params.put("UpdaterTargetBranch", BRANCH_NAME) - - -def setup_homescreen(click, pm: PubMaster, scroll=None): - pass - - -def setup_homescreen_update_available(click, pm: PubMaster, scroll=None): - params = Params() - params.put_bool("UpdateAvailable", True) - put_update_params(params) - setup_offroad_alert(click, pm) - - -def setup_settings(click, pm: PubMaster, scroll=None): - click(100, 100) - - -def close_settings(click, pm: PubMaster, scroll=None): - click(140, 120) - - -def setup_settings_network(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - click(278, 450) - - -def setup_settings_network_advanced(click, pm: PubMaster, scroll=None): - setup_settings_network(click, pm, scroll=scroll) - click(1880, 100) - - -def setup_settings_toggles(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - click(278, 620) - - -def setup_settings_software(click, pm: PubMaster, scroll=None): - put_update_params(Params()) - setup_settings(click, pm) - click(278, 730) - - -def setup_settings_software_download(click, pm: PubMaster, scroll=None): - params = Params() - # setup_settings_software but with "DOWNLOAD" button to test long text - params.put("UpdaterState", "idle") - params.put_bool("UpdaterFetchAvailable", True) - setup_settings_software(click, pm) - - -def setup_settings_software_release_notes(click, pm: PubMaster, scroll=None): - setup_settings_software(click, pm, scroll=scroll) - click(588, 110) # expand description for current version - - -def setup_settings_software_branch_switcher(click, pm: PubMaster, scroll=None): - setup_settings_software(click, pm, scroll=scroll) - params = Params() - params.put("UpdaterAvailableBranches", f"master,nightly,release,{BRANCH_NAME}") - params.put("GitBranch", BRANCH_NAME) # should be on top - params.put("UpdaterTargetBranch", "nightly") # should be selected - click(1984, 449) - - -def setup_settings_firehose(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - scroll(-20, 278, 950) - click(278, 850) - - -def setup_settings_developer(click, pm: PubMaster, scroll=None): - CP = car.CarParams() - CP.alphaLongitudinalAvailable = True # show alpha long control toggle - Params().put("CarParamsPersistent", CP.to_bytes()) - - setup_settings(click, pm) - scroll(-20, 278, 950) - click(278, 950) - - -def setup_keyboard(click, pm: PubMaster, scroll=None): - setup_settings_developer(click, pm, scroll=scroll) - click(1930, 470) - - -def setup_pair_device(click, pm: PubMaster, scroll=None): - click(1950, 800) - - -def setup_offroad_alert(click, pm: PubMaster, scroll=None): - put_update_params(Params()) - set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C') - set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal') - for alert in OFFROAD_ALERTS: - set_offroad_alert(alert, True) - - setup_settings(click, pm) - close_settings(click, pm) - - -def setup_confirmation_dialog(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - click(1985, 791) # reset calibration - - -def setup_experimental_mode_description(click, pm: PubMaster, scroll=None): - setup_settings_toggles(click, pm) - click(1200, 280) # expand description for experimental mode - - -def setup_openpilot_long_confirmation_dialog(click, pm: PubMaster, scroll=None): - setup_settings_developer(click, pm, scroll=scroll) - click(650, 960) # toggle sunnypilot longitudinal control - - -def setup_settings_sunnylink(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - click(278, 510) - - -def setup_settings_models(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - click(278, 840) - - -def setup_settings_steering(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - click(278, 950) - - -def setup_settings_cruise(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - scroll(-4, 278, 950) - click(278, 860) - - -def setup_settings_visuals(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - scroll(-20, 278, 950) - click(278, 330) - - -def setup_settings_display(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - scroll(-20, 278, 950) - click(278, 420) - - -def setup_settings_osm(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - scroll(-20, 278, 950) - click(278, 520) - - -def setup_settings_trips(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - scroll(-20, 278, 950) - click(278, 630) - - -def setup_settings_vehicle(click, pm: PubMaster, scroll=None): - setup_settings(click, pm) - scroll(-20, 278, 950) - click(278, 750) - - -def setup_onroad(click, pm: PubMaster, scroll=None): - ds = messaging.new_message('deviceState') - ds.deviceState.started = True - - ps = messaging.new_message('pandaStates', 1) - ps.pandaStates[0].pandaType = log.PandaState.PandaType.dos - ps.pandaStates[0].ignitionLine = True - - driverState = messaging.new_message('driverStateV2') - driverState.driverStateV2.leftDriverData.faceOrientation = [0, 0, 0] - - for _ in range(5): - pm.send('deviceState', ds) - pm.send('pandaStates', ps) - pm.send('driverStateV2', driverState) - ds.clear_write_flag() - ps.clear_write_flag() - driverState.clear_write_flag() - time.sleep(0.05) - - -def setup_onroad_sidebar(click, pm: PubMaster, scroll=None): - setup_onroad(click, pm) - click(100, 100) # open sidebar - - -def setup_onroad_alert(click, pm: PubMaster, size: log.SelfdriveState.AlertSize, text1: str, text2: str, status: log.SelfdriveState.AlertStatus): - setup_onroad(click, pm) - alert = messaging.new_message('selfdriveState') - ss = alert.selfdriveState - ss.alertSize = size - ss.alertText1 = text1 - ss.alertText2 = text2 - ss.alertStatus = status - for _ in range(5): - pm.send('selfdriveState', alert) - alert.clear_write_flag() - time.sleep(0.05) - - -def setup_onroad_small_alert(click, pm: PubMaster, scroll=None): - setup_onroad_alert(click, pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal) - - -def setup_onroad_medium_alert(click, pm: PubMaster, scroll=None): - setup_onroad_alert(click, pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt) - - -def setup_onroad_full_alert(click, pm: PubMaster, scroll=None): - setup_onroad_alert(click, pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical) - - -def setup_onroad_full_alert_multiline(click, pm: PubMaster, scroll=None): - setup_onroad_alert(click, pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal) - - -def setup_onroad_full_alert_long_text(click, pm: PubMaster, scroll=None): - setup_onroad_alert(click, pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt) - - -CASES = { - "homescreen": setup_homescreen, - "homescreen_paired": setup_homescreen, - "homescreen_prime": setup_homescreen, - "homescreen_update_available": setup_homescreen_update_available, - "homescreen_unifont": setup_homescreen, - "settings_device": setup_settings, - "settings_network": setup_settings_network, - "settings_network_advanced": setup_settings_network_advanced, - "settings_toggles": setup_settings_toggles, - "settings_software": setup_settings_software, - "settings_software_download": setup_settings_software_download, - "settings_software_release_notes": setup_settings_software_release_notes, - "settings_software_branch_switcher": setup_settings_software_branch_switcher, - "settings_firehose": setup_settings_firehose, - "settings_developer": setup_settings_developer, - "keyboard": setup_keyboard, - "pair_device": setup_pair_device, - "offroad_alert": setup_offroad_alert, - "confirmation_dialog": setup_confirmation_dialog, - "experimental_mode_description": setup_experimental_mode_description, - "openpilot_long_confirmation_dialog": setup_openpilot_long_confirmation_dialog, - "onroad": setup_onroad, - "onroad_sidebar": setup_onroad_sidebar, - "onroad_small_alert": setup_onroad_small_alert, - "onroad_medium_alert": setup_onroad_medium_alert, - "onroad_full_alert": setup_onroad_full_alert, - "onroad_full_alert_multiline": setup_onroad_full_alert_multiline, - "onroad_full_alert_long_text": setup_onroad_full_alert_long_text, -} - -# sunnypilot cases -CASES.update({ - "settings_sunnylink": setup_settings_sunnylink, - "settings_models": setup_settings_models, - "settings_steering": setup_settings_steering, - "settings_cruise": setup_settings_cruise, - "settings_visuals": setup_settings_visuals, - "settings_display": setup_settings_display, - "settings_osm": setup_settings_osm, - "settings_trips": setup_settings_trips, - "settings_vehicle": setup_settings_vehicle, -}) - - -class TestUI: - def __init__(self): - os.environ["SCALE"] = os.getenv("SCALE", "1") - os.environ["BIG"] = "1" - sys.modules["mouseinfo"] = False - - def setup(self): - # Seed minimal offroad state - self.pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"]) - ds = messaging.new_message('deviceState') - ds.deviceState.networkType = log.DeviceState.NetworkType.wifi - for _ in range(5): - self.pm.send('deviceState', ds) - ds.clear_write_flag() - time.sleep(0.05) - time.sleep(0.5) - try: - self.ui = pywinctl.getWindowsWithTitle("UI")[0] - except Exception as e: - print(f"failed to find ui window, assuming that it's in the top left (for Xvfb) {e}") - self.ui = namedtuple("bb", ["left", "top", "width", "height"])(0, 0, 2160, 1080) - - def screenshot(self, name: str): - full_screenshot = pyautogui.screenshot() - cropped = full_screenshot.crop((self.ui.left, self.ui.top, self.ui.left + self.ui.width, self.ui.top + self.ui.height)) - cropped.save(SCREENSHOTS_DIR / f"{name}.png") - - def click(self, x: int, y: int, *args, **kwargs): - pyautogui.mouseDown(self.ui.left + x, self.ui.top + y, *args, **kwargs) - time.sleep(0.01) - pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs) - - def scroll(self, clicks: int, x, y, *args, **kwargs): - if clicks == 0: - return - click = -1 if clicks < 0 else 1 # -1 = down, 1 = up - for _ in range(abs(clicks)): - pyautogui.scroll(click, self.ui.left + x, self.ui.top + y, *args, **kwargs) # scroll for individual clicks since we need to delay between clicks - time.sleep(0.01) # small delay between scroll clicks to work properly - time.sleep(2) # wait for scroll to fully settle - - @with_processes(["ui"]) - def test_ui(self, name, setup_case): - self.setup() - time.sleep(UI_DELAY) # wait for UI to start - setup_case(self.click, self.pm, self.scroll) - self.screenshot(name) - - -def create_screenshots(): - if TEST_OUTPUT_DIR.exists(): - shutil.rmtree(TEST_OUTPUT_DIR) - SCREENSHOTS_DIR.mkdir(parents=True) - - t = TestUI() - for name, setup in CASES.items(): - with OpenpilotPrefix(): - params = Params() - params.put("DongleId", "123456789012345") - params.put("SunnylinkDongleId", "123456789012345") - - # Set branch name - params.put("UpdaterCurrentDescription", VERSION) - params.put("UpdaterNewDescription", VERSION) - - # Set terms and training version (to skip onboarding) - params.put("HasAcceptedTerms", terms_version) - params.put("CompletedTrainingVersion", training_version) - params.put("HasAcceptedTermsSP", terms_version_sp) - params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version) - - if name == "homescreen_paired": - params.put("PrimeType", 0) # NONE - elif name == "homescreen_prime": - params.put("PrimeType", 2) # LITE - elif name == "homescreen_unifont": - params.put("LanguageSetting", "zh-CHT") # Traditional Chinese - - t.test_ui(name, setup) - - -if __name__ == "__main__": - create_screenshots() diff --git a/selfdrive/ui/tests/test_ui/template.html b/selfdrive/ui/tests/test_ui/template.html deleted file mode 100644 index 68df5879e6..0000000000 --- a/selfdrive/ui/tests/test_ui/template.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - -{% for name, (image, ref_image) in cases.items() %} - -

{{name}}

-
-
- -
-
- -
- -{% endfor %} - \ No newline at end of file diff --git a/selfdrive/ui/translations/app_fr.po b/selfdrive/ui/translations/app_fr.po index 409761588e..ed9900a6ba 100644 --- a/selfdrive/ui/translations/app_fr.po +++ b/selfdrive/ui/translations/app_fr.po @@ -21,12 +21,12 @@ msgstr "" #: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." -msgstr " L'étalonnage de la réponse du couple de direction est terminé." +msgstr " Calibration de la réponse du couple de direction terminée." #: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." -msgstr " L'étalonnage de la réponse du couple de direction est terminé à {}%." +msgstr " Calibration du couple de direction : {}% effectué." #: selfdrive/ui/layouts/settings/device.py:133 #, python-format @@ -61,32 +61,31 @@ msgstr "5G" #: selfdrive/ui/layouts/settings/developer.py:23 msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." +"WARNING: openpilot longitudinal control is in alpha for this car and will" +" disable Automatic Emergency Braking (AEB).

On this car, " +"openpilot defaults to the car's built-in ACC instead of openpilot's " +"longitudinal control. Enable this to switch to openpilot longitudinal " +"control. Enabling Experimental mode is recommended when enabling openpilot " +"longitudinal control alpha. Changing this setting will restart openpilot if " +"the car is powered on." msgstr "" "ATTENTION : le contrôle longitudinal openpilot est en alpha pour cette " -"voiture et désactivera le freinage d'urgence automatique (AEB).

Sur cette voiture, openpilot utilise par défaut le régulateur de " -"vitesse adaptatif intégré au véhicule plutôt que le contrôle longitudinal " -"d'openpilot. Activez ceci pour passer au contrôle longitudinal openpilot. Il " -"est recommandé d'activer le mode expérimental lors de l'activation du " -"contrôle longitudinal openpilot alpha." +"voiture et désactivera le freinage d'urgence automatique " +"(AEB).


Sur cette voiture, openpilot utilise par défaut le " +"régulateur de vitesse adaptatif intégré au véhicule plutôt que le contrôle " +"longitudinal d'openpilot. Activez ceci pour passer au contrôle longitudinal " +"openpilot. Il est recommandé d'activer le mode expérimental lors de " +"l'activation du contrôle longitudinal openpilot alpha." #: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." -msgstr "

L'étalonnage du délai de réponse de la direction est terminé." +msgstr "

La calibration du délai de direction est terminée." #: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." -msgstr "" -"

L'étalonnage du délai de réponse de la direction est terminé à {}%." +msgstr "

Calibration du délai de direction : {}% effectué." #: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format @@ -95,11 +94,12 @@ msgstr "ACTIF" #: selfdrive/ui/layouts/settings/developer.py:15 msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +"ADB (Android Debug Bridge) allows connecting to your device over USB or over" +" the network. See https://docs.comma.ai/how-to/connect-to-comma for more " +"info." msgstr "" -"ADB (Android Debug Bridge) permet de connecter votre appareil via USB ou via " -"le réseau. Voir https://docs.comma.ai/how-to/connect-to-comma pour plus " +"ADB (Android Debug Bridge) permet de connecter votre appareil via USB ou via" +" le réseau. Voir https://docs.comma.ai/how-to/connect-to-comma pour plus " "d'informations." #: selfdrive/ui/widgets/ssh_key.py:30 @@ -109,7 +109,7 @@ msgstr "AJOUTER" #: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" -msgstr "Paramètres APN" +msgstr "Paramètre APN" #: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format @@ -139,8 +139,8 @@ msgstr "Surveillance continue du conducteur" #: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." +"An alpha version of openpilot longitudinal control can be tested, along with" +" Experimental mode, on non-release branches." msgstr "" "Une version alpha du contrôle longitudinal openpilot peut être testée, avec " "le mode expérimental, sur des branches non publiées." @@ -210,10 +210,11 @@ msgstr "CONNECTER" #: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." -msgstr "CONNECTER..." +msgstr "CONNEXION..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 +#: system/ui/widgets/option_dialog.py:35 system/ui/widgets/keyboard.py:81 +#: system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "Annuler" @@ -221,7 +222,7 @@ msgstr "Annuler" #: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" -msgstr "Données cellulaire limitées" +msgstr "Données cellulaires limitées" #: selfdrive/ui/layouts/settings/device.py:68 #, python-format @@ -320,7 +321,7 @@ msgstr "Personnalité de conduite" #: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" -msgstr "EDITER" +msgstr "MODIFIER" #: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" @@ -354,7 +355,7 @@ msgstr "Activer les alertes de sortie de voie" #: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" -msgstr "Activer openpilot" +msgstr "Activer l'itinérance" #: selfdrive/ui/layouts/settings/developer.py:48 #, python-format @@ -364,7 +365,7 @@ msgstr "Activer SSH" #: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" -msgstr "Activer les alertes de sortie de voie" +msgstr "Activer le partage de connexion" #: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." @@ -389,22 +390,22 @@ msgstr "" #: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" -msgstr "Saisir l'APN" +msgstr "Entrez l'APN" #: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" -msgstr "Entrer le SSID" +msgstr "Entrez le SSID" #: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" -msgstr "Saisir le mot de passe du partage de connexion" +msgstr "Entrez un nouveau mot de passe de partage" #: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" -msgstr "Saisir le mot de passe" +msgstr "Entrez le mot de passe" #: selfdrive/ui/widgets/ssh_key.py:89 #, python-format @@ -424,8 +425,8 @@ msgstr "Mode expérimental" #: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +"Experimental mode is currently unavailable on this car since the car's stock" +" ACC is used for longitudinal control." msgstr "" "Le mode expérimental est actuellement indisponible sur cette voiture car " "l'ACC d'origine est utilisé pour le contrôle longitudinal." @@ -433,12 +434,12 @@ msgstr "" #: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." -msgstr "OUBLIER..." +msgstr "SUPPRESSION..." #: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" -msgstr "Terminer la configuration" +msgstr "Finir la config." #: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" @@ -450,47 +451,35 @@ msgstr "Mode Firehose" #: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" +"For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n" "\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" +"Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n" "\n" "\n" "Frequently Asked Questions\n" "\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" +"Does it matter how or where I drive? Nope, just drive as you normally would.\n" "\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a subset of your segments.\n" "\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be fine.\n" "\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." +"Does it matter which software I run? Yes, only upstream openpilot (and particular forks) are able to be used for training." msgstr "" -"Pour une efficacité maximale, rentrez votre appareil et connectez-le chaque " -"semaine à un bon adaptateur USB-C et au Wi‑Fi.\n" +"Pour une efficacité maximale, rentrez votre appareil et connectez-le chaque semaine à un bon adaptateur USB-C et au Wi‑Fi.\n" "\n" -"Le Mode Firehose peut aussi fonctionner pendant que vous conduisez si vous " -"êtes connecté à un hotspot ou à une carte SIM illimitée.\n" +"Le Mode Firehose peut aussi fonctionner pendant que vous conduisez si vous êtes connecté à un hotspot ou à une carte SIM illimitée.\n" "\n" "\n" "Foire aux questions\n" "\n" -"Est-ce que la manière ou l'endroit où je conduis compte ? Non, conduisez " -"normalement.\n" +"Est-ce que la manière ou l'endroit où je conduis compte ? Non, conduisez normalement.\n" "\n" -"Tous mes segments sont-ils récupérés en Mode Firehose ? Non, nous récupérons " -"de façon sélective un sous-ensemble de vos segments.\n" +"Tous mes segments sont-ils récupérés en Mode Firehose ? Non, nous récupérons de façon sélective un sous-ensemble de vos segments.\n" "\n" -"Quel est un bon adaptateur USB-C ? Tout chargeur rapide de téléphone ou " -"d'ordinateur portable convient.\n" +"Quel est un bon adaptateur USB-C ? Tout chargeur rapide de téléphone ou d'ordinateur portable convient.\n" "\n" -"Le logiciel utilisé importe-t-il ? Oui, seul openpilot amont (et certains " -"forks) peut être utilisé pour l'entraînement." +"Le logiciel utilisé importe-t-il ? Oui, seul openpilot amont (et certains forks) peut être utilisé pour l'entraînement." #: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format @@ -500,11 +489,11 @@ msgstr "Oublier" #: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" -msgstr "Oublier le réseau Wi-Fi \"{}\" ?" +msgstr "Oublier le réseau Wi‑Fi \"{}\" ?" #: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" -msgstr "BON" +msgstr "BONNE" #: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format @@ -518,7 +507,7 @@ msgstr "ÉLEVÉ" #: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" -msgstr "Réseau" +msgstr "Réseau masqué" #: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format @@ -569,14 +558,14 @@ msgstr "MAX" msgid "" "Maximize your training data uploads to improve openpilot's driving models." msgstr "" -"Maximisez vos envois de données d'entraînement pour améliorer les modèles de " -"conduite d'openpilot." +"Maximisez vos envois de données d'entraînement pour améliorer les modèles de" +" conduite d'openpilot." #: selfdrive/ui/layouts/settings/device.py:59 #: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" -msgstr "NC" +msgstr "N/A" #: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" @@ -661,8 +650,8 @@ msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " "prime offer." msgstr "" -"Associez votre appareil à comma connect (connect.comma.ai) et réclamez votre " -"offre comma prime." +"Associez votre appareil à comma connect (connect.comma.ai) et réclamez votre" +" offre comma prime." #: selfdrive/ui/widgets/setup.py:91 #, python-format @@ -679,15 +668,13 @@ msgstr "Éteindre" #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -"Eviter les transferts de données volumineux lorsque vous êtes connecté à un " -"réseau Wi-Fi limité" +"Empêcher les téléversements volumineux sur une connexion Wi‑Fi limitée" #: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -"Eviter les transferts de données volumineux lors d'une connexion à un réseau " -"cellulaire limité" +"Empêcher les téléversements volumineux sur une connexion cellulaire limitée" #: selfdrive/ui/layouts/settings/device.py:25 msgid "" @@ -736,11 +723,11 @@ msgstr "Redémarrer et mettre à jour" #: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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)." +"detected lane line without a turn signal activated while driving over 31 mph" +" (50 km/h)." msgstr "" -"Recevez des alertes pour revenir dans la voie lorsque votre véhicule dépasse " -"une ligne de voie détectée sans clignotant activé en roulant au-delà de 31 " +"Recevez des alertes pour revenir dans la voie lorsque votre véhicule dépasse" +" une ligne de voie détectée sans clignotant activé en roulant au-delà de 31 " "mph (50 km/h)." #: selfdrive/ui/layouts/settings/toggles.py:76 @@ -808,17 +795,17 @@ msgstr "Consultez les règles, fonctionnalités et limitations d'openpilot" #: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" -msgstr "SELECTIONNER" +msgstr "CHOISIR" #: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" -msgstr "Clefs SSH" +msgstr "Clés SSH" #: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." -msgstr "Analyse des réseaux Wi-Fi..." +msgstr "Recherche des réseaux Wi‑Fi..." #: system/ui/widgets/option_dialog.py:36 #, python-format @@ -833,7 +820,7 @@ msgstr "Sélectionner une branche" #: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" -msgstr "Sélectionner un langage" +msgstr "Sélectionner une langue" #: selfdrive/ui/layouts/settings/device.py:60 #, python-format @@ -856,8 +843,8 @@ msgstr "Standard" #: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed 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." msgstr "" @@ -881,7 +868,7 @@ msgstr "REPRENEZ IMMÉDIATEMENT LE CONTRÔLE" #: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 #: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" -msgstr "TEMPÉRATURE" +msgstr "TEMP." #: selfdrive/ui/layouts/settings/software.py:61 #, python-format @@ -891,7 +878,7 @@ msgstr "Branche cible" #: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" -msgstr "Mot de passe du partage de connexion" +msgstr "Mot de passe de partage" #: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" @@ -900,7 +887,7 @@ msgstr "Options" #: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" -msgstr "DÉSINSTALLER" +msgstr "SUPPRIMER" #: selfdrive/ui/layouts/home.py:155 #, python-format @@ -926,15 +913,15 @@ msgstr "" #: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" -msgstr "Mettre à niveau maintenant" +msgstr "Mettre à jour" #: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." msgstr "" -"Téléverser les données de la caméra orientée conducteur et aider à améliorer " -"l'algorithme de surveillance du conducteur." +"Téléverser les données de la caméra orientée conducteur et aider à améliorer" +" l'algorithme de surveillance du conducteur." #: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format @@ -971,8 +958,8 @@ msgid "" "NEVER ask you to add their GitHub username." msgstr "" "Avertissement : Ceci accorde un accès SSH à toutes les clés publiques dans " -"vos paramètres GitHub. N'entrez jamais un nom d'utilisateur GitHub autre que " -"le vôtre. Un employé comma ne vous demandera JAMAIS d'ajouter son nom " +"vos paramètres GitHub. N'entrez jamais un nom d'utilisateur GitHub autre que" +" le vôtre. Un employé comma ne vous demandera JAMAIS d'ajouter son nom " "d'utilisateur GitHub." #: selfdrive/ui/layouts/onboarding.py:111 @@ -992,12 +979,12 @@ msgstr "Wi‑Fi" #: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" -msgstr "Réseau Wi-Fi limité" +msgstr "Réseau Wi‑Fi limité" #: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" -msgstr "Mauvais mot de passe" +msgstr "Mot de passe incorrect" #: selfdrive/ui/layouts/onboarding.py:145 #, python-format @@ -1026,7 +1013,7 @@ msgstr "comma prime" #: system/ui/widgets/network.py:142 #, python-format msgid "default" -msgstr "défaut" +msgstr "par défaut" #: selfdrive/ui/layouts/settings/device.py:133 #, python-format @@ -1051,7 +1038,7 @@ msgstr "km/h" #: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" -msgstr "ne pas remplir pour une configuration automatique" +msgstr "laisser vide pour configuration automatique" #: selfdrive/ui/layouts/settings/device.py:134 #, python-format @@ -1091,30 +1078,30 @@ msgstr "openpilot indisponible" #: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" -"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:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

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." +"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:

End-to-End Longitudinal Control


Let the " +"driving model control the gas and brakes. openpilot will drive as it thinks " +"a human would, including stopping for red lights and stop signs. Since the " +"driving model decides the speed to drive, the set speed will only act as an " +"upper bound. This is an alpha quality feature; mistakes should be " +"expected.

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." msgstr "" "openpilot roule par défaut en mode chill. Le mode expérimental active des " "fonctionnalités de niveau alpha qui ne sont pas prêtes pour le mode chill. " "Les fonctionnalités expérimentales sont listées ci‑dessous:

Contrôle " -"longitudinal de bout en bout


Laissez le modèle de conduite contrôler " -"l'accélérateur et les freins. openpilot conduira comme il pense qu'un humain " -"le ferait, y compris s'arrêter aux feux rouges et aux panneaux stop. Comme " -"le modèle décide de la vitesse à adopter, la vitesse réglée n'agira que " -"comme une limite supérieure. C'est une fonctionnalité de qualité alpha ; des " -"erreurs sont à prévoir.

Nouvelle visualisation de conduite


La " -"visualisation passera à la caméra grand angle orientée route à basse vitesse " -"pour mieux montrer certains virages. Le logo du mode expérimental sera " -"également affiché en haut à droite." +"longitudinal de bout en bout
Laissez le modèle de conduite contrôler" +" l'accélérateur et les freins. openpilot conduira comme il pense qu'un " +"humain le ferait, y compris s'arrêter aux feux rouges et aux panneaux stop. " +"Comme le modèle décide de la vitesse à adopter, la vitesse réglée n'agira " +"que comme une limite supérieure. C'est une fonctionnalité de qualité alpha ;" +" des erreurs sont à prévoir.

Nouvelle visualisation de " +"conduite


La visualisation passera à la caméra grand angle orientée " +"route à basse vitesse pour mieux montrer certains virages. Le logo du mode " +"expérimental sera également affiché en haut à droite." #: selfdrive/ui/layouts/settings/device.py:165 #, python-format @@ -1122,24 +1109,18 @@ msgid "" "openpilot is continuously calibrating, resetting is rarely required. " "Resetting calibration will restart openpilot if the car is powered on." msgstr "" -"La modification de ce réglage redémarrera openpilot si la voiture est sous " +" La modification de ce réglage redémarrera openpilot si la voiture est sous " "tension." #: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." +"Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode." msgstr "" -"openpilot apprend à conduire en regardant des humains, comme vous, " -"conduire.\n" +"openpilot apprend à conduire en regardant des humains, comme vous, conduire.\n" "\n" -"Le Mode Firehose vous permet de maximiser vos envois de données " -"d'entraînement pour améliorer les modèles de conduite d'openpilot. Plus de " -"données signifie des modèles plus grands, ce qui signifie un meilleur Mode " -"expérimental." +"Le Mode Firehose vous permet de maximiser vos envois de données d'entraînement pour améliorer les modèles de conduite d'openpilot. Plus de données signifie des modèles plus grands, ce qui signifie un meilleur Mode expérimental." #: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format @@ -1153,8 +1134,8 @@ msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." msgstr "" -"openpilot exige que l'appareil soit monté à moins de 4° à gauche ou à droite " -"et à moins de 5° vers le haut ou 9° vers le bas." +"openpilot exige que l'appareil soit monté à moins de 4° à gauche ou à droite" +" et à moins de 5° vers le haut ou 9° vers le bas." #: selfdrive/ui/layouts/settings/device.py:134 #, python-format @@ -1164,7 +1145,7 @@ msgstr "droite" #: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" -msgstr "non limité" +msgstr "illimité" #: selfdrive/ui/layouts/settings/device.py:133 #, python-format @@ -1234,3 +1215,1563 @@ msgstr "✓ ABONNÉ" #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Mode Firehose 🔥" + +msgid " (Default)" +msgstr " (Par défaut)" + +msgid "%" +msgstr "%" + +msgid "" +"(Only for highest tiers, and does NOT bring ANY benefit to you yet. We are " +"just testing data volume.)" +msgstr "" +"(Réservé aux niveaux les plus élevés, et ne vous apporte AUCUN avantage pour" +" l'instant. Nous testons juste le volume de données.)" + +msgid ", but we'll be here when you're ready to come back." +msgstr ", mais nous serons là quand vous serez prêt à revenir." + +msgid "" +"WARNING: sunnypilot longitudinal control is in alpha for this car and " +"will disable Automatic Emergency Braking (AEB).

On this car, " +"sunnypilot defaults to the car's built-in ACC instead of sunnypilot's " +"longitudinal control. Enable this to switch to sunnypilot longitudinal " +"control. Enabling Experimental mode is recommended when enabling sunnypilot " +"longitudinal control alpha. Changing this setting will restart sunnypilot if" +" the car is powered on." +msgstr "" +"ATTENTION : le contrôle longitudinal sunnypilot est en alpha pour cette " +"voiture et désactivera le freinage d'urgence automatique " +"(AEB).

Sur cette voiture, sunnypilot utilise par défaut le " +"régulateur de vitesse adaptatif intégré au véhicule plutôt que le contrôle " +"longitudinal de sunnypilot. Activez ceci pour passer au contrôle " +"longitudinal sunnypilot. Il est recommandé d'activer le mode expérimental " +"lors de l'activation du contrôle longitudinal sunnypilot alpha. La " +"modification de ce réglage redémarrera sunnypilot si la voiture est sous " +"tension." + +msgid "" +"A beautiful rainbow effect on the path the model wants to take. It does not " +"affect driving in any way." +msgstr "" +"Un magnifique effet arc-en-ciel sur le trajet que le modèle souhaite " +"emprunter. Cela n'affecte en rien la conduite." + +msgid "" +"A chime and on-screen alert will play when the traffic light you are waiting" +" for turns green and you have no vehicle in front of you.
Note: This " +"chime is only designed as a notification. It is the driver's responsibility " +"to observe their environment and make decisions accordingly." +msgstr "" +"Un carillon et une alerte à l'écran se déclencheront lorsque le feu de " +"circulation que vous attendez passera au vert et qu'il n'y a pas de véhicule" +" devant vous.
Note : Ce carillon est uniquement conçu comme une " +"notification. Il incombe au conducteur d'observer son environnement et de " +"prendre les décisions en conséquence." + +msgid "" +"A chime and on-screen alert will play when you are stopped, and the vehicle " +"in front of you start moving.
Note: This chime is only designed as a " +"notification. It is the driver's responsibility to observe their environment" +" and make decisions accordingly." +msgstr "" +"Un carillon et une alerte à l'écran se déclencheront lorsque vous êtes à " +"l'arrêt et que le véhicule devant vous commence à avancer.
Note : Ce " +"carillon est uniquement conçu comme une notification. Il incombe au " +"conducteur d'observer son environnement et de prendre les décisions en " +"conséquence." + +msgid "ALL TIME" +msgstr "TOUT TEMPS" + +msgid "Actuator Delay:" +msgstr "Délai actionneur :" + +msgid "Adjust Lane Turn Speed" +msgstr "Ajuster la vitesse de virage de voie" + +msgid "Adjust Software Delay" +msgstr "Ajuster le délai logiciel" + +msgid "" +"Adjust the software delay when Live Learning Steer Delay is toggled off. The" +" default software delay value is 0.2" +msgstr "" +"Ajuster le délai logiciel lorsque l'apprentissage en direct du délai de " +"direction est désactivé. La valeur par défaut du délai logiciel est 0.2" + +msgid "All" +msgstr "Tout" + +msgid "All states (~6.0 GB)" +msgstr "Toutes les régions (~6.0 Go)" + +msgid "" +"Allows the driver to provide limited steering input while openpilot is " +"engaged." +msgstr "" +"Permet au conducteur de fournir une entrée de direction limitée pendant " +"qu'openpilot est engagé." + +msgid "Always On" +msgstr "Toujours allumé" + +msgid "" +"An alpha version of sunnypilot longitudinal control can be tested, along " +"with Experimental mode, on non-release branches." +msgstr "" +"Une version alpha du contrôle longitudinal sunnypilot peut être testée, avec" +" le mode expérimental, sur des branches non publiées." + +msgid "" +"Apply a custom timeout for settings UI.
This is the time after which " +"settings UI closes automatically if user is not interacting with the screen." +msgstr "" +"Appliquer un délai d'inactivité personnalisé pour l'interface des " +"paramètres.
C'est le temps après lequel l'interface des paramètres se " +"ferme automatiquement si l'utilisateur n'interagit pas avec l'écran." + +msgid "Are you sure you want to backup your current sunnypilot settings?" +msgstr "" +"Êtes-vous sûr de vouloir sauvegarder vos paramètres sunnypilot actuels ?" + +msgid "Are you sure you want to enter Always Offroad mode?" +msgstr "Êtes-vous sûr de vouloir entrer en mode toujours hors route ?" + +msgid "Are you sure you want to exit Always Offroad mode?" +msgstr "Êtes-vous sûr de vouloir quitter le mode toujours hors route ?" + +msgid "" +"Are you sure you want to reset all sunnypilot settings to default? Once the " +"settings are reset, there is no going back." +msgstr "" +"Êtes-vous sûr de vouloir réinitialiser tous les paramètres sunnypilot par " +"défaut ? Une fois les paramètres réinitialisés, il n'y a pas de retour en " +"arrière." + +msgid "" +"Are you sure you want to restore the last backed up sunnypilot settings?" +msgstr "" +"Êtes-vous sûr de vouloir restaurer la dernière sauvegarde des paramètres " +"sunnypilot ?" + +msgid "Assist" +msgstr "Assistance" + +msgid "" +"Assist: Adjusts the vehicle's cruise speed based on the current road's speed" +" limit when operating the +/- buttons." +msgstr "" +"Assistance : Ajuste la vitesse de croisière du véhicule en fonction de la " +"limite de vitesse de la route actuelle lors de l'utilisation des boutons " +"+/-." + +msgid "Auto (Dark)" +msgstr "Auto (Sombre)" + +msgid "Auto (Default)" +msgstr "Auto (Par défaut)" + +msgid "Auto Lane Change by Blinker" +msgstr "Changement de voie auto par clignotant" + +msgid "Auto Lane Change: Delay with Blind Spot" +msgstr "Changement de voie auto : Délai avec angle mort" + +msgid "Backup Failed" +msgstr "Échec de la sauvegarde" + +msgid "Backup Settings" +msgstr "Sauvegarder les paramètres" + +msgid "" +"Become a sponsor of sunnypilot to get early access to sunnylink features " +"when they become available." +msgstr "" +"Devenez parrain de sunnypilot pour obtenir un accès anticipé aux " +"fonctionnalités sunnylink dès leur disponibilité." + +msgid "Bottom" +msgstr "Bas" + +msgid "CLEAR" +msgstr "VIDER" + +msgid "Cancel Download" +msgstr "Annuler le téléchargement" + +msgid "Car First" +msgstr "Voiture d'abord" + +msgid "" +"Car First: Use Speed Limit data from Car if available, else use from " +"OpenStreetMaps" +msgstr "" +"Voiture d'abord : Utiliser les données de limite de vitesse de la voiture si" +" disponibles, sinon utiliser OpenStreetMaps" + +msgid "Car Only" +msgstr "Voiture uniquement" + +msgid "Car Only: Use Speed Limit data only from Car" +msgstr "" +"Voiture uniquement : Utiliser les données de limite de vitesse uniquement de" +" la voiture" + +msgid "" +"Changing this setting will restart sunnypilot if the car is powered on." +msgstr "" +"La modification de ce réglage redémarrera sunnypilot si la voiture est sous " +"tension." + +msgid "" +"Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is " +"manually pressed in sunnypilot." +msgstr "" +"Choisissez comment le centrage automatique de voie (ALC) se comporte après " +"un appui manuel sur la pédale de frein dans sunnypilot." + +msgid "Choose your sponsorship tier and confirm your support" +msgstr "Choisissez votre niveau de parrainage et confirmez votre soutien" + +msgid "Clear Cache" +msgstr "Vider le cache" + +msgid "Clear Model Cache" +msgstr "Vider le cache des modèles" + +msgid "Click the Sponsor button for more details" +msgstr "Cliquez sur le bouton Sponsor pour plus de détails" + +msgid "Colors represent vehicle fingerprint status:" +msgstr "Les couleurs représentent le statut d'empreinte du véhicule :" + +msgid "Combined" +msgstr "Combiné" + +msgid "Combined: Use combined Speed Limit data from Car & OpenStreetMaps" +msgstr "" +"Combiné : Utiliser les données combinées de limite de vitesse de la voiture " +"et d'OpenStreetMaps" + +msgid "Confirm" +msgstr "Confirmer" + +msgid "Controls state of the device after boot/sleep." +msgstr "Contrôle l'état de l'appareil après démarrage/veille." + +msgid "Cooperative Steering (Beta)" +msgstr "Direction coopérative (Bêta)" + +msgid "Country" +msgstr "Pays" + +msgid "Cruise" +msgstr "Régulateur" + +msgid "Current Model" +msgstr "Modèle actuel" + +msgid "Custom ACC Speed Increments" +msgstr "Incréments de vitesse ACC personnalisés" + +msgid "Custom Longitudinal Tuning" +msgstr "Réglage longitudinal personnalisé" + +msgid "Customize Lane Change" +msgstr "Personnaliser le changement de voie" + +msgid "Customize MADS" +msgstr "Personnaliser MADS" + +msgid "Customize Source" +msgstr "Personnaliser la source" + +msgid "Customize Torque Params" +msgstr "Personnaliser les paramètres de couple" + +msgid "DELETE" +msgstr "SUPPRIMER" + +msgid "DISABLED" +msgstr "DÉSACTIVÉ" + +msgid "Database Update" +msgstr "Mise à jour de la base de données" + +msgid "Decline, uninstall sunnypilot" +msgstr "Refuser, désinstaller sunnypilot" + +msgid "Default" +msgstr "Par défaut" + +msgid "Default Model" +msgstr "Modèle par défaut" + +msgid "Default: Device will boot/wake-up normally & will be ready to engage." +msgstr "Par défaut : L'appareil démarrera normalement et sera prêt à engager." + +msgid "Delay before lateral control resumes after the turn signal ends." +msgstr "" +"Délai avant la reprise du contrôle latéral après la fin du clignotant." + +msgid "Developer UI" +msgstr "Interface développeur" + +msgid "" +"Device will automatically shutdown after set time once the engine is turned off.\n" +"(30h is the default)" +msgstr "" +"L'appareil s'éteindra automatiquement après le temps défini une fois le moteur coupé.\n" +"(30h par défaut)" + +msgid "Disable" +msgstr "Désactiver" + +msgid "Disable Updates" +msgstr "Désactiver les mises à jour" + +msgid "" +"Disable the sunnypilot Longitudinal Control (alpha) toggle to allow " +"Intelligent Cruise Button Management." +msgstr "" +"Désactivez l'option contrôle longitudinal sunnypilot (alpha) pour permettre " +"la gestion intelligente des boutons de régulateur." + +msgid "Disengage" +msgstr "Désengager" + +msgid "Disengage to Enter Always Offroad Mode" +msgstr "Désengager pour entrer en mode toujours hors route" + +msgid "Disengage: ALC will disengage when the brake pedal is pressed." +msgstr "" +"Désengager : ALC se désengagera lorsque la pédale de frein est enfoncée." + +msgid "Display" +msgstr "Affichage" + +msgid "Display Metrics Below Chevron" +msgstr "Afficher les métriques sous le chevron" + +msgid "Display Road Name" +msgstr "Afficher le nom de la route" + +msgid "Display Turn Signals" +msgstr "Afficher les clignotants" + +msgid "Display real-time parameters and metrics from various sources." +msgstr "" +"Afficher les paramètres et métriques en temps réel provenant de diverses " +"sources." + +msgid "" +"Display steering arc on the driving screen when lateral control is enabled." +msgstr "" +"Afficher l'arc de direction sur l'écran de conduite lorsque le contrôle " +"latéral est activé." + +msgid "" +"Display useful metrics below the chevron that tracks the lead car only " +"applicable to cars with sunnypilot longitudinal control." +msgstr "" +"Afficher des métriques utiles sous le chevron qui suit le véhicule en tête, " +"uniquement applicable aux voitures avec le contrôle longitudinal sunnypilot." + +msgid "" +"Displays the name of the road the car is traveling on.
The OpenStreetMap " +"database of the location must be downloaded from the OSM panel to fetch the " +"road name." +msgstr "" +"Affiche le nom de la route sur laquelle la voiture circule.
La base de " +"données OpenStreetMap de la localisation doit être téléchargée depuis le " +"panneau OSM pour récupérer le nom de la route." + +msgid "Distance" +msgstr "Distance" + +msgid "Downloaded Maps" +msgstr "Cartes téléchargées" + +msgid "Downloading Map" +msgstr "Téléchargement de la carte" + +msgid "Downloading Maps..." +msgstr "Téléchargement des cartes..." + +msgid "Driver Camera Preview" +msgstr "Aperçu caméra conducteur" + +msgid "Drives" +msgstr "Trajets" + +msgid "Driving Model" +msgstr "Modèle de conduite" + +msgid "Dynamic" +msgstr "Dynamique" + +msgid "Early Access: Become a sunnypilot Sponsor" +msgstr "Accès anticipé : Devenez parrain sunnypilot" + +msgid "Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle." +msgstr "" +"Activez \"Toujours hors route\" dans le panneau Appareil, ou éteignez le " +"véhicule pour basculer." + +msgid "Enable Always Offroad" +msgstr "Activer le mode toujours hors route" + +msgid "Enable Custom Tuning" +msgstr "Activer le réglage personnalisé" + +msgid "Enable Dynamic Experimental Control" +msgstr "Activer le contrôle expérimental dynamique" + +msgid "Enable Standstill Timer" +msgstr "Activer le chronomètre à l'arrêt" + +msgid "Enable Tesla Rainbow Mode" +msgstr "Activer le mode arc-en-ciel Tesla" + +msgid "" +"Enable custom Short & Long press increments for cruise speed " +"increase/decrease." +msgstr "" +"Activer les incréments personnalisés d'appui court et long pour " +"augmenter/diminuer la vitesse de croisière." + +msgid "Enable driver monitoring even when sunnypilot is not engaged." +msgstr "" +"Activer la surveillance du conducteur même lorsque sunnypilot n'est pas " +"engagé." + +msgid "Enable sunnylink" +msgstr "Activer sunnylink" + +msgid "Enable sunnylink uploader (infrastructure test)" +msgstr "Activer le téléversement sunnylink" + +msgid "" +"Enable sunnylink uploader to allow sunnypilot to upload your driving data to" +" sunnypilot servers. " +msgstr "" +"Activer le téléversement sunnylink pour permettre à sunnypilot de téléverser" +" vos données de conduite vers les serveurs sunnypilot. " + +msgid "Enable sunnypilot" +msgstr "Activer sunnypilot" + +msgid "" +"Enable the beloved MADS feature. Disable toggle to revert back to stock " +"sunnypilot engagement/disengagement." +msgstr "" +"Activer la fonctionnalité MADS. Désactivez l'option pour revenir au " +"comportement d'engagement/désengagement sunnypilot d'origine." + +msgid "" +"Enable the sunnypilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "" +"Activez l'option de contrôle longitudinal sunnypilot (alpha) pour autoriser " +"le mode expérimental." + +msgid "" +"Enable this for the car to learn and adapt its steering response time. " +"Disable to use a fixed steering response time. Keeping this on provides the " +"stock openpilot experience." +msgstr "" +"Activez pour que la voiture apprenne et adapte son temps de réponse de " +"direction. Désactivez pour utiliser un temps de réponse fixe. Garder activé " +"offre l'expérience openpilot d'origine." + +msgid "" +"Enable this to enforce sunnypilot to steer with Torque lateral control." +msgstr "" +"Activez ceci pour forcer sunnypilot à diriger avec le contrôle latéral par " +"couple." + +msgid "" +"Enable toggle to allow the model to determine when to use sunnypilot ACC or " +"sunnypilot End to End Longitudinal." +msgstr "" +"Activez l'option pour permettre au modèle de déterminer quand utiliser le " +"ACC sunnypilot ou le contrôle longitudinal de bout en bout sunnypilot." + +msgid "" +"Enables custom tuning for Torque lateral control. Modifying Lateral " +"Acceleration Factor and Friction below will override the offline values " +"indicated in the YAML files within \"opendbc/car/torque_data\". The values " +"will also be used live when \"Manual Real-Time Tuning\" toggle is enabled." +msgstr "" +"Active le réglage personnalisé pour le contrôle latéral par couple. La " +"modification du facteur d'accélération latérale et de la friction ci-dessous" +" remplacera les valeurs hors ligne indiquées dans les fichiers YAML du " +"dossier \"opendbc/car/torque_data\". Les valeurs seront également utilisées " +"en direct lorsque l'option \"Réglage manuel en temps réel\" est activée." + +msgid "Enables or disables the GitHub runner service." +msgstr "Active ou désactive le service GitHub runner." + +msgid "" +"Enables self-tune for Torque lateral control for platforms that do not use " +"Torque lateral control by default." +msgstr "" +"Active l'auto-réglage pour le contrôle latéral par couple pour les " +"plateformes qui n'utilisent pas le contrôle latéral par couple par défaut." + +msgid "" +"Enabling this will display warnings when a vehicle is detected in your blind" +" spot as long as your car has BSM supported." +msgstr "" +"L'activation affichera des avertissements lorsqu'un véhicule est détecté " +"dans votre angle mort, à condition que votre voiture supporte le BSM." + +msgid "Enforce Factory Longitudinal Control" +msgstr "Forcer le contrôle longitudinal d'usine" + +msgid "Enforce Torque Lateral Control" +msgstr "Forcer le contrôle latéral par couple" + +msgid "" +"Enforces the torque lateral controller to use the fixed values instead of " +"the learned values from Self-Tune. Enabling this toggle overrides Self-Tune " +"values." +msgstr "" +"Force le contrôleur latéral par couple à utiliser les valeurs fixes au lieu " +"des valeurs apprises par l'auto-réglage. L'activation de cette option " +"remplace les valeurs de l'auto-réglage." + +msgid "" +"Engage lateral and longitudinal control with cruise control engagement." +msgstr "" +"Engager le contrôle latéral et longitudinal avec l'activation du régulateur " +"de vitesse." + +msgid "Enter model year (e.g., 2021) and model (Toyota Corolla):" +msgstr "Entrez l'année (ex. 2021) et le modèle (Toyota Corolla) :" + +msgid "Enter search query" +msgstr "Entrez votre recherche" + +msgid "Error Log" +msgstr "Journal d'erreurs" + +msgid "Error: Invalid download. Retry." +msgstr "Erreur : Téléchargement invalide. Réessayez." + +msgid "Exit Always Offroad" +msgstr "Quitter le mode toujours hors route" + +msgid "" +"Experimental feature to enable auto-resume during stop-and-go for certain " +"supported Subaru platforms." +msgstr "" +"Fonctionnalité expérimentale pour activer la reprise automatique en stop-" +"and-go pour certaines plateformes Subaru supportées." + +msgid "" +"Experimental feature to enable stop and go for Subaru Global models with " +"manual handbrake. Models with electric parking brake should keep this " +"disabled. Thanks to martinl for this implementation!" +msgstr "" +"Fonctionnalité expérimentale pour activer le stop and go pour les modèles " +"Subaru Global avec frein à main manuel. Les modèles avec frein de " +"stationnement électrique doivent garder ceci désactivé. Merci à martinl pour" +" cette implémentation !" + +msgid "FAULT" +msgstr "DÉFAUT" + +msgid "FETCHING..." +msgstr "RÉCUPÉRATION..." + +msgid "Fetching Latest Models" +msgstr "Récupération des derniers modèles" + +msgid "Fingerprinted automatically" +msgstr "Empreinte automatique" + +msgid "Fixed" +msgstr "Fixe" + +msgid "Fixed: Adds a fixed offset [Speed Limit + Offset]" +msgstr "Fixe : Ajoute un décalage fixe [Limite de vitesse + Décalage]" + +msgid "Follow the prompts to complete the pairing process" +msgstr "Suivez les instructions pour terminer le processus d'association" + +msgid "" +"For applicable vehicles, always display the true vehicle current speed from " +"wheel speed sensors." +msgstr "" +"Pour les véhicules compatibles, toujours afficher la vitesse réelle actuelle" +" du véhicule à partir des capteurs de vitesse de roue." + +msgid "For secure backup, restore, and remote configuration" +msgstr "" +"Pour la sauvegarde sécurisée, la restauration et la configuration à distance" + +msgid "Friction" +msgstr "Friction" + +msgid "GitHub Runner Service" +msgstr "Service GitHub Runner" + +msgid "Green Traffic Light Alert (Beta)" +msgstr "Alerte feu vert (Bêta)" + +msgid "Hours" +msgstr "Heures" + +msgid "" +"If sponsorship status was not updated, please contact a moderator on the " +"community forum at https://community.sunnypilot.ai" +msgstr "" +"Si le statut de parrainage n'a pas été mis à jour, veuillez contacter un " +"modérateur sur le forum communautaire à https://community.sunnypilot.ai" + +msgid "" +"If you're driving at 20 mph (32 km/h) or below and have your blinker on, the" +" car will plan a turn in that direction at the nearest drivable path. This " +"prevents situations (like at red lights) where the car might plan the wrong " +"turn direction." +msgstr "" +"Si vous roulez à 32 km/h ou moins avec le clignotant activé, la voiture " +"planifiera un virage dans cette direction vers le chemin praticable le plus " +"proche. Cela évite les situations (comme aux feux rouges) où la voiture " +"pourrait planifier la mauvaise direction de virage." + +msgid "Info" +msgstr "Info" + +msgid "Information: Displays the current road's speed limit." +msgstr "Information : Affiche la limite de vitesse de la route actuelle." + +msgid "Intelligent Cruise Button Management (ICBM) (Alpha)" +msgstr "Gestion intelligente des boutons de régulateur (ICBM) (Alpha)" + +msgid "" +"Intelligent Cruise Button Management is currently unavailable on this " +"platform." +msgstr "" +"La gestion intelligente des boutons de régulateur est actuellement " +"indisponible sur cette plateforme." + +msgid "Interactivity Timeout" +msgstr "Délai d'inactivité" + +msgid "" +"Join our Community Forum at https://community.sunnypilot.ai and reach out to" +" a moderator if you have issues" +msgstr "" +"Rejoignez notre forum communautaire à https://community.sunnypilot.ai et " +"contactez un modérateur si vous avez des problèmes" + +msgid "KM" +msgstr "KM" + +msgid "Last checked {}" +msgstr "Dernière vérification {}" + +msgid "Lateral Acceleration Factor" +msgstr "Facteur d'accélération latérale" + +msgid "Lead Departure Alert (Beta)" +msgstr "Alerte de départ du véhicule en tête (Bêta)" + +msgid "Less Restrict Settings for Self-Tune (Beta)" +msgstr "Paramètres moins restrictifs pour l'auto-réglage (Bêta)" + +msgid "" +"Less strict settings when using Self-Tune. This allows torqued to be more " +"forgiving when learning values." +msgstr "" +"Paramètres moins stricts lors de l'utilisation de l'auto-réglage. Cela " +"permet à torqued d'être plus tolérant lors de l'apprentissage des valeurs." + +msgid "Live Learning Steer Delay" +msgstr "Apprentissage en direct du délai de direction" + +msgid "Live Steer Delay:" +msgstr "Délai direction en direct :" + +msgid "Long Press Increment" +msgstr "Incrément appui long" + +msgid "Manual Real-Time Tuning" +msgstr "Réglage manuel en temps réel" + +msgid "Manually selected fingerprint" +msgstr "Empreinte sélectionnée manuellement" + +msgid "Map First" +msgstr "Carte d'abord" + +msgid "" +"Map First: Use Speed Limit data from OpenStreetMaps if available, else use " +"from Car" +msgstr "" +"Carte d'abord : Utiliser les données de limite de vitesse d'OpenStreetMaps " +"si disponibles, sinon utiliser celles de la voiture" + +msgid "Map Only" +msgstr "Carte uniquement" + +msgid "Map Only: Use Speed Limit data only from OpenStreetMaps" +msgstr "" +"Carte uniquement : Utiliser les données de limite de vitesse uniquement " +"d'OpenStreetMaps" + +msgid "Mapd Version" +msgstr "Version Mapd" + +msgid "Max Time Offroad" +msgstr "Durée max hors route" + +msgid "Miles" +msgstr "Km" + +msgid "Minimum Speed to Pause Lateral Control" +msgstr "Vitesse minimum pour suspendre le contrôle latéral" + +msgid "" +"Model download has started in the background. We suggest resetting " +"calibration. Would you like to do that now?" +msgstr "" +"Le téléchargement du modèle a démarré en arrière-plan. Nous suggérons de " +"réinitialiser la calibration. Voulez-vous le faire maintenant ?" + +msgid "Models" +msgstr "Modèles" + +msgid "Modular Assistive Driving System (MADS)" +msgstr "Système d'aide à la conduite modulaire (MADS)" + +msgid "Near" +msgstr "Proche" + +msgid "Neural Network Lateral Control (NNLC)" +msgstr "Contrôle latéral par réseau neuronal (NNLC)" + +msgid "No" +msgstr "Non" + +msgid "No vehicle selected" +msgstr "Aucun véhicule sélectionné" + +msgid "None" +msgstr "Aucun" + +msgid "None: No Offset" +msgstr "Aucun : Pas de décalage" + +msgid "Not Paired" +msgstr "Non associé" + +msgid "Not Sponsor" +msgstr "Non parrain" + +msgid "Not fingerprinted or manually selected" +msgstr "Pas d'empreinte ou sélection manuelle" + +msgid "Not going to lie, it's sad to see you disabled sunnylink" +msgstr "Honnêtement, c'est triste de voir que vous avez désactivé sunnylink" + +msgid "" +"Note: For vehicles without LFA/LKAS button, disabling this will prevent " +"lateral control engagement." +msgstr "" +"Note : Pour les véhicules sans bouton LFA/LKAS, désactiver ceci empêchera " +"l'engagement du contrôle latéral." + +msgid "" +"Note: Once lateral control is engaged via UEM, it will remain engaged until " +"it is manually disabled via the MADS button or car shut off." +msgstr "" +"Note : Une fois le contrôle latéral engagé via UEM, il restera engagé " +"jusqu'à ce qu'il soit désactivé manuellement via le bouton MADS ou " +"l'extinction de la voiture." + +msgid "Nudge" +msgstr "Impulsion" + +msgid "Nudgeless" +msgstr "Sans impulsion" + +msgid "OSM" +msgstr "OSM" + +msgid "Off" +msgstr "Désactivé" + +msgid "Off: Disables the Speed Limit functions." +msgstr "Désactivé : Désactive les fonctions de limite de vitesse." + +msgid "Offline Only" +msgstr "Hors ligne uniquement" + +msgid "Offroad" +msgstr "Hors route" + +msgid "Offroad: Device will be in Always Offroad mode after boot/wake-up." +msgstr "" +"Hors route : L'appareil sera en mode toujours hors route après démarrage." + +msgid "Only available when vehicle is off, or always offroad mode is on" +msgstr "" +"Disponible uniquement lorsque le véhicule est éteint ou en mode toujours " +"hors route" + +msgid "Onroad Brightness" +msgstr "Luminosité en conduite" + +msgid "Onroad Brightness Delay" +msgstr "Délai de luminosité en conduite" + +msgid "Onroad Uploads" +msgstr "Téléversements en conduite" + +msgid "PAST WEEK" +msgstr "SEMAINE PASSÉE" + +msgid "Pair GitHub Account" +msgstr "Associer un compte GitHub" + +msgid "Pair your GitHub account" +msgstr "Associer votre compte GitHub" + +msgid "" +"Pair your GitHub account to grant your device sponsor benefits, including " +"API access on sunnylink." +msgstr "" +"Associez votre compte GitHub pour accorder à votre appareil les avantages de" +" parrain, y compris l'accès API sur sunnylink." + +msgid "Paired" +msgstr "Associé" + +msgid "Pause" +msgstr "Pause" + +msgid "Pause Lateral Control with Blinker" +msgstr "Suspendre le contrôle latéral avec le clignotant" + +msgid "" +"Pause lateral control with blinker when traveling below the desired speed " +"selected." +msgstr "" +"Suspendre le contrôle latéral avec le clignotant lorsque la vitesse est " +"inférieure à la vitesse souhaitée sélectionnée." + +msgid "Pause: ALC will pause when the brake pedal is pressed." +msgstr "" +"Pause : ALC se mettra en pause lorsque la pédale de frein est enfoncée." + +msgid "Percent: Adds a percent offset [Speed Limit + (Offset % Speed Limit)]" +msgstr "" +"Pourcentage : Ajoute un décalage en pourcentage [Limite de vitesse + " +"(Décalage % Limite de vitesse)]" + +msgid "" +"Please enable \"Always Offroad\" mode or turn off the vehicle to adjust " +"these toggles." +msgstr "" +"Veuillez activer le mode \"Toujours hors route\" ou éteindre le véhicule " +"pour ajuster ces options." + +msgid "Please reboot and try again." +msgstr "Veuillez redémarrer et réessayer." + +msgid "Policy Model" +msgstr "Modèle de politique" + +msgid "Post-Blinker Delay" +msgstr "Délai post-clignotant" + +msgid "Predictive" +msgstr "Prédictif" + +msgid "Quickboot Mode" +msgstr "Mode démarrage rapide" + +msgid "" +"Quickboot mode requires updates to be disabled.
Enable 'Disable Updates' " +"in the Software panel first." +msgstr "" +"Le mode démarrage rapide nécessite que les mises à jour soient " +"désactivées.
Activez d'abord 'Désactiver les mises à jour' dans le " +"panneau Logiciel." + +msgid "Quiet Mode" +msgstr "Mode silencieux" + +msgid "REFRESH" +msgstr "ACTUALISER" + +msgid "REGIST..." +msgstr "ENREG..." + +msgid "Re-enter the \"sunnylink\" panel to verify sponsorship status" +msgstr "" +"Retournez dans le panneau \"sunnylink\" pour vérifier le statut de " +"parrainage" + +msgid "Real-Time & Offline" +msgstr "Temps réel et hors ligne" + +msgid "Real-time Acceleration Bar" +msgstr "Barre d'accélération en temps réel" + +msgid "Refresh Model List" +msgstr "Actualiser la liste des modèles" + +msgid "Remain Active" +msgstr "Rester actif" + +msgid "Remain Active: ALC will remain active when the brake pedal is pressed." +msgstr "" +"Rester actif : ALC restera actif lorsque la pédale de frein est enfoncée." + +msgid "Reset Settings" +msgstr "Réinitialiser les paramètres" + +msgid "Restore Failed" +msgstr "Échec de la restauration" + +msgid "Restore Settings" +msgstr "Restaurer les paramètres" + +msgid "Review the rules, features, and limitations of sunnypilot" +msgstr "Consultez les règles, fonctionnalités et limitations de sunnypilot" + +msgid "Right" +msgstr "Droite" + +msgid "Right & Bottom" +msgstr "Droite et bas" + +msgid "SPONSOR" +msgstr "PARRAINER" + +msgid "SUNNYLINK" +msgstr "SUNNYLINK" + +msgid "Scan" +msgstr "Analyser" + +msgid "Scan the QR code to login to your GitHub account" +msgstr "Scannez le code QR pour vous connecter à votre compte GitHub" + +msgid "Scan the QR code to visit sunnyhaibin's GitHub Sponsors page" +msgstr "" +"Scannez le code QR pour visiter la page GitHub Sponsors de sunnyhaibin" + +msgid "Scanning..." +msgstr "Analyse en cours..." + +msgid "Search" +msgstr "Rechercher" + +msgid "Search your vehicle" +msgstr "Rechercher votre véhicule" + +msgid "Select a Model" +msgstr "Sélectionner un modèle" + +msgid "Select a vehicle" +msgstr "Sélectionner un véhicule" + +msgid "Select vehicle to force fingerprint manually." +msgstr "Sélectionnez le véhicule pour forcer l'empreinte manuellement." + +msgid "Self-Tune" +msgstr "Auto-réglage" + +msgid "" +"Set a timer to delay the auto lane change operation when the blinker is " +"used. No nudge on the steering wheel is required to auto lane change if a " +"timer is set. Default is Nudge.
Please use caution when using this " +"feature. Only use the blinker when traffic and road conditions permit." +msgstr "" +"Définir un minuteur pour retarder le changement de voie automatique lorsque " +"le clignotant est utilisé. Aucune impulsion sur le volant n'est nécessaire " +"pour le changement de voie automatique si un minuteur est défini. Par défaut" +" : Impulsion.
Veuillez utiliser cette fonctionnalité avec prudence. " +"N'utilisez le clignotant que lorsque la circulation et les conditions " +"routières le permettent." + +msgid "Set the maximum speed for lane turn desires. Default is 19 mph." +msgstr "" +"Définir la vitesse max. pour les changements de voie assistés. Par défaut : " +"30 km/h." + +msgid "Settings backup completed." +msgstr "Sauvegarde des paramètres terminée." + +msgid "Settings restored. Confirm to restart the interface." +msgstr "Paramètres restaurés. Confirmez pour redémarrer l'interface." + +msgid "Short Press Increment" +msgstr "Incrément appui court" + +msgid "Show Advanced Controls" +msgstr "Afficher les contrôles avancés" + +msgid "Show Blind Spot Warnings" +msgstr "Afficher les alertes d'angle mort" + +msgid "Show a timer on the HUD when the car is at a standstill." +msgstr "Afficher un chronomètre sur le HUD lorsque la voiture est à l'arrêt." + +msgid "" +"Show an indicator on the left side of the screen to display real-time " +"vehicle acceleration and deceleration. This displays what the car is " +"currently doing, not what the planner is requesting." +msgstr "" +"Afficher un indicateur à gauche de l'écran pour visualiser l'accélération et" +" la décélération du véhicule en temps réel. Cela affiche ce que la voiture " +"fait actuellement, pas ce que le planificateur demande." + +msgid "Smart Cruise Control - Map" +msgstr "Régulateur intelligent - Carte" + +msgid "Smart Cruise Control - Vision" +msgstr "Régulateur intelligent - Vision" + +msgid "Software Delay:" +msgstr "Délai logiciel :" + +msgid "Speed" +msgstr "Vitesse" + +msgid "Speed Limit" +msgstr "Limite de vitesse" + +msgid "Speed Limit Offset" +msgstr "Décalage de limite de vitesse" + +msgid "Speed Limit Source" +msgstr "Source de limite de vitesse" + +msgid "Speedometer: Always Display True Speed" +msgstr "Compteur : Toujours afficher la vitesse réelle" + +msgid "Speedometer: Hide from Onroad Screen" +msgstr "Compteur : Masquer de l'écran de conduite" + +msgid "Sponsor Status" +msgstr "Statut de parrainage" + +msgid "Sponsorship isn't required for basic backup/restore" +msgstr "" +"Le parrainage n'est pas requis pour la sauvegarde/restauration de base" + +msgid "" +"Standard is recommended. In aggressive mode, sunnypilot will follow lead " +"cars closer and be more aggressive with the gas and brake. In relaxed mode " +"sunnypilot will stay further away from lead cars. On supported cars, you can" +" cycle through these personalities with your steering wheel distance button." +msgstr "" +"Le mode standard est recommandé. En mode agressif, sunnypilot suivra les " +"véhicules en tête de plus près et sera plus agressif avec l'accélérateur et " +"le frein. En mode détendu, sunnypilot restera plus éloigné des véhicules en " +"tête. Sur les voitures compatibles, vous pouvez parcourir ces personnalités " +"avec le bouton de distance sur le volant." + +msgid "Start Download" +msgstr "Démarrer le téléchargement" + +msgid "Start the vehicle to check vehicle compatibility." +msgstr "Démarrez le véhicule pour vérifier la compatibilité du véhicule." + +msgid "State" +msgstr "Région" + +msgid "Steering" +msgstr "Direction" + +msgid "Steering Arc" +msgstr "Arc de direction" + +msgid "Steering Mode on Brake Pedal" +msgstr "Mode direction au freinage" + +msgid "Stop and Go (Beta)" +msgstr "Stop and Go (Bêta)" + +msgid "Stop and Go for Manual Parking Brake (Beta)" +msgstr "Stop and Go pour frein à main manuel (Bêta)" + +msgid "System reboot required for changes to take effect. Reboot now?" +msgstr "" +"Redémarrage système nécessaire pour appliquer les changements. Redémarrer " +"maintenant ?" + +msgid "THANKS ♥" +msgstr "MERCI ♥" + +msgid "The reset cannot be undone. You have been warned." +msgstr "La réinitialisation ne peut pas être annulée. Vous êtes prévenu." + +msgid "" +"This feature can only be used with sunnypilot longitudinal control enabled." +msgstr "" +"Cette fonctionnalité ne peut être utilisée qu'avec le contrôle longitudinal " +"sunnypilot activé." + +msgid "" +"This feature defaults to OFF, and does not allow selection due to vehicle " +"limitations." +msgstr "" +"Cette fonctionnalité est désactivée par défaut et ne permet pas la sélection" +" en raison des limitations du véhicule." + +msgid "" +"This feature defaults to ON, and does not allow selection due to vehicle " +"limitations." +msgstr "" +"Cette fonctionnalité est activée par défaut et ne permet pas la sélection en" +" raison des limitations du véhicule." + +msgid "This feature is currently not available on this platform." +msgstr "" +"Cette fonctionnalité n'est actuellement pas disponible sur cette plateforme." + +msgid "" +"This feature is not supported on this platform due to vehicle limitations." +msgstr "" +"Cette fonctionnalité n'est pas prise en charge sur cette plateforme en " +"raison des limitations du véhicule." + +msgid "" +"This feature is unavailable because sunnypilot Longitudinal Control (Alpha) " +"is not enabled." +msgstr "" +"Cette fonctionnalité est indisponible car le contrôle longitudinal " +"sunnypilot (Alpha) n'est pas activé." + +msgid "This feature is unavailable while the car is onroad." +msgstr "" +"Cette fonctionnalité est indisponible lorsque la voiture est sur la route." + +msgid "This feature requires sunnypilot longitudinal control to be available." +msgstr "" +"Cette fonctionnalité nécessite que le contrôle longitudinal sunnypilot soit " +"disponible." + +msgid "" +"This is the master switch, it will allow you to cutoff any sunnylink " +"requests should you want to do that." +msgstr "" +"C'est l'interrupteur principal, il vous permettra de couper toutes les " +"requêtes sunnylink si vous le souhaitez." + +msgid "" +"This may be due to weak internet connection or sunnylink registration issue." +" " +msgstr "" +"Cela peut être dû à une connexion internet faible ou un problème " +"d'enregistrement sunnylink. " + +msgid "This platform only supports Disengage mode due to vehicle limitations." +msgstr "" +"Cette plateforme ne supporte que le mode Désengager en raison des " +"limitations du véhicule." + +msgid "This platform supports all MADS settings." +msgstr "Cette plateforme supporte tous les paramètres MADS." + +msgid "This platform supports limited MADS settings." +msgstr "Cette plateforme supporte des paramètres MADS limités." + +msgid "This setting will take effect immediately." +msgstr "Ce paramètre prendra effet immédiatement." + +msgid "This setting will take effect once the device enters offroad state." +msgstr "Ce paramètre prendra effet une fois l'appareil en mode hors route." + +msgid "" +"This will delete ALL downloaded maps\n" +"\n" +"Are you sure you want to delete all maps?" +msgstr "" +"Cela supprimera TOUTES les cartes téléchargées\n" +"\n" +"Êtes-vous sûr de vouloir supprimer toutes les cartes ?" + +msgid "" +"This will delete ALL downloaded models from the cache except the currently " +"active model. Are you sure?" +msgstr "" +"Cela supprimera TOUS les modèles téléchargés du cache sauf le modèle " +"actuellement actif. Êtes-vous sûr ?" + +msgid "" +"This will start the download process and it might take a while to complete." +msgstr "Cela démarrera le téléchargement et peut prendre un certain temps." + +msgid "Time" +msgstr "Temps" + +msgid "" +"Toggle to enable a delay timer for seamless lane changes when blind spot " +"monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering." +msgstr "" +"Activer un minuteur de délai pour des changements de voie fluides lorsque la" +" surveillance d'angle mort (BSM) détecte un véhicule gênant, assurant une " +"manœuvre en toute sécurité." + +msgid "" +"Toggle visibility of advanced sunnypilot controls.
This only changes the " +"visibility of the toggles; it does not change the actual enabled/disabled " +"state." +msgstr "" +"Basculer la visibilité des contrôles avancés sunnypilot.
Cela ne change " +"que la visibilité des options, pas leur état activé/désactivé réel." + +msgid "Toggle with Main Cruise" +msgstr "Basculer avec le régulateur principal" + +msgid "Total Delay:" +msgstr "Délai total :" + +msgid "Training Guide" +msgstr "Guide d'entraînement" + +msgid "Trips" +msgstr "Trajets" + +msgid "UI Debug Mode" +msgstr "Mode débogage UI" + +msgid "Unable to restore the settings, try again later." +msgstr "Impossible de restaurer les paramètres, réessayez plus tard." + +msgid "Unified Engagement Mode (UEM)" +msgstr "Mode d'engagement unifié (UEM)" + +msgid "Unrecognized Vehicle" +msgstr "Véhicule non reconnu" + +msgid "Use Lane Turn Desires" +msgstr "Changements de voie assistés" + +msgid "" +"Use map data to estimate the appropriate speed to drive through turns ahead." +msgstr "" +"Utiliser les données cartographiques pour estimer la vitesse appropriée pour" +" aborder les virages à venir." + +msgid "" +"Use the sunnypilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"Utiliser le système sunnypilot pour le régulateur de vitesse adaptatif et " +"l'aide au maintien de voie. Votre attention est requise en permanence pour " +"utiliser cette fonctionnalité." + +msgid "" +"Use vision path predictions to estimate the appropriate speed to drive " +"through turns ahead." +msgstr "" +"Utiliser les prédictions de trajectoire visuelle pour estimer la vitesse " +"appropriée pour aborder les virages à venir." + +msgid "Vehicle" +msgstr "Véhicule" + +msgid "View the error log for sunnypilot crashes." +msgstr "Voir le journal d'erreurs pour les plantages sunnypilot." + +msgid "Vision Model" +msgstr "Modèle de vision" + +msgid "Visuals" +msgstr "Visuels" + +msgid "Wake Up Behavior" +msgstr "Comportement au réveil" + +msgid "Warning" +msgstr "Avertissement" + +msgid "" +"Warning: Provides a warning when exceeding the current road's speed limit." +msgstr "" +"Avertissement : Fournit un avertissement lorsque la limite de vitesse de la " +"route actuelle est dépassée." + +msgid "Welcome back!! We're excited to see you've enabled sunnylink again!" +msgstr "" +"Bon retour !! Nous sommes ravis de voir que vous avez réactivé sunnylink !" + +msgid "Welcome to sunnypilot" +msgstr "Bienvenue sur sunnypilot" + +msgid "" +"When enabled, automatic software updates will be off.
This requires a " +"reboot to take effect." +msgstr "" +"Lorsqu'activé, les mises à jour automatiques seront désactivées.
Cela " +"nécessite un redémarrage pour prendre effet." + +msgid "" +"When enabled, pressing the accelerator pedal will disengage sunnypilot." +msgstr "" +"Lorsqu'activé, appuyer sur la pédale d'accélérateur désengagera sunnypilot." + +msgid "" +"When enabled, sunnypilot will attempt to manage the built-in cruise control " +"buttons by emulating button presses for limited longitudinal control." +msgstr "" +"Lorsqu'activé, sunnypilot tentera de gérer les boutons de régulateur de " +"vitesse intégrés en émulant des appuis de bouton pour un contrôle " +"longitudinal limité." + +msgid "When enabled, the speedometer on the onroad screen is not displayed." +msgstr "" +"Lorsqu'activé, le compteur de vitesse sur l'écran de conduite n'est pas " +"affiché." + +msgid "When enabled, visual turn indicators are drawn on the HUD." +msgstr "" +"Lorsqu'activé, les indicateurs visuels de virage sont affichés sur le HUD." + +msgid "" +"When toggled on, this creates a prebuilt file to allow accelerated boot " +"times. When toggled off, it removes the prebuilt file so compilation of " +"locally edited cpp files can be made." +msgstr "" +"Lorsqu'activé, cela crée un fichier précompilé pour accélérer le temps de " +"démarrage. Lorsque désactivé, le fichier précompilé est supprimé pour " +"permettre la compilation des fichiers cpp modifiés localement." + +msgid "Would you like to delete this log?" +msgstr "Voulez-vous supprimer ce journal ?" + +msgid "Yes" +msgstr "Oui" + +msgid "Yes, delete all maps" +msgstr "Oui, supprimer toutes les cartes" + +msgid "You must accept the Terms of Service in order to use sunnypilot." +msgstr "" +"Vous devez accepter les conditions d'utilisation pour utiliser sunnypilot." + +msgid "" +"You must accept the Terms of Service to use sunnypilot. Read the latest " +"terms at https://sunnypilot.ai/terms before continuing." +msgstr "" +"Vous devez accepter les conditions d'utilisation pour utiliser sunnypilot. " +"Consultez les dernières conditions sur https://sunnypilot.ai/terms avant de " +"continuer." + +msgid "Your vehicle will use the Default longitudinal tuning." +msgstr "Votre véhicule utilisera le réglage longitudinal par défaut." + +msgid "Your vehicle will use the Dynamic longitudinal tuning." +msgstr "Votre véhicule utilisera le réglage longitudinal dynamique." + +msgid "Your vehicle will use the Predictive longitudinal tuning." +msgstr "Votre véhicule utilisera le réglage longitudinal prédictif." + +msgid "backing up" +msgstr "sauvegarde en cours" + +msgid "backup" +msgstr "sauvegarde" + +msgid "backup settings" +msgstr "sauvegarder les paramètres" + +msgid "become a sunnypilot sponsor" +msgstr "devenir sponsor sunnypilot" + +msgid "cancel download" +msgstr "annuler le téléchargement" + +msgid "checking..." +msgstr "vérification..." + +msgid "copyparty Service" +msgstr "Service copyparty" + +msgid "" +"copyparty is a very capable file server, you can use it to download your " +"routes, view your logs and even make some edits on some files from your " +"browser. Requires you to connect to your comma locally via its IP address." +msgstr "" +"copyparty est un serveur de fichiers très performant, vous pouvez l'utiliser" +" pour télécharger vos trajets, consulter vos logs et même modifier certains " +"fichiers depuis votre navigateur. Nécessite de vous connecter à votre comma " +"localement via son adresse IP." + +msgid "current model" +msgstr "modèle actuel" + +msgid "default model" +msgstr "modèle par défaut" + +msgid "downloading..." +msgstr "téléchargement..." + +msgid "enable sunnylink" +msgstr "activer sunnylink" + +msgid "failed" +msgstr "échoué" + +msgid "finalizing update..." +msgstr "finalisation de la mise à jour..." + +msgid "from cache" +msgstr "depuis le cache" + +msgid "h" +msgstr "h" + +msgid "m" +msgstr "min" + +msgid "pair" +msgstr "associer" + +msgid "pair with sunnylink" +msgstr "associer avec sunnylink" + +msgid "paired" +msgstr "associé" + +msgid "ready" +msgstr "prêt" + +msgid "recommend disabling this feature if you experience these." +msgstr "" +"nous recommandons de désactiver cette fonctionnalité si vous rencontrez ces " +"problèmes." + +msgid "restore" +msgstr "restaurer" + +msgid "restore settings" +msgstr "restaurer les paramètres" + +msgid "restoring" +msgstr "restauration en cours" + +msgid "s" +msgstr "s" + +msgid "settings backed up" +msgstr "paramètres sauvegardés" + +msgid "slide to backup" +msgstr "glisser pour sauvegarder" + +msgid "slide to restore" +msgstr "glisser pour restaurer" + +msgid "sponsor" +msgstr "sponsor" + +msgid "sunnylink" +msgstr "sunnylink" + +msgid "sunnylink Dongle ID not found. " +msgstr "ID Dongle sunnylink introuvable. " + +msgid "sunnylink Dongle ID not found. Please reboot & try again." +msgstr "ID Dongle sunnylink introuvable. Veuillez redémarrer et réessayer." + +msgid "" +"sunnylink enables secured remote access to your comma device from anywhere, " +"including settings management, remote monitoring, real-time dashboard, etc." +msgstr "" +"sunnylink permet un accès à distance sécurisé à votre appareil comma depuis " +"n'importe où, y compris la gestion des paramètres, la surveillance à " +"distance, le tableau de bord en temps réel, etc." + +msgid "" +"sunnylink is designed to be enabled as part of sunnypilot's core " +"functionality. If sunnylink is disabled, features such as settings " +"management, remote monitoring, real-time dashboards will be unavailable." +msgstr "" +"sunnylink est conçu pour être activé dans le cadre des fonctionnalités de " +"base de sunnypilot. Si sunnylink est désactivé, des fonctionnalités telles " +"que la gestion des paramètres, la surveillance à distance et les tableaux de" +" bord en temps réel seront indisponibles." + +msgid "sunnylink uploader" +msgstr "téléverseur sunnylink" + +msgid "sunnypilot Longitudinal Control (Alpha)" +msgstr "Contrôle longitudinal sunnypilot (Alpha)" + +msgid "" +"sunnypilot Longitudinal Control is the default longitudinal control for this" +" platform." +msgstr "" +"Le contrôle longitudinal sunnypilot est le contrôle longitudinal par défaut " +"pour cette plateforme." + +msgid "sunnypilot Unavailable" +msgstr "sunnypilot indisponible" + +msgid "" +"sunnypilot defaults to driving in chill mode. Experimental mode enables " +"alpha-level features that aren't ready for chill mode. Experimental features" +" are listed below:

End-to-End Longitudinal Control


Let the " +"driving model control the gas and brakes. sunnypilot 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.

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." +msgstr "" +"sunnypilot conduit par défaut en mode chill. Le mode expérimental active des" +" fonctionnalités de niveau alpha qui ne sont pas prêtes pour le mode chill. " +"Les fonctionnalités expérimentales sont listées ci-dessous :

Contrôle" +" longitudinal de bout en bout


Laissez le modèle de conduite " +"contrôler l'accélérateur et les freins. sunnypilot conduira comme il pense " +"qu'un humain le ferait, y compris s'arrêter aux feux rouges et aux panneaux " +"stop. Puisque le modèle de conduite décide de la vitesse, la vitesse définie" +" ne servira que de limite supérieure. Il s'agit d'une fonctionnalité de " +"qualité alpha ; des erreurs sont à prévoir.

Nouvelle visualisation de" +" conduite


La visualisation de conduite passera à la caméra grand " +"angle orientée route à basse vitesse pour mieux montrer certains virages. Le" +" logo du mode expérimental sera également affiché dans le coin supérieur " +"droit." + +msgid "" +"sunnypilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart sunnypilot if the car is powered on." +msgstr "" +"sunnypilot se calibre en permanence, la réinitialisation est rarement " +"nécessaire. La réinitialisation de la calibration redémarrera sunnypilot si " +"la voiture est sous tension." + +msgid "" +"sunnypilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode." +msgstr "" +"sunnypilot apprend à conduire en regardant des humains, comme vous, conduire.\n" +"\n" +"Le mode Firehose vous permet de maximiser vos envois de données d'entraînement pour améliorer les modèles de conduite d'openpilot. Plus de données signifie de plus grands modèles, ce qui signifie un meilleur mode expérimental." + +msgid "sunnypilot longitudinal control may come in a future update." +msgstr "" +"Le contrôle longitudinal sunnypilot pourrait arriver dans une future mise à " +"jour." + +msgid "" +"sunnypilot will not take over control of gas and brakes. Factory Toyota " +"longitudinal control will be used." +msgstr "" +"sunnypilot ne prendra pas le contrôle de l'accélérateur et des freins. Le " +"contrôle longitudinal Toyota d'origine sera utilisé." diff --git a/selfdrive/ui/translations/potools.py b/selfdrive/ui/translations/potools.py new file mode 100644 index 0000000000..7571cccdd6 --- /dev/null +++ b/selfdrive/ui/translations/potools.py @@ -0,0 +1,362 @@ +"""Pure Python tools for managing .po translation files. + +Replaces GNU gettext CLI tools (xgettext, msginit, msgmerge) with Python +implementations for extracting, creating, and updating .po files. +""" + +import ast +import os +import re +from dataclasses import dataclass, field +from datetime import UTC, datetime +from pathlib import Path + + +@dataclass +class POEntry: + msgid: str = "" + msgstr: str = "" + msgid_plural: str = "" + msgstr_plural: dict[int, str] = field(default_factory=dict) + comments: list[str] = field(default_factory=list) + source_refs: list[str] = field(default_factory=list) + flags: list[str] = field(default_factory=list) + + @property + def is_plural(self) -> bool: + return bool(self.msgid_plural) + + +# ──── PO file parsing ──── + +def _parse_quoted(s: str) -> str: + """Parse a PO-format quoted string, handling escape sequences.""" + s = s.strip() + if not (s.startswith('"') and s.endswith('"')): + raise ValueError(f"Expected quoted string: {s!r}") + s = s[1:-1] + result = [] + i = 0 + while i < len(s): + if s[i] == '\\' and i + 1 < len(s): + c = s[i + 1] + if c == 'n': + result.append('\n') + elif c == 't': + result.append('\t') + elif c == '"': + result.append('"') + elif c == '\\': + result.append('\\') + else: + result.append(s[i:i + 2]) + i += 2 + else: + result.append(s[i]) + i += 1 + return ''.join(result) + + +def parse_po(path: str | Path) -> tuple[POEntry | None, list[POEntry]]: + """Parse a .po/.pot file. Returns (header_entry, entries).""" + with open(path, encoding='utf-8') as f: + lines = f.readlines() + + entries: list[POEntry] = [] + header: POEntry | None = None + cur: POEntry | None = None + cur_field: str | None = None + plural_idx = 0 + + def finish(): + nonlocal cur, header + if cur is None: + return + if cur.msgid == "" and cur.msgstr: + header = cur + elif cur.msgid != "" or cur.is_plural: + entries.append(cur) + cur = None + + for raw in lines: + line = raw.rstrip('\n') + stripped = line.strip() + + if not stripped: + finish() + cur_field = None + continue + + # Skip obsolete entries + if stripped.startswith('#~'): + continue + + if stripped.startswith('#'): + if cur is None: + cur = POEntry() + if stripped.startswith('#:'): + cur.source_refs.append(stripped[2:].strip()) + elif stripped.startswith('#,'): + cur.flags.extend(f.strip() for f in stripped[2:].split(',') if f.strip()) + else: + cur.comments.append(line) + continue + + if stripped.startswith('msgid_plural '): + if cur is None: + cur = POEntry() + cur.msgid_plural = _parse_quoted(stripped[len('msgid_plural '):]) + cur_field = 'msgid_plural' + continue + + if stripped.startswith('msgid '): + if cur is None: + cur = POEntry() + cur.msgid = _parse_quoted(stripped[len('msgid '):]) + cur_field = 'msgid' + continue + + m = re.match(r'msgstr\[(\d+)]\s+(.*)', stripped) + if m: + plural_idx = int(m.group(1)) + cur.msgstr_plural[plural_idx] = _parse_quoted(m.group(2)) + cur_field = 'msgstr_plural' + continue + + if stripped.startswith('msgstr '): + cur.msgstr = _parse_quoted(stripped[len('msgstr '):]) + cur_field = 'msgstr' + continue + + if stripped.startswith('"'): + val = _parse_quoted(stripped) + if cur_field == 'msgid': + cur.msgid += val + elif cur_field == 'msgid_plural': + cur.msgid_plural += val + elif cur_field == 'msgstr': + cur.msgstr += val + elif cur_field == 'msgstr_plural': + cur.msgstr_plural[plural_idx] += val + + finish() + return header, entries + + +# ──── PO file writing ──── + +def _quote(s: str) -> str: + """Quote a string for .po file output.""" + s = s.replace('\\', '\\\\').replace('"', '\\"').replace('\t', '\\t') + if '\n' in s and s != '\n': + parts = s.split('\n') + lines = ['""'] + for i, part in enumerate(parts): + text = part + ('\\n' if i < len(parts) - 1 else '') + if text: + lines.append(f'"{text}"') + return '\n'.join(lines) + return f'"{s}"'.replace('\n', '\\n') + + +def write_po(path: str | Path, header: POEntry | None, entries: list[POEntry]) -> None: + """Write a .po/.pot file.""" + with open(path, 'w', encoding='utf-8') as f: + if header: + for c in header.comments: + f.write(c + '\n') + if header.flags: + f.write('#, ' + ', '.join(header.flags) + '\n') + f.write(f'msgid {_quote("")}\n') + f.write(f'msgstr {_quote(header.msgstr)}\n\n') + + for entry in entries: + for c in entry.comments: + f.write(c + '\n') + for ref in entry.source_refs: + f.write(f'#: {ref}\n') + if entry.flags: + f.write('#, ' + ', '.join(entry.flags) + '\n') + f.write(f'msgid {_quote(entry.msgid)}\n') + if entry.is_plural: + f.write(f'msgid_plural {_quote(entry.msgid_plural)}\n') + for idx in sorted(entry.msgstr_plural): + f.write(f'msgstr[{idx}] {_quote(entry.msgstr_plural[idx])}\n') + else: + f.write(f'msgstr {_quote(entry.msgstr)}\n') + f.write('\n') + + +# ──── String extraction (replaces xgettext) ──── + +def extract_strings(files: list[str], basedir: str) -> list[POEntry]: + """Extract tr/trn/tr_noop calls from Python source files.""" + seen: dict[str, POEntry] = {} + + for filepath in files: + full = os.path.join(basedir, filepath) + with open(full, encoding='utf-8') as f: + source = f.read() + try: + tree = ast.parse(source, filename=filepath) + except SyntaxError: + continue + + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + + func = node.func + if isinstance(func, ast.Name): + name = func.id + elif isinstance(func, ast.Attribute): + name = func.attr + else: + continue + + if name not in ('tr', 'trn', 'tr_noop'): + continue + + ref = f'{filepath}:{node.lineno}' + is_flagged = name in ('tr', 'trn') + + if name in ('tr', 'tr_noop'): + if not node.args or not isinstance(node.args[0], ast.Constant) or not isinstance(node.args[0].value, str): + continue + msgid = node.args[0].value + if msgid in seen: + if ref not in seen[msgid].source_refs: + seen[msgid].source_refs.append(ref) + else: + flags = ['python-format'] if is_flagged else [] + seen[msgid] = POEntry(msgid=msgid, source_refs=[ref], flags=flags) + + elif name == 'trn': + if len(node.args) < 2: + continue + a1, a2 = node.args[0], node.args[1] + if not (isinstance(a1, ast.Constant) and isinstance(a1.value, str)): + continue + if not (isinstance(a2, ast.Constant) and isinstance(a2.value, str)): + continue + msgid, msgid_plural = a1.value, a2.value + if msgid in seen: + if ref not in seen[msgid].source_refs: + seen[msgid].source_refs.append(ref) + else: + flags = ['python-format'] if is_flagged else [] + seen[msgid] = POEntry( + msgid=msgid, msgid_plural=msgid_plural, + source_refs=[ref], flags=flags, + msgstr_plural={0: '', 1: ''}, + ) + + return list(seen.values()) + + +# ──── POT generation ──── + +def generate_pot(entries: list[POEntry], pot_path: str | Path) -> None: + """Generate a .pot template file from extracted entries.""" + now = datetime.now(UTC).strftime('%Y-%m-%d %H:%M%z') + header = POEntry( + comments=[ + '# SOME DESCRIPTIVE TITLE.', + "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER", + '# This file is distributed under the same license as the PACKAGE package.', + '# FIRST AUTHOR , YEAR.', + '#', + ], + flags=['fuzzy'], + msgstr='Project-Id-Version: PACKAGE VERSION\n' + + 'Report-Msgid-Bugs-To: \n' + + f'POT-Creation-Date: {now}\n' + + 'PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n' + + 'Last-Translator: FULL NAME \n' + + 'Language-Team: LANGUAGE \n' + + 'Language: \n' + + 'MIME-Version: 1.0\n' + + 'Content-Type: text/plain; charset=UTF-8\n' + + 'Content-Transfer-Encoding: 8bit\n' + + 'Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n', + ) + write_po(pot_path, header, entries) + + +# ──── PO init (replaces msginit) ──── + +PLURAL_FORMS: dict[str, str] = { + 'en': 'nplurals=2; plural=(n != 1);', + 'de': 'nplurals=2; plural=(n != 1);', + 'fr': 'nplurals=2; plural=(n > 1);', + 'es': 'nplurals=2; plural=(n != 1);', + 'pt-BR': 'nplurals=2; plural=(n > 1);', + 'tr': 'nplurals=2; plural=(n != 1);', + 'uk': 'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);', + 'th': 'nplurals=1; plural=0;', + 'zh-CHT': 'nplurals=1; plural=0;', + 'zh-CHS': 'nplurals=1; plural=0;', + 'ko': 'nplurals=1; plural=0;', + 'ja': 'nplurals=1; plural=0;', +} + + +def init_po(pot_path: str | Path, po_path: str | Path, language: str) -> None: + """Create a new .po file from a .pot template (replaces msginit).""" + _, entries = parse_po(pot_path) + plural_forms = PLURAL_FORMS.get(language, 'nplurals=2; plural=(n != 1);') + now = datetime.now(UTC).strftime('%Y-%m-%d %H:%M%z') + + header = POEntry( + comments=[ + f'# {language} translations for PACKAGE package.', + "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER", + '# This file is distributed under the same license as the PACKAGE package.', + '# Automatically generated.', + '#', + ], + msgstr='Project-Id-Version: PACKAGE VERSION\n' + + 'Report-Msgid-Bugs-To: \n' + + f'POT-Creation-Date: {now}\n' + + f'PO-Revision-Date: {now}\n' + + 'Last-Translator: Automatically generated\n' + + 'Language-Team: none\n' + + f'Language: {language}\n' + + 'MIME-Version: 1.0\n' + + 'Content-Type: text/plain; charset=UTF-8\n' + + 'Content-Transfer-Encoding: 8bit\n' + + f'Plural-Forms: {plural_forms}\n', + ) + + nplurals = int(re.search(r'nplurals=(\d+)', plural_forms).group(1)) + for e in entries: + if e.is_plural: + e.msgstr_plural = dict.fromkeys(range(nplurals), '') + + write_po(po_path, header, entries) + + +# ──── PO merge (replaces msgmerge) ──── + +def merge_po(po_path: str | Path, pot_path: str | Path) -> None: + """Update a .po file with entries from a .pot template (replaces msgmerge --update).""" + po_header, po_entries = parse_po(po_path) + _, pot_entries = parse_po(pot_path) + + existing = {e.msgid: e for e in po_entries} + merged = [] + + for pot_e in pot_entries: + if pot_e.msgid in existing: + old = existing[pot_e.msgid] + old.source_refs = pot_e.source_refs + old.flags = pot_e.flags + old.comments = pot_e.comments + if pot_e.is_plural: + old.msgid_plural = pot_e.msgid_plural + merged.append(old) + else: + merged.append(pot_e) + + merged.sort(key=lambda e: e.msgid) + write_po(po_path, po_header, merged) diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index 7fe0dfbbc9..e3cac2618e 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 import os -import pyray as rl from openpilot.system.hardware import TICI from openpilot.common.realtime import config_realtime_process, set_core_affinity @@ -9,22 +8,22 @@ from openpilot.selfdrive.ui.layouts.main import MainLayout from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout from openpilot.selfdrive.ui.ui_state import ui_state +BIG_UI = gui_app.big_ui() + def main(): cores = {5, } config_realtime_process(0, 51) gui_app.init_window("UI") - if gui_app.big_ui(): - main_layout = MainLayout() + if BIG_UI: + MainLayout() else: - main_layout = MiciMainLayout() - main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + MiciMainLayout() + for should_render in gui_app.render(): ui_state.update() if should_render: - main_layout.render() - # reaffine after power save offlines our core if TICI and os.sched_getaffinity(0) != cores: try: diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index bded80b2e5..6ff3667d8a 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -3,6 +3,7 @@ from itertools import chain import os from openpilot.common.basedir import BASEDIR from openpilot.system.ui.lib.multilang import SYSTEM_UI_DIR, UI_DIR, TRANSLATIONS_DIR, multilang +from openpilot.selfdrive.ui.translations.potools import extract_strings, generate_pot, merge_po, init_po LANGUAGES_FILE = os.path.join(str(TRANSLATIONS_DIR), "languages.json") POT_FILE = os.path.join(str(TRANSLATIONS_DIR), "app.pot") @@ -18,24 +19,17 @@ def update_translations(): if filename.endswith(".py"): files.append(os.path.relpath(os.path.join(root, filename), BASEDIR)) - # Create main translation file - cmd = ("xgettext -L Python --keyword=tr --keyword=trn:1,2 --keyword=tr_noop --from-code=UTF-8 " + - "--flag=tr:1:python-brace-format --flag=trn:1:python-brace-format --flag=trn:2:python-brace-format " + - f"-D {BASEDIR} -o {POT_FILE} {' '.join(files)}") - - ret = os.system(cmd) - assert ret == 0 + # Extract translatable strings and generate .pot template + entries = extract_strings(files, BASEDIR) + generate_pot(entries, POT_FILE) # Generate/update translation files for each language for name in multilang.languages.values(): - if os.path.exists(os.path.join(TRANSLATIONS_DIR, f"app_{name}.po")): - cmd = f"msgmerge --update --no-fuzzy-matching --backup=none --sort-output {TRANSLATIONS_DIR}/app_{name}.po {POT_FILE}" - ret = os.system(cmd) - assert ret == 0 + po_file = os.path.join(TRANSLATIONS_DIR, f"app_{name}.po") + if os.path.exists(po_file): + merge_po(po_file, POT_FILE) else: - cmd = f"msginit -l {name} --no-translator --input {POT_FILE} --output-file {TRANSLATIONS_DIR}/app_{name}.po" - ret = os.system(cmd) - assert ret == 0 + init_po(POT_FILE, po_file, name) if __name__ == "__main__": diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 802243ff3e..a87727e27f 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -63,7 +63,7 @@ class ActionButton(Widget): @property def text(self) -> str: - return self._text() if callable(self._text) else self._text + return self._text if isinstance(self._text, str) else self._text() def _render(self, _): text_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), self.text, AlertConstants.FONT_SIZE) diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index f960cf723e..1ff550e4b6 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -26,7 +26,7 @@ class PairingDialog(Widget): self.qr_texture: rl.Texture | None = None self.last_qr_generation = float('-inf') self._close_btn = IconButton(gui_app.texture("icons/close.png", 80, 80)) - self._close_btn.set_click_callback(lambda: gui_app.set_modal_overlay(None)) + self._close_btn.set_click_callback(gui_app.pop_widget) def _get_pairing_url(self) -> str: try: @@ -69,7 +69,7 @@ class PairingDialog(Widget): def _update_state(self): if ui_state.prime_state.is_paired(): - gui_app.set_modal_overlay(None) + gui_app.pop_widget() def _render(self, rect: rl.Rectangle) -> int: rl.clear_background(rl.Color(224, 224, 224, 255)) @@ -162,10 +162,9 @@ class PairingDialog(Widget): if __name__ == "__main__": gui_app.init_window("pairing device") pairing = PairingDialog() + gui_app.push_widget(pairing) try: for _ in gui_app.render(): - result = pairing.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - if result != -1: - break + pass finally: del pairing diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index 3c9406688f..c9452fc535 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -15,7 +15,6 @@ class SetupWidget(Widget): def __init__(self): super().__init__() self._open_settings_callback = None - self._pairing_dialog: PairingDialog | None = None self._pair_device_btn = Button(lambda: tr("Pair device"), self._show_pairing, button_style=ButtonStyle.PRIMARY) self._open_settings_btn = Button(lambda: tr("Open"), lambda: self._open_settings_callback() if self._open_settings_callback else None, button_style=ButtonStyle.PRIMARY) @@ -86,16 +85,11 @@ class SetupWidget(Widget): button_rect = rl.Rectangle(x, y, w, button_height) self._open_settings_btn.render(button_rect) - def _show_pairing(self): + @staticmethod + def _show_pairing(): if not system_time_valid(): dlg = alert_dialog(tr("Please connect to Wi-Fi to complete initial pairing")) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) return - if not self._pairing_dialog: - self._pairing_dialog = PairingDialog() - gui_app.set_modal_overlay(self._pairing_dialog, lambda result: setattr(self, '_pairing_dialog', None)) - - def __del__(self): - if self._pairing_dialog: - del self._pairing_dialog + gui_app.push_widget(PairingDialog()) diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index 88389cb053..b31a9eb3bd 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -59,7 +59,7 @@ class SshKeyAction(ItemAction): # Show error dialog if there's an error if self._error_message: message = copy.copy(self._error_message) - gui_app.set_modal_overlay(alert_dialog(message)) + gui_app.push_widget(alert_dialog(message)) self._username = "" self._error_message = "" @@ -87,7 +87,8 @@ class SshKeyAction(ItemAction): if self._state == SshKeyActionState.ADD: self._keyboard.reset() self._keyboard.set_title(tr("Enter your GitHub username")) - gui_app.set_modal_overlay(self._keyboard, callback=self._on_username_submit) + self._keyboard.set_callback(self._on_username_submit) + gui_app.push_widget(self._keyboard) elif self._state == SshKeyActionState.REMOVE: self._params.remove("GithubUsername") self._params.remove("GithubSshKeys") diff --git a/sunnypilot/SConscript b/sunnypilot/SConscript index eb3698f9d0..09ad39ab43 100644 --- a/sunnypilot/SConscript +++ b/sunnypilot/SConscript @@ -1,4 +1,3 @@ SConscript(['common/transformations/SConscript']) -SConscript(['modeld/SConscript']) SConscript(['modeld_v2/SConscript']) SConscript(['selfdrive/locationd/SConscript']) diff --git a/sunnypilot/common/version.h b/sunnypilot/common/version.h index 6833a508de..1c7fcd2e64 100644 --- a/sunnypilot/common/version.h +++ b/sunnypilot/common/version.h @@ -1 +1 @@ -#define SUNNYPILOT_VERSION "2025.003.000" +#define SUNNYPILOT_VERSION "2026.001.000" diff --git a/sunnypilot/modeld/.gitignore b/sunnypilot/modeld/.gitignore deleted file mode 100644 index 742d3d1205..0000000000 --- a/sunnypilot/modeld/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*_pyx.cpp diff --git a/sunnypilot/modeld/SConscript b/sunnypilot/modeld/SConscript deleted file mode 100644 index 777f842ac0..0000000000 --- a/sunnypilot/modeld/SConscript +++ /dev/null @@ -1,44 +0,0 @@ -Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') -lenv = env.Clone() -lenvCython = envCython.Clone() - -libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] -frameworks = [] - -common_src = [ - "models/commonmodel.cc", - "transforms/loadyuv.cc", - "transforms/transform.cc", -] - -thneed_src_common = [ - "thneed/clutil_legacy.cc", - "thneed/thneed_common.cc", - "thneed/serialize.cc", -] - -thneed_src_qcom = thneed_src_common + ["thneed/thneed_qcom2.cc"] -thneed_src_pc = thneed_src_common + ["thneed/thneed_pc.cc"] -thneed_src = thneed_src_qcom if arch == "larch64" else thneed_src_pc - -# OpenCL is a framework on Mac -if arch == "Darwin": - frameworks += ['OpenCL'] -else: - libs += ['OpenCL'] - -# Set path definitions -for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): - for xenv in (lenv, lenvCython): - xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') - -cython_libs = envCython["LIBS"] + libs -commonmodel_lib = lenv.Library('commonmodel', common_src) - -lenvCython.Program('runners/runmodel_pyx.so', 'runners/runmodel_pyx.pyx', LIBS=cython_libs, FRAMEWORKS=frameworks) -lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) - -if arch == "larch64": - thneed_lib = env.SharedLibrary('thneed', thneed_src, LIBS=[common, 'OpenCL', 'dl']) - thneedmodel_lib = env.Library('thneedmodel', ['runners/thneedmodel.cc']) - lenvCython.Program('runners/thneedmodel_pyx.so', 'runners/thneedmodel_pyx.pyx', LIBS=envCython["LIBS"]+[thneedmodel_lib, thneed_lib, common, 'dl', 'OpenCL']) diff --git a/sunnypilot/modeld/fill_model_msg.py b/sunnypilot/modeld/fill_model_msg.py deleted file mode 100644 index a62c451efd..0000000000 --- a/sunnypilot/modeld/fill_model_msg.py +++ /dev/null @@ -1,223 +0,0 @@ -import os -import capnp -import numpy as np -from cereal import log -from openpilot.sunnypilot.modeld.constants import ModelConstants, Plan -from openpilot.sunnypilot.models.helpers import plan_x_idxs_helper -from openpilot.sunnypilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_lag_adjusted_curvature, MIN_SPEED - -SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') - -ConfidenceClass = log.ModelDataV2.ConfidenceClass - -class PublishState: - def __init__(self): - self.disengage_buffer = np.zeros(ModelConstants.CONFIDENCE_BUFFER_LEN*ModelConstants.DISENGAGE_WIDTH, dtype=np.float32) - self.prev_brake_5ms2_probs = np.zeros(ModelConstants.FCW_5MS2_PROBS_WIDTH, dtype=np.float32) - self.prev_brake_3ms2_probs = np.zeros(ModelConstants.FCW_3MS2_PROBS_WIDTH, dtype=np.float32) - -def fill_xyzt(builder, t, x, y, z, x_std=None, y_std=None, z_std=None): - builder.t = t - builder.x = x.tolist() - builder.y = y.tolist() - builder.z = z.tolist() - if x_std is not None: - builder.xStd = x_std.tolist() - if y_std is not None: - builder.yStd = y_std.tolist() - if z_std is not None: - builder.zStd = z_std.tolist() - -def fill_xyvat(builder, t, x, y, v, a, x_std=None, y_std=None, v_std=None, a_std=None): - builder.t = t - builder.x = x.tolist() - builder.y = y.tolist() - builder.v = v.tolist() - builder.a = a.tolist() - if x_std is not None: - builder.xStd = x_std.tolist() - if y_std is not None: - builder.yStd = y_std.tolist() - if v_std is not None: - builder.vStd = v_std.tolist() - if a_std is not None: - builder.aStd = a_std.tolist() - -def fill_xyz_poly(builder, degree, x, y, z): - xyz = np.stack([x, y, z], axis=1) - coeffs = np.polynomial.polynomial.polyfit(ModelConstants.T_IDXS, xyz, deg=degree) - builder.xCoefficients = coeffs[:, 0].tolist() - builder.yCoefficients = coeffs[:, 1].tolist() - builder.zCoefficients = coeffs[:, 2].tolist() - -def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._DynamicStructBuilder, - net_output_data: dict[str, np.ndarray], action: log.ModelDataV2.Action, publish_state: PublishState, - vipc_frame_id: int, vipc_frame_id_extra: int, frame_id: int, frame_drop: float, - timestamp_eof: int, model_execution_time: float, valid: bool, - v_ego: float, steer_delay: float, meta_const) -> None: - frame_age = frame_id - vipc_frame_id if frame_id > vipc_frame_id else 0 - frame_drop_perc = frame_drop * 100 - extended_msg.valid = valid - base_msg.valid = valid - - if 'lat_planner_solution' in net_output_data: - x, y, yaw, yawRate = [net_output_data['lat_planner_solution'][0, :, i].tolist() for i in range(4)] - x_sol = np.column_stack([x, y, yaw, yawRate]) - v_ego = max(MIN_SPEED, v_ego) - psis = x_sol[0:CONTROL_N, 2].tolist() - curvatures = (x_sol[0:CONTROL_N, 3] / v_ego).tolist() - desired_curvature = get_lag_adjusted_curvature(steer_delay, v_ego, psis, curvatures) - else: - desired_curvature = float(net_output_data['desired_curvature'][0, 0]) - - driving_model_data = base_msg.drivingModelData - - driving_model_data.frameId = vipc_frame_id - driving_model_data.frameIdExtra = vipc_frame_id_extra - driving_model_data.frameDropPerc = frame_drop_perc - driving_model_data.modelExecutionTime = model_execution_time - - # Populate drivingModelData.action - driving_model_data_action = driving_model_data.action - driving_model_data_action.desiredAcceleration = action.desiredAcceleration - driving_model_data_action.shouldStop = action.shouldStop - driving_model_data_action.desiredCurvature = desired_curvature - - modelV2 = extended_msg.modelV2 - modelV2.frameId = vipc_frame_id - modelV2.frameIdExtra = vipc_frame_id_extra - modelV2.frameAge = frame_age - modelV2.frameDropPerc = frame_drop_perc - modelV2.timestampEof = timestamp_eof - modelV2.modelExecutionTime = model_execution_time - - # plan - position = modelV2.position - fill_xyzt(position, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.POSITION].T, *net_output_data['plan_stds'][0,:,Plan.POSITION].T) - velocity = modelV2.velocity - fill_xyzt(velocity, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.VELOCITY].T) - acceleration = modelV2.acceleration - fill_xyzt(acceleration, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ACCELERATION].T) - orientation = modelV2.orientation - fill_xyzt(orientation, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.T_FROM_CURRENT_EULER].T) - orientation_rate = modelV2.orientationRate - fill_xyzt(orientation_rate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T) - - # temporal pose - temporal_pose = modelV2.temporalPoseDEPRECATED - temporal_pose.trans = net_output_data['plan'][0,0,Plan.VELOCITY].tolist() - temporal_pose.transStd = net_output_data['plan_stds'][0,0,Plan.VELOCITY].tolist() - temporal_pose.rot = net_output_data['plan'][0,0,Plan.ORIENTATION_RATE].tolist() - temporal_pose.rotStd = net_output_data['plan_stds'][0,0,Plan.ORIENTATION_RATE].tolist() - - # poly path - poly_path = driving_model_data.path - fill_xyz_poly(poly_path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T) - - # lateral planning - modelV2_action = modelV2.action - modelV2_action.desiredAcceleration = action.desiredAcceleration - modelV2_action.shouldStop = action.shouldStop - modelV2_action.desiredCurvature = desired_curvature - - # times at X_IDXS according to model plan - PLAN_T_IDXS: list[float] = plan_x_idxs_helper(ModelConstants, Plan, net_output_data) - - # lane lines - modelV2.init('laneLines', 4) - for i in range(4): - lane_line = modelV2.laneLines[i] - fill_xyzt(lane_line, PLAN_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['lane_lines'][0,i,:,0], net_output_data['lane_lines'][0,i,:,1]) - modelV2.laneLineStds = net_output_data['lane_lines_stds'][0,:,0,0].tolist() - modelV2.laneLineProbs = net_output_data['lane_lines_prob'][0,1::2].tolist() - - lane_line_meta = driving_model_data.laneLineMeta - lane_line_meta.leftY = modelV2.laneLines[1].y[0] - lane_line_meta.leftProb = modelV2.laneLineProbs[1] - lane_line_meta.rightY = modelV2.laneLines[2].y[0] - lane_line_meta.rightProb = modelV2.laneLineProbs[2] - - # road edges - modelV2.init('roadEdges', 2) - for i in range(2): - road_edge = modelV2.roadEdges[i] - fill_xyzt(road_edge, PLAN_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['road_edges'][0,i,:,0], net_output_data['road_edges'][0,i,:,1]) - modelV2.roadEdgeStds = net_output_data['road_edges_stds'][0,:,0,0].tolist() - - # leads - modelV2.init('leadsV3', 3) - for i in range(3): - lead = modelV2.leadsV3[i] - fill_xyvat(lead, ModelConstants.LEAD_T_IDXS, *net_output_data['lead'][0,i].T, *net_output_data['lead_stds'][0,i].T) - lead.prob = net_output_data['lead_prob'][0,i].tolist() - lead.probTime = ModelConstants.LEAD_T_OFFSETS[i] - - # meta - meta = modelV2.meta - meta.desireState = net_output_data['desire_state'][0].reshape(-1).tolist() - meta.desirePrediction = net_output_data['desire_pred'][0].reshape(-1).tolist() - meta.engagedProb = net_output_data['meta'][0,meta_const.ENGAGED].item() - meta.init('disengagePredictions') - disengage_predictions = meta.disengagePredictions - disengage_predictions.t = ModelConstants.META_T_IDXS - disengage_predictions.brakeDisengageProbs = net_output_data['meta'][0,meta_const.BRAKE_DISENGAGE].tolist() - disengage_predictions.gasDisengageProbs = net_output_data['meta'][0,meta_const.GAS_DISENGAGE].tolist() - disengage_predictions.steerOverrideProbs = net_output_data['meta'][0,meta_const.STEER_OVERRIDE].tolist() - disengage_predictions.brake3MetersPerSecondSquaredProbs = net_output_data['meta'][0,meta_const.HARD_BRAKE_3].tolist() - disengage_predictions.brake4MetersPerSecondSquaredProbs = net_output_data['meta'][0,meta_const.HARD_BRAKE_4].tolist() - disengage_predictions.brake5MetersPerSecondSquaredProbs = net_output_data['meta'][0,meta_const.HARD_BRAKE_5].tolist() - if 'sim_pose' not in net_output_data: - disengage_predictions.gasPressProbs = net_output_data['meta'][0,meta_const.GAS_PRESS].tolist() - disengage_predictions.brakePressProbs = net_output_data['meta'][0,meta_const.BRAKE_PRESS].tolist() - - publish_state.prev_brake_5ms2_probs[:-1] = publish_state.prev_brake_5ms2_probs[1:] - publish_state.prev_brake_5ms2_probs[-1] = net_output_data['meta'][0,meta_const.HARD_BRAKE_5][0] - publish_state.prev_brake_3ms2_probs[:-1] = publish_state.prev_brake_3ms2_probs[1:] - publish_state.prev_brake_3ms2_probs[-1] = net_output_data['meta'][0,meta_const.HARD_BRAKE_3][0] - hard_brake_predicted = (publish_state.prev_brake_5ms2_probs > ModelConstants.FCW_THRESHOLDS_5MS2).all() and \ - (publish_state.prev_brake_3ms2_probs > ModelConstants.FCW_THRESHOLDS_3MS2).all() - meta.hardBrakePredicted = hard_brake_predicted.item() - - # confidence - if vipc_frame_id % (2*ModelConstants.MODEL_FREQ) == 0: - # any disengage prob - brake_disengage_probs = net_output_data['meta'][0,meta_const.BRAKE_DISENGAGE] - gas_disengage_probs = net_output_data['meta'][0,meta_const.GAS_DISENGAGE] - steer_override_probs = net_output_data['meta'][0,meta_const.STEER_OVERRIDE] - any_disengage_probs = 1-((1-brake_disengage_probs)*(1-gas_disengage_probs)*(1-steer_override_probs)) - # independent disengage prob for each 2s slice - ind_disengage_probs = np.r_[any_disengage_probs[0], np.diff(any_disengage_probs) / (1 - any_disengage_probs[:-1])] - # rolling buf for 2, 4, 6, 8, 10s - publish_state.disengage_buffer[:-ModelConstants.DISENGAGE_WIDTH] = publish_state.disengage_buffer[ModelConstants.DISENGAGE_WIDTH:] - publish_state.disengage_buffer[-ModelConstants.DISENGAGE_WIDTH:] = ind_disengage_probs - - score = 0. - for i in range(ModelConstants.DISENGAGE_WIDTH): - score += publish_state.disengage_buffer[i*ModelConstants.DISENGAGE_WIDTH+ModelConstants.DISENGAGE_WIDTH-1-i].item() / ModelConstants.DISENGAGE_WIDTH - if score < ModelConstants.RYG_GREEN: - modelV2.confidence = ConfidenceClass.green - elif score < ModelConstants.RYG_YELLOW: - modelV2.confidence = ConfidenceClass.yellow - else: - modelV2.confidence = ConfidenceClass.red - - # raw prediction if enabled - if SEND_RAW_PRED: - modelV2.rawPredictions = net_output_data['raw_pred'].tobytes() - -def fill_pose_msg(msg: capnp._DynamicStructBuilder, net_output_data: dict[str, np.ndarray], - vipc_frame_id: int, vipc_dropped_frames: int, timestamp_eof: int, live_calib_seen: bool) -> None: - msg.valid = live_calib_seen & (vipc_dropped_frames < 1) - cameraOdometry = msg.cameraOdometry - - cameraOdometry.frameId = vipc_frame_id - cameraOdometry.timestampEof = timestamp_eof - - cameraOdometry.trans = net_output_data['pose'][0,:3].tolist() - cameraOdometry.rot = net_output_data['pose'][0,3:].tolist() - cameraOdometry.wideFromDeviceEuler = net_output_data['wide_from_device_euler'][0,:].tolist() - cameraOdometry.roadTransformTrans = net_output_data['road_transform'][0,:3].tolist() - cameraOdometry.transStd = net_output_data['pose_stds'][0,:3].tolist() - cameraOdometry.rotStd = net_output_data['pose_stds'][0,3:].tolist() - cameraOdometry.wideFromDeviceEulerStd = net_output_data['wide_from_device_euler_stds'][0,:].tolist() - cameraOdometry.roadTransformTransStd = net_output_data['road_transform_stds'][0,:3].tolist() diff --git a/sunnypilot/modeld/get_model_metadata.py b/sunnypilot/modeld/get_model_metadata.py deleted file mode 100755 index 144860204f..0000000000 --- a/sunnypilot/modeld/get_model_metadata.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -import sys -import pathlib -import onnx -import codecs -import pickle - -def get_name_and_shape(value_info:onnx.ValueInfoProto) -> tuple[str, tuple[int,...]]: - shape = tuple([int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim]) - name = value_info.name - return name, shape - -if __name__ == "__main__": - model_path = pathlib.Path(sys.argv[1]) - model = onnx.load(str(model_path)) - i = [x.key for x in model.metadata_props].index('output_slices') - output_slices = model.metadata_props[i].value - - metadata = {} - metadata['output_slices'] = pickle.loads(codecs.decode(output_slices.encode(), "base64")) - metadata['input_shapes'] = dict([get_name_and_shape(x) for x in model.graph.input]) - metadata['output_shapes'] = dict([get_name_and_shape(x) for x in model.graph.output]) - - metadata_path = model_path.parent / (model_path.stem + '_metadata.pkl') - with open(metadata_path, 'wb') as f: - pickle.dump(metadata, f) - - print(f'saved metadata to {metadata_path}') diff --git a/sunnypilot/modeld/modeld b/sunnypilot/modeld/modeld deleted file mode 100755 index e1cef4dcc3..0000000000 --- a/sunnypilot/modeld/modeld +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -cd "$DIR/../../" - -if [ -f "$DIR/libthneed.so" ]; then - export LD_PRELOAD="$DIR/libthneed.so" -fi - -exec "$DIR/modeld.py" "$@" diff --git a/sunnypilot/modeld/modeld.py b/sunnypilot/modeld/modeld.py deleted file mode 100755 index b36aea405a..0000000000 --- a/sunnypilot/modeld/modeld.py +++ /dev/null @@ -1,348 +0,0 @@ -#!/usr/bin/env python3 -import os -import time -import numpy as np -import cereal.messaging as messaging -from cereal import car, log -from pathlib import Path -from setproctitle import setproctitle -from cereal.messaging import PubMaster, SubMaster -from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf -from opendbc.car.car_helpers import get_demo_car_params -from openpilot.common.swaglog import cloudlog -from openpilot.common.params import Params -from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.common.realtime import DT_MDL, config_realtime_process -from numpy import interp -from openpilot.common.transformations.camera import DEVICE_CAMERAS -from openpilot.common.transformations.model import get_warp_matrix -from openpilot.system import sentry -from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper -from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value - -from openpilot.sunnypilot.livedelay.helpers import get_lat_delay -from openpilot.sunnypilot.modeld.runners import ModelRunner, Runtime -from openpilot.sunnypilot.modeld.parse_model_outputs import Parser -from openpilot.sunnypilot.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState -from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper -from openpilot.sunnypilot.modeld.constants import ModelConstants, Plan -from openpilot.sunnypilot.models.helpers import get_active_bundle, get_model_path, load_metadata, prepare_inputs, load_meta_constants -from openpilot.sunnypilot.modeld.models.commonmodel_pyx import ModelFrame, CLContext -from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase - - -PROCESS_NAME = "selfdrive.modeld.modeld_snpe" -SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') - -MODEL_PATHS = { - ModelRunner.THNEED: Path(__file__).parent / 'models/supercombo.thneed', - ModelRunner.ONNX: Path(__file__).parent / 'models/supercombo.onnx'} - -METADATA_PATH = Path(__file__).parent / 'models/supercombo_metadata.pkl' - - -class FrameMeta: - frame_id: int = 0 - timestamp_sof: int = 0 - timestamp_eof: int = 0 - - def __init__(self, vipc=None): - if vipc is not None: - self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof - -class ModelState(ModelStateBase): - frame: ModelFrame - wide_frame: ModelFrame - inputs: dict[str, np.ndarray] - output: np.ndarray - prev_desire: np.ndarray # for tracking the rising edge of the pulse - model: ModelRunner - - def __init__(self, context: CLContext): - ModelStateBase.__init__(self) - self.frame = ModelFrame(context) - self.wide_frame = ModelFrame(context) - self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) - bundle = get_active_bundle() - overrides = {override.key: override.value for override in bundle.overrides} - self.LAT_SMOOTH_SECONDS = float(overrides.get('lat', ".0")) - self.LONG_SMOOTH_SECONDS = float(overrides.get('long', ".0")) - - model_paths = get_model_path() - self.model_metadata = load_metadata() - self.inputs = prepare_inputs(self.model_metadata) - self.meta = load_meta_constants(self.model_metadata) - - self.output_slices = self.model_metadata['output_slices'] - net_output_size = self.model_metadata['output_shapes']['outputs'][1] - self.output = np.zeros(net_output_size, dtype=np.float32) - self.parser = Parser() - - self.model = ModelRunner(model_paths, self.output, Runtime.GPU, False, context) - self.model.addInput("input_imgs", None) - self.model.addInput("big_input_imgs", None) - for k,v in self.inputs.items(): - self.model.addInput(k, v) - - def slice_outputs(self, model_outputs: np.ndarray) -> dict[str, np.ndarray]: - parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in self.output_slices.items()} - if SEND_RAW_PRED: - parsed_model_outputs['raw_pred'] = model_outputs.copy() - return parsed_model_outputs - - def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_wide: np.ndarray, - inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None: - # Model decides when action is completed, so desire input is just a pulse triggered on rising edge - inputs['desire'][0] = 0 - self.inputs['desire'][:-ModelConstants.DESIRE_LEN] = self.inputs['desire'][ModelConstants.DESIRE_LEN:] - self.inputs['desire'][-ModelConstants.DESIRE_LEN:] = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0) - self.prev_desire[:] = inputs['desire'] - - for k in self.inputs: - if k in inputs and k != 'desire': - self.inputs[k][:] = inputs[k] - - # 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"))) - if wbuf is not None: - self.model.setInputBuffer("big_input_imgs", self.wide_frame.prepare(wbuf, transform_wide.flatten(), self.model.getCLBuffer("big_input_imgs"))) - - if prepare_only: - return None - - self.model.execute() - outputs = self.parser.parse_outputs(self.slice_outputs(self.output)) - - self.inputs['features_buffer'][:-ModelConstants.FEATURE_LEN] = self.inputs['features_buffer'][ModelConstants.FEATURE_LEN:] - self.inputs['features_buffer'][-ModelConstants.FEATURE_LEN:] = outputs['hidden_state'][0, :] - - if "lat_planner_solution" in outputs and "lat_planner_state" in self.inputs.keys(): - self.inputs['lat_planner_state'][2] = interp(DT_MDL, ModelConstants.T_IDXS, outputs['lat_planner_solution'][0, :, 2]) - self.inputs['lat_planner_state'][3] = interp(DT_MDL, ModelConstants.T_IDXS, outputs['lat_planner_solution'][0, :, 3]) - - if "desired_curvature" in outputs: - if "prev_desired_curvs" in self.inputs.keys(): - self.inputs['prev_desired_curvs'][:-1] = self.inputs['prev_desired_curvs'][1:] - self.inputs['prev_desired_curvs'][-1] = outputs['desired_curvature'][0, 0] - - if "prev_desired_curv" in self.inputs.keys(): - self.inputs['prev_desired_curv'][:-1] = self.inputs['prev_desired_curv'][1:] - self.inputs['prev_desired_curv'][-1:] = outputs['desired_curvature'][0, :] - return outputs - - def get_action_from_model(self, model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, - long_action_t: float) -> log.ModelDataV2.Action: - plan = model_output['plan'][0] - desired_accel, should_stop = get_accel_from_plan(plan[:, Plan.VELOCITY][:, 0], plan[:, Plan.ACCELERATION][:, 0], ModelConstants.T_IDXS, - action_t=long_action_t) - desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS) - - return log.ModelDataV2.Action(desiredAcceleration=float(desired_accel), shouldStop=bool(should_stop)) - - -def main(demo=False): - cloudlog.warning("modeld init") - - sentry.set_tag("daemon", PROCESS_NAME) - cloudlog.bind(daemon=PROCESS_NAME) - setproctitle(PROCESS_NAME) - config_realtime_process(7, 54) - - cloudlog.warning("setting up CL context") - cl_context = CLContext() - cloudlog.warning("CL context ready; loading model") - model = ModelState(cl_context) - cloudlog.warning("models loaded, modeld starting") - - # visionipc clients - while True: - available_streams = VisionIpcClient.available_streams("camerad", block=False) - if available_streams: - use_extra_client = VisionStreamType.VISION_STREAM_WIDE_ROAD in available_streams and VisionStreamType.VISION_STREAM_ROAD in available_streams - main_wide_camera = VisionStreamType.VISION_STREAM_ROAD not in available_streams - break - time.sleep(.1) - - vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) - cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") - - while not vipc_client_main.connect(False): - time.sleep(0.1) - while use_extra_client and not vipc_client_extra.connect(False): - time.sleep(0.1) - - cloudlog.warning(f"connected main cam with buffer size: {vipc_client_main.buffer_len} ({vipc_client_main.width} x {vipc_client_main.height})") - if use_extra_client: - cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})") - - # messaging - pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"]) - sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"]) - - publish_state = PublishState() - params = Params() - - # setup filter to track dropped frames - frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_FREQ) - frame_id = 0 - last_vipc_frame_id = 0 - run_count = 0 - - model_transform_main = np.zeros((3, 3), dtype=np.float32) - model_transform_extra = np.zeros((3, 3), dtype=np.float32) - live_calib_seen = False - buf_main, buf_extra = None, None - meta_main = FrameMeta() - meta_extra = FrameMeta() - camera_offset_helper = CameraOffsetHelper() - - - if demo: - CP = get_demo_car_params() - else: - CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) - - cloudlog.info("modeld got CarParams: %s", CP.brand) - - # Enable lagd support for sunnypilot modeld - long_delay = CP.longitudinalActuatorDelay + model.LONG_SMOOTH_SECONDS - prev_action = log.ModelDataV2.Action() - - DH = DesireHelper() - - while True: - # Keep receiving frames until we are at least 1 frame ahead of previous extra frame - while meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: - buf_main = vipc_client_main.recv() - meta_main = FrameMeta(vipc_client_main) - if buf_main is None: - break - - if buf_main is None: - cloudlog.debug("vipc_client_main no frame") - continue - - if use_extra_client: - # Keep receiving extra frames until frame id matches main camera - while True: - buf_extra = vipc_client_extra.recv() - meta_extra = FrameMeta(vipc_client_extra) - if buf_extra is None or meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: - break - - if buf_extra is None: - cloudlog.debug("vipc_client_extra no frame") - continue - - if abs(meta_main.timestamp_sof - meta_extra.timestamp_sof) > 10000000: - 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 - buf_extra = buf_main - meta_extra = meta_main - - sm.update(0) - desire = DH.desire - v_ego = sm["carState"].vEgo - is_rhd = sm["driverMonitoringState"].isRHD - frame_id = sm["roadCameraState"].frameId - if sm.frame % 60 == 0: - model.lat_delay = get_lat_delay(params, sm["liveDelay"].lateralDelay) - camera_offset_helper.set_offset(params.get("CameraOffset", return_default=True)) - lat_delay = model.lat_delay + model.LAT_SMOOTH_SECONDS - if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: - device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=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) - model_transform_main, model_transform_extra = camera_offset_helper.update(model_transform_main, model_transform_extra, sm, main_wide_camera) - live_calib_seen = True - - traffic_convention = np.zeros(2) - traffic_convention[int(is_rhd)] = 1 - - vec_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) - if desire >= 0 and desire < ModelConstants.DESIRE_LEN: - vec_desire[desire] = 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)) - if run_count < 10: # let frame drops warm up - frame_dropped_filter.x = 0. - frames_dropped = 0. - run_count = run_count + 1 - - frame_drop_ratio = frames_dropped / (1 + frames_dropped) - prepare_only = vipc_dropped_frames > 0 - if prepare_only: - cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames") - - inputs:dict[str, np.ndarray] = { - 'desire': vec_desire, - 'traffic_convention': traffic_convention, - } - - if "lateral_control_params" in model.inputs.keys(): - inputs['lateral_control_params'] = np.array([max(v_ego, 0.), lat_delay], dtype=np.float32) - - if "driving_style" in model.inputs.keys(): - inputs['driving_style'] = np.array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], dtype=np.float32) - - if "nav_features" in model.inputs.keys(): - inputs['nav_features'] = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) - - if "nav_instructions" in model.inputs.keys(): - inputs['nav_instructions'] = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) - - mt1 = time.perf_counter() - model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only) - mt2 = time.perf_counter() - model_execution_time = mt2 - mt1 - - if model_output is not None: - modelv2_send = messaging.new_message('modelV2') - drivingdata_send = messaging.new_message('drivingModelData') - posenet_send = messaging.new_message('cameraOdometry') - mdv2sp_send = messaging.new_message('modelDataV2SP') - action = model.get_action_from_model(model_output, prev_action, long_delay + DT_MDL) - fill_model_msg(drivingdata_send, modelv2_send, model_output, action, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, - frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, live_calib_seen, - v_ego, lat_delay, model.meta) - - desire_state = modelv2_send.modelV2.meta.desireState - l_lane_change_prob = desire_state[log.Desire.laneChangeLeft] - r_lane_change_prob = desire_state[log.Desire.laneChangeRight] - lane_change_prob = l_lane_change_prob + r_lane_change_prob - DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob) - modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state - modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction - mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction - drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state - drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction - - fill_pose_msg(posenet_send, model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen) - pm.send('modelV2', modelv2_send) - pm.send('drivingModelData', drivingdata_send) - pm.send('cameraOdometry', posenet_send) - pm.send('modelDataV2SP', mdv2sp_send) - - last_vipc_frame_id = meta_main.frame_id - - -if __name__ == "__main__": - try: - import argparse - parser = argparse.ArgumentParser() - parser.add_argument('--demo', action='store_true', help='A boolean for demo mode.') - args = parser.parse_args() - main(demo=args.demo) - except KeyboardInterrupt: - cloudlog.warning(f"child {PROCESS_NAME} got SIGINT") - except Exception: - sentry.capture_exception() - raise diff --git a/sunnypilot/modeld/models/commonmodel.cc b/sunnypilot/modeld/models/commonmodel.cc deleted file mode 100644 index f1e15e5a4e..0000000000 --- a/sunnypilot/modeld/models/commonmodel.cc +++ /dev/null @@ -1,50 +0,0 @@ -#include "sunnypilot/modeld/models/commonmodel.h" - -#include -#include -#include - -#include "common/clutil.h" - -ModelFrame::ModelFrame(cl_device_id device_id, cl_context context) { - input_frames = std::make_unique(buf_size); - - q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); - y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, MODEL_WIDTH * MODEL_HEIGHT, NULL, &err)); - u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (MODEL_WIDTH / 2) * (MODEL_HEIGHT / 2), NULL, &err)); - v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (MODEL_WIDTH / 2) * (MODEL_HEIGHT / 2), NULL, &err)); - net_input_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, MODEL_FRAME_SIZE * sizeof(float), NULL, &err)); - - transform_init(&transform, context, device_id); - loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); -} - -float* ModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3 &projection, cl_mem *output) { - transform_queue(&this->transform, q, - yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, - y_cl, u_cl, v_cl, MODEL_WIDTH, MODEL_HEIGHT, projection); - - if (output == NULL) { - loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, net_input_cl); - - std::memmove(&input_frames[0], &input_frames[MODEL_FRAME_SIZE], sizeof(float) * MODEL_FRAME_SIZE); - CL_CHECK(clEnqueueReadBuffer(q, net_input_cl, CL_TRUE, 0, MODEL_FRAME_SIZE * sizeof(float), &input_frames[MODEL_FRAME_SIZE], 0, nullptr, nullptr)); - clFinish(q); - return &input_frames[0]; - } else { - loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, *output, true); - // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. - clFinish(q); - return NULL; - } -} - -ModelFrame::~ModelFrame() { - transform_destroy(&transform); - loadyuv_destroy(&loadyuv); - CL_CHECK(clReleaseMemObject(net_input_cl)); - CL_CHECK(clReleaseMemObject(v_cl)); - CL_CHECK(clReleaseMemObject(u_cl)); - CL_CHECK(clReleaseMemObject(y_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} diff --git a/sunnypilot/modeld/models/commonmodel.h b/sunnypilot/modeld/models/commonmodel.h deleted file mode 100644 index 9d34ebb6d3..0000000000 --- a/sunnypilot/modeld/models/commonmodel.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include - -#include - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" -#include "sunnypilot/modeld/transforms/loadyuv.h" -#include "sunnypilot/modeld/transforms/transform.h" - -class ModelFrame { -public: - ModelFrame(cl_device_id device_id, cl_context context); - ~ModelFrame(); - float* prepare(cl_mem yuv_cl, int width, int height, int frame_stride, int frame_uv_offset, const mat3& transform, cl_mem *output); - - const int MODEL_WIDTH = 512; - const int MODEL_HEIGHT = 256; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const int buf_size = MODEL_FRAME_SIZE * 2; - -private: - Transform transform; - LoadYUVState loadyuv; - cl_command_queue q; - cl_mem y_cl, u_cl, v_cl, net_input_cl; - std::unique_ptr input_frames; -}; diff --git a/sunnypilot/modeld/models/commonmodel.pxd b/sunnypilot/modeld/models/commonmodel.pxd deleted file mode 100644 index c7fd85411d..0000000000 --- a/sunnypilot/modeld/models/commonmodel.pxd +++ /dev/null @@ -1,18 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem - -cdef extern from "common/mat.h": - cdef struct mat3: - float v[9] - -cdef extern from "common/clutil.h": - cdef unsigned long CL_DEVICE_TYPE_DEFAULT - cl_device_id cl_get_device_id(unsigned long) - cl_context cl_create_context(cl_device_id) - -cdef extern from "sunnypilot/modeld/models/commonmodel.h": - cppclass ModelFrame: - int buf_size - ModelFrame(cl_device_id, cl_context) - float * prepare(cl_mem, int, int, int, int, mat3, cl_mem*) diff --git a/sunnypilot/modeld/models/commonmodel_pyx.pxd b/sunnypilot/modeld/models/commonmodel_pyx.pxd deleted file mode 100644 index 0bb798625b..0000000000 --- a/sunnypilot/modeld/models/commonmodel_pyx.pxd +++ /dev/null @@ -1,13 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext - -cdef class CLContext(BaseCLContext): - pass - -cdef class CLMem: - cdef cl_mem * mem - - @staticmethod - cdef create(void*) diff --git a/sunnypilot/modeld/models/commonmodel_pyx.pyx b/sunnypilot/modeld/models/commonmodel_pyx.pyx deleted file mode 100644 index d66fddea54..0000000000 --- a/sunnypilot/modeld/models/commonmodel_pyx.pyx +++ /dev/null @@ -1,45 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii, language_level=3 - -import numpy as np -cimport numpy as cnp -from libc.string cimport memcpy - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext -from openpilot.sunnypilot.modeld.models.commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context -from openpilot.sunnypilot.modeld.models.commonmodel cimport mat3, ModelFrame as cppModelFrame - - -cdef class CLContext(BaseCLContext): - def __cinit__(self): - self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) - self.context = cl_create_context(self.device_id) - -cdef class CLMem: - @staticmethod - cdef create(void * cmem): - mem = CLMem() - mem.mem = cmem - return mem - -cdef class ModelFrame: - cdef cppModelFrame * frame - - def __cinit__(self, CLContext context): - self.frame = new cppModelFrame(context.device_id, context.context) - - def __dealloc__(self): - del self.frame - - def prepare(self, VisionBuf buf, float[:] projection, CLMem output): - cdef mat3 cprojection - memcpy(cprojection.v, &projection[0], 9*sizeof(float)) - 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/sunnypilot/modeld/parse_model_outputs.py b/sunnypilot/modeld/parse_model_outputs.py deleted file mode 100644 index 9cd5f1465c..0000000000 --- a/sunnypilot/modeld/parse_model_outputs.py +++ /dev/null @@ -1,107 +0,0 @@ -import numpy as np -from openpilot.sunnypilot.modeld.constants import ModelConstants - -def safe_exp(x, out=None): - # -11 is around 10**14, more causes float16 overflow - return np.exp(np.clip(x, -np.inf, 11), out=out) - -def sigmoid(x): - return 1. / (1. + safe_exp(-x)) - -def softmax(x, axis=-1): - x -= np.max(x, axis=axis, keepdims=True) - if x.dtype == np.float32 or x.dtype == np.float64: - safe_exp(x, out=x) - else: - x = safe_exp(x) - x /= np.sum(x, axis=axis, keepdims=True) - return x - -class Parser: - def __init__(self, ignore_missing=False): - self.ignore_missing = ignore_missing - - def check_missing(self, outs, name): - if name not in outs and not self.ignore_missing: - raise ValueError(f"Missing output {name}") - return name not in outs - - def parse_categorical_crossentropy(self, name, outs, out_shape=None): - if self.check_missing(outs, name): - return - raw = outs[name] - if out_shape is not None: - raw = raw.reshape((raw.shape[0],) + out_shape) - outs[name] = softmax(raw, axis=-1) - - def parse_binary_crossentropy(self, name, outs): - if self.check_missing(outs, name): - return - raw = outs[name] - outs[name] = sigmoid(raw) - - def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=None): - if self.check_missing(outs, name): - return - raw = outs[name] - raw = raw.reshape((raw.shape[0], max(in_N, 1), -1)) - - n_values = (raw.shape[2] - out_N)//2 - pred_mu = raw[:,:,:n_values] - pred_std = safe_exp(raw[:,:,n_values: 2*n_values]) - - if in_N > 1: - weights = np.zeros((raw.shape[0], in_N, out_N), dtype=raw.dtype) - for i in range(out_N): - weights[:,:,i - out_N] = softmax(raw[:,:,i - out_N], axis=-1) - - if out_N == 1: - for fidx in range(weights.shape[0]): - idxs = np.argsort(weights[fidx][:,0])[::-1] - weights[fidx] = weights[fidx][idxs] - pred_mu[fidx] = pred_mu[fidx][idxs] - pred_std[fidx] = pred_std[fidx][idxs] - full_shape = tuple([raw.shape[0], in_N] + list(out_shape)) - outs[name + '_weights'] = weights - outs[name + '_hypotheses'] = pred_mu.reshape(full_shape) - outs[name + '_stds_hypotheses'] = pred_std.reshape(full_shape) - - pred_mu_final = np.zeros((raw.shape[0], out_N, n_values), dtype=raw.dtype) - pred_std_final = np.zeros((raw.shape[0], out_N, n_values), dtype=raw.dtype) - for fidx in range(weights.shape[0]): - for hidx in range(out_N): - idxs = np.argsort(weights[fidx,:,hidx])[::-1] - pred_mu_final[fidx, hidx] = pred_mu[fidx, idxs[0]] - pred_std_final[fidx, hidx] = pred_std[fidx, idxs[0]] - else: - pred_mu_final = pred_mu - pred_std_final = pred_std - - if out_N > 1: - final_shape = tuple([raw.shape[0], out_N] + list(out_shape)) - else: - final_shape = tuple([raw.shape[0],] + list(out_shape)) - outs[name] = pred_mu_final.reshape(final_shape) - outs[name + '_stds'] = pred_std_final.reshape(final_shape) - - def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: - self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION, - out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH)) - self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) - self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) - self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) - self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) - if 'sim_pose' in outs: - self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) - self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) - self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION, - out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH)) - if 'lat_planner_solution' in outs: - self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_WIDTH)) - if 'desired_curvature' in outs: - self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,)) - for k in ['lead_prob', 'lane_lines_prob', 'meta']: - self.parse_binary_crossentropy(k, outs) - self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) - self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) - return outs diff --git a/sunnypilot/modeld/runners/__init__.py b/sunnypilot/modeld/runners/__init__.py deleted file mode 100644 index d105685bd8..0000000000 --- a/sunnypilot/modeld/runners/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -from openpilot.system.hardware import TICI -from openpilot.sunnypilot.modeld.runners.runmodel_pyx import RunModel, Runtime -assert Runtime - -USE_THNEED = int(os.getenv('USE_THNEED', str(int(TICI)))) - -class ModelRunner(RunModel): - THNEED = 'THNEED' - ONNX = 'ONNX' - - def __new__(cls, paths, *args, **kwargs): - if ModelRunner.THNEED in paths and USE_THNEED: - from openpilot.sunnypilot.modeld.runners.thneedmodel_pyx import ThneedModel as Runner - runner_type = ModelRunner.THNEED - elif ModelRunner.ONNX in paths: - from openpilot.sunnypilot.modeld.runners.onnxmodel import ONNXModel as Runner - runner_type = ModelRunner.ONNX - else: - raise Exception("Couldn't select a model runner, make sure to pass at least one valid model path") - - return Runner(str(paths[runner_type]), *args, **kwargs) diff --git a/sunnypilot/modeld/runners/onnxmodel.py b/sunnypilot/modeld/runners/onnxmodel.py deleted file mode 100644 index 047f13dc9d..0000000000 --- a/sunnypilot/modeld/runners/onnxmodel.py +++ /dev/null @@ -1,98 +0,0 @@ -import onnx -import itertools -import os -import sys -import numpy as np -from typing import Any - -from openpilot.sunnypilot.modeld.runners.runmodel_pyx import RunModel - -ORT_TYPES_TO_NP_TYPES = {'tensor(float16)': np.float16, 'tensor(float)': np.float32, 'tensor(uint8)': np.uint8} - -def attributeproto_fp16_to_fp32(attr): - float32_list = np.frombuffer(attr.raw_data, dtype=np.float16) - attr.data_type = 1 - attr.raw_data = float32_list.astype(np.float32).tobytes() - -def convert_fp16_to_fp32(onnx_path_or_bytes): - if isinstance(onnx_path_or_bytes, bytes): - model = onnx.load_from_string(onnx_path_or_bytes) - elif isinstance(onnx_path_or_bytes, str): - model = onnx.load(onnx_path_or_bytes) - - for i in model.graph.initializer: - if i.data_type == 10: - attributeproto_fp16_to_fp32(i) - for i in itertools.chain(model.graph.input, model.graph.output): - if i.type.tensor_type.elem_type == 10: - i.type.tensor_type.elem_type = 1 - for i in model.graph.node: - if i.op_type == 'Cast' and i.attribute[0].i == 10: - i.attribute[0].i = 1 - for a in i.attribute: - if hasattr(a, 't'): - if a.t.data_type == 10: - attributeproto_fp16_to_fp32(a.t) - return model.SerializeToString() - -def create_ort_session(path, fp16_to_fp32): - os.environ["OMP_NUM_THREADS"] = "4" - os.environ["OMP_WAIT_POLICY"] = "PASSIVE" - - import onnxruntime as ort - print("Onnx available providers: ", ort.get_available_providers(), file=sys.stderr) - options = ort.SessionOptions() - options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_DISABLE_ALL - - provider: str | tuple[str, dict[Any, Any]] - if 'OpenVINOExecutionProvider' in ort.get_available_providers() and 'ONNXCPU' not in os.environ: - provider = 'OpenVINOExecutionProvider' - elif 'CUDAExecutionProvider' in ort.get_available_providers() and 'ONNXCPU' not in os.environ: - options.intra_op_num_threads = 2 - provider = ('CUDAExecutionProvider', {'cudnn_conv_algo_search': 'DEFAULT'}) - else: - options.intra_op_num_threads = 2 - options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL - options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL - provider = 'CPUExecutionProvider' - - model_data = convert_fp16_to_fp32(path) if fp16_to_fp32 else path - print("Onnx selected provider: ", [provider], file=sys.stderr) - ort_session = ort.InferenceSession(model_data, options, providers=[provider]) - print("Onnx using ", ort_session.get_providers(), file=sys.stderr) - return ort_session - - -class ONNXModel(RunModel): - def __init__(self, path, output, runtime, use_tf8, cl_context): - self.inputs = {} - self.output = output - - self.session = create_ort_session(path, fp16_to_fp32=True) - self.input_names = [x.name for x in self.session.get_inputs()] - self.input_shapes = {x.name: [1, *x.shape[1:]] for x in self.session.get_inputs()} - self.input_dtypes = {x.name: ORT_TYPES_TO_NP_TYPES[x.type] for x in self.session.get_inputs()} - - # run once to initialize CUDA provider - if "CUDAExecutionProvider" in self.session.get_providers(): - self.session.run(None, {k: np.zeros(self.input_shapes[k], dtype=self.input_dtypes[k]) for k in self.input_names}) - print("ready to run onnx model", self.input_shapes, file=sys.stderr) - - def addInput(self, name, buffer): - assert name in self.input_names - self.inputs[name] = buffer - - def setInputBuffer(self, name, buffer): - assert name in self.inputs - self.inputs[name] = buffer - - def getCLBuffer(self, name): - return None - - def execute(self): - inputs = {k: v.view(self.input_dtypes[k]) for k,v in self.inputs.items()} - inputs = {k: v.reshape(self.input_shapes[k]).astype(self.input_dtypes[k]) for k,v in inputs.items()} - outputs = self.session.run(None, inputs) - assert len(outputs) == 1, "Only single model outputs are supported" - self.output[:] = outputs[0] - return self.output diff --git a/sunnypilot/modeld/runners/run.h b/sunnypilot/modeld/runners/run.h deleted file mode 100644 index 55e703ef97..0000000000 --- a/sunnypilot/modeld/runners/run.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#include "sunnypilot/modeld/runners/runmodel.h" diff --git a/sunnypilot/modeld/runners/runmodel.h b/sunnypilot/modeld/runners/runmodel.h deleted file mode 100644 index 18cc180cb7..0000000000 --- a/sunnypilot/modeld/runners/runmodel.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "common/clutil.h" -#include "common/swaglog.h" - -#define USE_CPU_RUNTIME 0 -#define USE_GPU_RUNTIME 1 -#define USE_DSP_RUNTIME 2 - -struct ModelInput { - const std::string name; - float *buffer; - int size; - - ModelInput(const std::string _name, float *_buffer, int _size) : name(_name), buffer(_buffer), size(_size) {} - virtual void setBuffer(float *_buffer, int _size) { - assert(size == _size || size == 0); - buffer = _buffer; - size = _size; - } -}; - -class RunModel { -public: - std::vector> inputs; - - virtual ~RunModel() {} - virtual void execute() {} - virtual void* getCLBuffer(const std::string name) { return nullptr; } - - virtual void addInput(const std::string name, float *buffer, int size) { - inputs.push_back(std::unique_ptr(new ModelInput(name, buffer, size))); - } - virtual void setInputBuffer(const std::string name, float *buffer, int size) { - for (auto &input : inputs) { - if (name == input->name) { - input->setBuffer(buffer, size); - return; - } - } - LOGE("Tried to update input `%s` but no input with this name exists", name.c_str()); - assert(false); - } -}; diff --git a/sunnypilot/modeld/runners/runmodel.pxd b/sunnypilot/modeld/runners/runmodel.pxd deleted file mode 100644 index b83434473d..0000000000 --- a/sunnypilot/modeld/runners/runmodel.pxd +++ /dev/null @@ -1,14 +0,0 @@ -# distutils: language = c++ - -from libcpp.string cimport string - -cdef extern from "sunnypilot/modeld/runners/runmodel.h": - cdef int USE_CPU_RUNTIME - cdef int USE_GPU_RUNTIME - cdef int USE_DSP_RUNTIME - - cdef cppclass RunModel: - void addInput(string, float*, int) - void setInputBuffer(string, float*, int) - void * getCLBuffer(string) - void execute() diff --git a/sunnypilot/modeld/runners/runmodel_pyx.pxd b/sunnypilot/modeld/runners/runmodel_pyx.pxd deleted file mode 100644 index b6ede7cf37..0000000000 --- a/sunnypilot/modeld/runners/runmodel_pyx.pxd +++ /dev/null @@ -1,6 +0,0 @@ -# distutils: language = c++ - -from .runmodel cimport RunModel as cppRunModel - -cdef class RunModel: - cdef cppRunModel * model diff --git a/sunnypilot/modeld/runners/runmodel_pyx.pyx b/sunnypilot/modeld/runners/runmodel_pyx.pyx deleted file mode 100644 index d2b4485944..0000000000 --- a/sunnypilot/modeld/runners/runmodel_pyx.pyx +++ /dev/null @@ -1,37 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii, language_level=3 - -from libcpp.string cimport string - -from .runmodel cimport USE_CPU_RUNTIME, USE_GPU_RUNTIME, USE_DSP_RUNTIME -from openpilot.sunnypilot.modeld.models.commonmodel_pyx cimport CLMem - -class Runtime: - CPU = USE_CPU_RUNTIME - GPU = USE_GPU_RUNTIME - DSP = USE_DSP_RUNTIME - -cdef class RunModel: - def __dealloc__(self): - del self.model - - def addInput(self, string name, float[:] buffer): - if buffer is not None: - self.model.addInput(name, &buffer[0], len(buffer)) - else: - self.model.addInput(name, NULL, 0) - - def setInputBuffer(self, string name, float[:] buffer): - if buffer is not None: - self.model.setInputBuffer(name, &buffer[0], len(buffer)) - else: - self.model.setInputBuffer(name, NULL, 0) - - def getCLBuffer(self, string name): - cdef void * cl_buf = self.model.getCLBuffer(name) - if not cl_buf: - return None - return CLMem.create(cl_buf) - - def execute(self): - self.model.execute() diff --git a/sunnypilot/modeld/runners/thneedmodel.cc b/sunnypilot/modeld/runners/thneedmodel.cc deleted file mode 100644 index d83fc0a5f9..0000000000 --- a/sunnypilot/modeld/runners/thneedmodel.cc +++ /dev/null @@ -1,58 +0,0 @@ -#include "sunnypilot/modeld/runners/thneedmodel.h" - -#include - -#include "common/swaglog.h" - -ThneedModel::ThneedModel(const std::string path, float *_output, size_t _output_size, int runtime, bool luse_tf8, cl_context context) { - thneed = new Thneed(true, context); - thneed->load(path.c_str()); - thneed->clexec(); - - recorded = false; - output = _output; -} - -void* ThneedModel::getCLBuffer(const std::string name) { - int index = -1; - for (int i = 0; i < inputs.size(); i++) { - if (name == inputs[i]->name) { - index = i; - break; - } - } - - if (index == -1) { - LOGE("Tried to get CL buffer for input `%s` but no input with this name exists", name.c_str()); - assert(false); - } - - if (thneed->input_clmem.size() >= inputs.size()) { - return &thneed->input_clmem[inputs.size() - index - 1]; - } else { - return nullptr; - } -} - -void ThneedModel::execute() { - if (!recorded) { - thneed->record = true; - float *input_buffers[inputs.size()]; - for (int i = 0; i < inputs.size(); i++) { - input_buffers[inputs.size() - i - 1] = inputs[i]->buffer; - } - - thneed->copy_inputs(input_buffers); - thneed->clexec(); - thneed->copy_output(output); - thneed->stop(); - - recorded = true; - } else { - float *input_buffers[inputs.size()]; - for (int i = 0; i < inputs.size(); i++) { - input_buffers[inputs.size() - i - 1] = inputs[i]->buffer; - } - thneed->execute(input_buffers, output); - } -} diff --git a/sunnypilot/modeld/runners/thneedmodel.h b/sunnypilot/modeld/runners/thneedmodel.h deleted file mode 100644 index ecc7207c1f..0000000000 --- a/sunnypilot/modeld/runners/thneedmodel.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -#include "sunnypilot/modeld/runners/runmodel.h" -#include "sunnypilot/modeld/thneed/thneed.h" - -class ThneedModel : public RunModel { -public: - ThneedModel(const std::string path, float *_output, size_t _output_size, int runtime, bool use_tf8 = false, cl_context context = NULL); - void *getCLBuffer(const std::string name); - void execute(); -private: - Thneed *thneed = NULL; - bool recorded; - float *output; -}; diff --git a/sunnypilot/modeld/runners/thneedmodel.pxd b/sunnypilot/modeld/runners/thneedmodel.pxd deleted file mode 100644 index ace7443b50..0000000000 --- a/sunnypilot/modeld/runners/thneedmodel.pxd +++ /dev/null @@ -1,9 +0,0 @@ -# distutils: language = c++ - -from libcpp.string cimport string - -from msgq.visionipc.visionipc cimport cl_context - -cdef extern from "sunnypilot/modeld/runners/thneedmodel.h": - cdef cppclass ThneedModel: - ThneedModel(string, float*, size_t, int, bool, cl_context) diff --git a/sunnypilot/modeld/runners/thneedmodel_pyx.pyx b/sunnypilot/modeld/runners/thneedmodel_pyx.pyx deleted file mode 100644 index eaabc4a776..0000000000 --- a/sunnypilot/modeld/runners/thneedmodel_pyx.pyx +++ /dev/null @@ -1,14 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii, language_level=3 - -from libcpp cimport bool -from libcpp.string cimport string - -from .thneedmodel cimport ThneedModel as cppThneedModel -from openpilot.sunnypilot.modeld.models.commonmodel_pyx cimport CLContext -from openpilot.sunnypilot.modeld.runners.runmodel_pyx cimport RunModel -from openpilot.sunnypilot.modeld.runners.runmodel cimport RunModel as cppRunModel - -cdef class ThneedModel(RunModel): - def __cinit__(self, string path, float[:] output, int runtime, bool use_tf8, CLContext context): - self.model = new cppThneedModel(path, &output[0], len(output), runtime, use_tf8, context.context) diff --git a/sunnypilot/modeld/thneed/README b/sunnypilot/modeld/thneed/README deleted file mode 100644 index f3bc66d8fc..0000000000 --- a/sunnypilot/modeld/thneed/README +++ /dev/null @@ -1,8 +0,0 @@ -thneed is an SNPE accelerator. I know SNPE is already an accelerator, but sometimes things need to go even faster.. - -It runs on the local device, and caches a single model run. Then it replays it, but fast. - -thneed slices through abstraction layers like a fish. - -You need a thneed. - diff --git a/sunnypilot/modeld/thneed/__init__.py b/sunnypilot/modeld/thneed/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/sunnypilot/modeld/thneed/clutil_legacy.cc b/sunnypilot/modeld/thneed/clutil_legacy.cc deleted file mode 100644 index 3bf9316432..0000000000 --- a/sunnypilot/modeld/thneed/clutil_legacy.cc +++ /dev/null @@ -1,126 +0,0 @@ -#include "common/clutil.h" - -#include -#include -#include - -#include "common/util.h" -#include "common/swaglog.h" -#include "sunnypilot/modeld/thneed/clutil_legacy.h" - -void cl_print_build_errors(cl_program program, cl_device_id device) { - cl_build_status status; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL); - size_t log_size; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); - std::string log(log_size, '\0'); - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); - - LOGE("build failed; status=%d, log: %s", status, log.c_str()); -} - -cl_program cl_program_from_binary(cl_context ctx, cl_device_id device_id, const uint8_t* binary, size_t length, const char* args) { - cl_program prg = CL_CHECK_ERR(clCreateProgramWithBinary(ctx, 1, &device_id, &length, &binary, NULL, &err)); - if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { - cl_print_build_errors(prg, device_id); - assert(0); - } - return prg; -} - -// Given a cl code and return a string representation -#define CL_ERR_TO_STR(err) case err: return #err -const char* cl_get_error_string(int err) { - switch (err) { - CL_ERR_TO_STR(CL_SUCCESS); - CL_ERR_TO_STR(CL_DEVICE_NOT_FOUND); - CL_ERR_TO_STR(CL_DEVICE_NOT_AVAILABLE); - CL_ERR_TO_STR(CL_COMPILER_NOT_AVAILABLE); - CL_ERR_TO_STR(CL_MEM_OBJECT_ALLOCATION_FAILURE); - CL_ERR_TO_STR(CL_OUT_OF_RESOURCES); - CL_ERR_TO_STR(CL_OUT_OF_HOST_MEMORY); - CL_ERR_TO_STR(CL_PROFILING_INFO_NOT_AVAILABLE); - CL_ERR_TO_STR(CL_MEM_COPY_OVERLAP); - CL_ERR_TO_STR(CL_IMAGE_FORMAT_MISMATCH); - CL_ERR_TO_STR(CL_IMAGE_FORMAT_NOT_SUPPORTED); - CL_ERR_TO_STR(CL_MAP_FAILURE); - CL_ERR_TO_STR(CL_MISALIGNED_SUB_BUFFER_OFFSET); - CL_ERR_TO_STR(CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST); - CL_ERR_TO_STR(CL_COMPILE_PROGRAM_FAILURE); - CL_ERR_TO_STR(CL_LINKER_NOT_AVAILABLE); - CL_ERR_TO_STR(CL_LINK_PROGRAM_FAILURE); - CL_ERR_TO_STR(CL_DEVICE_PARTITION_FAILED); - CL_ERR_TO_STR(CL_KERNEL_ARG_INFO_NOT_AVAILABLE); - CL_ERR_TO_STR(CL_INVALID_VALUE); - CL_ERR_TO_STR(CL_INVALID_DEVICE_TYPE); - CL_ERR_TO_STR(CL_INVALID_PLATFORM); - CL_ERR_TO_STR(CL_INVALID_DEVICE); - CL_ERR_TO_STR(CL_INVALID_CONTEXT); - CL_ERR_TO_STR(CL_INVALID_QUEUE_PROPERTIES); - CL_ERR_TO_STR(CL_INVALID_COMMAND_QUEUE); - CL_ERR_TO_STR(CL_INVALID_HOST_PTR); - CL_ERR_TO_STR(CL_INVALID_MEM_OBJECT); - CL_ERR_TO_STR(CL_INVALID_IMAGE_FORMAT_DESCRIPTOR); - CL_ERR_TO_STR(CL_INVALID_IMAGE_SIZE); - CL_ERR_TO_STR(CL_INVALID_SAMPLER); - CL_ERR_TO_STR(CL_INVALID_BINARY); - CL_ERR_TO_STR(CL_INVALID_BUILD_OPTIONS); - CL_ERR_TO_STR(CL_INVALID_PROGRAM); - CL_ERR_TO_STR(CL_INVALID_PROGRAM_EXECUTABLE); - CL_ERR_TO_STR(CL_INVALID_KERNEL_NAME); - CL_ERR_TO_STR(CL_INVALID_KERNEL_DEFINITION); - CL_ERR_TO_STR(CL_INVALID_KERNEL); - CL_ERR_TO_STR(CL_INVALID_ARG_INDEX); - CL_ERR_TO_STR(CL_INVALID_ARG_VALUE); - CL_ERR_TO_STR(CL_INVALID_ARG_SIZE); - CL_ERR_TO_STR(CL_INVALID_KERNEL_ARGS); - CL_ERR_TO_STR(CL_INVALID_WORK_DIMENSION); - CL_ERR_TO_STR(CL_INVALID_WORK_GROUP_SIZE); - CL_ERR_TO_STR(CL_INVALID_WORK_ITEM_SIZE); - CL_ERR_TO_STR(CL_INVALID_GLOBAL_OFFSET); - CL_ERR_TO_STR(CL_INVALID_EVENT_WAIT_LIST); - CL_ERR_TO_STR(CL_INVALID_EVENT); - CL_ERR_TO_STR(CL_INVALID_OPERATION); - CL_ERR_TO_STR(CL_INVALID_GL_OBJECT); - CL_ERR_TO_STR(CL_INVALID_BUFFER_SIZE); - CL_ERR_TO_STR(CL_INVALID_MIP_LEVEL); - CL_ERR_TO_STR(CL_INVALID_GLOBAL_WORK_SIZE); - CL_ERR_TO_STR(CL_INVALID_PROPERTY); - CL_ERR_TO_STR(CL_INVALID_IMAGE_DESCRIPTOR); - CL_ERR_TO_STR(CL_INVALID_COMPILER_OPTIONS); - CL_ERR_TO_STR(CL_INVALID_LINKER_OPTIONS); - CL_ERR_TO_STR(CL_INVALID_DEVICE_PARTITION_COUNT); - case -69: return "CL_INVALID_PIPE_SIZE"; - case -70: return "CL_INVALID_DEVICE_QUEUE"; - case -71: return "CL_INVALID_SPEC_ID"; - case -72: return "CL_MAX_SIZE_RESTRICTION_EXCEEDED"; - case -1002: return "CL_INVALID_D3D10_DEVICE_KHR"; - case -1003: return "CL_INVALID_D3D10_RESOURCE_KHR"; - case -1004: return "CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR"; - case -1005: return "CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR"; - case -1006: return "CL_INVALID_D3D11_DEVICE_KHR"; - case -1007: return "CL_INVALID_D3D11_RESOURCE_KHR"; - case -1008: return "CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR"; - case -1009: return "CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR"; - case -1010: return "CL_INVALID_DX9_MEDIA_ADAPTER_KHR"; - case -1011: return "CL_INVALID_DX9_MEDIA_SURFACE_KHR"; - case -1012: return "CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR"; - case -1013: return "CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR"; - case -1093: return "CL_INVALID_EGL_OBJECT_KHR"; - case -1092: return "CL_EGL_RESOURCE_NOT_ACQUIRED_KHR"; - case -1001: return "CL_PLATFORM_NOT_FOUND_KHR"; - case -1057: return "CL_DEVICE_PARTITION_FAILED_EXT"; - case -1058: return "CL_INVALID_PARTITION_COUNT_EXT"; - case -1059: return "CL_INVALID_PARTITION_NAME_EXT"; - case -1094: return "CL_INVALID_ACCELERATOR_INTEL"; - case -1095: return "CL_INVALID_ACCELERATOR_TYPE_INTEL"; - case -1096: return "CL_INVALID_ACCELERATOR_DESCRIPTOR_INTEL"; - case -1097: return "CL_ACCELERATOR_TYPE_NOT_SUPPORTED_INTEL"; - case -1000: return "CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR"; - case -1098: return "CL_INVALID_VA_API_MEDIA_ADAPTER_INTEL"; - case -1099: return "CL_INVALID_VA_API_MEDIA_SURFACE_INTEL"; - case -1100: return "CL_VA_API_MEDIA_SURFACE_ALREADY_ACQUIRED_INTEL"; - case -1101: return "CL_VA_API_MEDIA_SURFACE_NOT_ACQUIRED_INTEL"; - default: return "CL_UNKNOWN_ERROR"; - } -} \ No newline at end of file diff --git a/sunnypilot/modeld/thneed/clutil_legacy.h b/sunnypilot/modeld/thneed/clutil_legacy.h deleted file mode 100644 index 8aebdcf1a9..0000000000 --- a/sunnypilot/modeld/thneed/clutil_legacy.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "common/clutil.h" -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include - -cl_program cl_program_from_binary(cl_context ctx, cl_device_id device_id, const uint8_t* binary, size_t length, const char* args = nullptr); -const char* cl_get_error_string(int err); \ No newline at end of file diff --git a/sunnypilot/modeld/thneed/serialize.cc b/sunnypilot/modeld/thneed/serialize.cc deleted file mode 100644 index 9279a90e0f..0000000000 --- a/sunnypilot/modeld/thneed/serialize.cc +++ /dev/null @@ -1,155 +0,0 @@ -#include -#include - -#include "third_party/json11/json11.hpp" -#include "common/util.h" -#include "common/clutil.h" -#include "common/swaglog.h" -#include "sunnypilot/modeld/thneed/thneed.h" -#include "sunnypilot/modeld/thneed/clutil_legacy.h" -using namespace json11; - -extern map g_program_source; - -void Thneed::load(const char *filename) { - LOGD("Thneed::load: loading from %s\n", filename); - - string buf = util::read_file(filename); - int jsz = *(int *)buf.data(); - string jsonerr; - string jj(buf.data() + sizeof(int), jsz); - Json jdat = Json::parse(jj, jsonerr); - - map real_mem; - real_mem[NULL] = NULL; - - int ptr = sizeof(int)+jsz; - for (auto &obj : jdat["objects"].array_items()) { - auto mobj = obj.object_items(); - int sz = mobj["size"].int_value(); - cl_mem clbuf = NULL; - - if (mobj["buffer_id"].string_value().size() > 0) { - // image buffer must already be allocated - clbuf = real_mem[*(cl_mem*)(mobj["buffer_id"].string_value().data())]; - assert(mobj["needs_load"].bool_value() == false); - } else { - if (mobj["needs_load"].bool_value()) { - clbuf = clCreateBuffer(context, CL_MEM_COPY_HOST_PTR | CL_MEM_READ_WRITE, sz, &buf[ptr], NULL); - if (debug >= 1) printf("loading %p %d @ 0x%X\n", clbuf, sz, ptr); - ptr += sz; - } else { - // TODO: is there a faster way to init zeroed out buffers? - void *host_zeros = calloc(sz, 1); - clbuf = clCreateBuffer(context, CL_MEM_COPY_HOST_PTR | CL_MEM_READ_WRITE, sz, host_zeros, NULL); - free(host_zeros); - } - } - assert(clbuf != NULL); - - if (mobj["arg_type"] == "image2d_t" || mobj["arg_type"] == "image1d_t") { - cl_image_desc desc = {0}; - desc.image_type = (mobj["arg_type"] == "image2d_t") ? CL_MEM_OBJECT_IMAGE2D : CL_MEM_OBJECT_IMAGE1D_BUFFER; - desc.image_width = mobj["width"].int_value(); - desc.image_height = mobj["height"].int_value(); - desc.image_row_pitch = mobj["row_pitch"].int_value(); - assert(sz == desc.image_height*desc.image_row_pitch); -#ifdef QCOM2 - desc.buffer = clbuf; -#else - // TODO: we are creating unused buffers on PC - clReleaseMemObject(clbuf); -#endif - cl_image_format format = {0}; - format.image_channel_order = CL_RGBA; - format.image_channel_data_type = mobj["float32"].bool_value() ? CL_FLOAT : CL_HALF_FLOAT; - - cl_int errcode; - -#ifndef QCOM2 - if (mobj["needs_load"].bool_value()) { - clbuf = clCreateImage(context, CL_MEM_COPY_HOST_PTR | CL_MEM_READ_WRITE, &format, &desc, &buf[ptr-sz], &errcode); - } else { - clbuf = clCreateImage(context, CL_MEM_READ_WRITE, &format, &desc, NULL, &errcode); - } -#else - clbuf = clCreateImage(context, CL_MEM_READ_WRITE, &format, &desc, NULL, &errcode); -#endif - if (clbuf == NULL) { - LOGE("clError: %s create image %zux%zu rp %zu with buffer %p\n", cl_get_error_string(errcode), - desc.image_width, desc.image_height, desc.image_row_pitch, desc.buffer); - } - assert(clbuf != NULL); - } - - real_mem[*(cl_mem*)(mobj["id"].string_value().data())] = clbuf; - } - - map g_programs; - for (const auto &[name, source] : jdat["programs"].object_items()) { - if (debug >= 1) printf("building %s with size %zu\n", name.c_str(), source.string_value().size()); - g_programs[name] = cl_program_from_source(context, device_id, source.string_value()); - } - - for (auto &obj : jdat["inputs"].array_items()) { - auto mobj = obj.object_items(); - int sz = mobj["size"].int_value(); - cl_mem aa = real_mem[*(cl_mem*)(mobj["buffer_id"].string_value().data())]; - input_clmem.push_back(aa); - input_sizes.push_back(sz); - LOGD("Thneed::load: adding input %s with size %d\n", mobj["name"].string_value().data(), sz); - - cl_int cl_err; - void *ret = clEnqueueMapBuffer(command_queue, aa, CL_TRUE, CL_MAP_WRITE, 0, sz, 0, NULL, NULL, &cl_err); - if (cl_err != CL_SUCCESS) LOGE("clError: %s map %p %d\n", cl_get_error_string(cl_err), aa, sz); - assert(cl_err == CL_SUCCESS); - inputs.push_back(ret); - } - - for (auto &obj : jdat["outputs"].array_items()) { - auto mobj = obj.object_items(); - int sz = mobj["size"].int_value(); - LOGD("Thneed::save: adding output with size %d\n", sz); - // TODO: support multiple outputs - output = real_mem[*(cl_mem*)(mobj["buffer_id"].string_value().data())]; - assert(output != NULL); - } - - for (auto &obj : jdat["binaries"].array_items()) { - string name = obj["name"].string_value(); - size_t length = obj["length"].int_value(); - if (debug >= 1) printf("binary %s with size %zu\n", name.c_str(), length); - g_programs[name] = cl_program_from_binary(context, device_id, (const uint8_t*)&buf[ptr], length); - ptr += length; - } - - for (auto &obj : jdat["kernels"].array_items()) { - auto gws = obj["global_work_size"]; - auto lws = obj["local_work_size"]; - auto kk = shared_ptr(new CLQueuedKernel(this)); - - kk->name = obj["name"].string_value(); - kk->program = g_programs[kk->name]; - kk->work_dim = obj["work_dim"].int_value(); - for (int i = 0; i < kk->work_dim; i++) { - kk->global_work_size[i] = gws[i].int_value(); - kk->local_work_size[i] = lws[i].int_value(); - } - kk->num_args = obj["num_args"].int_value(); - for (int i = 0; i < kk->num_args; i++) { - string arg = obj["args"].array_items()[i].string_value(); - int arg_size = obj["args_size"].array_items()[i].int_value(); - kk->args_size.push_back(arg_size); - if (arg_size == 8) { - cl_mem val = *(cl_mem*)(arg.data()); - val = real_mem[val]; - kk->args.push_back(string((char*)&val, sizeof(val))); - } else { - kk->args.push_back(arg); - } - } - kq.push_back(kk); - } - - clFinish(command_queue); -} diff --git a/sunnypilot/modeld/thneed/thneed.h b/sunnypilot/modeld/thneed/thneed.h deleted file mode 100644 index 47e18e0be3..0000000000 --- a/sunnypilot/modeld/thneed/thneed.h +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once - -#ifndef __user -#define __user __attribute__(()) -#endif - -#include -#include -#include -#include -#include - -#include - -#include "third_party/linux/include/msm_kgsl.h" - -using namespace std; - -cl_int thneed_clSetKernelArg(cl_kernel kernel, cl_uint arg_index, size_t arg_size, const void *arg_value); - -namespace json11 { - class Json; -} -class Thneed; - -class GPUMalloc { - public: - GPUMalloc(int size, int fd); - ~GPUMalloc(); - void *alloc(int size); - private: - uint64_t base; - int remaining; -}; - -class CLQueuedKernel { - public: - CLQueuedKernel(Thneed *lthneed) { thneed = lthneed; } - CLQueuedKernel(Thneed *lthneed, - cl_kernel _kernel, - cl_uint _work_dim, - const size_t *_global_work_size, - const size_t *_local_work_size); - cl_int exec(); - void debug_print(bool verbose); - int get_arg_num(const char *search_arg_name); - cl_program program; - string name; - cl_uint num_args; - vector arg_names; - vector arg_types; - vector args; - vector args_size; - cl_kernel kernel = NULL; - json11::Json to_json() const; - - cl_uint work_dim; - size_t global_work_size[3] = {0}; - size_t local_work_size[3] = {0}; - private: - Thneed *thneed; -}; - -class CachedIoctl { - public: - virtual void exec() {} -}; - -class CachedSync: public CachedIoctl { - public: - CachedSync(Thneed *lthneed, string ldata) { thneed = lthneed; data = ldata; } - void exec(); - private: - Thneed *thneed; - string data; -}; - -class CachedCommand: public CachedIoctl { - public: - CachedCommand(Thneed *lthneed, struct kgsl_gpu_command *cmd); - void exec(); - private: - void disassemble(int cmd_index); - struct kgsl_gpu_command cache; - unique_ptr cmds; - unique_ptr objs; - Thneed *thneed; - vector > kq; -}; - -class Thneed { - public: - Thneed(bool do_clinit=false, cl_context _context = NULL); - void stop(); - void execute(float **finputs, float *foutput, bool slow=false); - void wait(); - - vector input_clmem; - vector inputs; - vector input_sizes; - cl_mem output = NULL; - - cl_context context = NULL; - cl_command_queue command_queue; - cl_device_id device_id; - int context_id; - - // protected? - bool record = false; - int debug; - int timestamp; - -#ifdef QCOM2 - unique_ptr ram; - vector > cmds; - int fd; -#endif - - // all CL kernels - void copy_inputs(float **finputs, bool internal=false); - void copy_output(float *foutput); - cl_int clexec(); - vector > kq; - - // pending CL kernels - vector > ckq; - - // loading - void load(const char *filename); - private: - void clinit(); -}; - diff --git a/sunnypilot/modeld/thneed/thneed_common.cc b/sunnypilot/modeld/thneed/thneed_common.cc deleted file mode 100644 index 56fbe70d8f..0000000000 --- a/sunnypilot/modeld/thneed/thneed_common.cc +++ /dev/null @@ -1,216 +0,0 @@ -#include "sunnypilot/modeld/thneed/thneed.h" - -#include -#include -#include - -#include "common/clutil.h" -#include "common/timing.h" - -map, string> g_args; -map, int> g_args_size; -map g_program_source; - -void Thneed::stop() { - //printf("Thneed::stop: recorded %lu commands\n", cmds.size()); - record = false; -} - -void Thneed::clinit() { - device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); - if (context == NULL) context = CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); - //cl_command_queue_properties props[3] = {CL_QUEUE_PROPERTIES, CL_QUEUE_PROFILING_ENABLE, 0}; - cl_command_queue_properties props[3] = {CL_QUEUE_PROPERTIES, 0, 0}; - command_queue = CL_CHECK_ERR(clCreateCommandQueueWithProperties(context, device_id, props, &err)); - printf("Thneed::clinit done\n"); -} - -cl_int Thneed::clexec() { - if (debug >= 1) printf("Thneed::clexec: running %lu queued kernels\n", kq.size()); - for (auto &k : kq) { - if (record) ckq.push_back(k); - cl_int ret = k->exec(); - assert(ret == CL_SUCCESS); - } - return clFinish(command_queue); -} - -void Thneed::copy_inputs(float **finputs, bool internal) { - for (int idx = 0; idx < inputs.size(); ++idx) { - if (debug >= 1) printf("copying %lu -- %p -> %p (cl %p)\n", input_sizes[idx], finputs[idx], inputs[idx], input_clmem[idx]); - - if (internal) { - // if it's internal, using memcpy is fine since the buffer sync is cached in the ioctl layer - if (finputs[idx] != NULL) memcpy(inputs[idx], finputs[idx], input_sizes[idx]); - } else { - if (finputs[idx] != NULL) CL_CHECK(clEnqueueWriteBuffer(command_queue, input_clmem[idx], CL_TRUE, 0, input_sizes[idx], finputs[idx], 0, NULL, NULL)); - } - } -} - -void Thneed::copy_output(float *foutput) { - if (output != NULL) { - size_t sz; - clGetMemObjectInfo(output, CL_MEM_SIZE, sizeof(sz), &sz, NULL); - if (debug >= 1) printf("copying %lu for output %p -> %p\n", sz, output, foutput); - CL_CHECK(clEnqueueReadBuffer(command_queue, output, CL_TRUE, 0, sz, foutput, 0, NULL, NULL)); - } else { - printf("CAUTION: model output is NULL, does it have no outputs?\n"); - } -} - -// *********** CLQueuedKernel *********** - -CLQueuedKernel::CLQueuedKernel(Thneed *lthneed, - cl_kernel _kernel, - cl_uint _work_dim, - const size_t *_global_work_size, - const size_t *_local_work_size) { - thneed = lthneed; - kernel = _kernel; - work_dim = _work_dim; - assert(work_dim <= 3); - for (int i = 0; i < work_dim; i++) { - global_work_size[i] = _global_work_size[i]; - local_work_size[i] = _local_work_size[i]; - } - - char _name[0x100]; - clGetKernelInfo(kernel, CL_KERNEL_FUNCTION_NAME, sizeof(_name), _name, NULL); - name = string(_name); - clGetKernelInfo(kernel, CL_KERNEL_NUM_ARGS, sizeof(num_args), &num_args, NULL); - - // get args - for (int i = 0; i < num_args; i++) { - char arg_name[0x100] = {0}; - clGetKernelArgInfo(kernel, i, CL_KERNEL_ARG_NAME, sizeof(arg_name), arg_name, NULL); - arg_names.push_back(string(arg_name)); - clGetKernelArgInfo(kernel, i, CL_KERNEL_ARG_TYPE_NAME, sizeof(arg_name), arg_name, NULL); - arg_types.push_back(string(arg_name)); - - args.push_back(g_args[make_pair(kernel, i)]); - args_size.push_back(g_args_size[make_pair(kernel, i)]); - } - - // get program - clGetKernelInfo(kernel, CL_KERNEL_PROGRAM, sizeof(program), &program, NULL); -} - -int CLQueuedKernel::get_arg_num(const char *search_arg_name) { - for (int i = 0; i < num_args; i++) { - if (arg_names[i] == search_arg_name) return i; - } - printf("failed to find %s in %s\n", search_arg_name, name.c_str()); - assert(false); -} - -cl_int CLQueuedKernel::exec() { - if (kernel == NULL) { - kernel = clCreateKernel(program, name.c_str(), NULL); - arg_names.clear(); - arg_types.clear(); - - for (int j = 0; j < num_args; j++) { - char arg_name[0x100] = {0}; - clGetKernelArgInfo(kernel, j, CL_KERNEL_ARG_NAME, sizeof(arg_name), arg_name, NULL); - arg_names.push_back(string(arg_name)); - clGetKernelArgInfo(kernel, j, CL_KERNEL_ARG_TYPE_NAME, sizeof(arg_name), arg_name, NULL); - arg_types.push_back(string(arg_name)); - - cl_int ret; - if (args[j].size() != 0) { - assert(args[j].size() == args_size[j]); - ret = thneed_clSetKernelArg(kernel, j, args[j].size(), args[j].data()); - } else { - ret = thneed_clSetKernelArg(kernel, j, args_size[j], NULL); - } - assert(ret == CL_SUCCESS); - } - } - - if (thneed->debug >= 1) { - debug_print(thneed->debug >= 2); - } - - return clEnqueueNDRangeKernel(thneed->command_queue, - kernel, work_dim, NULL, global_work_size, local_work_size, 0, NULL, NULL); -} - -void CLQueuedKernel::debug_print(bool verbose) { - printf("%p %56s -- ", kernel, name.c_str()); - for (int i = 0; i < work_dim; i++) { - printf("%4zu ", global_work_size[i]); - } - printf(" -- "); - for (int i = 0; i < work_dim; i++) { - printf("%4zu ", local_work_size[i]); - } - printf("\n"); - - if (verbose) { - for (int i = 0; i < num_args; i++) { - string arg = args[i]; - printf(" %s %s", arg_types[i].c_str(), arg_names[i].c_str()); - void *arg_value = (void*)arg.data(); - int arg_size = arg.size(); - if (arg_size == 0) { - printf(" (size) %d", args_size[i]); - } else if (arg_size == 1) { - printf(" = %d", *((char*)arg_value)); - } else if (arg_size == 2) { - printf(" = %d", *((short*)arg_value)); - } else if (arg_size == 4) { - if (arg_types[i] == "float") { - printf(" = %f", *((float*)arg_value)); - } else { - printf(" = %d", *((int*)arg_value)); - } - } else if (arg_size == 8) { - cl_mem val = (cl_mem)(*((uintptr_t*)arg_value)); - printf(" = %p", val); - if (val != NULL) { - cl_mem_object_type obj_type; - clGetMemObjectInfo(val, CL_MEM_TYPE, sizeof(obj_type), &obj_type, NULL); - if (arg_types[i] == "image2d_t" || arg_types[i] == "image1d_t" || obj_type == CL_MEM_OBJECT_IMAGE2D) { - cl_image_format format; - size_t width, height, depth, array_size, row_pitch, slice_pitch; - cl_mem buf; - clGetImageInfo(val, CL_IMAGE_FORMAT, sizeof(format), &format, NULL); - assert(format.image_channel_order == CL_RGBA); - assert(format.image_channel_data_type == CL_HALF_FLOAT || format.image_channel_data_type == CL_FLOAT); - clGetImageInfo(val, CL_IMAGE_WIDTH, sizeof(width), &width, NULL); - clGetImageInfo(val, CL_IMAGE_HEIGHT, sizeof(height), &height, NULL); - clGetImageInfo(val, CL_IMAGE_ROW_PITCH, sizeof(row_pitch), &row_pitch, NULL); - clGetImageInfo(val, CL_IMAGE_DEPTH, sizeof(depth), &depth, NULL); - clGetImageInfo(val, CL_IMAGE_ARRAY_SIZE, sizeof(array_size), &array_size, NULL); - clGetImageInfo(val, CL_IMAGE_SLICE_PITCH, sizeof(slice_pitch), &slice_pitch, NULL); - assert(depth == 0); - assert(array_size == 0); - assert(slice_pitch == 0); - - clGetImageInfo(val, CL_IMAGE_BUFFER, sizeof(buf), &buf, NULL); - size_t sz = 0; - if (buf != NULL) clGetMemObjectInfo(buf, CL_MEM_SIZE, sizeof(sz), &sz, NULL); - printf(" image %zu x %zu rp %zu @ %p buffer %zu", width, height, row_pitch, buf, sz); - } else { - size_t sz; - clGetMemObjectInfo(val, CL_MEM_SIZE, sizeof(sz), &sz, NULL); - printf(" buffer %zu", sz); - } - } - } - printf("\n"); - } - } -} - -cl_int thneed_clSetKernelArg(cl_kernel kernel, cl_uint arg_index, size_t arg_size, const void *arg_value) { - g_args_size[make_pair(kernel, arg_index)] = arg_size; - if (arg_value != NULL) { - g_args[make_pair(kernel, arg_index)] = string((char*)arg_value, arg_size); - } else { - g_args[make_pair(kernel, arg_index)] = string(""); - } - cl_int ret = clSetKernelArg(kernel, arg_index, arg_size, arg_value); - return ret; -} diff --git a/sunnypilot/modeld/thneed/thneed_pc.cc b/sunnypilot/modeld/thneed/thneed_pc.cc deleted file mode 100644 index 629d0ee359..0000000000 --- a/sunnypilot/modeld/thneed/thneed_pc.cc +++ /dev/null @@ -1,32 +0,0 @@ -#include "sunnypilot/modeld/thneed/thneed.h" - -#include - -#include "common/clutil.h" -#include "common/timing.h" - -Thneed::Thneed(bool do_clinit, cl_context _context) { - context = _context; - if (do_clinit) clinit(); - char *thneed_debug_env = getenv("THNEED_DEBUG"); - debug = (thneed_debug_env != NULL) ? atoi(thneed_debug_env) : 0; -} - -void Thneed::execute(float **finputs, float *foutput, bool slow) { - uint64_t tb, te; - if (debug >= 1) tb = nanos_since_boot(); - - // ****** copy inputs - copy_inputs(finputs); - - // ****** run commands - clexec(); - - // ****** copy outputs - copy_output(foutput); - - if (debug >= 1) { - te = nanos_since_boot(); - printf("model exec in %lu us\n", (te-tb)/1000); - } -} diff --git a/sunnypilot/modeld/thneed/thneed_qcom2.cc b/sunnypilot/modeld/thneed/thneed_qcom2.cc deleted file mode 100644 index b940da1ce1..0000000000 --- a/sunnypilot/modeld/thneed/thneed_qcom2.cc +++ /dev/null @@ -1,258 +0,0 @@ -#include "sunnypilot/modeld/thneed/thneed.h" - -#include -#include - -#include -#include -#include -#include -#include - -#include "common/clutil.h" -#include "common/timing.h" - -Thneed *g_thneed = NULL; -int g_fd = -1; - -void hexdump(uint8_t *d, int len) { - assert((len%4) == 0); - printf(" dumping %p len 0x%x\n", d, len); - for (int i = 0; i < len/4; i++) { - if (i != 0 && (i%0x10) == 0) printf("\n"); - printf("%8x ", d[i]); - } - printf("\n"); -} - -// *********** ioctl interceptor *********** - -extern "C" { - -int (*my_ioctl)(int filedes, unsigned long request, void *argp) = NULL; -#undef ioctl -int ioctl(int filedes, unsigned long request, void *argp) { - request &= 0xFFFFFFFF; // needed on QCOM2 - if (my_ioctl == NULL) my_ioctl = reinterpret_cast(dlsym(RTLD_NEXT, "ioctl")); - Thneed *thneed = g_thneed; - - // save the fd - if (request == IOCTL_KGSL_GPUOBJ_ALLOC) g_fd = filedes; - - // note that this runs always, even without a thneed object - if (request == IOCTL_KGSL_DRAWCTXT_CREATE) { - struct kgsl_drawctxt_create *create = (struct kgsl_drawctxt_create *)argp; - create->flags &= ~KGSL_CONTEXT_PRIORITY_MASK; - create->flags |= 6 << KGSL_CONTEXT_PRIORITY_SHIFT; // priority from 1-15, 1 is max priority - printf("IOCTL_KGSL_DRAWCTXT_CREATE: creating context with flags 0x%x\n", create->flags); - } - - if (thneed != NULL) { - if (request == IOCTL_KGSL_GPU_COMMAND) { - struct kgsl_gpu_command *cmd = (struct kgsl_gpu_command *)argp; - if (thneed->record) { - thneed->timestamp = cmd->timestamp; - thneed->context_id = cmd->context_id; - thneed->cmds.push_back(unique_ptr(new CachedCommand(thneed, cmd))); - } - if (thneed->debug >= 1) { - printf("IOCTL_KGSL_GPU_COMMAND(%2zu): flags: 0x%lx context_id: %u timestamp: %u numcmds: %d numobjs: %d\n", - thneed->cmds.size(), - cmd->flags, - cmd->context_id, cmd->timestamp, cmd->numcmds, cmd->numobjs); - } - } else if (request == IOCTL_KGSL_GPUOBJ_SYNC) { - struct kgsl_gpuobj_sync *cmd = (struct kgsl_gpuobj_sync *)argp; - struct kgsl_gpuobj_sync_obj *objs = (struct kgsl_gpuobj_sync_obj *)(cmd->objs); - - if (thneed->debug >= 2) { - printf("IOCTL_KGSL_GPUOBJ_SYNC count:%d ", cmd->count); - for (int i = 0; i < cmd->count; i++) { - printf(" -- offset:0x%lx len:0x%lx id:%d op:%d ", objs[i].offset, objs[i].length, objs[i].id, objs[i].op); - } - printf("\n"); - } - - if (thneed->record) { - thneed->cmds.push_back(unique_ptr(new - CachedSync(thneed, string((char *)objs, sizeof(struct kgsl_gpuobj_sync_obj)*cmd->count)))); - } - } else if (request == IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID) { - struct kgsl_device_waittimestamp_ctxtid *cmd = (struct kgsl_device_waittimestamp_ctxtid *)argp; - if (thneed->debug >= 1) { - printf("IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID: context_id: %d timestamp: %d timeout: %d\n", - cmd->context_id, cmd->timestamp, cmd->timeout); - } - } else if (request == IOCTL_KGSL_SETPROPERTY) { - if (thneed->debug >= 1) { - struct kgsl_device_getproperty *prop = (struct kgsl_device_getproperty *)argp; - printf("IOCTL_KGSL_SETPROPERTY: 0x%x sizebytes:%zu\n", prop->type, prop->sizebytes); - if (thneed->debug >= 2) { - hexdump((uint8_t *)prop->value, prop->sizebytes); - if (prop->type == KGSL_PROP_PWR_CONSTRAINT) { - struct kgsl_device_constraint *constraint = (struct kgsl_device_constraint *)prop->value; - hexdump((uint8_t *)constraint->data, constraint->size); - } - } - } - } else if (request == IOCTL_KGSL_DRAWCTXT_CREATE || request == IOCTL_KGSL_DRAWCTXT_DESTROY) { - // this happens - } else if (request == IOCTL_KGSL_GPUOBJ_ALLOC || request == IOCTL_KGSL_GPUOBJ_FREE) { - // this happens - } else { - if (thneed->debug >= 1) { - printf("other ioctl %lx\n", request); - } - } - } - - int ret = my_ioctl(filedes, request, argp); - // NOTE: This error message goes into stdout and messes up pyenv - // if (ret != 0) printf("ioctl returned %d with errno %d\n", ret, errno); - return ret; -} - -} - -// *********** GPUMalloc *********** - -GPUMalloc::GPUMalloc(int size, int fd) { - struct kgsl_gpuobj_alloc alloc; - memset(&alloc, 0, sizeof(alloc)); - alloc.size = size; - alloc.flags = 0x10000a00; - ioctl(fd, IOCTL_KGSL_GPUOBJ_ALLOC, &alloc); - void *addr = mmap64(NULL, alloc.mmapsize, 0x3, 0x1, fd, alloc.id*0x1000); - assert(addr != MAP_FAILED); - - base = (uint64_t)addr; - remaining = size; -} - -GPUMalloc::~GPUMalloc() { - // TODO: free the GPU malloced area -} - -void *GPUMalloc::alloc(int size) { - void *ret = (void*)base; - size = (size+0xff) & (~0xFF); - assert(size <= remaining); - remaining -= size; - base += size; - return ret; -} - -// *********** CachedSync, at the ioctl layer *********** - -void CachedSync::exec() { - struct kgsl_gpuobj_sync cmd; - - cmd.objs = (uint64_t)data.data(); - cmd.obj_len = data.length(); - cmd.count = data.length() / sizeof(struct kgsl_gpuobj_sync_obj); - - int ret = ioctl(thneed->fd, IOCTL_KGSL_GPUOBJ_SYNC, &cmd); - assert(ret == 0); -} - -// *********** CachedCommand, at the ioctl layer *********** - -CachedCommand::CachedCommand(Thneed *lthneed, struct kgsl_gpu_command *cmd) { - thneed = lthneed; - assert(cmd->numsyncs == 0); - - memcpy(&cache, cmd, sizeof(cache)); - - if (cmd->numcmds > 0) { - cmds = make_unique(cmd->numcmds); - memcpy(cmds.get(), (void *)cmd->cmdlist, sizeof(struct kgsl_command_object)*cmd->numcmds); - cache.cmdlist = (uint64_t)cmds.get(); - for (int i = 0; i < cmd->numcmds; i++) { - void *nn = thneed->ram->alloc(cmds[i].size); - memcpy(nn, (void*)cmds[i].gpuaddr, cmds[i].size); - cmds[i].gpuaddr = (uint64_t)nn; - } - } - - if (cmd->numobjs > 0) { - objs = make_unique(cmd->numobjs); - memcpy(objs.get(), (void *)cmd->objlist, sizeof(struct kgsl_command_object)*cmd->numobjs); - cache.objlist = (uint64_t)objs.get(); - for (int i = 0; i < cmd->numobjs; i++) { - void *nn = thneed->ram->alloc(objs[i].size); - memset(nn, 0, objs[i].size); - objs[i].gpuaddr = (uint64_t)nn; - } - } - - kq = thneed->ckq; - thneed->ckq.clear(); -} - -void CachedCommand::exec() { - cache.timestamp = ++thneed->timestamp; - int ret = ioctl(thneed->fd, IOCTL_KGSL_GPU_COMMAND, &cache); - - if (thneed->debug >= 1) printf("CachedCommand::exec got %d\n", ret); - - if (thneed->debug >= 2) { - for (auto &it : kq) { - it->debug_print(false); - } - } - - assert(ret == 0); -} - -// *********** Thneed *********** - -Thneed::Thneed(bool do_clinit, cl_context _context) { - // TODO: QCOM2 actually requires a different context - //context = _context; - if (do_clinit) clinit(); - assert(g_fd != -1); - fd = g_fd; - ram = make_unique(0x80000, fd); - timestamp = -1; - g_thneed = this; - char *thneed_debug_env = getenv("THNEED_DEBUG"); - debug = (thneed_debug_env != NULL) ? atoi(thneed_debug_env) : 0; -} - -void Thneed::wait() { - struct kgsl_device_waittimestamp_ctxtid wait; - wait.context_id = context_id; - wait.timestamp = timestamp; - wait.timeout = -1; - - uint64_t tb = nanos_since_boot(); - int wret = ioctl(fd, IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID, &wait); - uint64_t te = nanos_since_boot(); - - if (debug >= 1) printf("wait %d after %lu us\n", wret, (te-tb)/1000); -} - -void Thneed::execute(float **finputs, float *foutput, bool slow) { - uint64_t tb, te; - if (debug >= 1) tb = nanos_since_boot(); - - // ****** copy inputs - copy_inputs(finputs, true); - - // ****** run commands - int i = 0; - for (auto &it : cmds) { - ++i; - if (debug >= 1) printf("run %2d @ %7lu us: ", i, (nanos_since_boot()-tb)/1000); - it->exec(); - if ((i == cmds.size()) || slow) wait(); - } - - // ****** copy outputs - copy_output(foutput); - - if (debug >= 1) { - te = nanos_since_boot(); - printf("model exec in %lu us\n", (te-tb)/1000); - } -} diff --git a/sunnypilot/modeld/transforms/loadyuv.cc b/sunnypilot/modeld/transforms/loadyuv.cc deleted file mode 100644 index f7d571416c..0000000000 --- a/sunnypilot/modeld/transforms/loadyuv.cc +++ /dev/null @@ -1,74 +0,0 @@ -#include "sunnypilot/modeld/transforms/loadyuv.h" - -#include -#include -#include - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { - memset(s, 0, sizeof(*s)); - - s->width = width; - s->height = height; - - char args[1024]; - snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " - "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", - width, height); - cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); - - s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); - s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); - s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); - - // done with this - CL_CHECK(clReleaseProgram(prg)); -} - -void loadyuv_destroy(LoadYUVState* s) { - CL_CHECK(clReleaseKernel(s->loadys_krnl)); - CL_CHECK(clReleaseKernel(s->loaduv_krnl)); - CL_CHECK(clReleaseKernel(s->copy_krnl)); -} - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl, bool do_shift) { - cl_int global_out_off = 0; - if (do_shift) { - // shift the image in slot 1 to slot 0, then place the new image in slot 1 - global_out_off += (s->width*s->height) + (s->width/2)*(s->height/2)*2; - CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_int), &global_out_off)); - const size_t copy_work_size = global_out_off/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, - ©_work_size, NULL, 0, 0, NULL)); - } - - CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); - - const size_t loadys_work_size = (s->width*s->height)/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, - &loadys_work_size, NULL, 0, 0, NULL)); - - const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; - global_out_off += (s->width*s->height); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); - - global_out_off += (s->width/2)*(s->height/2); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); -} diff --git a/sunnypilot/modeld/transforms/loadyuv.cl b/sunnypilot/modeld/transforms/loadyuv.cl deleted file mode 100644 index 7dd3d973a3..0000000000 --- a/sunnypilot/modeld/transforms/loadyuv.cl +++ /dev/null @@ -1,47 +0,0 @@ -#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) - -__kernel void loadys(__global uchar8 const * const Y, - __global float * out, - int out_offset) -{ - const int gid = get_global_id(0); - const int ois = gid * 8; - const int oy = ois / TRANSFORMED_WIDTH; - const int ox = ois % TRANSFORMED_WIDTH; - - const uchar8 ys = Y[gid]; - const float8 ysf = convert_float8(ys); - - // 02 - // 13 - - __global float* outy0; - __global float* outy1; - if ((oy & 1) == 0) { - outy0 = out + out_offset; //y0 - outy1 = out + out_offset + UV_SIZE*2; //y2 - } else { - outy0 = out + out_offset + UV_SIZE; //y1 - outy1 = out + out_offset + UV_SIZE*3; //y3 - } - - vstore4(ysf.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); - vstore4(ysf.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); -} - -__kernel void loaduv(__global uchar8 const * const in, - __global float8 * out, - int out_offset) -{ - const int gid = get_global_id(0); - const uchar8 inv = in[gid]; - const float8 outv = convert_float8(inv); - out[gid + out_offset / 8] = outv; -} - -__kernel void copy(__global float8 * inout, - int in_offset) -{ - const int gid = get_global_id(0); - inout[gid] = inout[gid + in_offset / 8]; -} diff --git a/sunnypilot/modeld/transforms/loadyuv.h b/sunnypilot/modeld/transforms/loadyuv.h deleted file mode 100644 index 7d27ef5d46..0000000000 --- a/sunnypilot/modeld/transforms/loadyuv.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "common/clutil.h" - -typedef struct { - int width, height; - cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; -} LoadYUVState; - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); - -void loadyuv_destroy(LoadYUVState* s); - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl, bool do_shift = false); diff --git a/sunnypilot/modeld/transforms/transform.cc b/sunnypilot/modeld/transforms/transform.cc deleted file mode 100644 index ebab670b53..0000000000 --- a/sunnypilot/modeld/transforms/transform.cc +++ /dev/null @@ -1,97 +0,0 @@ -#include "sunnypilot/modeld/transforms/transform.h" - -#include -#include - -#include "common/clutil.h" - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { - memset(s, 0, sizeof(*s)); - - cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); - s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); - // done with this - CL_CHECK(clReleaseProgram(prg)); - - s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); - s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); -} - -void transform_destroy(Transform* s) { - CL_CHECK(clReleaseMemObject(s->m_y_cl)); - CL_CHECK(clReleaseMemObject(s->m_uv_cl)); - CL_CHECK(clReleaseKernel(s->krnl)); -} - -void transform_queue(Transform* s, - cl_command_queue q, - cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection) { - const int zero = 0; - - // sampled using pixel center origin - // (because that's how fastcv and opencv does it) - - mat3 projection_y = projection; - - // in and out uv is half the size of y. - mat3 projection_uv = transform_scale_buffer(projection, 0.5); - - CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); - CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); - - const int in_y_width = in_width; - const int in_y_height = in_height; - const int in_y_px_stride = 1; - const int in_uv_width = in_width/2; - const int in_uv_height = in_height/2; - const int in_uv_px_stride = 2; - const int in_u_offset = in_uv_offset; - const int in_v_offset = in_uv_offset + 1; - - const int out_y_width = out_width; - const int out_y_height = out_height; - const int out_uv_width = out_width/2; - const int out_uv_height = out_height/2; - - CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src - CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M - - const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_y, NULL, 0, 0, NULL)); - - const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; - - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); -} diff --git a/sunnypilot/modeld/transforms/transform.cl b/sunnypilot/modeld/transforms/transform.cl deleted file mode 100644 index 2ca25920cd..0000000000 --- a/sunnypilot/modeld/transforms/transform.cl +++ /dev/null @@ -1,54 +0,0 @@ -#define INTER_BITS 5 -#define INTER_TAB_SIZE (1 << INTER_BITS) -#define INTER_SCALE 1.f / INTER_TAB_SIZE - -#define INTER_REMAP_COEF_BITS 15 -#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) - -__kernel void warpPerspective(__global const uchar * src, - int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, - __global uchar * dst, - int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, - __constant float * M) -{ - int dx = get_global_id(0); - int dy = get_global_id(1); - - if (dx < dst_cols && dy < dst_rows) - { - float X0 = M[0] * dx + M[1] * dy + M[2]; - float Y0 = M[3] * dx + M[4] * dy + M[5]; - float W = M[6] * dx + M[7] * dy + M[8]; - W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; - int X = rint(X0 * W), Y = rint(Y0 * W); - - int sx = convert_short_sat(X >> INTER_BITS); - int sy = convert_short_sat(Y >> INTER_BITS); - - short sx_clamp = clamp(sx, 0, src_cols - 1); - short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); - short sy_clamp = clamp(sy, 0, src_rows - 1); - short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); - int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - - short ay = (short)(Y & (INTER_TAB_SIZE - 1)); - short ax = (short)(X & (INTER_TAB_SIZE - 1)); - float taby = 1.f/INTER_TAB_SIZE*ay; - float tabx = 1.f/INTER_TAB_SIZE*ax; - - int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); - - int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); - int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); - - int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; - - uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); - dst[dst_index] = pix; - } -} diff --git a/sunnypilot/modeld/transforms/transform.h b/sunnypilot/modeld/transforms/transform.h deleted file mode 100644 index 771a7054b3..0000000000 --- a/sunnypilot/modeld/transforms/transform.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" - -typedef struct { - cl_kernel krnl; - cl_mem m_y_cl, m_uv_cl; -} Transform; - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); - -void transform_destroy(Transform* transform); - -void transform_queue(Transform* s, cl_command_queue q, - cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection); diff --git a/sunnypilot/modeld_v2/SConscript b/sunnypilot/modeld_v2/SConscript index 94033846b0..ddf889c0c0 100644 --- a/sunnypilot/modeld_v2/SConscript +++ b/sunnypilot/modeld_v2/SConscript @@ -1,34 +1,8 @@ import os import glob -Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') +Import('env', 'arch') lenv = env.Clone() -lenvCython = envCython.Clone() - -libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] -frameworks = [] - -common_src = [ - "models/commonmodel.cc", - "transforms/loadyuv.cc", - "transforms/transform.cc", -] - -# OpenCL is a framework on Mac -if arch == "Darwin": - frameworks += ['OpenCL'] -else: - libs += ['OpenCL'] - -# Set path definitions -for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): - for xenv in (lenv, lenvCython): - xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') - -# Compile cython -cython_libs = envCython["LIBS"] + libs -commonmodel_lib = lenv.Library('commonmodel', common_src) -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) if 'pycache' not in x] # Get model metadata @@ -47,20 +21,39 @@ if PC: if outputs: lenv.Command(outputs, inputs, cmd) +tg_flags = { + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU THREADS=0 HOME={os.path.expanduser("~")}', +}.get(arch, 'DEV=CPU CPU_LLVM=1 THREADS=0') + +image_flag = { + 'larch64': 'IMAGE=2', +}.get(arch, 'IMAGE=0') + def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath + out = fn + "_tinygrad.pkl" + return lenv.Command( - fn + "_tinygrad.pkl", + out, [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {out}' ) -# Compile small models +# Compile models for model_name in ['supercombo', 'driving_vision', 'driving_off_policy', 'driving_policy']: if File(f"models/{model_name}.onnx").exists(): - flags = { - 'larch64': 'DEV=QCOM', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")} IMAGE=0', # tinygrad calls brew which needs a $HOME in the env - }.get(arch, 'DEV=CPU CPU_LLVM=1 IMAGE=0') - tg_compile(flags, model_name) + tg_compile(tg_flags, model_name) + +script_files = [File("warp.py"), File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] +pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + ':' + env.Dir("#").abspath + '"' +compile_warp_cmd = f'{pythonpath_string} {tg_flags} python3 -m sunnypilot.modeld_v2.warp' + +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye +warp_targets = [] +for cam in [_ar_ox_fisheye, _os_fisheye]: + w, h = cam.width, cam.height + for bl in [2, 5]: + warp_targets.append(File(f"models/warp_{w}x{h}_b{bl}_tinygrad.pkl").abspath) +lenv.Command(warp_targets, tinygrad_files + script_files, compile_warp_cmd) diff --git a/sunnypilot/modeld_v2/get_model_metadata.py b/sunnypilot/modeld_v2/get_model_metadata.py index e0b5adc51b..838b1e9f40 100755 --- a/sunnypilot/modeld_v2/get_model_metadata.py +++ b/sunnypilot/modeld_v2/get_model_metadata.py @@ -1,36 +1,51 @@ #!/usr/bin/env python3 import sys import pathlib -import onnx import codecs import pickle from typing import Any +from tinygrad.nn.onnx import OnnxPBParser -def get_name_and_shape(value_info:onnx.ValueInfoProto) -> tuple[str, tuple[int,...]]: - shape = tuple([int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim]) - name = value_info.name + +class MetadataOnnxPBParser(OnnxPBParser): + def _parse_ModelProto(self) -> dict: + obj: dict[str, Any] = {"graph": {"input": [], "output": []}, "metadata_props": []} + for fid, wire_type in self._parse_message(self.reader.len): + match fid: + case 7: + obj["graph"] = self._parse_GraphProto() + case 14: + obj["metadata_props"].append(self._parse_StringStringEntryProto()) + case _: + self.reader.skip_field(wire_type) + return obj + + +def get_name_and_shape(value_info: dict[str, Any]) -> tuple[str, tuple[int, ...]]: + shape = tuple(int(dim) if isinstance(dim, int) else 0 for dim in value_info["parsed_type"].shape) + name = value_info["name"] return name, shape -def get_metadata_value_by_name(model:onnx.ModelProto, name:str) -> str | Any: - for prop in model.metadata_props: - if prop.key == name: - return prop.value +def get_metadata_value_by_name(model: dict[str, Any], name: str) -> str | Any: + for prop in model["metadata_props"]: + if prop["key"] == name: + return prop["value"] return None if __name__ == "__main__": model_path = pathlib.Path(sys.argv[1]) - model = onnx.load(str(model_path)) + model = MetadataOnnxPBParser(model_path).parse() output_slices = get_metadata_value_by_name(model, 'output_slices') assert output_slices is not None, 'output_slices not found in metadata' metadata = { 'model_checkpoint': get_metadata_value_by_name(model, 'model_checkpoint'), 'output_slices': pickle.loads(codecs.decode(output_slices.encode(), "base64")), - 'input_shapes': dict([get_name_and_shape(x) for x in model.graph.input]), - 'output_shapes': dict([get_name_and_shape(x) for x in model.graph.output]) + 'input_shapes': dict(get_name_and_shape(x) for x in model["graph"]["input"]), + 'output_shapes': dict(get_name_and_shape(x) for x in model["graph"]["output"]), } metadata_path = model_path.parent / (model_path.stem + '_metadata.pkl') diff --git a/sunnypilot/modeld_v2/install_models_pc.py b/sunnypilot/modeld_v2/install_models_pc.py index a378d90b11..d203de3487 100755 --- a/sunnypilot/modeld_v2/install_models_pc.py +++ b/sunnypilot/modeld_v2/install_models_pc.py @@ -3,41 +3,27 @@ import sys import shutil import pickle import codecs -import onnx from pathlib import Path from openpilot.system.hardware.hw import Paths - - -def get_name_and_shape(value_info): - shape = tuple([int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim]) - return value_info.name, shape - - -def get_metadata_value_by_name(model, name): - for prop in model.metadata_props: - if prop.key == name: - return prop.value - return None +from sunnypilot.modeld_v2.get_model_metadata import MetadataOnnxPBParser, get_name_and_shape, get_metadata_value_by_name def generate_metadata_pkl(model_path, output_path): try: - model = onnx.load(str(model_path)) + model = MetadataOnnxPBParser(model_path).parse() output_slices = get_metadata_value_by_name(model, 'output_slices') - - if output_slices: - metadata = { - 'model_checkpoint': get_metadata_value_by_name(model, 'model_checkpoint'), - 'output_slices': pickle.loads(codecs.decode(output_slices.encode(), "base64")), - 'input_shapes': dict([get_name_and_shape(x) for x in model.graph.input]), - 'output_shapes': dict([get_name_and_shape(x) for x in model.graph.output]) - } - with open(output_path, 'wb') as f: - pickle.dump(metadata, f) - return True - else: + if not output_slices: return False + metadata = { + 'model_checkpoint': get_metadata_value_by_name(model, 'model_checkpoint'), + 'output_slices': pickle.loads(codecs.decode(output_slices.encode(), "base64")), + 'input_shapes': dict(get_name_and_shape(x) for x in model["graph"]["input"]), + 'output_shapes': dict(get_name_and_shape(x) for x in model["graph"]["output"]), + } + with open(output_path, 'wb') as f: + pickle.dump(metadata, f) + return True except Exception: return False diff --git a/sunnypilot/modeld_v2/modeld.py b/sunnypilot/modeld_v2/modeld.py index be0724db0d..f862286181 100755 --- a/sunnypilot/modeld_v2/modeld.py +++ b/sunnypilot/modeld_v2/modeld.py @@ -1,4 +1,11 @@ #!/usr/bin/env python3 +import os +from openpilot.system.hardware import TICI +os.environ['DEV'] = 'QCOM' if TICI else 'CPU' +USBGPU = "USBGPU" in os.environ +if USBGPU: + os.environ['DEV'] = 'AMD' + os.environ['AMD_IFACE'] = 'USB' import time import numpy as np import cereal.messaging as messaging @@ -19,12 +26,12 @@ from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, from openpilot.sunnypilot.modeld_v2.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState, get_curvature_from_output from openpilot.sunnypilot.modeld_v2.constants import Plan -from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import DrivingModelFrame, CLContext +from openpilot.sunnypilot.modeld_v2.warp import Warp from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper from openpilot.sunnypilot.livedelay.helpers import get_lat_delay -from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase +from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase from openpilot.sunnypilot.models.helpers import get_active_bundle from openpilot.sunnypilot.models.runners.helpers import get_model_runner @@ -42,12 +49,12 @@ class FrameMeta: class ModelState(ModelStateBase): - frames: dict[str, DrivingModelFrame] + frames: dict[str, Warp] inputs: dict[str, np.ndarray] prev_desire: np.ndarray # for tracking the rising edge of the pulse temporal_idxs: slice | np.ndarray - def __init__(self, context: CLContext): + def __init__(self): ModelStateBase.__init__(self) try: self.model_runner = get_model_runner() @@ -66,17 +73,16 @@ class ModelState(ModelStateBase): self.PLANPLUS_CONTROL: float = 1.0 buffer_length = 5 if self.model_runner.is_20hz else 2 - self.frames = {name: DrivingModelFrame(context, buffer_length) for name in self.model_runner.vision_input_names} + self.warp = Warp(buffer_length) self.prev_desire = np.zeros(self.constants.DESIRE_LEN, dtype=np.float32) - - # img buffers are managed in openCL transform code self.numpy_inputs = {} self.temporal_buffers = {} self.temporal_idxs_map = {} for key, shape in self.model_runner.input_shapes.items(): - if key not in self.frames: # Managed by opencl + if key not in self.model_runner.vision_input_names: # Policy inputs self.numpy_inputs[key] = np.zeros(shape, dtype=np.float32) + # Temporal input: shape is [batch, history, features] if len(shape) == 3 and shape[1] > 1: buffer_history_len = shape[1] * 4 if shape[1] < 99 else shape[1] # Allow for higher history buffers in the future @@ -122,10 +128,10 @@ class ModelState(ModelStateBase): if key in inputs and key not in [self.desire_key]: self.numpy_inputs[key][:] = inputs[key] - imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.model_runner.vision_input_names} - - # Prepare inputs using the model runner - self.model_runner.prepare_inputs(imgs_cl, self.numpy_inputs, self.frames) + imgs_tensors = self.warp.process(bufs, transforms) + for name, tensor in imgs_tensors.items(): + self.model_runner.inputs[name] = tensor + self.model_runner.prepare_inputs(self.numpy_inputs) if prepare_only: return None @@ -140,12 +146,11 @@ class ModelState(ModelStateBase): if "desired_curvature" in outputs: input_name_prev = None - if "prev_desired_curvs" in self.numpy_inputs.keys(): - input_name_prev = 'prev_desired_curvs' - elif "prev_desired_curv" in self.numpy_inputs.keys(): + if "prev_desired_curv" in self.numpy_inputs.keys(): input_name_prev = 'prev_desired_curv' if input_name_prev and input_name_prev in self.temporal_buffers: self.process_desired_curvature(outputs, input_name_prev) + return outputs def process_desired_curvature(self, outputs, input_name_prev): @@ -158,14 +163,12 @@ class ModelState(ModelStateBase): def get_action_from_model(self, model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: plan = model_output['plan'][0] - if 'planplus' in model_output: - recovery_power = self.PLANPLUS_CONTROL * (0.75 if v_ego > 20.0 else 1.0) - plan = plan + recovery_power * model_output['planplus'][0] desired_accel, should_stop = get_accel_from_plan(plan[:, Plan.VELOCITY][:, 0], plan[:, Plan.ACCELERATION][:, 0], self.constants.T_IDXS, action_t=long_action_t) desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS) - desired_curvature = get_curvature_from_output(model_output, plan, v_ego, lat_action_t, self.mlsim) + curvature_plan = plan + (self.PLANPLUS_CONTROL - 1.0) * model_output['planplus'][0] if 'planplus' in model_output and self.PLANPLUS_CONTROL != 1.0 else plan + desired_curvature = get_curvature_from_output(model_output, curvature_plan, v_ego, lat_action_t, self.mlsim) if self.generation is not None and self.generation >= 10: # smooth curvature for post FOF models if v_ego > self.MIN_LAT_CONTROL_SPEED: desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS) @@ -183,10 +186,8 @@ def main(demo=False): setproctitle(PROCESS_NAME) config_realtime_process(7, 54) - cloudlog.warning("setting up CL context") - cl_context = CLContext() - cloudlog.warning("CL context ready; loading model") - model = ModelState(cl_context) + cloudlog.warning("loading model") + model = ModelState() cloudlog.warning("models loaded, modeld starting") # visionipc clients @@ -199,8 +200,8 @@ def main(demo=False): time.sleep(.1) vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") while not vipc_client_main.connect(False): diff --git a/sunnypilot/modeld/modeld_base.py b/sunnypilot/modeld_v2/modeld_base.py similarity index 100% rename from sunnypilot/modeld/modeld_base.py rename to sunnypilot/modeld_v2/modeld_base.py diff --git a/sunnypilot/modeld_v2/models/commonmodel.cc b/sunnypilot/modeld_v2/models/commonmodel.cc deleted file mode 100644 index 5cd3a84fcc..0000000000 --- a/sunnypilot/modeld_v2/models/commonmodel.cc +++ /dev/null @@ -1,62 +0,0 @@ -#include "sunnypilot/modeld_v2/models/commonmodel.h" - -#include -#include - -#include "common/clutil.h" - -DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, uint8_t buffer_length) : ModelFrame(device_id, context), buffer_length(buffer_length) { - input_frames = std::make_unique(buf_size); - input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buffer_length*frame_size_bytes, NULL, &err)); - region.origin = (buffer_length - 1) * frame_size_bytes; - region.size = frame_size_bytes; - last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); - // printf("Buffer length: %d, region origin: %lu, region size: %lu\n", buffer_length, region.origin, region.size); - - loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - - for (int i = 0; i < (buffer_length - 1); i++) { - CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); - } - loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); - - copy_queue(&loadyuv, q, img_buffer_20hz_cl, input_frames_cl, 0, 0, frame_size_bytes); - copy_queue(&loadyuv, q, last_img_cl, input_frames_cl, 0, frame_size_bytes, frame_size_bytes); - - // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. - clFinish(q); - return &input_frames_cl; -} - -DrivingModelFrame::~DrivingModelFrame() { - deinit_transform(); - loadyuv_destroy(&loadyuv); - CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl)); - CL_CHECK(clReleaseMemObject(last_img_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} - - -MonitoringModelFrame::MonitoringModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - input_frame_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - clFinish(q); - return &y_cl; -} - -MonitoringModelFrame::~MonitoringModelFrame() { - deinit_transform(); - CL_CHECK(clReleaseCommandQueue(q)); -} diff --git a/sunnypilot/modeld_v2/models/commonmodel.h b/sunnypilot/modeld_v2/models/commonmodel.h deleted file mode 100644 index 8203e064e0..0000000000 --- a/sunnypilot/modeld_v2/models/commonmodel.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" -#include "sunnypilot/modeld_v2/transforms/loadyuv.h" -#include "sunnypilot/modeld_v2/transforms/transform.h" - -class ModelFrame { -public: - ModelFrame(cl_device_id device_id, cl_context context) { - q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); - } - virtual ~ModelFrame() {} - virtual cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { return NULL; } - uint8_t* buffer_from_cl(cl_mem *in_frames, int buffer_size) { - CL_CHECK(clEnqueueReadBuffer(q, *in_frames, CL_TRUE, 0, buffer_size, input_frames.get(), 0, nullptr, nullptr)); - clFinish(q); - return &input_frames[0]; - } - - int MODEL_WIDTH; - int MODEL_HEIGHT; - int MODEL_FRAME_SIZE; - int buf_size; - -protected: - cl_mem y_cl, u_cl, v_cl; - Transform transform; - cl_command_queue q; - std::unique_ptr input_frames; - - void init_transform(cl_device_id device_id, cl_context context, int model_width, int model_height) { - y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, model_width * model_height, NULL, &err)); - u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - transform_init(&transform, context, device_id); - } - - void deinit_transform() { - transform_destroy(&transform); - CL_CHECK(clReleaseMemObject(v_cl)); - CL_CHECK(clReleaseMemObject(u_cl)); - CL_CHECK(clReleaseMemObject(y_cl)); - } - - void run_transform(cl_mem yuv_cl, int model_width, int model_height, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - transform_queue(&transform, q, - yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, - y_cl, u_cl, v_cl, model_width, model_height, projection); - } -}; - -class DrivingModelFrame : public ModelFrame { -public: - DrivingModelFrame(cl_device_id device_id, cl_context context, uint8_t buffer_length); - ~DrivingModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 512; - const int MODEL_HEIGHT = 256; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const int buf_size = MODEL_FRAME_SIZE * 2; - const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); - const uint8_t buffer_length; - -private: - LoadYUVState loadyuv; - cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; - cl_buffer_region region; -}; - -class MonitoringModelFrame : public ModelFrame { -public: - MonitoringModelFrame(cl_device_id device_id, cl_context context); - ~MonitoringModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 1440; - const int MODEL_HEIGHT = 960; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT; - const int buf_size = MODEL_FRAME_SIZE; - -private: - cl_mem input_frame_cl; -}; diff --git a/sunnypilot/modeld_v2/models/commonmodel.pxd b/sunnypilot/modeld_v2/models/commonmodel.pxd deleted file mode 100644 index 55023ac4b9..0000000000 --- a/sunnypilot/modeld_v2/models/commonmodel.pxd +++ /dev/null @@ -1,27 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem - -cdef extern from "common/mat.h": - cdef struct mat3: - float v[9] - -cdef extern from "common/clutil.h": - cdef unsigned long CL_DEVICE_TYPE_DEFAULT - cl_device_id cl_get_device_id(unsigned long) - cl_context cl_create_context(cl_device_id) - void cl_release_context(cl_context) - -cdef extern from "sunnypilot/modeld_v2/models/commonmodel.h": - cppclass ModelFrame: - int buf_size - unsigned char * buffer_from_cl(cl_mem*, int); - cl_mem * prepare(cl_mem, int, int, int, int, mat3) - - cppclass DrivingModelFrame: - int buf_size - DrivingModelFrame(cl_device_id, cl_context, unsigned char) - - cppclass MonitoringModelFrame: - int buf_size - MonitoringModelFrame(cl_device_id, cl_context) diff --git a/sunnypilot/modeld_v2/models/commonmodel_pyx.pxd b/sunnypilot/modeld_v2/models/commonmodel_pyx.pxd deleted file mode 100644 index 0bb798625b..0000000000 --- a/sunnypilot/modeld_v2/models/commonmodel_pyx.pxd +++ /dev/null @@ -1,13 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext - -cdef class CLContext(BaseCLContext): - pass - -cdef class CLMem: - cdef cl_mem * mem - - @staticmethod - cdef create(void*) diff --git a/sunnypilot/modeld_v2/models/commonmodel_pyx.pyx b/sunnypilot/modeld_v2/models/commonmodel_pyx.pyx deleted file mode 100644 index 78a891f031..0000000000 --- a/sunnypilot/modeld_v2/models/commonmodel_pyx.pyx +++ /dev/null @@ -1,74 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii, language_level=3 - -import numpy as np -cimport numpy as cnp -from libc.string cimport memcpy -from libc.stdint cimport uintptr_t, uint8_t - -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, cl_release_context -from .commonmodel cimport mat3, ModelFrame as cppModelFrame, DrivingModelFrame as cppDrivingModelFrame, MonitoringModelFrame as cppMonitoringModelFrame - - -cdef class CLContext(BaseCLContext): - def __cinit__(self): - self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) - self.context = cl_create_context(self.device_id) - - def __dealloc__(self): - if self.context: - cl_release_context(self.context) - -cdef class CLMem: - @staticmethod - cdef create(void * cmem): - mem = CLMem() - mem.mem = cmem - return mem - - @property - def mem_address(self): - return (self.mem) - -def cl_from_visionbuf(VisionBuf buf): - return CLMem.create(&buf.buf.buf_cl) - - -cdef class ModelFrame: - cdef cppModelFrame * frame - cdef int buf_size - - def __dealloc__(self): - del self.frame - - def prepare(self, VisionBuf buf, float[:] projection): - cdef mat3 cprojection - memcpy(cprojection.v, &projection[0], 9*sizeof(float)) - cdef cl_mem * data - data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection) - return CLMem.create(data) - - def buffer_from_cl(self, CLMem in_frames): - cdef unsigned char * data2 - data2 = self.frame.buffer_from_cl(in_frames.mem, self.buf_size) - return np.asarray( data2) - - -cdef class DrivingModelFrame(ModelFrame): - cdef cppDrivingModelFrame * _frame - - def __cinit__(self, CLContext context, int buffer_length=2): - self._frame = new cppDrivingModelFrame(context.device_id, context.context, buffer_length) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - -cdef class MonitoringModelFrame(ModelFrame): - cdef cppMonitoringModelFrame * _frame - - def __cinit__(self, CLContext context): - self._frame = new cppMonitoringModelFrame(context.device_id, context.context) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - diff --git a/sunnypilot/modeld_v2/parse_model_outputs_split.py b/sunnypilot/modeld_v2/parse_model_outputs_split.py index a099facd15..831649e3c1 100644 --- a/sunnypilot/modeld_v2/parse_model_outputs_split.py +++ b/sunnypilot/modeld_v2/parse_model_outputs_split.py @@ -108,8 +108,8 @@ class Parser: plan_in_N, plan_out_N = (SplitModelConstants.PLAN_MHP_N, SplitModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0) self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH)) - if 'planplus' in outs: - self.parse_mdn('planplus', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH)) + if 'planplus' in outs: + self.parse_mdn('planplus', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH)) def split_outputs(self, outs: dict[str, np.ndarray]) -> None: if 'desired_curvature' in outs: diff --git a/sunnypilot/modeld_v2/runners/ort_helpers.py b/sunnypilot/modeld_v2/runners/ort_helpers.py deleted file mode 100644 index 26afb03562..0000000000 --- a/sunnypilot/modeld_v2/runners/ort_helpers.py +++ /dev/null @@ -1,36 +0,0 @@ -import onnx -import onnxruntime as ort -import numpy as np -import itertools - -ORT_TYPES_TO_NP_TYPES = {'tensor(float16)': np.float16, 'tensor(float)': np.float32, 'tensor(uint8)': np.uint8} - -def attributeproto_fp16_to_fp32(attr): - float32_list = np.frombuffer(attr.raw_data, dtype=np.float16) - attr.data_type = 1 - attr.raw_data = float32_list.astype(np.float32).tobytes() - -def convert_fp16_to_fp32(model): - for i in model.graph.initializer: - if i.data_type == 10: - attributeproto_fp16_to_fp32(i) - for i in itertools.chain(model.graph.input, model.graph.output): - if i.type.tensor_type.elem_type == 10: - i.type.tensor_type.elem_type = 1 - for i in model.graph.node: - if i.op_type == 'Cast' and i.attribute[0].i == 10: - i.attribute[0].i = 1 - for a in i.attribute: - if hasattr(a, 't'): - if a.t.data_type == 10: - attributeproto_fp16_to_fp32(a.t) - return model.SerializeToString() - - -def make_onnx_cpu_runner(model_path): - options = ort.SessionOptions() - options.intra_op_num_threads = 4 - options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL - options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL - model_data = convert_fp16_to_fp32(onnx.load(model_path)) - return ort.InferenceSession(model_data, options, providers=['CPUExecutionProvider']) diff --git a/sunnypilot/modeld_v2/runners/tinygrad_helpers.py b/sunnypilot/modeld_v2/runners/tinygrad_helpers.py deleted file mode 100644 index 776381341c..0000000000 --- a/sunnypilot/modeld_v2/runners/tinygrad_helpers.py +++ /dev/null @@ -1,8 +0,0 @@ - -from tinygrad.tensor import Tensor -from tinygrad.helpers import to_mv - -def qcom_tensor_from_opencl_address(opencl_address, shape, dtype): - cl_buf_desc_ptr = to_mv(opencl_address, 8).cast('Q')[0] - rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer. - return Tensor.from_blob(rawbuf_ptr, shape, dtype=dtype, device='QCOM') diff --git a/sunnypilot/modeld_v2/tests/test_buffer_logic_inspect.py b/sunnypilot/modeld_v2/tests/test_buffer_logic_inspect.py index f664db31b3..15009c94d9 100644 --- a/sunnypilot/modeld_v2/tests/test_buffer_logic_inspect.py +++ b/sunnypilot/modeld_v2/tests/test_buffer_logic_inspect.py @@ -53,7 +53,7 @@ class DummyModelRunner: self.is_20hz = False # Minimal prepare/run methods so ModelState can be run without actually running the model - def prepare_inputs(self, imgs_cl, numpy_inputs, frames): + def prepare_inputs(self, numpy_inputs): return None def run_model(self): @@ -105,7 +105,7 @@ def get_expected_indices(shape, constants, mode, key=None): @pytest.mark.parametrize("shapes,mode", SHAPE_MODE_PARAMS, indirect=["shapes"]) def test_buffer_shapes_and_indices(shapes, mode, apply_patches): - state = ModelState(None) + state = ModelState() constants = DummyModelRunner(shapes).constants for key in shapes: buf = state.temporal_buffers.get(key, None) @@ -236,7 +236,7 @@ def dynamic_buffer_update(state, key, new_val, mode): @pytest.mark.parametrize("shapes,mode", SHAPE_MODE_PARAMS, indirect=["shapes"]) @pytest.mark.parametrize("key", ["desire", "features_buffer", "prev_desired_curv"]) def test_buffer_update_equivalence(shapes, mode, key, apply_patches): - state = ModelState(None) + state = ModelState() if key == "desire": desire_keys = [k for k in shapes.keys() if k.startswith('desire')] if desire_keys: diff --git a/sunnypilot/modeld_v2/tests/test_recovery_power.py b/sunnypilot/modeld_v2/tests/test_recovery_power.py index e38b2c86dc..cfa2272386 100644 --- a/sunnypilot/modeld_v2/tests/test_recovery_power.py +++ b/sunnypilot/modeld_v2/tests/test_recovery_power.py @@ -15,7 +15,7 @@ class MockStruct: def test_recovery_power_scaling(): state = MockStruct( - PLANPLUS_CONTROL=1.0, + PLANPLUS_CONTROL=0.75, LONG_SMOOTH_SECONDS=0.3, LAT_SMOOTH_SECONDS=0.1, MIN_LAT_CONTROL_SPEED=0.3, @@ -25,37 +25,46 @@ def test_recovery_power_scaling(): ) prev_action = log.ModelDataV2.Action() recorded_vel: list = [] + recorded_curv_plans: list = [] def mock_accel(plan_vel, plan_accel, t_idxs, action_t=0.0): recorded_vel.append(plan_vel.copy()) return 0.0, False + def mock_curvature(output, plan, vego, lat_action_t, mlsim): + recorded_curv_plans.append(plan.copy()) + return 0.0 + modeld.get_accel_from_plan = mock_accel - modeld.get_curvature_from_output = lambda *args: 0.0 + modeld.get_curvature_from_output = mock_curvature plan = np.random.rand(1, 100, 15).astype(np.float32) planplus = np.random.rand(1, 100, 15).astype(np.float32) + merged_plan = plan + planplus model_output: dict = { - 'plan': plan.copy(), + 'plan': merged_plan.copy(), 'planplus': planplus.copy() } test_cases: list = [ - # (control, v_ego, expected_factor) - (0.55, 20.0, 1.0), - (1.0, 25.0, .75), - (1.5, 25.1, 0.75), - (2.0, 20.0, 1.0), - (0.75, 19.0, 1.0), - (0.8, 25.1, 0.75), + # (control, v_ego) + (0.55, 20.0), + (1.0, 25.0), + (1.5, 25.1), + (2.0, 20.0), + (0.75, 19.0), + (0.8, 25.1), ] - for control, v_ego, factor in test_cases: + for control, v_ego in test_cases: state.PLANPLUS_CONTROL = control recorded_vel.clear() + recorded_curv_plans.clear() ModelState.get_action_from_model(state, model_output, prev_action, 0.0, 0.0, v_ego) - expected_recovery_power = control * factor - expected_plan_vel = plan[0, :, Plan.VELOCITY][:, 0] + expected_recovery_power * planplus[0, :, Plan.VELOCITY][:, 0] + expected_accel_plan_vel = plan[0, :, Plan.VELOCITY][:, 0] + planplus[0, :, Plan.VELOCITY][:, 0] + np.testing.assert_allclose(recorded_vel[0], expected_accel_plan_vel, rtol=1e-5, atol=1e-6) - np.testing.assert_allclose(recorded_vel[0], expected_plan_vel, rtol=1e-5, atol=1e-6) + # For the below, yes, I know this isn't the same slicing as fillmodlmsg. This is to show that the values are only scaled on curv + expected_curv_plan_vel = plan[0, :, Plan.VELOCITY][:, 0] + control * planplus[0, :, Plan.VELOCITY][:, 0] + np.testing.assert_allclose(recorded_curv_plans[0][:, Plan.VELOCITY][:, 0], expected_curv_plan_vel, rtol=1e-5, atol=1e-6) diff --git a/sunnypilot/modeld_v2/tests/test_warp.py b/sunnypilot/modeld_v2/tests/test_warp.py new file mode 100644 index 0000000000..daf0dd528c --- /dev/null +++ b/sunnypilot/modeld_v2/tests/test_warp.py @@ -0,0 +1,102 @@ +import os +os.environ['DEV'] = 'CPU' +import pytest +import numpy as np +from openpilot.selfdrive.modeld.compile_warp import get_nv12_info, CAMERA_CONFIGS +from openpilot.sunnypilot.modeld_v2.warp import Warp, MODEL_W, MODEL_H + +VISION_NAME_PAIRS = [ # needed to account for supercombos input_imgs + ('img', 'big_img'), + ('input_imgs', 'big_input_imgs'), +] + + +class MockVisionBuf: + def __init__(self, w, h): + self.width = w + self.height = h + _, _, _, yuv_size = get_nv12_info(w, h) + self.data = np.zeros(yuv_size, dtype=np.uint8) + + +@pytest.mark.parametrize("buffer_length", [2, 5]) +def test_warp_initialization(buffer_length): + warp = Warp(buffer_length) + assert warp.buffer_length == buffer_length + assert warp.img_buffer_shape == (buffer_length * 6, MODEL_H // 2, MODEL_W // 2) + + +@pytest.mark.parametrize("buffer_length", [2, 5]) +@pytest.mark.parametrize("cam_w, cam_h", CAMERA_CONFIGS) +@pytest.mark.parametrize("road, wide", VISION_NAME_PAIRS) +def test_warp_process(buffer_length, cam_w, cam_h, road, wide): + warp = Warp(buffer_length) + mock_buf = MockVisionBuf(cam_w, cam_h) + transform = np.eye(3, dtype=np.float32).flatten() + bufs = {road: mock_buf, wide: mock_buf} + transforms = {road: transform, wide: transform} + + out = warp.process(bufs, transforms) + assert isinstance(out, dict) + assert road in out and wide in out + assert out[road].shape == (1, 12, MODEL_H // 2, MODEL_W // 2) + assert out[wide].shape == (1, 12, MODEL_H // 2, MODEL_W // 2) + + key = (cam_w, cam_h) + assert key in warp.jit_cache + + out2 = warp.process(bufs, transforms) + assert out2[road].shape == out[road].shape + + +@pytest.mark.parametrize("road, wide", VISION_NAME_PAIRS) +def test_warp_buffer_shift(road, wide): + warp = Warp(2) + cam_w, cam_h = CAMERA_CONFIGS[1] + transform = np.eye(3, dtype=np.float32).flatten() + + buf1 = MockVisionBuf(cam_w, cam_h) + buf1.data[0] = 255 + bufs1 = {road: buf1, wide: buf1} + transforms = {road: transform, wide: transform} + out1 = warp.process(bufs1, transforms) + road1 = out1[road].numpy().copy() + + buf2 = MockVisionBuf(cam_w, cam_h) + buf2.data[0] = 128 + bufs2 = {road: buf2, wide: buf2} + out2 = warp.process(bufs2, transforms) + assert not np.array_equal(road1, out2[road].numpy()) + + +@pytest.mark.parametrize("buffer_length", [2, 5]) +@pytest.mark.parametrize("road, wide", VISION_NAME_PAIRS) +def test_warp_buffer_accumulation(buffer_length, road, wide): + warp = Warp(buffer_length) + cam_w, cam_h = CAMERA_CONFIGS[0] + transform = np.eye(3, dtype=np.float32).flatten() + transforms = {road: transform, wide: transform} + outputs = [] + + for i in range(buffer_length + 1): + buf = MockVisionBuf(cam_w, cam_h) + buf.data[:] = i * 10 + out = warp.process({road: buf, wide: buf}, transforms) + outputs.append(out[road].numpy().copy()) + + assert warp.full_buffers['img'].shape == (buffer_length * 6, MODEL_H // 2, MODEL_W // 2) + for i in range(1, len(outputs)): + assert not np.array_equal(outputs[i - 1], outputs[i]) + + +def test_warp_different_cameras_same_instance(): + warp = Warp(2) + transform = np.eye(3, dtype=np.float32).flatten() + + buf1 = MockVisionBuf(*CAMERA_CONFIGS[0]) + warp.process({'img': buf1, 'big_img': buf1}, {'img': transform, 'big_img': transform}) + assert len(warp.jit_cache) == 1 + + buf2 = MockVisionBuf(*CAMERA_CONFIGS[1]) + warp.process({'img': buf2, 'big_img': buf2}, {'img': transform, 'big_img': transform}) + assert len(warp.jit_cache) == 2 diff --git a/sunnypilot/modeld_v2/transforms/loadyuv.cc b/sunnypilot/modeld_v2/transforms/loadyuv.cc deleted file mode 100644 index eb669a5929..0000000000 --- a/sunnypilot/modeld_v2/transforms/loadyuv.cc +++ /dev/null @@ -1,76 +0,0 @@ -#include "sunnypilot/modeld_v2/transforms/loadyuv.h" - -#include -#include -#include - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { - memset(s, 0, sizeof(*s)); - - s->width = width; - s->height = height; - - char args[1024]; - snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " - "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", - width, height); - cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); - - s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); - s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); - s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); - - // done with this - CL_CHECK(clReleaseProgram(prg)); -} - -void loadyuv_destroy(LoadYUVState* s) { - CL_CHECK(clReleaseKernel(s->loadys_krnl)); - CL_CHECK(clReleaseKernel(s->loaduv_krnl)); - CL_CHECK(clReleaseKernel(s->copy_krnl)); -} - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl) { - cl_int global_out_off = 0; - - CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); - - const size_t loadys_work_size = (s->width*s->height)/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, - &loadys_work_size, NULL, 0, 0, NULL)); - - const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; - global_out_off += (s->width*s->height); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); - - global_out_off += (s->width/2)*(s->height/2); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); -} - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size) { - CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset)); - const size_t copy_work_size = size/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, - ©_work_size, NULL, 0, 0, NULL)); -} \ No newline at end of file diff --git a/sunnypilot/modeld_v2/transforms/loadyuv.cl b/sunnypilot/modeld_v2/transforms/loadyuv.cl deleted file mode 100644 index 970187a6d7..0000000000 --- a/sunnypilot/modeld_v2/transforms/loadyuv.cl +++ /dev/null @@ -1,47 +0,0 @@ -#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) - -__kernel void loadys(__global uchar8 const * const Y, - __global uchar * out, - int out_offset) -{ - const int gid = get_global_id(0); - const int ois = gid * 8; - const int oy = ois / TRANSFORMED_WIDTH; - const int ox = ois % TRANSFORMED_WIDTH; - - const uchar8 ys = Y[gid]; - - // 02 - // 13 - - __global uchar* outy0; - __global uchar* outy1; - if ((oy & 1) == 0) { - outy0 = out + out_offset; //y0 - outy1 = out + out_offset + UV_SIZE*2; //y2 - } else { - outy0 = out + out_offset + UV_SIZE; //y1 - outy1 = out + out_offset + UV_SIZE*3; //y3 - } - - vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); - vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); -} - -__kernel void loaduv(__global uchar8 const * const in, - __global uchar8 * out, - int out_offset) -{ - const int gid = get_global_id(0); - const uchar8 inv = in[gid]; - out[gid + out_offset / 8] = inv; -} - -__kernel void copy(__global uchar8 * in, - __global uchar8 * out, - int in_offset, - int out_offset) -{ - const int gid = get_global_id(0); - out[gid + out_offset / 8] = in[gid + in_offset / 8]; -} diff --git a/sunnypilot/modeld_v2/transforms/loadyuv.h b/sunnypilot/modeld_v2/transforms/loadyuv.h deleted file mode 100644 index 659059cd25..0000000000 --- a/sunnypilot/modeld_v2/transforms/loadyuv.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "common/clutil.h" - -typedef struct { - int width, height; - cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; -} LoadYUVState; - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); - -void loadyuv_destroy(LoadYUVState* s); - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl); - - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size); \ No newline at end of file diff --git a/sunnypilot/modeld_v2/transforms/transform.cc b/sunnypilot/modeld_v2/transforms/transform.cc deleted file mode 100644 index adc9bcebf4..0000000000 --- a/sunnypilot/modeld_v2/transforms/transform.cc +++ /dev/null @@ -1,97 +0,0 @@ -#include "sunnypilot/modeld_v2/transforms/transform.h" - -#include -#include - -#include "common/clutil.h" - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { - memset(s, 0, sizeof(*s)); - - cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); - s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); - // done with this - CL_CHECK(clReleaseProgram(prg)); - - s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); - s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); -} - -void transform_destroy(Transform* s) { - CL_CHECK(clReleaseMemObject(s->m_y_cl)); - CL_CHECK(clReleaseMemObject(s->m_uv_cl)); - CL_CHECK(clReleaseKernel(s->krnl)); -} - -void transform_queue(Transform* s, - cl_command_queue q, - cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection) { - const int zero = 0; - - // sampled using pixel center origin - // (because that's how fastcv and opencv does it) - - mat3 projection_y = projection; - - // in and out uv is half the size of y. - mat3 projection_uv = transform_scale_buffer(projection, 0.5); - - CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); - CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); - - const int in_y_width = in_width; - const int in_y_height = in_height; - const int in_y_px_stride = 1; - const int in_uv_width = in_width/2; - const int in_uv_height = in_height/2; - const int in_uv_px_stride = 2; - const int in_u_offset = in_uv_offset; - const int in_v_offset = in_uv_offset + 1; - - const int out_y_width = out_width; - const int out_y_height = out_height; - const int out_uv_width = out_width/2; - const int out_uv_height = out_height/2; - - CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src - CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M - - const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_y, NULL, 0, 0, NULL)); - - const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; - - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); -} diff --git a/sunnypilot/modeld_v2/transforms/transform.cl b/sunnypilot/modeld_v2/transforms/transform.cl deleted file mode 100644 index 2ca25920cd..0000000000 --- a/sunnypilot/modeld_v2/transforms/transform.cl +++ /dev/null @@ -1,54 +0,0 @@ -#define INTER_BITS 5 -#define INTER_TAB_SIZE (1 << INTER_BITS) -#define INTER_SCALE 1.f / INTER_TAB_SIZE - -#define INTER_REMAP_COEF_BITS 15 -#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) - -__kernel void warpPerspective(__global const uchar * src, - int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, - __global uchar * dst, - int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, - __constant float * M) -{ - int dx = get_global_id(0); - int dy = get_global_id(1); - - if (dx < dst_cols && dy < dst_rows) - { - float X0 = M[0] * dx + M[1] * dy + M[2]; - float Y0 = M[3] * dx + M[4] * dy + M[5]; - float W = M[6] * dx + M[7] * dy + M[8]; - W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; - int X = rint(X0 * W), Y = rint(Y0 * W); - - int sx = convert_short_sat(X >> INTER_BITS); - int sy = convert_short_sat(Y >> INTER_BITS); - - short sx_clamp = clamp(sx, 0, src_cols - 1); - short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); - short sy_clamp = clamp(sy, 0, src_rows - 1); - short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); - int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - - short ay = (short)(Y & (INTER_TAB_SIZE - 1)); - short ax = (short)(X & (INTER_TAB_SIZE - 1)); - float taby = 1.f/INTER_TAB_SIZE*ay; - float tabx = 1.f/INTER_TAB_SIZE*ax; - - int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); - - int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); - int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); - - int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; - - uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); - dst[dst_index] = pix; - } -} diff --git a/sunnypilot/modeld_v2/transforms/transform.h b/sunnypilot/modeld_v2/transforms/transform.h deleted file mode 100644 index 771a7054b3..0000000000 --- a/sunnypilot/modeld_v2/transforms/transform.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" - -typedef struct { - cl_kernel krnl; - cl_mem m_y_cl, m_uv_cl; -} Transform; - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); - -void transform_destroy(Transform* transform); - -void transform_queue(Transform* s, cl_command_queue q, - cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection); diff --git a/sunnypilot/modeld_v2/warp.py b/sunnypilot/modeld_v2/warp.py new file mode 100644 index 0000000000..829cbcca49 --- /dev/null +++ b/sunnypilot/modeld_v2/warp.py @@ -0,0 +1,137 @@ +import pickle +import time +import numpy as np +from pathlib import Path +from tinygrad.tensor import Tensor +from tinygrad.engine.jit import TinyJit +from tinygrad.device import Device + +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.selfdrive.modeld.compile_warp import ( + CAMERA_CONFIGS, MEDMODEL_INPUT_SIZE, make_frame_prepare, make_update_both_imgs, + warp_pkl_path, +) + +MODELS_DIR = Path(__file__).parent / 'models' +MODEL_W, MODEL_H = MEDMODEL_INPUT_SIZE +UPSTREAM_BUFFER_LENGTH = 5 + + +def v2_warp_pkl_path(cam_w, cam_h, buffer_length): + return MODELS_DIR / f'warp_{cam_w}x{cam_h}_b{buffer_length}_tinygrad.pkl' + + +def compile_v2_warp(cam_w, cam_h, buffer_length): + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + img_buffer_shape = (buffer_length * 6, MODEL_H // 2, MODEL_W // 2) + + print(f"Compiling v2 warp for {cam_w}x{cam_h} buffer_length={buffer_length}...") + + frame_prepare = make_frame_prepare(cam_w, cam_h, MODEL_W, MODEL_H) + update_both_imgs = make_update_both_imgs(frame_prepare, MODEL_W, MODEL_H) + update_img_jit = TinyJit(update_both_imgs, prune=True) + + full_buffer = Tensor.zeros(img_buffer_shape, dtype='uint8').contiguous().realize() + big_full_buffer = Tensor.zeros(img_buffer_shape, dtype='uint8').contiguous().realize() + full_buffer_np = np.zeros(img_buffer_shape, dtype=np.uint8) + big_full_buffer_np = np.zeros(img_buffer_shape, dtype=np.uint8) + + for i in range(10): + new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + img_inputs = [full_buffer, + Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + big_img_inputs = [big_full_buffer, + Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + inputs = img_inputs + big_img_inputs + Device.default.synchronize() + + inputs_np = [x.numpy() for x in inputs] + inputs_np[0] = full_buffer_np + inputs_np[3] = big_full_buffer_np + + st = time.perf_counter() + out = update_img_jit(*inputs) + full_buffer = out[0].contiguous().realize().clone() + big_full_buffer = out[2].contiguous().realize().clone() + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = v2_warp_pkl_path(cam_w, cam_h, buffer_length) + with open(pkl_path, "wb") as f: + pickle.dump(update_img_jit, f) + print(f" Saved to {pkl_path}") + + jit = pickle.load(open(pkl_path, "rb")) + jit(*inputs) + + +class Warp: + def __init__(self, buffer_length=2): + self.buffer_length = buffer_length + self.img_buffer_shape = (buffer_length * 6, MODEL_H // 2, MODEL_W // 2) + + self.jit_cache = {} + self.full_buffers = {k: Tensor.zeros(self.img_buffer_shape, dtype='uint8').contiguous().realize() for k in ['img', 'big_img']} + self._blob_cache: dict[int, Tensor] = {} + self._nv12_cache: dict[tuple[int, int], int] = {} + self.transforms_np = {k: np.zeros((3, 3), dtype=np.float32) for k in ['img', 'big_img']} + self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} + + def process(self, bufs, transforms): + if not bufs: + return {} + road = next(n for n in bufs if 'big' not in n) + wide = next(n for n in bufs if 'big' in n) + cam_w, cam_h = bufs[road].width, bufs[road].height + key = (cam_w, cam_h) + + if key not in self.jit_cache: + v2_pkl = v2_warp_pkl_path(cam_w, cam_h, self.buffer_length) + if v2_pkl.exists(): + with open(v2_pkl, 'rb') as f: + self.jit_cache[key] = pickle.load(f) + elif self.buffer_length == UPSTREAM_BUFFER_LENGTH: + upstream_pkl = warp_pkl_path(cam_w, cam_h) + if upstream_pkl.exists(): + with open(upstream_pkl, 'rb') as f: + self.jit_cache[key] = pickle.load(f) + if key not in self.jit_cache: + frame_prepare = make_frame_prepare(cam_w, cam_h, MODEL_W, MODEL_H) + update_both_imgs = make_update_both_imgs(frame_prepare, MODEL_W, MODEL_H) + self.jit_cache[key] = TinyJit(update_both_imgs, prune=True) + + if key not in self._nv12_cache: + self._nv12_cache[key] = get_nv12_info(cam_w, cam_h)[3] + yuv_size = self._nv12_cache[key] + + road_ptr = bufs[road].data.ctypes.data + wide_ptr = bufs[wide].data.ctypes.data + if road_ptr not in self._blob_cache: + self._blob_cache[road_ptr] = Tensor.from_blob(road_ptr, (yuv_size,), dtype='uint8') + if wide_ptr not in self._blob_cache: + self._blob_cache[wide_ptr] = Tensor.from_blob(wide_ptr, (yuv_size,), dtype='uint8') + road_blob = self._blob_cache[road_ptr] + wide_blob = self._blob_cache[wide_ptr] if wide_ptr != road_ptr else Tensor.from_blob(wide_ptr, (yuv_size,), dtype='uint8') + np.copyto(self.transforms_np['img'], transforms[road].reshape(3, 3)) + np.copyto(self.transforms_np['big_img'], transforms[wide].reshape(3, 3)) + + Device.default.synchronize() + res = self.jit_cache[key]( + self.full_buffers['img'], road_blob, self.transforms['img'], + self.full_buffers['big_img'], wide_blob, self.transforms['big_img'], + ) + self.full_buffers['img'], out_road = res[0].realize(), res[1].realize() + self.full_buffers['big_img'], out_wide = res[2].realize(), res[3].realize() + + return {road: out_road, wide: out_wide} + + +if __name__ == "__main__": + for cam_w, cam_h in CAMERA_CONFIGS: + for bl in [2, 5]: + compile_v2_warp(cam_w, cam_h, bl) diff --git a/sunnypilot/modeld/constants.py b/sunnypilot/models/constants.py similarity index 100% rename from sunnypilot/modeld/constants.py rename to sunnypilot/models/constants.py diff --git a/sunnypilot/models/fetcher.py b/sunnypilot/models/fetcher.py index 1d8da083a9..5990ee2e41 100644 --- a/sunnypilot/models/fetcher.py +++ b/sunnypilot/models/fetcher.py @@ -116,7 +116,7 @@ class ModelCache: class ModelFetcher: """Handles fetching and caching of model data from remote source""" - MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v11.json" + MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v15.json" def __init__(self, params: Params): self.params = params diff --git a/sunnypilot/models/helpers.py b/sunnypilot/models/helpers.py index ce6625a1f0..5627080319 100644 --- a/sunnypilot/models/helpers.py +++ b/sunnypilot/models/helpers.py @@ -12,17 +12,14 @@ import numpy as np from openpilot.common.params import Params from cereal import custom -from openpilot.sunnypilot.modeld.constants import Meta, MetaTombRaider, MetaSimPose -from openpilot.sunnypilot.modeld.runners import ModelRunner -from openpilot.system.hardware import PC +from openpilot.sunnypilot.models.constants import Meta, MetaTombRaider, MetaSimPose from openpilot.system.hardware.hw import Paths from pathlib import Path # see the README.md for more details on the model selector versioning -CURRENT_SELECTOR_VERSION = 13 -REQUIRED_MIN_SELECTOR_VERSION = 12 +CURRENT_SELECTOR_VERSION = 15 +REQUIRED_MIN_SELECTOR_VERSION = 14 -USE_ONNX = os.getenv('USE_ONNX', PC) CUSTOM_MODEL_PATH = Paths.model_root() METADATA_PATH = Path(__file__).parent / '../models/supercombo_metadata.pkl' @@ -122,16 +119,6 @@ def _get_model(): return None -def get_model_path(): - if USE_ONNX: - return {ModelRunner.ONNX: Path(__file__).parent / '../models/supercombo.onnx'} - - if model := _get_model(): - return {ModelRunner.THNEED: f"{CUSTOM_MODEL_PATH}/{model.artifact.fileName}"} - - return {ModelRunner.THNEED: Path(__file__).parent / '../models/supercombo.thneed'} - - def load_metadata(): metadata_path = METADATA_PATH diff --git a/sunnypilot/models/manager.py b/sunnypilot/models/manager.py index 2d3d670bfd..8fee0798b6 100644 --- a/sunnypilot/models/manager.py +++ b/sunnypilot/models/manager.py @@ -171,7 +171,7 @@ class ModelManagerSP: self.available_models = self.model_fetcher.get_available_bundles() self.active_bundle = get_active_bundle(self.params) - if index_to_download := self.params.get("ModelManager_DownloadIndex"): + if (index_to_download := self.params.get("ModelManager_DownloadIndex")) is not None: if model_to_download := next((model for model in self.available_models if model.index == index_to_download), None): try: self.download(model_to_download, Paths.model_root()) diff --git a/sunnypilot/models/runners/constants.py b/sunnypilot/models/runners/constants.py index cbd1fdb376..acb316888c 100644 --- a/sunnypilot/models/runners/constants.py +++ b/sunnypilot/models/runners/constants.py @@ -1,6 +1,5 @@ import os import numpy as np -from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import DrivingModelFrame, CLMem from openpilot.system.hardware.hw import Paths from cereal import custom @@ -8,8 +7,6 @@ from cereal import custom NumpyDict = dict[str, np.ndarray] ShapeDict = dict[str, tuple[int, ...]] SliceDict = dict[str, slice] -CLMemDict = dict[str, CLMem] -FrameDict = dict[str, DrivingModelFrame] ModelType = custom.ModelManagerSP.Model.Type Model = custom.ModelManagerSP.Model diff --git a/sunnypilot/models/runners/model_runner.py b/sunnypilot/models/runners/model_runner.py index 6902ed09b8..051fa349db 100644 --- a/sunnypilot/models/runners/model_runner.py +++ b/sunnypilot/models/runners/model_runner.py @@ -1,25 +1,14 @@ -import os from abc import abstractmethod, ABC import numpy as np from openpilot.sunnypilot.models.helpers import get_active_bundle -from openpilot.system.hardware import TICI -from openpilot.sunnypilot.models.runners.constants import NumpyDict, ShapeDict, CLMemDict, FrameDict, Model, SliceDict, SEND_RAW_PRED +from openpilot.sunnypilot.models.runners.constants import NumpyDict, ShapeDict, Model, SliceDict, SEND_RAW_PRED from openpilot.system.hardware.hw import Paths import pickle CUSTOM_MODEL_PATH = Paths.model_root() -# Set QCOM environment variable for TICI devices, potentially enabling hardware acceleration -USBGPU = "USBGPU" in os.environ -if USBGPU: - os.environ['DEV'] = 'AMD' - os.environ['AMD_IFACE'] = 'USB' -else: - os.environ['DEV'] = 'QCOM' if TICI else 'CPU' - - class ModelData: """ Stores metadata and configuration for a specific machine learning model. @@ -144,13 +133,11 @@ class ModelRunner(ModularRunner): raise ValueError("Model data is not available. Ensure the model is loaded correctly.") @abstractmethod - def prepare_inputs(self, imgs_cl: CLMemDict, numpy_inputs: NumpyDict, frames: FrameDict) -> dict: + def prepare_inputs(self, numpy_inputs: NumpyDict) -> dict: """ Abstract method to prepare inputs for model inference. - :param imgs_cl: Dictionary of OpenCL memory objects for image inputs. :param numpy_inputs: Dictionary of numpy arrays for non-image inputs. - :param frames: Dictionary of DrivingModelFrame objects for context. :return: Dictionary of prepared inputs ready for the model. """ raise NotImplementedError diff --git a/sunnypilot/models/runners/onnx/onnx_runner.py b/sunnypilot/models/runners/onnx/onnx_runner.py deleted file mode 100644 index 1ffead4560..0000000000 --- a/sunnypilot/models/runners/onnx/onnx_runner.py +++ /dev/null @@ -1,62 +0,0 @@ -import numpy as np - -from openpilot.sunnypilot.modeld_v2 import MODEL_PATH -from openpilot.sunnypilot.modeld_v2.runners.ort_helpers import make_onnx_cpu_runner, ORT_TYPES_TO_NP_TYPES -from openpilot.sunnypilot.models.runners.constants import ModelType, ShapeDict, CLMemDict, NumpyDict, FrameDict -from openpilot.sunnypilot.models.runners.model_runner import ModelRunner -from openpilot.sunnypilot.modeld_v2.constants import ModelConstants - - -class ONNXRunner(ModelRunner): - """ - A ModelRunner implementation for executing ONNX models using ONNX Runtime CPU. - - Handles loading the ONNX model, preparing inputs as numpy arrays, running - inference, and parsing outputs. This runner is typically used on non-TICI platforms. - """ - def __init__(self): - super().__init__() - # Initialize ONNX Runtime session for the model at MODEL_PATH - self.runner = make_onnx_cpu_runner(MODEL_PATH) - # Map expected input names to numpy dtypes - self.input_to_nptype = { - model_input.name: ORT_TYPES_TO_NP_TYPES[model_input.type] - for model_input in self.runner.get_inputs() - } - # For ONNX, _model_data isn't strictly necessary as shapes/types come from the runner - # However, we might still need output_slices if custom models define them. - # We assume supercombo type for potentially loading output_slices metadata if available. - self._model_data = self.models.get(ModelType.supercombo) - self._constants = ModelConstants # Constants for ONNX models, if needed - - @property - def input_shapes(self) -> ShapeDict: - """Returns the input shapes defined in the ONNX model.""" - # ONNX shapes are derived directly from the model definition via the runner - return {runner_input.name: runner_input.shape for runner_input in self.runner.get_inputs()} - - def prepare_inputs(self, imgs_cl: CLMemDict, numpy_inputs: NumpyDict, frames: FrameDict) -> dict: - """Prepares inputs for the ONNX model as numpy arrays.""" - self.inputs = numpy_inputs # Start with non-image numpy inputs - # Convert image inputs from OpenCL buffers to numpy arrays - for key in imgs_cl: - buffer = frames[key].buffer_from_cl(imgs_cl[key]) - reshaped_buffer = buffer.reshape(self.input_shapes[key]) - self.inputs[key] = reshaped_buffer.astype(dtype=self.input_to_nptype[key]) - return self.inputs - - def _parse_outputs(self, model_outputs: np.ndarray) -> NumpyDict: - """Parses the raw ONNX model outputs using the standard Parser.""" - # Use slicing if metadata is available, otherwise pass raw outputs - if self._model_data is None: - raise ValueError("Model data is not available. Ensure the model is loaded correctly.") - - outputs_to_parse = self._slice_outputs(model_outputs) if self._model_data else {'raw_pred': model_outputs} - result: NumpyDict = self.parser_method_dict[self._model_data.model.type.raw](outputs_to_parse) - return result - - def _run_model(self) -> NumpyDict: - """Runs the ONNX model inference and parses the outputs.""" - # Execute the ONNX Runtime session - outputs = self.runner.run(None, self.inputs)[0].flatten() - return self._parse_outputs(outputs) diff --git a/sunnypilot/models/runners/tinygrad/tinygrad_runner.py b/sunnypilot/models/runners/tinygrad/tinygrad_runner.py index 7b48e3086b..9033c892eb 100644 --- a/sunnypilot/models/runners/tinygrad/tinygrad_runner.py +++ b/sunnypilot/models/runners/tinygrad/tinygrad_runner.py @@ -1,11 +1,9 @@ import pickle import numpy as np -from openpilot.sunnypilot.modeld_v2.runners.tinygrad_helpers import qcom_tensor_from_opencl_address -from openpilot.sunnypilot.models.runners.constants import CLMemDict, FrameDict, NumpyDict, ModelType, ShapeDict, CUSTOM_MODEL_PATH, SliceDict +from openpilot.sunnypilot.models.runners.constants import NumpyDict, ModelType, ShapeDict, CUSTOM_MODEL_PATH, SliceDict from openpilot.sunnypilot.models.runners.model_runner import ModelRunner from openpilot.sunnypilot.models.runners.tinygrad.model_types import PolicyTinygrad, VisionTinygrad, SupercomboTinygrad, OffPolicyTinygrad -from openpilot.system.hardware import TICI from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants from openpilot.sunnypilot.modeld_v2.constants import ModelConstants @@ -51,40 +49,34 @@ class TinygradRunner(ModelRunner, SupercomboTinygrad, PolicyTinygrad, VisionTiny self.input_to_dtype = {} self.input_to_device = {} for idx, name in enumerate(self.model_run.captured.expected_names): - info = self.model_run.captured.expected_st_vars_dtype_device[idx] + info = self.model_run.captured.expected_input_info[idx] self.input_to_dtype[name] = info[2] # dtype self.input_to_device[name] = info[3] # device + self._policy_cached = False @property def vision_input_names(self) -> list[str]: """Returns the list of vision input names from the input shapes.""" return [name for name in self.input_shapes.keys() if 'img' in name] - def prepare_vision_inputs(self, imgs_cl: CLMemDict, frames: FrameDict): - """Prepares vision (image) inputs as Tinygrad Tensors.""" - for key in imgs_cl: - if TICI and key not in self.inputs: - # On TICI, directly use OpenCL memory address for efficiency via QCOM extensions - self.inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.input_shapes[key], dtype=self.input_to_dtype[key]) - elif not TICI: - # On other platforms, copy data from CL buffer to a numpy array first - shape = frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.input_shapes[key]) - self.inputs[key] = Tensor(shape, device=self.input_to_device[key], dtype=self.input_to_dtype[key]).realize() def prepare_policy_inputs(self, numpy_inputs: NumpyDict): - """Prepares non-image (policy) inputs as Tinygrad Tensors.""" - for key, value in numpy_inputs.items(): - self.inputs[key] = Tensor(value, device=self.input_to_device[key], dtype=self.input_to_dtype[key]).realize() + if not self._policy_cached: + for key, value in numpy_inputs.items(): + self.inputs[key] = Tensor(value, device='NPY').realize() + self._policy_cached = True - def prepare_inputs(self, imgs_cl: CLMemDict, numpy_inputs: NumpyDict, frames: FrameDict) -> dict: + def prepare_inputs(self, numpy_inputs: NumpyDict) -> dict: """Prepares all vision and policy inputs for the model.""" - self.prepare_vision_inputs(imgs_cl, frames) self.prepare_policy_inputs(numpy_inputs) + for key in self.vision_input_names: + if key in self.inputs: + self.inputs[key] = self.inputs[key].cast(self.input_to_dtype[key]) return self.inputs def _run_model(self) -> NumpyDict: """Runs the Tinygrad model inference and parses the outputs.""" - outputs = self.model_run(**self.inputs).contiguous().realize().uop.base.buffer.numpy() + outputs = self.model_run(**self.inputs).contiguous().realize().uop.base.buffer.numpy().flatten() return self._parse_outputs(outputs) def _parse_outputs(self, model_outputs: np.ndarray) -> NumpyDict: @@ -120,6 +112,9 @@ class TinygradSplitRunner(ModelRunner): off_policy_output = self.off_policy_runner.run_model() outputs.update(off_policy_output) + if 'planplus' in outputs and 'plan' in outputs: + outputs['plan'] = outputs['plan'] + outputs['planplus'] + return outputs @property @@ -143,17 +138,18 @@ class TinygradSplitRunner(ModelRunner): slices.update(self.off_policy_runner.output_slices) return slices - def prepare_inputs(self, imgs_cl: CLMemDict, numpy_inputs: NumpyDict, frames: FrameDict) -> dict: + def prepare_inputs(self, numpy_inputs: NumpyDict) -> dict: """Prepares inputs for both vision and policy models.""" # Policy inputs only depend on numpy_inputs self.policy_runner.prepare_policy_inputs(numpy_inputs) - # Vision inputs depend on imgs_cl and frames - self.vision_runner.prepare_vision_inputs(imgs_cl, frames) + + for key in self.vision_input_names: + if key in self.inputs: + self.vision_runner.inputs[key] = self.inputs[key].cast(self.vision_runner.input_to_dtype[key]) + inputs = {**self.policy_runner.inputs, **self.vision_runner.inputs} if self.off_policy_runner: self.off_policy_runner.prepare_policy_inputs(numpy_inputs) inputs.update(self.off_policy_runner.inputs) - - # Return combined inputs (though they are stored within respective runners) return inputs diff --git a/sunnypilot/selfdrive/assets/img_minus_arrow_down.png b/sunnypilot/selfdrive/assets/img_minus_arrow_down.png index 250f640585..2dc99789a5 100644 --- a/sunnypilot/selfdrive/assets/img_minus_arrow_down.png +++ b/sunnypilot/selfdrive/assets/img_minus_arrow_down.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f4a0bf26cfd2c64939759d43bee839aadfd017a6f74065095a27b03615e55a4 -size 26627 +oid sha256:ad42ecaeff96a0a6c1a6db67b01e3c0452566906dd3a7f0336a1010ae854d27b +size 60924 diff --git a/sunnypilot/selfdrive/assets/img_plus_arrow_up.png b/sunnypilot/selfdrive/assets/img_plus_arrow_up.png index 14c1529da3..4247827340 100644 --- a/sunnypilot/selfdrive/assets/img_plus_arrow_up.png +++ b/sunnypilot/selfdrive/assets/img_plus_arrow_up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e451ff31e9b3f144822ab282b8e47cd5a603ca59ff94bd1849271181b86c6b1 -size 29220 +oid sha256:1fdea39873f60a0c4b216b75792e826f3633091a72ea2abd05740bd6e4e72089 +size 63058 diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index a93f5724b5..e4db6c8e97 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -131,6 +131,7 @@ def initialize_params(params) -> list[dict[str, Any]]: # toyota keys.extend([ "ToyotaEnforceStockLongitudinal", + "ToyotaStopAndGoHack", ]) return [{k: params.get(k, return_default=True)} for k in keys] diff --git a/sunnypilot/selfdrive/car/tests/test_cruise_mode.py b/sunnypilot/selfdrive/car/tests/test_cruise_mode.py index 26338319f5..2fad14a703 100644 --- a/sunnypilot/selfdrive/car/tests/test_cruise_mode.py +++ b/sunnypilot/selfdrive/car/tests/test_cruise_mode.py @@ -1,5 +1,5 @@ -from parameterized import parameterized_class from cereal import car +from openpilot.common.parameterized import parameterized_class from openpilot.selfdrive.selfdrived.events import Events from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper, DISTANCE_LONG_PRESS diff --git a/sunnypilot/selfdrive/car/tests/test_custom_cruise.py b/sunnypilot/selfdrive/car/tests/test_custom_cruise.py index a1f7f8f027..7276af43e7 100644 --- a/sunnypilot/selfdrive/car/tests/test_custom_cruise.py +++ b/sunnypilot/selfdrive/car/tests/test_custom_cruise.py @@ -1,8 +1,8 @@ import pytest -from parameterized import parameterized_class from cereal import car from openpilot.common.constants import CV +from openpilot.common.parameterized import parameterized_class from openpilot.common.params import Params from openpilot.selfdrive.car.cruise import V_CRUISE_INITIAL from openpilot.selfdrive.car.tests.test_cruise_speed import TestVCruiseHelper diff --git a/sunnypilot/selfdrive/controls/controlsd_ext.py b/sunnypilot/selfdrive/controls/controlsd_ext.py index 3f6053d158..4b6c92ae2c 100644 --- a/sunnypilot/selfdrive/controls/controlsd_ext.py +++ b/sunnypilot/selfdrive/controls/controlsd_ext.py @@ -14,8 +14,9 @@ from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD from openpilot.sunnypilot.livedelay.helpers import get_lat_delay -from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase +from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase from openpilot.sunnypilot.selfdrive.controls.lib.blinker_pause_lateral import BlinkerPauseLateral +from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_v0 import LatControlTorque as LatControlTorqueV0 class ControlsExt(ModelStateBase): @@ -33,6 +34,17 @@ class ControlsExt(ModelStateBase): self.sm_services_ext = ['radarState', 'selfdriveStateSP'] self.pm_services_ext = ['carControlSP'] + def initialize_lateral_control(self, lac, CI, dt): + enforce_torque_control = self.params.get_bool("EnforceTorqueControl") + torque_versions = self.params.get("TorqueControlTune") + if not enforce_torque_control: + return lac + + if torque_versions == 0.0: # v0 + return LatControlTorqueV0(self.CP, self.CP_SP, CI, dt) + else: + return lac + def get_params_sp(self, sm: messaging.SubMaster) -> None: if time.monotonic() - self._param_update_time > PARAMS_UPDATE_PERIOD: self.blinker_pause_lateral.get_params() diff --git a/sunnypilot/selfdrive/controls/lib/drive_helpers.py b/sunnypilot/selfdrive/controls/lib/drive_helpers.py deleted file mode 100644 index b0fda7bd8c..0000000000 --- a/sunnypilot/selfdrive/controls/lib/drive_helpers.py +++ /dev/null @@ -1,27 +0,0 @@ -from numpy import clip, interp -from openpilot.common.realtime import DT_MDL -from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, MIN_SPEED, MAX_LATERAL_JERK -from openpilot.sunnypilot.modeld.constants import ModelConstants - - -def get_lag_adjusted_curvature(steer_delay, v_ego, psis, curvatures): - if len(psis) != CONTROL_N: - psis = [0.0]*CONTROL_N - curvatures = [0.0]*CONTROL_N - v_ego = max(MIN_SPEED, v_ego) - - # MPC can plan to turn the wheel and turn back before t_delay. This means - # in high delay cases some corrections never even get commanded. So just use - # psi to calculate a simple linearization of desired curvature - current_curvature_desired = curvatures[0] - psi = interp(steer_delay, ModelConstants.T_IDXS[:CONTROL_N], psis) - average_curvature_desired = psi / (v_ego * steer_delay) - desired_curvature = 2 * average_curvature_desired - current_curvature_desired - - # This is the "desired rate of the setpoint" not an actual desired rate - max_curvature_rate = MAX_LATERAL_JERK / (v_ego**2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755 - safe_desired_curvature = clip(desired_curvature, - current_curvature_desired - max_curvature_rate * DT_MDL, - current_curvature_desired + max_curvature_rate * DT_MDL) - - return float(safe_desired_curvature) diff --git a/sunnypilot/selfdrive/controls/lib/latcontrol_torque_v0.py b/sunnypilot/selfdrive/controls/lib/latcontrol_torque_v0.py new file mode 100644 index 0000000000..f7874cc539 --- /dev/null +++ b/sunnypilot/selfdrive/controls/lib/latcontrol_torque_v0.py @@ -0,0 +1,128 @@ +import math +import numpy as np +from collections import deque + +from cereal import log +from opendbc.car.lateral import get_friction +from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.selfdrive.controls.lib.latcontrol import LatControl +from openpilot.common.pid import PIDController + +from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_ext import LatControlTorqueExt + +# At higher speeds (25+mph) we can assume: +# Lateral acceleration achieved by a specific car correlates to +# torque applied to the steering rack. It does not correlate to +# wheel slip, or to speed. + +# This controller applies torque to achieve desired lateral +# accelerations. To compensate for the low speed effects the +# proportional gain is increased at low speeds by the PID controller. +# Additionally, there is friction in the steering wheel that needs +# to be overcome to move it at all, this is compensated for too. + +KP = 1.0 +KI = 0.3 +KD = 0.0 +INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30] +KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP] + +LP_FILTER_CUTOFF_HZ = 1.2 +LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0 +FRICTION_THRESHOLD = 0.3 +VERSION = 0 + + +class LatControlTorque(LatControl): + def __init__(self, CP, CP_SP, CI, dt): + super().__init__(CP, CP_SP, CI, dt) + self.torque_params = CP.lateralTuning.torque.as_builder() + self.torque_from_lateral_accel = CI.torque_from_lateral_accel() + self.lateral_accel_from_torque = CI.lateral_accel_from_torque() + self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, KD, rate=1/self.dt) + self.update_limits() + self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg + self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt) + self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len , maxlen=self.lat_accel_request_buffer_len) + self.previous_measurement = 0.0 + self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) + + self.extension = LatControlTorqueExt(self, CP, CP_SP, CI) + + def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction): + self.torque_params.latAccelFactor = latAccelFactor + self.torque_params.latAccelOffset = latAccelOffset + self.torque_params.friction = friction + self.update_limits() + + def update_limits(self): + self.pid.set_limits(self.lateral_accel_from_torque(self.steer_max, self.torque_params), + self.lateral_accel_from_torque(-self.steer_max, self.torque_params)) + + def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited, lat_delay): + # Override torque params from extension + if self.extension.update_override_torque_params(self.torque_params): + self.update_limits() + + pid_log = log.ControlsState.LateralTorqueState.new_message() + pid_log.version = VERSION + if not active: + output_torque = 0.0 + pid_log.active = False + else: + measured_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) + roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY + curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) + lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 + + delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len)) + expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] + # TODO factor out lateral jerk from error to later replace it with delay independent alternative + future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 + self.lat_accel_request_buffer.append(future_desired_lateral_accel) + gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation + desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay + + measurement = measured_curvature * CS.vEgo ** 2 + measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt) + self.previous_measurement = measurement + + setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel + error = setpoint - measurement + + # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly + pid_log.error = float(error) + ff = gravity_adjusted_future_lateral_accel + # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll + ff -= self.torque_params.latAccelOffset + # TODO jerk is weighted by lat_delay for legacy reasons, but should be made independent of it + ff += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) + + freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 + output_lataccel = self.pid.update(pid_log.error, + -measurement_rate, + feedforward=ff, + speed=CS.vEgo, + freeze_integrator=freeze_integrator) + output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params) + + # Lateral acceleration torque controller extension updates + # Overrides pid_log.error and output_torque + pid_log, output_torque = self.extension.update(CS, VM, self.pid, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation, + future_desired_lateral_accel, measurement, lateral_accel_deadzone, gravity_adjusted_future_lateral_accel, + desired_curvature, measured_curvature, steer_limited_by_safety, output_torque) + + pid_log.active = True + pid_log.p = float(self.pid.p) + pid_log.i = float(self.pid.i) + pid_log.d = float(self.pid.d) + pid_log.f = float(self.pid.f) + pid_log.output = float(-output_torque) # TODO: log lat accel? + pid_log.actualLateralAccel = float(measurement) + pid_log.desiredLateralAccel = float(setpoint) + pid_log.desiredLateralJerk = float(desired_lateral_jerk) + pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited)) + + # TODO left is positive in this convention + return -output_torque, 0.0, pid_log diff --git a/sunnypilot/selfdrive/controls/lib/latcontrol_torque_versions.json b/sunnypilot/selfdrive/controls/lib/latcontrol_torque_versions.json new file mode 100644 index 0000000000..21b5884a01 --- /dev/null +++ b/sunnypilot/selfdrive/controls/lib/latcontrol_torque_versions.json @@ -0,0 +1,8 @@ +{ + "v1.0": { + "version": "1.0" + }, + "v0.0": { + "version": "0.0" + } +} diff --git a/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_fingerprint.py b/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_fingerprint.py index 0f016ccaf0..07e7d2852d 100644 --- a/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_fingerprint.py +++ b/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_fingerprint.py @@ -1,11 +1,10 @@ -from parameterized import parameterized - from opendbc.car.car_helpers import interfaces from opendbc.car.honda.values import CAR as HONDA from opendbc.car.hyundai.values import CAR as HYUNDAI from opendbc.car.nissan.values import CAR as NISSAN from opendbc.car.toyota.values import CAR as TOYOTA from opendbc.car.tesla.values import CAR as TESLA +from openpilot.common.parameterized import parameterized from openpilot.common.params import Params from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces diff --git a/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_load_model.py b/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_load_model.py index 245b487315..3594588ea2 100644 --- a/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_load_model.py +++ b/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_load_model.py @@ -1,9 +1,8 @@ -from parameterized import parameterized - from opendbc.car.car_helpers import interfaces from opendbc.car.honda.values import CAR as HONDA from opendbc.car.hyundai.values import CAR as HYUNDAI from opendbc.car.toyota.values import CAR as TOYOTA +from openpilot.common.parameterized import parameterized from openpilot.common.params import Params from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car.helpers import convert_to_capnp diff --git a/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py b/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py index eb6839cc19..a108e4ce29 100644 --- a/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py +++ b/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py @@ -1,5 +1,4 @@ import numpy as np -from parameterized import parameterized from cereal import car, log, messaging from opendbc.car.car_helpers import interfaces @@ -8,6 +7,7 @@ from opendbc.car.honda.values import CAR as HONDA from opendbc.car.hyundai.values import CAR as HYUNDAI from opendbc.car.toyota.values import CAR as TOYOTA from opendbc.car.vehicle_model import VehicleModel +from openpilot.common.parameterized import parameterized from openpilot.common.params import Params from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car.helpers import convert_to_capnp diff --git a/sunnypilot/selfdrive/controls/lib/tests/test_auto_lane_change.py b/sunnypilot/selfdrive/controls/lib/tests/test_auto_lane_change.py index 5d1fa360ed..b4ec5041c8 100644 --- a/sunnypilot/selfdrive/controls/lib/tests/test_auto_lane_change.py +++ b/sunnypilot/selfdrive/controls/lib/tests/test_auto_lane_change.py @@ -4,9 +4,7 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ - -from parameterized import parameterized - +from openpilot.common.parameterized import parameterized from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper, LaneChangeState, LaneChangeDirection from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode, \ diff --git a/sunnypilot/modeld/__init__.py b/sunnypilot/selfdrive/pandad/__init__.py similarity index 100% rename from sunnypilot/modeld/__init__.py rename to sunnypilot/selfdrive/pandad/__init__.py diff --git a/sunnypilot/selfdrive/pandad/rivian_long_flasher.py b/sunnypilot/selfdrive/pandad/rivian_long_flasher.py new file mode 100755 index 0000000000..24191a73a2 --- /dev/null +++ b/sunnypilot/selfdrive/pandad/rivian_long_flasher.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import os +from itertools import accumulate + +from cereal import car, messaging +from panda import Panda +from openpilot.common.params import Params +from openpilot.common.swaglog import cloudlog + +FW_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "rivian_long_fw.bin.signed") +SECTOR_SIZES = [0x4000] * 4 + [0x10000] + [0x20000] * 11 + + +def _is_rivian() -> bool: + params = Params() + + # check fixed fingerprint + if bundle := params.get("CarPlatformBundle"): + if bundle.get("brand") == "rivian": + return True + + # check cached fingerprint + CP_bytes = params.get("CarParamsPersistent") + if CP_bytes is not None: + CP = messaging.log_from_bytes(CP_bytes, car.CarParams) + if CP.brand == "rivian": + return True + + return False + + +def _flash_static(handle, code): + assert Panda.flasher_present(handle) + last_sector = next((i + 1 for i, v in enumerate(accumulate(SECTOR_SIZES[1:])) if v > len(code)), -1) + assert 1 <= last_sector < 7, "Invalid firmware size" + + handle.controlWrite(Panda.REQUEST_IN, 0xb1, 0, 0, b'') + for i in range(1, last_sector + 1): + handle.controlWrite(Panda.REQUEST_IN, 0xb2, i, 0, b'') + for i in range(0, len(code), 0x10): + handle.bulkWrite(2, code[i:i + 0x10]) + try: + handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) + except Exception: + pass + + +def _flash_panda(panda: Panda) -> None: + expected_sig = Panda.get_signature_from_firmware(FW_PATH) + if not panda.bootstub and panda.get_signature() == expected_sig: + cloudlog.info(f"F4 panda {panda.get_usb_serial()} already up to date") + return + + cloudlog.info(f"Flashing F4 panda {panda.get_usb_serial()}") + with open(FW_PATH, "rb") as f: + code = f.read() + + if not panda.bootstub: + # enter bootstub directly, panda.reset() rejects deprecated hw types + try: + panda._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 1, 0, b'', timeout=15000, expect_disconnect=True) + except Exception: + pass + panda.close() + panda.reconnect() + + _flash_static(panda._handle, code) + panda.reconnect() + + +def flash_rivian_long(panda: Panda) -> None: + if not os.path.isfile(FW_PATH): + cloudlog.error(f"Rivian longitudinal upgrade firmware not found at {FW_PATH}") + return + + if not _is_rivian(): + cloudlog.info("Not a Rivian, skipping longitudinal upgrade...") + return + + # only flash external black pandas (HW_TYPE_BLACK = 0x03) + if panda.get_type() == b'\x03' and not panda.is_internal(): + try: + _flash_panda(panda) + except Exception: + cloudlog.exception(f"Failed to flash F4 panda {panda.get_usb_serial()}") + + +if __name__ == '__main__': + flash_rivian_long(Panda()) diff --git a/sunnypilot/selfdrive/pandad/rivian_long_fw.bin.signed b/sunnypilot/selfdrive/pandad/rivian_long_fw.bin.signed new file mode 100644 index 0000000000..bdbd237ba9 Binary files /dev/null and b/sunnypilot/selfdrive/pandad/rivian_long_fw.bin.signed differ diff --git a/sunnypilot/selfdrive/selfdrived/events.py b/sunnypilot/selfdrive/selfdrived/events.py index 44f289383d..c1ff180770 100644 --- a/sunnypilot/selfdrive/selfdrived/events.py +++ b/sunnypilot/selfdrive/selfdrived/events.py @@ -1,10 +1,16 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" import cereal.messaging as messaging from cereal import log, car, custom from openpilot.common.constants import CV from openpilot.sunnypilot.selfdrive.selfdrived.events_base import EventsBase, Priority, ET, Alert, \ NoEntryAlert, ImmediateDisableAlert, EngagementAlert, NormalPermanentAlert, AlertCallbackType, wrong_car_mode_alert from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED, CONFIRM_SPEED_THRESHOLD - +from openpilot.system.hardware import HARDWARE AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus @@ -17,6 +23,8 @@ EventNameSP = custom.OnroadEventSP.EventName # get event name from enum EVENT_NAME_SP = {v: k for k, v in EventNameSP.schema.enumerants.items()} +IS_MICI = HARDWARE.get_device_type() == 'mici' + def speed_limit_adjust_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: speedLimit = sm['longitudinalPlanSP'].speedLimit.resolver.speedLimit @@ -31,11 +39,14 @@ def speed_limit_adjust_alert(CP: car.CarParams, CS: car.CarState, sm: messaging. def speed_limit_pre_active_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: speed_conv = CV.MS_TO_KPH if metric else CV.MS_TO_MPH + v_cruise_cluster = CS.vCruiseCluster + set_speed = sm['controlsState'].vCruiseDEPRECATED if v_cruise_cluster == 0.0 else v_cruise_cluster + set_speed_conv = round(set_speed * speed_conv) + speed_limit_final_last = sm['longitudinalPlanSP'].speedLimit.resolver.speedLimitFinalLast speed_limit_final_last_conv = round(speed_limit_final_last * speed_conv) alert_1_str = "" - alert_2_str = "" - alert_size = AlertSize.none + alert_size = AlertSize.small if CP.openpilotLongitudinalControl and CP.pcmCruise: # PCM long @@ -44,13 +55,19 @@ def speed_limit_pre_active_alert(CP: car.CarParams, CS: car.CarState, sm: messag pcm_long_required_max_set_speed_conv = round(pcm_long_required_max * speed_conv) speed_unit = "km/h" if metric else "mph" - alert_1_str = "Speed Limit Assist: Activation Required" - alert_2_str = f"Manually change set speed to {pcm_long_required_max_set_speed_conv} {speed_unit} to activate" - alert_size = AlertSize.mid + alert_1_str = f"Speed Limit Assist: set to {pcm_long_required_max_set_speed_conv} {speed_unit} to engage" + else: + if IS_MICI: + if set_speed_conv < speed_limit_final_last_conv: + alert_1_str = "Press + to confirm speed limit" + elif set_speed_conv > speed_limit_final_last_conv: + alert_1_str = "Press - to confirm speed limit" + else: + alert_size = AlertSize.none return Alert( alert_1_str, - alert_2_str, + "", AlertStatus.normal, alert_size, Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleLow, .1) @@ -197,7 +214,7 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = { EventNameSP.speedLimitActive: { ET.WARNING: Alert( - "Automatically adjusting to the posted speed limit", + "Auto adjusting to speed limit", "", AlertStatus.normal, AlertSize.small, Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleHigh, 5.), @@ -217,7 +234,7 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = { EventNameSP.speedLimitPending: { ET.WARNING: Alert( - "Automatically adjusting to the last speed limit", + "Auto adjusting to last speed limit", "", AlertStatus.normal, AlertSize.small, Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleHigh, 5.), diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index fe0a248100..e8066dca49 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -202,28 +202,31 @@ def getParamsAllKeysV1() -> dict[str, str]: with open(METADATA_PATH) as f: metadata = json.load(f) except Exception: - cloudlog.exception("sunnylinkd.getParamsAllKeysV1.exception") + cloudlog.exception("sunnylinkd.getParamsAllKeysV1.metadata.exception") metadata = {} - available_keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()] + try: + available_keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()] - params_dict: dict[str, list[dict[str, str | bool | int | object | dict | None]]] = {"params": []} - for key in available_keys: - value = get_param_as_byte(key, get_default=True) + params_dict: dict[str, list[dict[str, str | bool | int | object | dict | None]]] = {"params": []} + for key in available_keys: + value = get_param_as_byte(key, get_default=True) - param_entry = { - "key": key, - "type": int(params.get_type(key).value), - "default_value": base64.b64encode(value).decode('utf-8') if value else None, - } + param_entry = { + "key": key, + "type": int(params.get_type(key).value), + "default_value": base64.b64encode(value).decode('utf-8') if value else None, + } - if key in metadata: - meta_copy = metadata[key].copy() - param_entry["_extra"] = meta_copy + if key in metadata: + meta_copy = metadata[key].copy() + param_entry["_extra"] = meta_copy - params_dict["params"].append(param_entry) - - return {"keys": json.dumps(params_dict.get("params", []))} + params_dict["params"].append(param_entry) + return {"keys": json.dumps(params_dict.get("params", []))} + except Exception: + cloudlog.exception("sunnylinkd.getParamsAllKeysV1.exception") + raise @dispatcher.add_method diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index ad94ad35ff..79c2b5caf9 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -845,86 +845,94 @@ }, { "value": 2, - "label": "5 %" + "label": "Screen Off" }, { "value": 3, - "label": "10 %" + "label": "5 %" }, { "value": 4, - "label": "15 %" + "label": "10 %" }, { "value": 5, - "label": "20 %" + "label": "15 %" }, { "value": 6, - "label": "25 %" + "label": "20 %" }, { "value": 7, - "label": "30 %" + "label": "25 %" }, { "value": 8, - "label": "35 %" + "label": "30 %" }, { "value": 9, - "label": "40 %" + "label": "35 %" }, { "value": 10, - "label": "45 %" + "label": "40 %" }, { "value": 11, - "label": "50 %" + "label": "45 %" }, { "value": 12, - "label": "55 %" + "label": "50 %" }, { "value": 13, - "label": "60 %" + "label": "55 %" }, { "value": 14, - "label": "65 %" + "label": "60 %" }, { "value": 15, - "label": "70 %" + "label": "65 %" }, { "value": 16, - "label": "75 %" + "label": "70 %" }, { "value": 17, - "label": "80 %" + "label": "75 %" }, { "value": 18, - "label": "85 %" + "label": "80 %" }, { "value": 19, - "label": "90 %" + "label": "85 %" }, { "value": 20, - "label": "95 %" + "label": "90 %" }, { "value": 21, + "label": "95 %" + }, + { + "value": 22, "label": "100 %" } ] }, + "OnroadScreenOffBrightnessMigrated": { + "title": "Onroad Brightness Migration Version", + "description": "This param is to track whether OnroadScreenOffBrightness needs to be migrated." + }, "OnroadScreenOffControl": { "title": "Onroad Brightness", "description": "Adjusts the screen brightness while it's in onroad state." @@ -1247,6 +1255,24 @@ "title": "[TIZI/TICI only] Steering Arc", "description": "Display steering arc on the driving screen when lateral control is enabled." }, + "TorqueControlTune": { + "title": "Torque Control Tune Version", + "description": "Select the version of Torque Control Tune to use.", + "options": [ + { + "value": "", + "label": "Default" + }, + { + "value": 1.0, + "label": "v1.0" + }, + { + "value": 0.0, + "label": "v0.0" + } + ] + }, "TorqueParamsOverrideEnabled": { "title": "Manual Real-Time Tuning", "description": "" @@ -1270,6 +1296,10 @@ "title": "Toyota: Enforce Factory Longitudinal Control", "description": "When enabled, sunnypilot will not take over control of gas and brakes. Factory Toyota longitudinal control will be used." }, + "ToyotaStopAndGoHack": { + "title": "Toyota: Stop and Go Hack (Alpha)", + "description": "sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models that are able to use longitudinal control. This is an alpha feature. Use at your own risk." + }, "TrainingVersion": { "title": "Training Version", "description": "" diff --git a/sunnypilot/sunnylink/sunnylink_state.py b/sunnypilot/sunnylink/sunnylink_state.py index acd20d23ae..927d041991 100644 --- a/sunnypilot/sunnylink/sunnylink_state.py +++ b/sunnypilot/sunnylink/sunnylink_state.py @@ -109,6 +109,8 @@ class SunnylinkState: self.sunnylink_dongle_id = self._params.get("SunnylinkDongleId") self._api = SunnylinkApi(self.sunnylink_dongle_id) + self._panel_open = False + self._load_initial_state() def _load_initial_state(self) -> None: @@ -166,10 +168,14 @@ class SunnylinkState: def _worker_thread(self) -> None: while self._running: - self._sm.update() - if self.is_connected(): - self._fetch_roles() - self._fetch_users() + with self._lock: + panel_open = self._panel_open + + if panel_open: + self._sm.update() + if self.is_connected(): + self._fetch_roles() + self._fetch_users() for _ in range(int(self.FETCH_INTERVAL / self.SLEEP_INTERVAL)): if not self._running: @@ -221,5 +227,9 @@ class SunnylinkState: else: return style.ITEM_TEXT_VALUE_COLOR + def set_settings_open(self, _open: bool) -> None: + with self._lock: + self._panel_open = _open + def __del__(self): self.stop() diff --git a/sunnypilot/sunnylink/tests/test_params_sync.py b/sunnypilot/sunnylink/tests/test_params_sync.py index 26bdca42d6..05114de49a 100644 --- a/sunnypilot/sunnylink/tests/test_params_sync.py +++ b/sunnypilot/sunnylink/tests/test_params_sync.py @@ -200,3 +200,85 @@ def test_known_params_metadata(): assert acc_long["min"] == 1 assert acc_long["max"] == 10 assert acc_long["step"] == 1 + + +def test_torque_control_tune_versions_in_sync(): + """ + Test that TorqueControlTune options in params_metadata.json match versions in latcontrol_torque_versions.json. + + Why: + The TorqueControlTune dropdown in the UI should always reflect the available torque tune versions. + If versions are added/removed from latcontrol_torque_versions.json, the metadata must be updated accordingly. + + Expected: + - TorqueControlTune should have a 'Default' option with empty string value + - All versions from latcontrol_torque_versions.json should be present in the options + - The version values and labels should match between both files + """ + from openpilot.common.basedir import BASEDIR + + versions_json_path = os.path.join(BASEDIR, "sunnypilot", "selfdrive", "controls", "lib", "latcontrol_torque_versions.json") + sync_script_path = "python3 sunnypilot/sunnylink/tools/sync_torque_versions.py" + + # Load both files + with open(METADATA_PATH) as f: + metadata = json.load(f) + + with open(versions_json_path) as f: + versions = json.load(f) + + # Get TorqueControlTune metadata + torque_tune = metadata.get("TorqueControlTune") + if torque_tune is None: + pytest.fail(f"TorqueControlTune not found in params_metadata.json. Please run '{sync_script_path}' to sync.") + + if "options" not in torque_tune: + pytest.fail(f"TorqueControlTune must have options. Please run '{sync_script_path}' to sync.") + + options = torque_tune["options"] + if not isinstance(options, list): + pytest.fail(f"TorqueControlTune options must be a list. Please run '{sync_script_path}' to sync.") + + if len(options) == 0: + pytest.fail(f"TorqueControlTune must have at least one option. Please run '{sync_script_path}' to sync.") + + # Check that Default option exists + default_option = next((opt for opt in options if opt.get("value") == ""), None) + if default_option is None: + pytest.fail(f"TorqueControlTune must have a 'Default' option with empty string value. Please run '{sync_script_path}' to sync.") + + if default_option.get("label") != "Default": + pytest.fail(f"Default option must have label 'Default'. Please run '{sync_script_path}' to sync.") + + # Build expected options from versions.json + expected_version_keys = set(versions.keys()) + actual_version_keys = set() + + for option in options: + if option.get("value") == "": + continue # Skip the default option + + label = option.get("label") + value = option.get("value") + + # Check that this option corresponds to a version + if label not in versions: + pytest.fail(f"Option label '{label}' not found in latcontrol_torque_versions.json. Please run '{sync_script_path}' to sync.") + + # Check that the value matches the version number + expected_value = float(versions[label]["version"]) + if value != expected_value: + pytest.fail(f"Option '{label}' has value {value}, expected {expected_value}. Please run '{sync_script_path}' to sync.") + + actual_version_keys.add(label) + + # Check that all versions are represented + missing_versions = expected_version_keys - actual_version_keys + if missing_versions: + pytest.fail(f"The following versions are missing from TorqueControlTune options: {missing_versions}. " + + f"Please run '{sync_script_path}' to sync.") + + extra_versions = actual_version_keys - expected_version_keys + if extra_versions: + pytest.fail("The following versions in TorqueControlTune options are not in latcontrol_torque_versions.json: " + + f"{extra_versions}. Please run '{sync_script_path}' to sync.") diff --git a/sunnypilot/sunnylink/tools/update_params_metadata.py b/sunnypilot/sunnylink/tools/update_params_metadata.py index ac2ef556e6..013544005b 100755 --- a/sunnypilot/sunnylink/tools/update_params_metadata.py +++ b/sunnypilot/sunnylink/tools/update_params_metadata.py @@ -8,9 +8,11 @@ See the LICENSE.md file in the root directory for more details. import json import os +from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params METADATA_PATH = os.path.join(os.path.dirname(__file__), "../params_metadata.json") +TORQUE_VERSIONS_JSON = os.path.join(BASEDIR, "sunnypilot", "selfdrive", "controls", "lib", "latcontrol_torque_versions.json") def main(): @@ -51,6 +53,59 @@ def main(): print(f"Updated {METADATA_PATH}") + # update onroad screen brightness params + update_onroad_brightness_param() + + # update torque versions param + update_torque_versions_param() + + +def update_onroad_brightness_param(): + try: + with open(METADATA_PATH) as f: + params_metadata = json.load(f) + if "OnroadScreenOffBrightness" in params_metadata: + options = [ + {"value": 0, "label": "Auto (Default)"}, + {"value": 1, "label": "Auto (Dark)"}, + {"value": 2, "label": "Screen Off"}, + ] + for i in range(3, 23): + options.append({"value": i, "label": f"{(i - 2) * 5} %"}) + params_metadata["OnroadScreenOffBrightness"]["options"] = options + with open(METADATA_PATH, 'w') as f: + json.dump(params_metadata, f, indent=2) + f.write('\n') + print(f"Updated OnroadScreenOffBrightness options in params_metadata.json with {len(options)} options.") + except Exception as e: + print(f"Failed to update OnroadScreenOffBrightness versions in params_metadata.json: {e}") + + +def update_torque_versions_param(): + with open(TORQUE_VERSIONS_JSON) as f: + current_versions = json.load(f) + + try: + with open(METADATA_PATH) as f: + params_metadata = json.load(f) + + options = [{"value": "", "label": "Default"}] + for version_key, version_data in current_versions.items(): + version_value = float(version_data["version"]) + options.append({"value": version_value, "label": str(version_key)}) + + if "TorqueControlTune" in params_metadata: + params_metadata["TorqueControlTune"]["options"] = options + + with open(METADATA_PATH, 'w') as f: + json.dump(params_metadata, f, indent=2) + f.write('\n') + + print(f"Updated TorqueControlTune options in params_metadata.json with {len(options)} options: \n{options}") + + except Exception as e: + print(f"Failed to update TorqueControlTune versions in params_metadata.json: {e}") + if __name__ == "__main__": main() diff --git a/sunnypilot/modeld/models/__init__.py b/sunnypilot/system/__init__.py similarity index 100% rename from sunnypilot/modeld/models/__init__.py rename to sunnypilot/system/__init__.py diff --git a/sunnypilot/system/params_migration.py b/sunnypilot/system/params_migration.py new file mode 100644 index 0000000000..6a82f1866d --- /dev/null +++ b/sunnypilot/system/params_migration.py @@ -0,0 +1,27 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.common.swaglog import cloudlog + +ONROAD_BRIGHTNESS_MIGRATION_VERSION: str = "1.0" + + +def run_migration(_params): + # migrate OnroadScreenOffBrightness + if _params.get("OnroadScreenOffBrightnessMigrated") != ONROAD_BRIGHTNESS_MIGRATION_VERSION: + try: + val = _params.get("OnroadScreenOffBrightness") + if val >= 2: # old: 5%, new: Screen Off + new_val = val + 1 + _params.put("OnroadScreenOffBrightness", new_val) + log_str = f"Successfully migrated OnroadScreenOffBrightness from {val} to {new_val}." + else: + log_str = "Migration not required for OnroadScreenOffBrightness." + + _params.put("OnroadScreenOffBrightnessMigrated", ONROAD_BRIGHTNESS_MIGRATION_VERSION) + cloudlog.info(log_str + f" Setting OnroadScreenOffBrightnessMigrated to {ONROAD_BRIGHTNESS_MIGRATION_VERSION}") + except Exception as e: + cloudlog.exception(f"Error migrating OnroadScreenOffBrightness: {e}") diff --git a/system/camerad/SConscript b/system/camerad/SConscript index e288c6d8b0..c28330b32c 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, 'OpenCL', messaging, visionipc] +libs = [common, messaging, visionipc] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 88bca7f775..329192b63a 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -7,7 +7,7 @@ #include "system/camerad/cameras/spectra.h" -void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { +void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { vipc_server = v; stream_type = type; frame_buf_count = frame_cnt; @@ -21,9 +21,8 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera * const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride; for (int i = 0; i < frame_buf_count; i++) { camera_bufs_raw[i].allocate(raw_frame_size); - camera_bufs_raw[i].init_cl(device_id, context); } - LOGD("allocated %d CL buffers", frame_buf_count); + LOGD("allocated %d buffers", frame_buf_count); } vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index c26859cbc4..7f35e06a83 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -36,7 +36,7 @@ public: CameraBuf() = default; ~CameraBuf(); - void init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); + void init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); void sendFrameToVipc(); }; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index d741e13cf3..6a7f599ab6 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -12,16 +12,8 @@ #include #include -#ifdef __TICI__ -#include "CL/cl_ext_qcom.h" -#else -#define CL_PRIORITY_HINT_HIGH_QCOM NULL -#define CL_CONTEXT_PRIORITY_HINT_QCOM NULL -#endif - #include "media/cam_sensor_cmn_header.h" -#include "common/clutil.h" #include "common/params.h" #include "common/swaglog.h" @@ -57,7 +49,7 @@ public: CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config) {}; ~CameraState(); - void init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void init(VisionIpcServer *v); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void set_exposure_rect(); @@ -68,8 +60,8 @@ public: } }; -void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { - camera.camera_open(v, device_id, ctx); +void CameraState::init(VisionIpcServer *v) { + camera.camera_open(v); if (!camera.enabled) return; @@ -257,11 +249,7 @@ void CameraState::sendState() { void camerad_thread() { // TODO: centralize enabled handling - cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); - const cl_context_properties props[] = {CL_CONTEXT_PRIORITY_HINT_QCOM, CL_PRIORITY_HINT_HIGH_QCOM, 0}; - cl_context ctx = CL_CHECK_ERR(clCreateContext(props, 1, &device_id, NULL, NULL, &err)); - - VisionIpcServer v("camerad", device_id, ctx); + VisionIpcServer v("camerad"); // *** initial ISP init *** SpectraMaster m; @@ -271,7 +259,7 @@ void camerad_thread() { std::vector> cams; for (const auto &config : ALL_CAMERA_CONFIGS) { auto cam = std::make_unique(&m, config); - cam->init(&v, device_id, ctx); + cam->init(&v); cams.emplace_back(std::move(cam)); } diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 5c3e7a9d23..73e0a78da3 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -274,7 +274,7 @@ int SpectraCamera::clear_req_queue() { return ret; } -void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { +void SpectraCamera::camera_open(VisionIpcServer *v) { if (!openSensor()) { return; } @@ -296,7 +296,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c linkDevices(); LOGD("camera init %d", cc.camera_num); - buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type); + buf.init(this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); clearAndRequeue(1); } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 13cb13f98f..a02b8a6cac 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -113,7 +113,7 @@ public: SpectraCamera(SpectraMaster *master, const CameraConfig &config); ~SpectraCamera(); - void camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void camera_open(VisionIpcServer *v); bool handle_camera_event(const cam_req_mgr_message *event_data); void camera_close(); void camera_map_bufs(); diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index 1f3f97b082..5f8de86899 100644 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -3,51 +3,103 @@ import time import pytest import numpy as np -import cereal.messaging as messaging from cereal.services import SERVICE_LIST -from openpilot.system.manager.process_config import managed_processes from openpilot.tools.lib.log_time_series import msgs_to_time_series +from openpilot.system.camerad.snapshot import get_snapshots +from openpilot.selfdrive.test.helpers import collect_logs, log_collector, processes_context TEST_TIMESPAN = 10 CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') +EXPOSURE_STABLE_COUNT = 3 +EXPOSURE_RANGE = (0.15, 0.35) +MAX_TEST_TIME = 25 + + +def _numpy_rgb2gray(im): + return np.clip(im[:,:,2] * 0.114 + im[:,:,1] * 0.587 + im[:,:,0] * 0.299, 0, 255).astype(np.uint8) + +def _exposure_stats(im): + h, w = im.shape[:2] + gray = _numpy_rgb2gray(im[h//10:9*h//10, w//10:9*w//10]) + return float(np.median(gray) / 255.), float(np.mean(gray) / 255.) + +def _in_range(median, mean): + lo, hi = EXPOSURE_RANGE + return lo < median < hi and lo < mean < hi + +def _exposure_stable(results): + return all( + len(v) >= EXPOSURE_STABLE_COUNT and all(_in_range(*s) for s in v[-EXPOSURE_STABLE_COUNT:]) + for v in results.values() + ) def run_and_log(procs, services, duration): - logs = [] - - try: - for p in procs: - managed_processes[p].start() - socks = [messaging.sub_sock(s, conflate=False, timeout=100) for s in services] - - start_time = time.monotonic() - while time.monotonic() - start_time < duration: - for s in socks: - logs.extend(messaging.drain_sock(s)) - for p in procs: - assert managed_processes[p].proc.is_alive() - finally: - for p in procs: - managed_processes[p].stop() - - return logs + with processes_context(procs): + return collect_logs(services, duration) @pytest.fixture(scope="module") -def logs(): - logs = run_and_log(["camerad", ], CAMERAS, TEST_TIMESPAN) - ts = msgs_to_time_series(logs) +def _camera_session(): + """Single camerad session that collects logs and exposure data. + Runs until exposure stabilizes (min TEST_TIMESPAN seconds for enough log data).""" + with processes_context(["camerad"]), log_collector(CAMERAS) as (raw_logs, lock): + exposure = {cam: [] for cam in CAMERAS} + start = time.monotonic() + while time.monotonic() - start < MAX_TEST_TIME: + rpic, dpic = get_snapshots(frame="roadCameraState", front_frame="driverCameraState") + wpic, _ = get_snapshots(frame="wideRoadCameraState") + for cam, img in zip(CAMERAS, [rpic, dpic, wpic], strict=True): + exposure[cam].append(_exposure_stats(img)) + + if time.monotonic() - start >= TEST_TIMESPAN and _exposure_stable(exposure): + break + + elapsed = time.monotonic() - start + + with lock: + ts = msgs_to_time_series(raw_logs) for cam in CAMERAS: - expected_frames = SERVICE_LIST[cam].frequency * TEST_TIMESPAN + expected_frames = SERVICE_LIST[cam].frequency * elapsed cnt = len(ts[cam]['t']) assert expected_frames*0.8 < cnt < expected_frames*1.2, f"unexpected frame count {cam}: {expected_frames=}, got {cnt}" dts = np.abs(np.diff([ts[cam]['timestampSof']/1e6]) - 1000/SERVICE_LIST[cam].frequency) assert (dts < 1.0).all(), f"{cam} dts(ms) out of spec: max diff {dts.max()}, 99 percentile {np.percentile(dts, 99)}" - return ts + + return ts, exposure + +@pytest.fixture(scope="module") +def logs(_camera_session): + return _camera_session[0] + +@pytest.fixture(scope="module") +def exposure_data(_camera_session): + return _camera_session[1] @pytest.mark.tici class TestCamerad: + @pytest.mark.parametrize("cam", CAMERAS) + def test_camera_exposure(self, exposure_data, cam): + lo, hi = EXPOSURE_RANGE + checks = exposure_data[cam] + assert len(checks) >= EXPOSURE_STABLE_COUNT, f"{cam}: only got {len(checks)} samples" + + # check that exposure converges into the valid range + passed = sum(_in_range(med, mean) for med, mean in checks) + assert passed >= EXPOSURE_STABLE_COUNT, \ + f"{cam}: only {passed}/{len(checks)} checks in range. " + \ + " | ".join(f"#{i+1}: med={m:.4f} mean={u:.4f}" for i, (m, u) in enumerate(checks)) + + # check that exposure is stable once converged (no regressions) + in_range = False + for i, (median, mean) in enumerate(checks): + ok = _in_range(median, mean) + if in_range and not ok: + pytest.fail(f"{cam}: exposure regressed on sample {i+1} " + + f"(median={median:.4f}, mean={mean:.4f}, expected: ({lo}, {hi}))") + in_range = ok + def test_frame_skips(self, logs): for c in CAMERAS: assert set(np.diff(logs[c]['frameId'])) == {1, }, f"{c} has frame skips" @@ -91,7 +143,10 @@ class TestCamerad: def test_stress_test(self): os.environ['SPECTRA_ERROR_PROB'] = '0.008' - logs = run_and_log(["camerad", ], CAMERAS, 10) + try: + logs = run_and_log(["camerad", ], CAMERAS, 10) + finally: + del os.environ['SPECTRA_ERROR_PROB'] ts = msgs_to_time_series(logs) # we should see some jumps from introduced errors diff --git a/system/camerad/test/test_exposure.py b/system/camerad/test/test_exposure.py deleted file mode 100644 index 6f89e04800..0000000000 --- a/system/camerad/test/test_exposure.py +++ /dev/null @@ -1,51 +0,0 @@ -import time -import numpy as np -import pytest - -from openpilot.selfdrive.test.helpers import with_processes -from openpilot.system.camerad.snapshot import get_snapshots - -TEST_TIME = 45 -REPEAT = 5 - -@pytest.mark.tici -class TestCamerad: - @classmethod - def setup_class(cls): - pass - - def _numpy_rgb2gray(self, im): - ret = np.clip(im[:,:,2] * 0.114 + im[:,:,1] * 0.587 + im[:,:,0] * 0.299, 0, 255).astype(np.uint8) - return ret - - def _is_exposure_okay(self, i, med_mean=None): - if med_mean is None: - med_mean = np.array([[0.18,0.3],[0.18,0.3]]) - h, w = i.shape[:2] - i = i[h//10:9*h//10,w//10:9*w//10] - med_ex, mean_ex = med_mean - i = self._numpy_rgb2gray(i) - i_median = np.median(i) / 255. - i_mean = np.mean(i) / 255. - print([i_median, i_mean]) - return med_ex[0] < i_median < med_ex[1] and mean_ex[0] < i_mean < mean_ex[1] - - @with_processes(['camerad']) - def test_camera_operation(self): - passed = 0 - start = time.monotonic() - while time.monotonic() - start < TEST_TIME and passed < REPEAT: - rpic, dpic = get_snapshots(frame="roadCameraState", front_frame="driverCameraState") - wpic, _ = get_snapshots(frame="wideRoadCameraState") - - res = self._is_exposure_okay(rpic) - res = res and self._is_exposure_okay(dpic) - res = res and self._is_exposure_okay(wpic) - - if passed > 0 and not res: - passed = -passed # fails test if any failure after first sus - break - - passed += int(res) - time.sleep(2) - assert passed >= REPEAT diff --git a/system/hardware/base.py b/system/hardware/base.py index c12c0758f5..1a19f908c6 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -52,7 +52,8 @@ class ThermalConfig: memory: ThermalZone | None = None intake: ThermalZone | None = None exhaust: ThermalZone | None = None - case: ThermalZone | None = None + gnss: ThermalZone | None = None + bottomSoc: ThermalZone | None = None def get_msg(self): ret = {} diff --git a/system/hardware/esim.py b/system/hardware/esim.py index 58ead6593f..9b7d4f9ec0 100755 --- a/system/hardware/esim.py +++ b/system/hardware/esim.py @@ -7,7 +7,6 @@ from openpilot.system.hardware import HARDWARE if __name__ == '__main__': parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai') - parser.add_argument('--backend', choices=['qmi', 'at'], default='qmi', help='use the specified backend, defaults to qmi') parser.add_argument('--switch', metavar='iccid', help='switch to profile') parser.add_argument('--delete', metavar='iccid', help='delete profile (warning: this cannot be undone)') parser.add_argument('--download', nargs=2, metavar=('qr', 'name'), help='download a profile using QR code (format: LPA:1$rsp.truphone.com$QRF-SPEEDTEST)') diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py deleted file mode 100644 index b489286f50..0000000000 --- a/system/hardware/tici/esim.py +++ /dev/null @@ -1,106 +0,0 @@ -import json -import os -import shutil -import subprocess -from typing import Literal - -from openpilot.system.hardware.base import LPABase, LPAError, LPAProfileNotFoundError, Profile - -class TiciLPA(LPABase): - def __init__(self, interface: Literal['qmi', 'at'] = 'qmi'): - self.env = os.environ.copy() - self.env['LPAC_APDU'] = interface - self.env['QMI_DEVICE'] = '/dev/cdc-wdm0' - self.env['AT_DEVICE'] = '/dev/ttyUSB2' - - self.timeout_sec = 45 - - if shutil.which('lpac') is None: - raise LPAError('lpac not found, must be installed!') - - def list_profiles(self) -> list[Profile]: - msgs = self._invoke('profile', 'list') - self._validate_successful(msgs) - return [Profile( - iccid=p['iccid'], - nickname=p['profileNickname'], - enabled=p['profileState'] == 'enabled', - provider=p['serviceProviderName'] - ) for p in msgs[-1]['payload']['data']] - - def get_active_profile(self) -> Profile | None: - return next((p for p in self.list_profiles() if p.enabled), None) - - def delete_profile(self, iccid: str) -> None: - self._validate_profile_exists(iccid) - latest = self.get_active_profile() - if latest is not None and latest.iccid == iccid: - raise LPAError('cannot delete active profile, switch to another profile first') - self._validate_successful(self._invoke('profile', 'delete', iccid)) - self._process_notifications() - - def download_profile(self, qr: str, nickname: str | None = None) -> None: - msgs = self._invoke('profile', 'download', '-a', qr) - self._validate_successful(msgs) - new_profile = next((m for m in msgs if m['payload']['message'] == 'es8p_meatadata_parse'), None) - if new_profile is None: - raise LPAError('no new profile found') - if nickname: - self.nickname_profile(new_profile['payload']['data']['iccid'], nickname) - self._process_notifications() - - def nickname_profile(self, iccid: str, nickname: str) -> None: - self._validate_profile_exists(iccid) - self._validate_successful(self._invoke('profile', 'nickname', iccid, nickname)) - - def switch_profile(self, iccid: str) -> None: - self._validate_profile_exists(iccid) - latest = self.get_active_profile() - if latest and latest.iccid == iccid: - return - self._validate_successful(self._invoke('profile', 'enable', iccid)) - self._process_notifications() - - def _invoke(self, *cmd: str): - proc = subprocess.Popen(['sudo', '-E', 'lpac'] + list(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env) - try: - out, err = proc.communicate(timeout=self.timeout_sec) - except subprocess.TimeoutExpired as e: - proc.kill() - raise LPAError(f"lpac {cmd} timed out after {self.timeout_sec} seconds") from e - - messages = [] - for line in out.decode().strip().splitlines(): - if line.startswith('{'): - message = json.loads(line) - - # lpac response format validations - assert 'type' in message, 'expected type in message' - assert message['type'] == 'lpa' or message['type'] == 'progress', 'expected lpa or progress message type' - assert 'payload' in message, 'expected payload in message' - assert 'code' in message['payload'], 'expected code in message payload' - assert 'data' in message['payload'], 'expected data in message payload' - - msg_ret_code = message['payload']['code'] - if msg_ret_code != 0: - raise LPAError(f"lpac {' '.join(cmd)} failed with code {msg_ret_code}: <{message['payload']['message']}> {message['payload']['data']}") - - messages.append(message) - - if len(messages) == 0: - raise LPAError(f"lpac {cmd} returned no messages") - - return messages - - def _process_notifications(self) -> None: - """ - Process notifications stored on the eUICC, typically to activate/deactivate the profile with the carrier. - """ - self._validate_successful(self._invoke('notification', 'process', '-a', '-r')) - - def _validate_profile_exists(self, iccid: str) -> None: - if not any(p.iccid == iccid for p in self.list_profiles()): - raise LPAProfileNotFoundError(f'profile {iccid} does not exist') - - def _validate_successful(self, msgs: list[dict]) -> None: - assert msgs[-1]['payload']['message'] == 'success', 'expected success notification' diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 5a84afce03..15c6e41695 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -12,7 +12,7 @@ from openpilot.common.utils import sudo_read, sudo_write from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action from openpilot.system.hardware.base import HardwareBase, LPABase, ThermalConfig, ThermalZone from openpilot.system.hardware.tici import iwlist -from openpilot.system.hardware.tici.esim import TiciLPA +from openpilot.system.hardware.tici.lpa import TiciLPA from openpilot.system.hardware.tici.pins import GPIO from openpilot.system.hardware.tici.amplifier import Amplifier @@ -321,11 +321,12 @@ class Tici(HardwareBase): os.system("sudo poweroff") def get_thermal_config(self): - intake, exhaust, case = None, None, None + intake, exhaust, gnss, bottomSoc = None, None, None, None if self.get_device_type() == "mici": - case = ThermalZone("case") + gnss = ThermalZone("gnss") intake = ThermalZone("intake") exhaust = ThermalZone("exhaust") + bottomSoc = ThermalZone("bottom_soc") return ThermalConfig(cpu=[ThermalZone(f"cpu{i}-silver-usr") for i in range(4)] + [ThermalZone(f"cpu{i}-gold-usr") for i in range(4)], gpu=[ThermalZone("gpu0-usr"), ThermalZone("gpu1-usr")], @@ -334,7 +335,8 @@ class Tici(HardwareBase): pmic=[ThermalZone("pm8998_tz"), ThermalZone("pm8005_tz")], intake=intake, exhaust=exhaust, - case=case) + gnss=gnss, + bottomSoc=bottomSoc) def set_display_power(self, on): try: @@ -456,14 +458,10 @@ class Tici(HardwareBase): def configure_modem(self): sim_id = self.get_sim_info().get('sim_id', '') - modem = self.get_modem() - try: - manufacturer = str(modem.Get(MM_MODEM, 'Manufacturer', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)) - except Exception: - manufacturer = None - cmds = [] + modem = self.get_modem() + # Quectel EG25 if self.get_device_type() in ("tizi", ): # clear out old blue prime initial APN os.system('mmcli -m any --3gpp-set-initial-eps-bearer-settings="apn="') @@ -478,16 +476,8 @@ class Tici(HardwareBase): 'AT+QNVFW="/nv/item_files/ims/IMS_enable",00', 'AT+QNVFW="/nv/item_files/modem/mmode/ue_usage_setting",01', ] - elif manufacturer == 'Cavli Inc.': - cmds += [ - 'AT^SIMSWAP=1', # use SIM slot, instead of internal eSIM - 'AT$QCSIMSLEEP=0', # disable SIM sleep - 'AT$QCSIMCFG=SimPowerSave,0', # more sleep disable - # ethernet config - 'AT$QCPCFG=usbNet,0', - 'AT$QCNETDEVCTL=3,1', - ] + # Quectel EG916 else: # this modem gets upset with too many AT commands if sim_id is None or len(sim_id) == 0: diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py new file mode 100644 index 0000000000..2e7e6a0ba9 --- /dev/null +++ b/system/hardware/tici/lpa.py @@ -0,0 +1,281 @@ +# SGP.22 v2.3: https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2021/07/SGP.22-v2.3.pdf + +import atexit +import base64 +import math +import os +import serial +import sys + +from collections.abc import Generator + +from openpilot.system.hardware.base import LPABase, Profile + + +DEFAULT_DEVICE = "/dev/ttyUSB2" +DEFAULT_BAUD = 9600 +DEFAULT_TIMEOUT = 5.0 +# https://euicc-manual.osmocom.org/docs/lpa/applet-id/ +ISDR_AID = "A0000005591010FFFFFFFF8900000100" +MM = "org.freedesktop.ModemManager1" +MM_MODEM = MM + ".Modem" +ES10X_MSS = 120 +DEBUG = os.environ.get("DEBUG") == "1" + +# TLV Tags +TAG_ICCID = 0x5A +TAG_PROFILE_INFO_LIST = 0xBF2D + +STATE_LABELS = {0: "disabled", 1: "enabled", 255: "unknown"} +ICON_LABELS = {0: "jpeg", 1: "png", 255: "unknown"} +CLASS_LABELS = {0: "test", 1: "provisioning", 2: "operational", 255: "unknown"} + + +def b64e(data: bytes) -> str: + return base64.b64encode(data).decode("ascii") + + +class AtClient: + def __init__(self, device: str, baud: int, timeout: float, debug: bool) -> None: + self.debug = debug + self.channel: str | None = None + self._timeout = timeout + self._serial: serial.Serial | None = None + try: + self._serial = serial.Serial(device, baudrate=baud, timeout=timeout) + self._serial.reset_input_buffer() + except (serial.SerialException, PermissionError, OSError): + pass + + def close(self) -> None: + try: + if self.channel: + self.query(f"AT+CCHC={self.channel}") + self.channel = None + finally: + if self._serial: + self._serial.close() + + def _send(self, cmd: str) -> None: + if self.debug: + print(f"SER >> {cmd}", file=sys.stderr) + self._serial.write((cmd + "\r").encode("ascii")) + + def _expect(self) -> list[str]: + lines: list[str] = [] + while True: + raw = self._serial.readline() + if not raw: + raise TimeoutError("AT command timed out") + line = raw.decode(errors="ignore").strip() + if not line: + continue + if self.debug: + print(f"SER << {line}", file=sys.stderr) + if line == "OK": + return lines + if line == "ERROR" or line.startswith("+CME ERROR"): + raise RuntimeError(f"AT command failed: {line}") + lines.append(line) + + def _get_modem(self): + import dbus + bus = dbus.SystemBus() + mm = bus.get_object(MM, '/org/freedesktop/ModemManager1') + objects = mm.GetManagedObjects(dbus_interface="org.freedesktop.DBus.ObjectManager", timeout=self._timeout) + modem_path = list(objects.keys())[0] + return bus.get_object(MM, modem_path) + + def _dbus_query(self, cmd: str) -> list[str]: + if self.debug: + print(f"DBUS >> {cmd}", file=sys.stderr) + try: + result = str(self._get_modem().Command(cmd, math.ceil(self._timeout), dbus_interface=MM_MODEM, timeout=self._timeout)) + except Exception as e: + raise RuntimeError(f"AT command failed: {e}") from e + lines = [line.strip() for line in result.splitlines() if line.strip()] + if self.debug: + for line in lines: + print(f"DBUS << {line}", file=sys.stderr) + return lines + + def query(self, cmd: str) -> list[str]: + if self._serial: + self._send(cmd) + return self._expect() + return self._dbus_query(cmd) + + def open_isdr(self) -> None: + # close any stale logical channel from a previous crashed session + try: + self.query("AT+CCHC=1") + except RuntimeError: + pass + for line in self.query(f'AT+CCHO="{ISDR_AID}"'): + if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()): + self.channel = ch + return + raise RuntimeError("Failed to open ISD-R application") + + def send_apdu(self, apdu: bytes) -> tuple[bytes, int, int]: + if not self.channel: + raise RuntimeError("Logical channel is not open") + hex_payload = apdu.hex().upper() + for line in self.query(f'AT+CGLA={self.channel},{len(hex_payload)},"{hex_payload}"'): + if line.startswith("+CGLA:"): + parts = line.split(":", 1)[1].split(",", 1) + if len(parts) == 2: + data = bytes.fromhex(parts[1].strip().strip('"')) + if len(data) >= 2: + return data[:-2], data[-2], data[-1] + raise RuntimeError("Missing +CGLA response") + + +# --- TLV utilities --- + +def iter_tlv(data: bytes, with_positions: bool = False) -> Generator: + idx, length = 0, len(data) + while idx < length: + start_pos = idx + tag = data[idx] + idx += 1 + if tag & 0x1F == 0x1F: # Multi-byte tag + tag_value = tag + while idx < length: + next_byte = data[idx] + idx += 1 + tag_value = (tag_value << 8) | next_byte + if not (next_byte & 0x80): + break + else: + tag_value = tag + if idx >= length: + break + size = data[idx] + idx += 1 + if size & 0x80: # Multi-byte length + num_bytes = size & 0x7F + if idx + num_bytes > length: + break + size = int.from_bytes(data[idx : idx + num_bytes], "big") + idx += num_bytes + if idx + size > length: + break + value = data[idx : idx + size] + idx += size + yield (tag_value, value, start_pos, idx) if with_positions else (tag_value, value) + + +def find_tag(data: bytes, target: int) -> bytes | None: + return next((v for t, v in iter_tlv(data) if t == target), None) + + +def tbcd_to_string(raw: bytes) -> str: + return "".join(str(n) for b in raw for n in (b & 0x0F, b >> 4) if n <= 9) + + +# Profile field decoders: TLV tag -> (field_name, decoder) +_PROFILE_FIELDS = { + TAG_ICCID: ("iccid", tbcd_to_string), + 0x4F: ("isdpAid", lambda v: v.hex().upper()), + 0x9F70: ("profileState", lambda v: STATE_LABELS.get(v[0], "unknown")), + 0x90: ("profileNickname", lambda v: v.decode("utf-8", errors="ignore") or None), + 0x91: ("serviceProviderName", lambda v: v.decode("utf-8", errors="ignore") or None), + 0x92: ("profileName", lambda v: v.decode("utf-8", errors="ignore") or None), + 0x93: ("iconType", lambda v: ICON_LABELS.get(v[0], "unknown")), + 0x94: ("icon", b64e), + 0x95: ("profileClass", lambda v: CLASS_LABELS.get(v[0], "unknown")), +} + + +def _decode_profile_fields(data: bytes) -> dict: + """Parse known profile metadata TLV fields into a dict.""" + result = {} + for tag, value in iter_tlv(data): + if (field := _PROFILE_FIELDS.get(tag)): + result[field[0]] = field[1](value) + return result + + +# --- ES10x command transport --- + +def es10x_command(client: AtClient, data: bytes) -> bytes: + response = bytearray() + sequence = 0 + offset = 0 + while offset < len(data): + chunk = data[offset : offset + ES10X_MSS] + offset += len(chunk) + is_last = offset == len(data) + apdu = bytes([0x80, 0xE2, 0x91 if is_last else 0x11, sequence & 0xFF, len(chunk)]) + chunk + segment, sw1, sw2 = client.send_apdu(apdu) + response.extend(segment) + while True: + if sw1 == 0x61: # More data available + segment, sw1, sw2 = client.send_apdu(bytes([0x80, 0xC0, 0x00, 0x00, sw2 or 0])) + response.extend(segment) + continue + if (sw1 & 0xF0) == 0x90: + break + raise RuntimeError(f"APDU failed with SW={sw1:02X}{sw2:02X}") + sequence += 1 + return bytes(response) + + +# --- Profile operations --- + +def decode_profiles(blob: bytes) -> list[dict]: + root = find_tag(blob, TAG_PROFILE_INFO_LIST) + if root is None: + raise RuntimeError("Missing ProfileInfoList") + list_ok = find_tag(root, 0xA0) + if list_ok is None: + return [] + defaults = {name: None for name, _ in _PROFILE_FIELDS.values()} + return [{**defaults, **_decode_profile_fields(value)} for tag, value in iter_tlv(list_ok) if tag == 0xE3] + + +def list_profiles(client: AtClient) -> list[dict]: + return decode_profiles(es10x_command(client, TAG_PROFILE_INFO_LIST.to_bytes(2, "big") + b"\x00")) + + +class TiciLPA(LPABase): + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self): + if hasattr(self, '_client'): + return + self._client = AtClient(DEFAULT_DEVICE, DEFAULT_BAUD, DEFAULT_TIMEOUT, debug=DEBUG) + self._client.open_isdr() + atexit.register(self._client.close) + + def list_profiles(self) -> list[Profile]: + return [ + Profile( + iccid=p.get("iccid", ""), + nickname=p.get("profileNickname") or "", + enabled=p.get("profileState") == "enabled", + provider=p.get("serviceProviderName") or "", + ) + for p in list_profiles(self._client) + ] + + def get_active_profile(self) -> Profile | None: + return None + + def delete_profile(self, iccid: str) -> None: + return None + + def download_profile(self, qr: str, nickname: str | None = None) -> None: + return None + + def nickname_profile(self, iccid: str, nickname: str) -> None: + return None + + def switch_profile(self, iccid: str) -> None: + return None diff --git a/system/hardware/tici/tests/test_esim.py b/system/hardware/tici/tests/test_esim.py deleted file mode 100644 index 6fab931cce..0000000000 --- a/system/hardware/tici/tests/test_esim.py +++ /dev/null @@ -1,51 +0,0 @@ -import pytest - -from openpilot.system.hardware import HARDWARE, TICI -from openpilot.system.hardware.base import LPAProfileNotFoundError - -# https://euicc-manual.osmocom.org/docs/rsp/known-test-profile -# iccid is always the same for the given activation code -TEST_ACTIVATION_CODE = 'LPA:1$rsp.truphone.com$QRF-BETTERROAMING-PMRDGIR2EARDEIT5' -TEST_ICCID = '8944476500001944011' - -TEST_NICKNAME = 'test_profile' - -def cleanup(): - lpa = HARDWARE.get_sim_lpa() - try: - lpa.delete_profile(TEST_ICCID) - except LPAProfileNotFoundError: - pass - lpa.process_notifications() - -class TestEsim: - - @classmethod - def setup_class(cls): - if not TICI: - pytest.skip() - cleanup() - - @classmethod - def teardown_class(cls): - cleanup() - - def test_provision_enable_disable(self): - lpa = HARDWARE.get_sim_lpa() - current_active = lpa.get_active_profile() - - lpa.download_profile(TEST_ACTIVATION_CODE, TEST_NICKNAME) - assert any(p.iccid == TEST_ICCID and p.nickname == TEST_NICKNAME for p in lpa.list_profiles()) - - lpa.enable_profile(TEST_ICCID) - new_active = lpa.get_active_profile() - assert new_active is not None - assert new_active.iccid == TEST_ICCID - assert new_active.nickname == TEST_NICKNAME - - lpa.disable_profile(TEST_ICCID) - new_active = lpa.get_active_profile() - assert new_active is None - - if current_active: - lpa.enable_profile(current_active.iccid) diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index a92e4c8de8..c4401c9583 100644 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -32,7 +32,7 @@ class Proc: PROCS = [ Proc(['camerad'], 1.65, atol=0.4, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']), - Proc(['modeld'], 1.24, atol=0.2, msgs=['modelV2']), + Proc(['modeld'], 1.5, atol=0.2, msgs=['modelV2']), Proc(['dmonitoringmodeld'], 0.65, atol=0.35, msgs=['driverStateV2']), Proc(['encoderd'], 0.23, msgs=[]), ] diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cf169f4dc6..b02c409240 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -1,17 +1,15 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, - 'avformat', 'avcodec', 'avutil', - 'yuv', 'OpenCL', 'pthread', 'zstd'] + 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', + 'pthread', 'z', 'm', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] + libs += ['yuv'] if arch == "Darwin": - # fix OpenCL - del libs[libs.index('OpenCL')] - env['FRAMEWORKS'] = ['OpenCL'] # exclude v4l del src[src.index('encoder/v4l_encoder.cc')] @@ -23,4 +21,4 @@ env.Program('encoderd', ['encoderd.cc'], LIBS=libs + ["jpeg"]) env.Program('bootlog.cc', LIBS=libs) if GetOption('extras'): - env.Program('tests/test_logger', ['tests/test_runner.cc', 'tests/test_logger.cc', 'tests/test_zstd_writer.cc'], LIBS=libs + ['curl', 'crypto']) + env.Program('tests/test_logger', ['tests/test_runner.cc', 'tests/test_logger.cc', 'tests/test_zstd_writer.cc'], LIBS=libs) diff --git a/system/loggerd/encoder/ffmpeg_encoder.cc b/system/loggerd/encoder/ffmpeg_encoder.cc index 4d6be47182..275a2e481e 100644 --- a/system/loggerd/encoder/ffmpeg_encoder.cc +++ b/system/loggerd/encoder/ffmpeg_encoder.cc @@ -9,7 +9,7 @@ #define __STDC_CONSTANT_MACROS -#include "third_party/libyuv/include/libyuv.h" +#include "libyuv.h" extern "C" { #include diff --git a/system/loggerd/encoder/v4l_encoder.cc b/system/loggerd/encoder/v4l_encoder.cc index 6ee3af13b0..cabd9fd997 100644 --- a/system/loggerd/encoder/v4l_encoder.cc +++ b/system/loggerd/encoder/v4l_encoder.cc @@ -7,7 +7,6 @@ #include "common/util.h" #include "common/timing.h" -#include "third_party/libyuv/include/libyuv.h" #include "third_party/linux/include/msm_media_info.h" // has to be in this order @@ -43,29 +42,29 @@ static void dequeue_buffer(int fd, v4l2_buf_type buf_type, unsigned int *index=N static void queue_buffer(int fd, v4l2_buf_type buf_type, unsigned int index, VisionBuf *buf, struct timeval timestamp={}) { v4l2_plane plane = { + .bytesused = (uint32_t)buf->len, .length = (unsigned int)buf->len, .m = { .userptr = (unsigned long)buf->addr, }, - .bytesused = (uint32_t)buf->len, .reserved = {(unsigned int)buf->fd} }; v4l2_buffer v4l_buf = { - .type = buf_type, .index = index, + .type = buf_type, + .flags = V4L2_BUF_FLAG_TIMESTAMP_COPY, + .timestamp = timestamp, .memory = V4L2_MEMORY_USERPTR, .m = { .planes = &plane, }, .length = 1, - .flags = V4L2_BUF_FLAG_TIMESTAMP_COPY, - .timestamp = timestamp }; util::safe_ioctl(fd, VIDIOC_QBUF, &v4l_buf, "VIDIOC_QBUF failed"); } static void request_buffers(int fd, v4l2_buf_type buf_type, unsigned int count) { struct v4l2_requestbuffers reqbuf = { + .count = count, .type = buf_type, .memory = V4L2_MEMORY_USERPTR, - .count = count }; util::safe_ioctl(fd, VIDIOC_REQBUFS, &reqbuf, "VIDIOC_REQBUFS failed"); } diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index 47da321024..3755929619 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -219,11 +219,11 @@ void handle_preserve_segment(LoggerdState *s) { void loggerd_thread() { // setup messaging - typedef struct ServiceState { + struct ServiceState { std::string name; int counter, freq; bool encoder, preserve_segment, record_audio; - } ServiceState; + }; std::unordered_map service_state; std::unordered_map remote_encoders; diff --git a/system/loggerd/loggerd.h b/system/loggerd/loggerd.h index 8e3a74d2d9..6aa0c8be40 100644 --- a/system/loggerd/loggerd.h +++ b/system/loggerd/loggerd.h @@ -125,10 +125,10 @@ const EncoderInfo stream_driver_encoder_info = { const EncoderInfo qcam_encoder_info = { .publish_name = "qRoadEncodeData", .filename = "qcamera.ts", - .get_settings = [](int){return EncoderSettings::QcamEncoderSettings();}, + .include_audio = Params().getBool("RecordAudio"), .frame_width = 526, .frame_height = 330, - .include_audio = Params().getBool("RecordAudio"), + .get_settings = [](int){return EncoderSettings::QcamEncoderSettings();}, INIT_ENCODE_FUNCTIONS(QRoadEncode), }; diff --git a/system/loggerd/tests/test_encoder.py b/system/loggerd/tests/test_encoder.py index e4dabd3df9..a9de0690aa 100644 --- a/system/loggerd/tests/test_encoder.py +++ b/system/loggerd/tests/test_encoder.py @@ -7,7 +7,7 @@ import subprocess import time from pathlib import Path -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from tqdm import trange from openpilot.common.params import Params diff --git a/system/loggerd/tests/test_logger.cc b/system/loggerd/tests/test_logger.cc index 40a45a68d5..61509c256c 100644 --- a/system/loggerd/tests/test_logger.cc +++ b/system/loggerd/tests/test_logger.cc @@ -56,7 +56,7 @@ void write_msg(LoggerState *logger) { TEST_CASE("logger") { const int segment_cnt = 100; const std::string log_root = "/tmp/test_logger"; - system(("rm " + log_root + " -rf").c_str()); + REQUIRE(system(("rm " + log_root + " -rf").c_str()) == 0); std::string route_name; { LoggerState logger(log_root); diff --git a/system/manager/manager.py b/system/manager/manager.py index 8c5d35d072..8c219909e4 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -22,6 +22,8 @@ from openpilot.system.version import get_build_metadata from openpilot.system.hardware.hw import Paths from openpilot.system.hardware import PC +from openpilot.sunnypilot.system.params_migration import run_migration + def manager_init() -> None: save_bootlog() @@ -49,6 +51,8 @@ def manager_init() -> None: if params.get_bool("RecordFrontLock"): params.put_bool("RecordFront", True) + run_migration(params) + # set unset params to their default value for k in params.all_keys(): default_value = params.get_default_value(k) diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 793fbc07fa..2e43dfada6 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -80,10 +80,6 @@ def use_sunnylink_uploader_shim(started, params, CP: car.CarParams) -> bool: """Shim for use_sunnylink_uploader to match the process manager signature.""" return use_sunnylink_uploader(params) -def is_snpe_model(started, params, CP: car.CarParams) -> bool: - """Check if the active model runner is SNPE.""" - return bool(get_active_model_runner(params, not started) == custom.ModelManagerSP.Runner.snpe) - def is_tinygrad_model(started, params, CP: car.CarParams) -> bool: """Check if the active model runner is SNPE.""" return bool(get_active_model_runner(params, not started) == custom.ModelManagerSP.Runner.tinygrad) @@ -170,7 +166,6 @@ procs = [ procs += [ # Models PythonProcess("models_manager", "sunnypilot.models.manager", only_offroad), - NativeProcess("modeld_snpe", "sunnypilot/modeld", ["./modeld"], and_(only_onroad, is_snpe_model)), NativeProcess("modeld_tinygrad", "sunnypilot/modeld_v2", ["./modeld"], and_(only_onroad, is_tinygrad_model)), # Backup diff --git a/system/ubloxd/binary_struct.py b/system/ubloxd/binary_struct.py index 7b229620a2..c144bd5696 100644 --- a/system/ubloxd/binary_struct.py +++ b/system/ubloxd/binary_struct.py @@ -174,7 +174,7 @@ class BinaryStruct: if not is_dataclass(cls): dataclass(init=False)(cls) fields = list(getattr(cls, '__annotations__', {}).items()) - cls.__binary_fields__ = fields # type: ignore[attr-defined] + cls.__binary_fields__ = fields @classmethod def _read(inner_cls, reader: BinaryReader): @@ -184,7 +184,7 @@ class BinaryStruct: setattr(obj, name, value) return obj - cls._read = _read # type: ignore[attr-defined] + cls._read = _read @classmethod def from_bytes(cls: type[T], data: bytes) -> T: diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 9e902508b8..df4153067c 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -12,7 +12,6 @@ import subprocess from contextlib import contextmanager from collections.abc import Callable from collections import deque -from dataclasses import dataclass from enum import StrEnum from pathlib import Path from typing import NamedTuple @@ -43,7 +42,8 @@ PROFILE_RENDER = int(os.getenv("PROFILE_RENDER", "0")) PROFILE_STATS = int(os.getenv("PROFILE_STATS", "100")) # Number of functions to show in profile output RECORD = os.getenv("RECORD") == "1" RECORD_OUTPUT = str(Path(os.getenv("RECORD_OUTPUT", "output")).with_suffix(".mp4")) -RECORD_BITRATE = os.getenv("RECORD_BITRATE", "") # Target bitrate e.g. "2000k" +RECORD_QUALITY = int(os.getenv("RECORD_QUALITY", "23")) # Dynamic bitrate quality level (CRF); 0 is lossless (bigger size), max is 51, default is 23 for x264 +RECORD_BITRATE = os.getenv("RECORD_BITRATE", "") # Target bitrate e.g. "2000k" (overrides RECORD_QUALITY when set) RECORD_SPEED = int(os.getenv("RECORD_SPEED", "1")) # Speed multiplier OFFSCREEN = os.getenv("OFFSCREEN") == "1" # Disable FPS limiting for fast offline rendering @@ -117,12 +117,6 @@ def font_fallback(font: rl.Font) -> rl.Font: return font -@dataclass -class ModalOverlay: - overlay: object = None - callback: Callable | None = None - - class MousePos(NamedTuple): x: float y: float @@ -228,9 +222,9 @@ class GuiApplication(GuiApplicationExt): self._last_fps_log_time: float = time.monotonic() self._frame = 0 self._window_close_requested = False - self._modal_overlay = ModalOverlay() - self._modal_overlay_shown = False - self._modal_overlay_tick: Callable[[], None] | None = None + self._nav_stack: list[object] = [] + self._nav_stack_ticks: list[Callable[[], None]] = [] + self._nav_stack_widgets_to_render = 1 if self.big_ui() else 2 self._mouse = MouseState(self._scale) self._mouse_events: list[MouseEvent] = [] @@ -259,6 +253,10 @@ class GuiApplication(GuiApplicationExt): def set_show_fps(self, show: bool): self._show_fps = show + @property + def show_touches(self) -> bool: + return self._show_touches + @property def target_fps(self): return self._target_fps @@ -303,8 +301,10 @@ class GuiApplication(GuiApplicationExt): '-r', str(output_fps), # Output frame rate (for speed multiplier) '-c:v', 'libx264', '-preset', 'ultrafast', + '-crf', str(RECORD_QUALITY) ] if RECORD_BITRATE: + # NOTE: custom bitrate overrides crf setting ffmpeg_args += ['-b:v', RECORD_BITRATE, '-maxrate', RECORD_BITRATE, '-bufsize', RECORD_BITRATE] ffmpeg_args += [ '-y', # Overwrite existing file @@ -375,36 +375,92 @@ class GuiApplication(GuiApplicationExt): except Exception: break - def set_modal_overlay(self, overlay, callback: Callable | None = None): - if self._modal_overlay.overlay is not None: - if hasattr(self._modal_overlay.overlay, 'hide_event'): - self._modal_overlay.overlay.hide_event() + def push_widget(self, widget: object): + if widget in self._nav_stack: + cloudlog.warning("Widget already in stack, cannot push again!") + return - if self._modal_overlay.callback is not None: - self._modal_overlay.callback(-1) + # disable previous widget to prevent input processing + if len(self._nav_stack) > 0: + prev_widget = self._nav_stack[-1] + # TODO: change these to touch_valid + prev_widget.set_enabled(False) - self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback) + self._nav_stack.append(widget) + widget.show_event() + widget.set_enabled(True) - def set_modal_overlay_tick(self, tick_function: Callable | None): - self._modal_overlay_tick = tick_function + def pop_widget(self, idx: int | None = None): + # Pops widget instantly without animation + if len(self._nav_stack) < 2: + cloudlog.warning("At least one widget should remain on the stack, ignoring pop!") + return + + idx_to_pop = len(self._nav_stack) - 1 if idx is None else idx + if idx_to_pop <= 0 or idx_to_pop >= len(self._nav_stack): + cloudlog.warning(f"Invalid index {idx_to_pop} to pop, ignoring!") + return + + # only re-enable previous widget if popping top widget + if idx_to_pop == len(self._nav_stack) - 1: + prev_widget = self._nav_stack[idx_to_pop - 1] + prev_widget.set_enabled(True) + + widget = self._nav_stack.pop(idx_to_pop) + widget.hide_event() + + def pop_widgets_to(self, widget: object, callback: Callable[[], None] | None = None, instant: bool = False): + # Pops middle widgets instantly without animation then dismisses top, animated out if NavWidget + if widget not in self._nav_stack: + cloudlog.warning("Widget not in stack, cannot pop to it!") + return + + # Nothing to pop, ensure we still run callback + top_widget = self._nav_stack[-1] + if top_widget == widget: + if callback: + callback() + return + + # instantly pop widgets in between, then dismiss top widget for animation + while len(self._nav_stack) > 1 and self._nav_stack[-2] != widget: + self.pop_widget(len(self._nav_stack) - 2) + + if not instant: + top_widget.dismiss(callback) + else: + self.pop_widget() + + def get_active_widget(self): + if len(self._nav_stack) > 0: + return self._nav_stack[-1] + return None + + def add_nav_stack_tick(self, tick_function: Callable[[], None]): + if tick_function not in self._nav_stack_ticks: + self._nav_stack_ticks.append(tick_function) + + def remove_nav_stack_tick(self, tick_function: Callable[[], None]): + if tick_function in self._nav_stack_ticks: + self._nav_stack_ticks.remove(tick_function) def set_should_render(self, should_render: bool): self._should_render = should_render def texture(self, asset_path: str, width: int | None = None, height: int | None = None, - alpha_premultiply=False, keep_aspect_ratio=True): - cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}{keep_aspect_ratio}" + alpha_premultiply=False, keep_aspect_ratio=True, flip_x: bool = False) -> rl.Texture: + cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}_{keep_aspect_ratio}_{flip_x}" if cache_key in self._textures: return self._textures[cache_key] with as_file(ASSETS_DIR.joinpath(asset_path)) as fspath: - image_obj = self._load_image_from_path(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio) + image_obj = self._load_image_from_path(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio, flip_x) texture_obj = self._load_texture_from_image(image_obj) self._textures[cache_key] = texture_obj return texture_obj def _load_image_from_path(self, image_path: str, width: int | None = None, height: int | None = None, - alpha_premultiply: bool = False, keep_aspect_ratio: bool = True) -> rl.Image: + alpha_premultiply: bool = False, keep_aspect_ratio: bool = True, flip_x: bool = False) -> rl.Image: """Load and resize an image, storing it for later automatic unloading.""" image = rl.load_image(image_path) @@ -433,6 +489,10 @@ class GuiApplication(GuiApplicationExt): rl.image_resize(image, width, height) else: assert keep_aspect_ratio, "Cannot resize without specifying width and height" + + if flip_x: + rl.image_flip_horizontal(image) + return image def _load_texture_from_image(self, image: rl.Image) -> rl.Texture: @@ -530,14 +590,15 @@ class GuiApplication(GuiApplicationExt): rl.begin_drawing() rl.clear_background(rl.BLACK) - # Handle modal overlay rendering and input processing - if self._handle_modal_overlay(): - # Allow a Widget to still run a function while overlay is shown - if self._modal_overlay_tick is not None: - self._modal_overlay_tick() - yield False - else: - yield True + # Allow a Widget to still run a function regardless of the stack depth + for tick in self._nav_stack_ticks: + tick() + + # Only render top widgets + for widget in self._nav_stack[-self._nav_stack_widgets_to_render:]: + widget.render(rl.Rectangle(0, 0, self.width, self.height)) + + yield True if self._render_texture: rl.end_texture_mode() @@ -594,33 +655,6 @@ class GuiApplication(GuiApplicationExt): def height(self): return self._height - def _handle_modal_overlay(self) -> bool: - if self._modal_overlay.overlay: - if hasattr(self._modal_overlay.overlay, 'render'): - result = self._modal_overlay.overlay.render(rl.Rectangle(0, 0, self.width, self.height)) - elif callable(self._modal_overlay.overlay): - result = self._modal_overlay.overlay() - else: - raise Exception - - # Send show event to Widget - if not self._modal_overlay_shown and hasattr(self._modal_overlay.overlay, 'show_event'): - self._modal_overlay.overlay.show_event() - self._modal_overlay_shown = True - - if result >= 0: - # Clear the overlay and execute the callback - original_modal = self._modal_overlay - self._modal_overlay = ModalOverlay() - if hasattr(original_modal.overlay, 'hide_event'): - original_modal.overlay.hide_event() - if original_modal.callback is not None: - original_modal.callback(result) - return True - else: - self._modal_overlay_shown = False - return False - def _load_fonts(self): for font_weight_file in FontWeight: with as_file(FONT_DIR) as fspath: diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index ad2a5f9399..506fc13b0a 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -1,7 +1,7 @@ from importlib.resources import files -import os import json -import gettext +import os +import re from openpilot.common.basedir import BASEDIR from openpilot.common.swaglog import cloudlog @@ -23,14 +23,138 @@ UNIFONT_LANGUAGES = [ "ja", ] +# Plural form selectors for supported languages +PLURAL_SELECTORS = { + 'en': lambda n: 0 if n == 1 else 1, + 'de': lambda n: 0 if n == 1 else 1, + 'fr': lambda n: 0 if n <= 1 else 1, + 'pt-BR': lambda n: 0 if n <= 1 else 1, + 'es': lambda n: 0 if n == 1 else 1, + 'tr': lambda n: 0 if n == 1 else 1, + 'uk': lambda n: 0 if n % 10 == 1 and n % 100 != 11 else (1 if 2 <= n % 10 <= 4 and not 12 <= n % 100 <= 14 else 2), + 'th': lambda n: 0, + 'zh-CHT': lambda n: 0, + 'zh-CHS': lambda n: 0, + 'ko': lambda n: 0, + 'ja': lambda n: 0, +} + + +def _parse_quoted(s: str) -> str: + """Parse a PO-format quoted string.""" + s = s.strip() + if not (s.startswith('"') and s.endswith('"')): + raise ValueError(f"Expected quoted string: {s!r}") + s = s[1:-1] + result: list[str] = [] + i = 0 + while i < len(s): + if s[i] == '\\' and i + 1 < len(s): + c = s[i + 1] + if c == 'n': + result.append('\n') + elif c == 't': + result.append('\t') + elif c == '"': + result.append('"') + elif c == '\\': + result.append('\\') + else: + result.append(s[i:i + 2]) + i += 2 + else: + result.append(s[i]) + i += 1 + return ''.join(result) + + +def load_translations(path) -> tuple[dict[str, str], dict[str, list[str]]]: + """Parse a .po file and return (translations, plurals) dicts. + + translations: msgid -> msgstr + plurals: msgid -> [msgstr[0], msgstr[1], ...] + """ + with open(str(path), encoding='utf-8') as f: + lines = f.readlines() + + translations: dict[str, str] = {} + plurals: dict[str, list[str]] = {} + + # Parser state + msgid = msgid_plural = msgstr = "" + msgstr_plurals: dict[int, str] = {} + field: str | None = None + plural_idx = 0 + + def finish(): + nonlocal msgid, msgid_plural, msgstr, msgstr_plurals, field + if msgid: # skip header (empty msgid) + if msgid_plural: + max_idx = max(msgstr_plurals.keys()) if msgstr_plurals else 0 + plurals[msgid] = [msgstr_plurals.get(i, '') for i in range(max_idx + 1)] + else: + if msgstr: + translations[msgid] = msgstr + msgid = msgid_plural = msgstr = "" + msgstr_plurals = {} + field = None + + for raw in lines: + line = raw.strip() + + if not line: + finish() + continue + + if line.startswith('#'): + continue + + if line.startswith('msgid_plural '): + msgid_plural = _parse_quoted(line[len('msgid_plural '):]) + field = 'msgid_plural' + continue + + if line.startswith('msgid '): + msgid = _parse_quoted(line[len('msgid '):]) + field = 'msgid' + continue + + m = re.match(r'msgstr\[(\d+)]\s+(.*)', line) + if m: + plural_idx = int(m.group(1)) + msgstr_plurals[plural_idx] = _parse_quoted(m.group(2)) + field = 'msgstr_plural' + continue + + if line.startswith('msgstr '): + msgstr = _parse_quoted(line[len('msgstr '):]) + field = 'msgstr' + continue + + if line.startswith('"'): + val = _parse_quoted(line) + if field == 'msgid': + msgid += val + elif field == 'msgid_plural': + msgid_plural += val + elif field == 'msgstr': + msgstr += val + elif field == 'msgstr_plural': + msgstr_plurals[plural_idx] += val + + finish() + return translations, plurals + class Multilang: def __init__(self): self._params = Params() if Params is not None else None self._language: str = "en" - self.languages = {} - self.codes = {} - self._translation: gettext.NullTranslations | gettext.GNUTranslations = gettext.NullTranslations() + self.languages: dict[str, str] = {} + self.codes: dict[str, str] = {} + self._translations: dict[str, str] = {} + self._plurals: dict[str, list[str]] = {} + self._plural_selector = PLURAL_SELECTORS.get('en', lambda n: 0) self._load_languages() @property @@ -43,27 +167,30 @@ class Multilang: def setup(self): try: - with TRANSLATIONS_DIR.joinpath(f'app_{self._language}.mo').open('rb') as fh: - translation = gettext.GNUTranslations(fh) - translation.install() - self._translation = translation + po_path = TRANSLATIONS_DIR.joinpath(f'app_{self._language}.po') + self._translations, self._plurals = load_translations(po_path) + self._plural_selector = PLURAL_SELECTORS.get(self._language, lambda n: 0) cloudlog.debug(f"Loaded translations for language: {self._language}") except FileNotFoundError: cloudlog.error(f"No translation file found for language: {self._language}, using default.") - gettext.install('app') - self._translation = gettext.NullTranslations() + self._translations = {} + self._plurals = {} def change_language(self, language_code: str) -> None: - # Reinstall gettext with the selected language self._params.put("LanguageSetting", language_code) self._language = language_code self.setup() def tr(self, text: str) -> str: - return self._translation.gettext(text) + return self._translations.get(text, text) or text def trn(self, singular: str, plural: str, n: int) -> str: - return self._translation.ngettext(singular, plural, n) + if singular in self._plurals: + idx = self._plural_selector(n) + forms = self._plurals[singular] + if idx < len(forms) and forms[idx]: + return forms[idx] + return singular if n == 1 else plural def _load_languages(self): with LANGUAGES_FILE.open(encoding='utf-8') as f: diff --git a/system/ui/lib/networkmanager.py b/system/ui/lib/networkmanager.py index ffa2ff4db9..d2d6b30b10 100644 --- a/system/ui/lib/networkmanager.py +++ b/system/ui/lib/networkmanager.py @@ -3,14 +3,34 @@ from enum import IntEnum # NetworkManager device states class NMDeviceState(IntEnum): + # https://networkmanager.dev/docs/api/1.46/nm-dbus-types.html#NMDeviceState UNKNOWN = 0 + UNMANAGED = 10 + UNAVAILABLE = 20 DISCONNECTED = 30 PREPARE = 40 - STATE_CONFIG = 50 + CONFIG = 50 NEED_AUTH = 60 IP_CONFIG = 70 + IP_CHECK = 80 + SECONDARIES = 90 ACTIVATED = 100 DEACTIVATING = 110 + FAILED = 120 + + +class NMDeviceStateReason(IntEnum): + # https://networkmanager.dev/docs/api/1.46/nm-dbus-types.html#NMDeviceStateReason + NONE = 0 + UNKNOWN = 1 + IP_CONFIG_UNAVAILABLE = 5 + NO_SECRETS = 7 + SUPPLICANT_DISCONNECT = 8 + SUPPLICANT_TIMEOUT = 11 + CONNECTION_REMOVED = 38 + USER_REQUESTED = 39 + SSID_NOT_FOUND = 53 + NEW_ACTIVATION = 60 # NetworkManager constants @@ -29,8 +49,6 @@ NM_IP4_CONFIG_IFACE = 'org.freedesktop.NetworkManager.IP4Config' NM_DEVICE_TYPE_WIFI = 2 NM_DEVICE_TYPE_MODEM = 8 -NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8 -NM_DEVICE_STATE_REASON_NEW_ACTIVATION = 60 # https://developer.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NM80211ApFlags NM_802_11_AP_FLAGS_NONE = 0x0 diff --git a/system/ui/lib/tests/test_handle_state_change.py b/system/ui/lib/tests/test_handle_state_change.py new file mode 100644 index 0000000000..69aae6fdf3 --- /dev/null +++ b/system/ui/lib/tests/test_handle_state_change.py @@ -0,0 +1,906 @@ +"""Tests for WifiManager._handle_state_change. + +Tests the state machine in isolation by constructing a WifiManager with mocked +DBus, then calling _handle_state_change directly with NM state transitions. +""" +import pytest +from jeepney.low_level import MessageType +from pytest_mock import MockerFixture + +from openpilot.system.ui.lib.networkmanager import NMDeviceState, NMDeviceStateReason +from openpilot.system.ui.lib.wifi_manager import WifiManager, WifiState, ConnectStatus + + +def _make_wm(mocker: MockerFixture, connections=None): + """Create a WifiManager with only the fields _handle_state_change touches.""" + mocker.patch.object(WifiManager, '_initialize') + wm = WifiManager.__new__(WifiManager) + wm._exit = True # prevent stop() from doing anything in __del__ + wm._conn_monitor = mocker.MagicMock() + wm._connections = dict(connections or {}) + wm._wifi_state = WifiState() + wm._user_epoch = 0 + wm._callback_queue = [] + wm._need_auth = [] + wm._activated = [] + wm._update_networks = mocker.MagicMock() + wm._update_active_connection_info = mocker.MagicMock() + wm._get_active_wifi_connection = mocker.MagicMock(return_value=(None, None)) + return wm + + +def fire(wm: WifiManager, new_state: int, prev_state: int = NMDeviceState.UNKNOWN, + reason: int = NMDeviceStateReason.NONE) -> None: + """Feed a state change into the handler.""" + wm._handle_state_change(new_state, prev_state, reason) + + +def fire_wpa_connect(wm: WifiManager) -> None: + """WPA handshake then IP negotiation through ACTIVATED, as seen on device.""" + fire(wm, NMDeviceState.NEED_AUTH) + fire(wm, NMDeviceState.PREPARE, prev_state=NMDeviceState.NEED_AUTH) + fire(wm, NMDeviceState.CONFIG) + fire(wm, NMDeviceState.IP_CONFIG) + fire(wm, NMDeviceState.IP_CHECK) + fire(wm, NMDeviceState.SECONDARIES) + fire(wm, NMDeviceState.ACTIVATED) + + +# --------------------------------------------------------------------------- +# Basic transitions +# --------------------------------------------------------------------------- + +class TestDisconnected: + def test_generic_disconnect_clears_state(self, mocker): + wm = _make_wm(mocker) + wm._wifi_state = WifiState(ssid="Net", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.UNKNOWN) + + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + wm._update_networks.assert_not_called() + + def test_new_activation_is_noop(self, mocker): + """NEW_ACTIVATION means NM is about to connect to another network — don't clear.""" + wm = _make_wm(mocker) + wm._wifi_state = WifiState(ssid="OldNet", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.NEW_ACTIVATION) + + assert wm._wifi_state.ssid == "OldNet" + assert wm._wifi_state.status == ConnectStatus.CONNECTED + + def test_connection_removed_keeps_other_connecting(self, mocker): + """Forget A while connecting to B: CONNECTION_REMOVED for A must not clear B.""" + wm = _make_wm(mocker, connections={"B": "/path/B"}) + wm._set_connecting("B") + + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.CONNECTION_REMOVED) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + def test_connection_removed_clears_when_forgotten(self, mocker): + """Forget A: A is no longer in _connections, so state should clear.""" + wm = _make_wm(mocker, connections={}) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.CONNECTION_REMOVED) + + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + +class TestDeactivating: + def test_deactivating_noop_for_non_connection_removed(self, mocker): + """DEACTIVATING with non-CONNECTION_REMOVED reason is a no-op.""" + wm = _make_wm(mocker) + wm._wifi_state = WifiState(ssid="Net", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DEACTIVATING, reason=NMDeviceStateReason.USER_REQUESTED) + + assert wm._wifi_state.ssid == "Net" + assert wm._wifi_state.status == ConnectStatus.CONNECTED + + @pytest.mark.parametrize("status, expected_clears", [ + (ConnectStatus.CONNECTED, True), + (ConnectStatus.CONNECTING, False), + ]) + def test_deactivating_connection_removed(self, mocker, status, expected_clears): + """DEACTIVATING(CONNECTION_REMOVED) clears CONNECTED but preserves CONNECTING. + + CONNECTED: forgetting the current network. The forgotten callback fires between + DEACTIVATING and DISCONNECTED — must clear here so the UI doesn't flash "connected" + after the eager _network_forgetting flag resets. + + CONNECTING: forget A while connecting to B. DEACTIVATING fires for A's removal, + but B's CONNECTING state must be preserved. + """ + wm = _make_wm(mocker, connections={"B": "/path/B"}) + wm._wifi_state = WifiState(ssid="B" if status == ConnectStatus.CONNECTING else "A", status=status) + + fire(wm, NMDeviceState.DEACTIVATING, reason=NMDeviceStateReason.CONNECTION_REMOVED) + + if expected_clears: + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + else: + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + +class TestPrepareConfig: + def test_user_initiated_skips_dbus_lookup(self, mocker): + """User called _set_connecting('B') — PREPARE must not overwrite via DBus. + + Reproduced on device: rapidly tap A then B. PREPARE's DBus lookup returns A's + stale conn_path, overwriting ssid to A for 1-2 frames. UI shows the "connecting" + indicator briefly jump to the wrong network row then back. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + wm._set_connecting("B") + wm._get_active_wifi_connection.return_value = ("/path/A", {}) + + fire(wm, NMDeviceState.PREPARE) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + wm._get_active_wifi_connection.assert_not_called() + + @pytest.mark.parametrize("state", [NMDeviceState.PREPARE, NMDeviceState.CONFIG]) + def test_auto_connect_looks_up_ssid(self, mocker, state): + """Auto-connection (ssid=None): PREPARE and CONFIG must look up ssid from NM.""" + wm = _make_wm(mocker, connections={"AutoNet": "/path/auto"}) + wm._get_active_wifi_connection.return_value = ("/path/auto", {}) + + fire(wm, state) + + assert wm._wifi_state.ssid == "AutoNet" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + def test_auto_connect_dbus_fails(self, mocker): + """Auto-connection but DBus returns None: ssid stays None, status CONNECTING.""" + wm = _make_wm(mocker) + + fire(wm, NMDeviceState.PREPARE) + + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + def test_auto_connect_conn_path_not_in_connections(self, mocker): + """DBus returns a conn_path that doesn't match any known connection.""" + wm = _make_wm(mocker, connections={"Other": "/path/other"}) + wm._get_active_wifi_connection.return_value = ("/path/unknown", {}) + + fire(wm, NMDeviceState.PREPARE) + + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + +class TestNeedAuth: + def test_wrong_password_fires_callback(self, mocker): + """NEED_AUTH+SUPPLICANT_DISCONNECT from CONFIG = real wrong password.""" + wm = _make_wm(mocker) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + wm._set_connecting("SecNet") + + fire(wm, NMDeviceState.NEED_AUTH, prev_state=NMDeviceState.CONFIG, + reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + assert len(wm._callback_queue) == 1 + wm.process_callbacks() + cb.assert_called_once_with("SecNet") + + def test_failed_no_secrets_fires_callback(self, mocker): + """FAILED+NO_SECRETS = wrong password (weak/gone network). + + Confirmed on device: also fires when a hotspot turns off during connection. + NM can't complete the WPA handshake (AP vanished) and reports NO_SECRETS + rather than SSID_NOT_FOUND. The need_auth callback fires, so the UI shows + "wrong password" — a false positive, but same signal path. + + Real device sequence (new connection, hotspot turned off immediately): + PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → NEED_AUTH(CONFIG, NONE) → FAILED(NEED_AUTH, NO_SECRETS) → DISCONNECTED(FAILED, NONE) + """ + wm = _make_wm(mocker) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + wm._set_connecting("WeakNet") + + fire(wm, NMDeviceState.FAILED, reason=NMDeviceStateReason.NO_SECRETS) + + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + assert len(wm._callback_queue) == 1 + wm.process_callbacks() + cb.assert_called_once_with("WeakNet") + + def test_need_auth_then_failed_no_double_fire(self, mocker): + """Real device sends NEED_AUTH(SUPPLICANT_DISCONNECT) then FAILED(NO_SECRETS) back-to-back. + + The first clears ssid, so the second must not fire a duplicate callback. + Real device sequence: NEED_AUTH(CONFIG, SUPPLICANT_DISCONNECT) → FAILED(NEED_AUTH, NO_SECRETS) + """ + wm = _make_wm(mocker) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + wm._set_connecting("BadPass") + + fire(wm, NMDeviceState.NEED_AUTH, prev_state=NMDeviceState.CONFIG, + reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + assert len(wm._callback_queue) == 1 + + fire(wm, NMDeviceState.FAILED, prev_state=NMDeviceState.NEED_AUTH, + reason=NMDeviceStateReason.NO_SECRETS) + assert len(wm._callback_queue) == 1 # no duplicate + + wm.process_callbacks() + cb.assert_called_once_with("BadPass") + + def test_no_ssid_no_callback(self, mocker): + """If ssid is None when NEED_AUTH fires, no callback enqueued.""" + wm = _make_wm(mocker) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + + fire(wm, NMDeviceState.NEED_AUTH, reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + + assert len(wm._callback_queue) == 0 + + def test_interrupted_auth_ignored(self, mocker): + """Switching A->B: NEED_AUTH from A (prev=DISCONNECTED) must not fire callback. + + Reproduced on device: rapidly switching between two saved networks can trigger a + rare false "wrong password" dialog for the previous network, even though both have + correct passwords. The stale NEED_AUTH has prev_state=DISCONNECTED (not CONFIG). + """ + wm = _make_wm(mocker) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + wm._set_connecting("A") + wm._set_connecting("B") + + fire(wm, NMDeviceState.NEED_AUTH, prev_state=NMDeviceState.DISCONNECTED, + reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + assert len(wm._callback_queue) == 0 + + +class TestPassthroughStates: + """NEED_AUTH (generic), IP_CONFIG, IP_CHECK, SECONDARIES, FAILED (generic) are no-ops.""" + + @pytest.mark.parametrize("state", [ + NMDeviceState.NEED_AUTH, + NMDeviceState.IP_CONFIG, + NMDeviceState.IP_CHECK, + NMDeviceState.SECONDARIES, + NMDeviceState.FAILED, + ]) + def test_passthrough_is_noop(self, mocker, state): + wm = _make_wm(mocker) + wm._set_connecting("Net") + + fire(wm, state, reason=NMDeviceStateReason.NONE) + + assert wm._wifi_state.ssid == "Net" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + assert len(wm._callback_queue) == 0 + + +class TestActivated: + def test_sets_connected(self, mocker): + """ACTIVATED sets status to CONNECTED and fires callback.""" + wm = _make_wm(mocker, connections={"MyNet": "/path/mynet"}) + cb = mocker.MagicMock() + wm.add_callbacks(activated=cb) + wm._set_connecting("MyNet") + wm._get_active_wifi_connection.return_value = ("/path/mynet", {}) + + fire(wm, NMDeviceState.ACTIVATED) + + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "MyNet" + assert len(wm._callback_queue) == 1 + wm.process_callbacks() + cb.assert_called_once() + + def test_conn_path_none_still_connected(self, mocker): + """ACTIVATED but DBus returns None: status CONNECTED, ssid unchanged.""" + wm = _make_wm(mocker) + wm._set_connecting("MyNet") + + fire(wm, NMDeviceState.ACTIVATED) + + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "MyNet" + + def test_activated_side_effects(self, mocker): + """ACTIVATED persists the volatile connection to disk and updates active connection info.""" + wm = _make_wm(mocker, connections={"Net": "/path/net"}) + wm._set_connecting("Net") + wm._get_active_wifi_connection.return_value = ("/path/net", {}) + + fire(wm, NMDeviceState.ACTIVATED) + + wm._conn_monitor.send_and_get_reply.assert_called_once() + wm._update_active_connection_info.assert_called_once() + wm._update_networks.assert_not_called() + + +# --------------------------------------------------------------------------- +# Thread races: _set_connecting on main thread vs _handle_state_change on monitor thread. +# Uses side_effect on the DBus mock to simulate _set_connecting running mid-handler. +# The epoch counter detects that a user action occurred during the slow DBus call +# and discards the stale update. +# --------------------------------------------------------------------------- +# The deterministic fixes (skip DBus lookup when ssid already set, prev_state guard +# on NEED_AUTH, DEACTIVATING clears CONNECTED on CONNECTION_REMOVED, CONNECTION_REMOVED +# guard) shrink these race windows significantly. The epoch counter closes the +# remaining gaps. + +class TestThreadRaces: + def test_prepare_race_user_tap_during_dbus(self, mocker): + """User taps B while PREPARE's DBus call is in flight for auto-connect. + + Monitor thread reads wifi_state (ssid=None), starts DBus call. + Main thread: _set_connecting("B"). Monitor thread writes back stale ssid from DBus. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + + def user_taps_b_during_dbus(*args, **kwargs): + wm._set_connecting("B") + return ("/path/A", {}) + + wm._get_active_wifi_connection.side_effect = user_taps_b_during_dbus + + fire(wm, NMDeviceState.PREPARE) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + def test_activated_race_user_tap_during_dbus(self, mocker): + """User taps B right as A finishes connecting (ACTIVATED handler running). + + Monitor thread reads wifi_state (A, CONNECTING), starts DBus call. + Main thread: _set_connecting("B"). Monitor thread writes (A, CONNECTED), losing B. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + wm._set_connecting("A") + + def user_taps_b_during_dbus(*args, **kwargs): + wm._set_connecting("B") + return ("/path/A", {}) + + wm._get_active_wifi_connection.side_effect = user_taps_b_during_dbus + + fire(wm, NMDeviceState.ACTIVATED) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + def test_init_wifi_state_race_user_tap_during_dbus(self, mocker): + """User taps B while _init_wifi_state's DBus calls are in flight. + + _init_wifi_state runs from set_active(True) or worker error paths. It does + 2 DBus calls (device State property + _get_active_wifi_connection) then + unconditionally writes _wifi_state. If the user taps a network during those + calls, _set_connecting("B") is overwritten with stale NM ground truth. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + wm._wifi_device = "/dev/wifi0" + wm._router_main = mocker.MagicMock() + + state_reply = mocker.MagicMock() + state_reply.body = [('u', NMDeviceState.ACTIVATED)] + wm._router_main.send_and_get_reply.return_value = state_reply + + def user_taps_b_during_dbus(*args, **kwargs): + wm._set_connecting("B") + return ("/path/A", {}) + + wm._get_active_wifi_connection.side_effect = user_taps_b_during_dbus + + wm._init_wifi_state() + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + +# --------------------------------------------------------------------------- +# Full sequences (NM signal order from real devices) +# --------------------------------------------------------------------------- + +class TestFullSequences: + def test_normal_connect(self, mocker): + """User connects to saved network: full happy path. + + Real device sequence (switching from another connected network): + DEACTIVATING(ACTIVATED, NEW_ACTIVATION) → DISCONNECTED(DEACTIVATING, NEW_ACTIVATION) + PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH, NONE) → CONFIG + → IP_CONFIG → IP_CHECK → SECONDARIES → ACTIVATED + """ + wm = _make_wm(mocker, connections={"Home": "/path/home"}) + wm._get_active_wifi_connection.return_value = ("/path/home", {}) + + wm._set_connecting("Home") + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire(wm, NMDeviceState.NEED_AUTH) # WPA handshake (reason=NONE) + fire(wm, NMDeviceState.PREPARE, prev_state=NMDeviceState.NEED_AUTH) + fire(wm, NMDeviceState.CONFIG) + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.IP_CONFIG) + fire(wm, NMDeviceState.IP_CHECK) + fire(wm, NMDeviceState.SECONDARIES) + fire(wm, NMDeviceState.ACTIVATED) + + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "Home" + + def test_wrong_password_then_retry(self, mocker): + """Wrong password → NEED_AUTH → FAILED → NM auto-reconnects to saved network. + + Confirmed on device: wrong password for Shane's iPhone, NM auto-connected to unifi. + + Real device sequence (switching from a connected network): + DEACTIVATING(ACTIVATED, NEW_ACTIVATION) → DISCONNECTED(DEACTIVATING, NEW_ACTIVATION) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) ← WPA handshake + → PREPARE(NEED_AUTH, NONE) → CONFIG + → NEED_AUTH(CONFIG, SUPPLICANT_DISCONNECT) ← wrong password + → FAILED(NEED_AUTH, NO_SECRETS) ← NM gives up + → DISCONNECTED(FAILED, NONE) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → IP_CONFIG → IP_CHECK → SECONDARIES → ACTIVATED ← auto-reconnect to other saved network + """ + wm = _make_wm(mocker, connections={"Sec": "/path/sec"}) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + + wm._set_connecting("Sec") + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire(wm, NMDeviceState.NEED_AUTH) # WPA handshake (reason=NONE) + fire(wm, NMDeviceState.PREPARE, prev_state=NMDeviceState.NEED_AUTH) + fire(wm, NMDeviceState.CONFIG) + + fire(wm, NMDeviceState.NEED_AUTH, prev_state=NMDeviceState.CONFIG, + reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + assert len(wm._callback_queue) == 1 + + # FAILED(NO_SECRETS) follows but ssid is already cleared — no double-fire + fire(wm, NMDeviceState.FAILED, reason=NMDeviceStateReason.NO_SECRETS) + assert len(wm._callback_queue) == 1 + + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.FAILED) + + # Retry + wm._callback_queue.clear() + wm._set_connecting("Sec") + wm._get_active_wifi_connection.return_value = ("/path/sec", {}) + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + + def test_switch_saved_networks(self, mocker): + """Switch from A to B (both saved): NM signal sequence from real device. + + Real device sequence: + DEACTIVATING(ACTIVATED, NEW_ACTIVATION) → DISCONNECTED(DEACTIVATING, NEW_ACTIVATION) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH, NONE) → CONFIG + → IP_CONFIG → IP_CHECK → SECONDARIES → ACTIVATED + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + wm._get_active_wifi_connection.return_value = ("/path/B", {}) + + wm._set_connecting("B") + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.ACTIVATED, + reason=NMDeviceStateReason.NEW_ACTIVATION) + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.NEW_ACTIVATION) + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "B" + + def test_rapid_switch_no_false_wrong_password(self, mocker): + """Switch A→B quickly: A's interrupted NEED_AUTH must NOT show wrong password. + + NOTE: The late NEED_AUTH(DISCONNECTED, SUPPLICANT_DISCONNECT) is common when rapidly + switching between networks with wrong/new passwords. Less common when switching between + saved networks with correct passwords. Not guaranteed — some switches skip it and go + straight from DISCONNECTED to PREPARE. The prev_state is consistently DISCONNECTED + for stale signals, so the prev_state guard reliably distinguishes them. + + Worst-case signal sequence this protects against: + DEACTIVATING(NEW_ACTIVATION) → DISCONNECTED(NEW_ACTIVATION) + → NEED_AUTH(DISCONNECTED, SUPPLICANT_DISCONNECT) ← A's stale auth failure + → PREPARE → CONFIG → ... → ACTIVATED ← B connects + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + wm._get_active_wifi_connection.return_value = ("/path/B", {}) + + wm._set_connecting("B") + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.ACTIVATED, + reason=NMDeviceStateReason.NEW_ACTIVATION) + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.NEW_ACTIVATION) + fire(wm, NMDeviceState.NEED_AUTH, prev_state=NMDeviceState.DISCONNECTED, + reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + assert len(wm._callback_queue) == 0 + + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + + def test_forget_while_connecting(self, mocker): + """Forget the network we're currently connecting to (not yet ACTIVATED). + + Confirmed on device: connected to unifi, tapped Shane's iPhone, then forgot + Shane's iPhone while at CONFIG. NM auto-connected to unifi afterward. + + Real device sequence (switching then forgetting mid-connection): + DEACTIVATING(ACTIVATED, NEW_ACTIVATION) → DISCONNECTED(DEACTIVATING, NEW_ACTIVATION) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → DEACTIVATING(CONFIG, CONNECTION_REMOVED) ← forget at CONFIG + → DISCONNECTED(DEACTIVATING, CONNECTION_REMOVED) + → PREPARE → CONFIG → ... → ACTIVATED ← NM auto-connects to other saved network + + Note: DEACTIVATING fires from CONFIG (not ACTIVATED). wifi_state.status is + CONNECTING, so the DEACTIVATING handler is a no-op. DISCONNECTED clears state + (ssid removed from _connections by ConnectionRemoved), then PREPARE recovers + via DBus lookup for the auto-connect. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "Other": "/path/other"}) + wm._get_active_wifi_connection.return_value = ("/path/other", {}) + + wm._set_connecting("A") + + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + assert wm._wifi_state.ssid == "A" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + # User forgets A: ConnectionRemoved processed first, then state changes + del wm._connections["A"] + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.CONFIG, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid == "A" + assert wm._wifi_state.status == ConnectStatus.CONNECTING # DEACTIVATING preserves CONNECTING + + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + # NM auto-connects to another saved network + fire(wm, NMDeviceState.PREPARE) + assert wm._wifi_state.ssid == "Other" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "Other" + + def test_forget_connected_network(self, mocker): + """Forget the currently connected network (not switching to another). + + Real device sequence: + DEACTIVATING(ACTIVATED, CONNECTION_REMOVED) → DISCONNECTED(DEACTIVATING, CONNECTION_REMOVED) + + ConnectionRemoved signal may or may not have been processed before state changes. + Either way, state must clear — we're forgetting what we're connected to, not switching. + """ + wm = _make_wm(mocker, connections={"A": "/path/A"}) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.ACTIVATED, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + # DISCONNECTED follows — harmless since state is already cleared + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + def test_forget_A_connect_B(self, mocker): + """Forget A while connecting to B: full signal sequence. + + Real device sequence: + DEACTIVATING(ACTIVATED, CONNECTION_REMOVED) → DISCONNECTED(DEACTIVATING, CONNECTION_REMOVED) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH, NONE) → CONFIG + → IP_CONFIG → IP_CHECK → SECONDARIES → ACTIVATED + + Signal order: + 1. User: _set_connecting("B"), forget("A") removes A from _connections + 2. NewConnection for B arrives → _connections["B"] = ... + 3. DEACTIVATING(CONNECTION_REMOVED) — no-op + 4. DISCONNECTED(CONNECTION_REMOVED) — B is in _connections, must not clear + 5. PREPARE → CONFIG → NEED_AUTH → PREPARE → CONFIG → ... → ACTIVATED + """ + wm = _make_wm(mocker, connections={"A": "/path/A"}) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + + wm._set_connecting("B") + del wm._connections["A"] + wm._connections["B"] = "/path/B" + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.ACTIVATED, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + wm._get_active_wifi_connection.return_value = ("/path/B", {}) + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "B" + + def test_forget_A_connect_B_late_new_connection(self, mocker): + """Forget A, connect B: NewConnection for B arrives AFTER DISCONNECTED. + + This is the worst-case race: B isn't in _connections when DISCONNECTED fires, + so the guard can't protect it and state clears. PREPARE must recover by doing + the DBus lookup (ssid is None at that point). + + Signal order: + 1. User: _set_connecting("B"), forget("A") removes A from _connections + 2. DEACTIVATING(CONNECTION_REMOVED) — B NOT in _connections, should be no-op + 3. DISCONNECTED(CONNECTION_REMOVED) — B STILL NOT in _connections, clears state + 4. NewConnection for B arrives late → _connections["B"] = ... + 5. PREPARE (ssid=None, so DBus lookup recovers) → CONFIG → ACTIVATED + """ + wm = _make_wm(mocker, connections={"A": "/path/A"}) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + + wm._set_connecting("B") + del wm._connections["A"] + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.ACTIVATED, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + # B not in _connections yet, so state clears — this is the known edge case + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + # NewConnection arrives late + wm._connections["B"] = "/path/B" + wm._get_active_wifi_connection.return_value = ("/path/B", {}) + + # PREPARE recovers: ssid is None so it looks up from DBus + fire(wm, NMDeviceState.PREPARE) + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "B" + + def test_auto_connect(self, mocker): + """NM auto-connects (no user action, ssid starts None).""" + wm = _make_wm(mocker, connections={"AutoNet": "/path/auto"}) + wm._get_active_wifi_connection.return_value = ("/path/auto", {}) + + fire(wm, NMDeviceState.PREPARE) + assert wm._wifi_state.ssid == "AutoNet" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "AutoNet" + + def test_network_lost_during_connection(self, mocker): + """Hotspot turned off while connecting (before ACTIVATED). + + Confirmed on device: started new connection to Shane's iPhone, immediately + turned off the hotspot. NM can't complete WPA handshake and reports + FAILED(NO_SECRETS) — same signal as wrong password (false positive). + + Real device sequence: + PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → NEED_AUTH(CONFIG, NONE) → FAILED(NEED_AUTH, NO_SECRETS) → DISCONNECTED(FAILED, NONE) + + Note: no DEACTIVATING, no SUPPLICANT_DISCONNECT. The NEED_AUTH(CONFIG, NONE) is the + normal WPA handshake (not an error). NM gives up with NO_SECRETS because the AP + vanished mid-handshake. + """ + wm = _make_wm(mocker, connections={"Hotspot": "/path/hs"}) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + + wm._set_connecting("Hotspot") + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire(wm, NMDeviceState.NEED_AUTH) # WPA handshake (reason=NONE) + fire(wm, NMDeviceState.PREPARE, prev_state=NMDeviceState.NEED_AUTH) + fire(wm, NMDeviceState.CONFIG) + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + # Second NEED_AUTH(CONFIG, NONE) — NM retries handshake, AP vanishing + fire(wm, NMDeviceState.NEED_AUTH) + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + # NM gives up — reports NO_SECRETS (same as wrong password) + fire(wm, NMDeviceState.FAILED, prev_state=NMDeviceState.NEED_AUTH, + reason=NMDeviceStateReason.NO_SECRETS) + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + assert len(wm._callback_queue) == 1 + + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.FAILED) + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + wm.process_callbacks() + cb.assert_called_once_with("Hotspot") + + @pytest.mark.xfail(reason="TODO: FAILED(SSID_NOT_FOUND) should emit error for UI") + def test_ssid_not_found(self, mocker): + """Network drops off while connected — hotspot turned off. + + NM docs: SSID_NOT_FOUND (53) = "The WiFi network could not be found" + + Confirmed on device: connected to Shane's iPhone, then turned off the hotspot. + No DEACTIVATING fires — NM goes straight from ACTIVATED to FAILED(SSID_NOT_FOUND). + NM retries connecting (PREPARE → CONFIG → ... → FAILED(CONFIG, SSID_NOT_FOUND)) + before finally giving up with DISCONNECTED. + + NOTE: turning off a hotspot during initial connection (before ACTIVATED) typically + produces FAILED(NO_SECRETS) instead of SSID_NOT_FOUND (see test_failed_no_secrets). + + Real device sequence (hotspot turned off while connected): + FAILED(ACTIVATED, SSID_NOT_FOUND) → DISCONNECTED(FAILED, NONE) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → FAILED(CONFIG, SSID_NOT_FOUND) → DISCONNECTED(FAILED, NONE) + + The UI error callback mechanism is intentionally deferred — for now just clear state. + """ + wm = _make_wm(mocker, connections={"GoneNet": "/path/gone"}) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + + wm._set_connecting("GoneNet") + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire(wm, NMDeviceState.FAILED, reason=NMDeviceStateReason.SSID_NOT_FOUND) + + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + assert wm._wifi_state.ssid is None + + def test_failed_then_disconnected_clears_state(self, mocker): + """After FAILED, NM always transitions to DISCONNECTED to clean up. + + NM docs: FAILED (120) = "failed to connect, cleaning up the connection request" + Full sequence: ... → FAILED(reason) → DISCONNECTED(NONE) + """ + wm = _make_wm(mocker) + wm._set_connecting("Net") + + fire(wm, NMDeviceState.FAILED, reason=NMDeviceStateReason.NONE) + assert wm._wifi_state.status == ConnectStatus.CONNECTING # FAILED(NONE) is a no-op + + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.NONE) + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + def test_user_requested_disconnect(self, mocker): + """User explicitly disconnects from the network. + + NM docs: USER_REQUESTED (39) = "Device disconnected by user or client" + Expected sequence: DEACTIVATING(USER_REQUESTED) → DISCONNECTED(USER_REQUESTED) + """ + wm = _make_wm(mocker) + wm._wifi_state = WifiState(ssid="MyNet", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DEACTIVATING, reason=NMDeviceStateReason.USER_REQUESTED) + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.USER_REQUESTED) + + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + +# --------------------------------------------------------------------------- +# Worker error recovery: DBus errors in activate/connect re-sync with NM +# --------------------------------------------------------------------------- +# Verified on device: when ActivateConnection returns UnknownConnection error, +# NM emits no state signals. The worker error path is the only recovery point. + +class TestWorkerErrorRecovery: + """Worker threads re-sync with NM via _init_wifi_state on DBus errors, + preserving actual NM state instead of blindly clearing to DISCONNECTED.""" + + def _mock_init_restores(self, wm, mocker, ssid, status): + """Replace _init_wifi_state with a mock that simulates NM reporting the given state.""" + mock = mocker.MagicMock( + side_effect=lambda: setattr(wm, '_wifi_state', WifiState(ssid=ssid, status=status)) + ) + wm._init_wifi_state = mock + return mock + + def test_activate_dbus_error_resyncs(self, mocker): + """ActivateConnection returns DBus error while A is connected. + NM rejects the request — no state signals emitted. Worker must re-read NM + state to discover A is still connected, not clear to DISCONNECTED. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + wm._wifi_device = "/dev/wifi0" + wm._nm = mocker.MagicMock() + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + wm._router_main = mocker.MagicMock() + + error_reply = mocker.MagicMock() + error_reply.header.message_type = MessageType.error + wm._router_main.send_and_get_reply.return_value = error_reply + + mock_init = self._mock_init_restores(wm, mocker, "A", ConnectStatus.CONNECTED) + + wm.activate_connection("B", block=True) + + mock_init.assert_called_once() + assert wm._wifi_state.ssid == "A" + assert wm._wifi_state.status == ConnectStatus.CONNECTED + + def test_connect_to_network_dbus_error_resyncs(self, mocker): + """AddAndActivateConnection2 returns DBus error while A is connected.""" + wm = _make_wm(mocker, connections={"A": "/path/A"}) + wm._wifi_device = "/dev/wifi0" + wm._nm = mocker.MagicMock() + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + wm._router_main = mocker.MagicMock() + wm._forgotten = [] + + error_reply = mocker.MagicMock() + error_reply.header.message_type = MessageType.error + wm._router_main.send_and_get_reply.return_value = error_reply + + mock_init = self._mock_init_restores(wm, mocker, "A", ConnectStatus.CONNECTED) + + # Run worker thread synchronously + workers = [] + mocker.patch('openpilot.system.ui.lib.wifi_manager.threading.Thread', + side_effect=lambda target, **kw: type('T', (), {'start': lambda self: workers.append(target)})()) + + wm.connect_to_network("B", "password123") + workers[-1]() + + mock_init.assert_called_once() + assert wm._wifi_state.ssid == "A" + assert wm._wifi_state.status == ConnectStatus.CONNECTED diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 2c76862ce5..1084e9e533 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -4,13 +4,13 @@ import time import uuid import subprocess from collections.abc import Callable -from dataclasses import dataclass +from dataclasses import dataclass, replace from enum import IntEnum from typing import Any from jeepney import DBusAddress, new_method_call from jeepney.bus_messages import MatchRule, message_bus -from jeepney.io.blocking import open_dbus_connection as open_dbus_connection_blocking +from jeepney.io.blocking import DBusConnection, open_dbus_connection as open_dbus_connection_blocking from jeepney.io.threading import DBusRouter, open_dbus_connection as open_dbus_connection_threading from jeepney.low_level import MessageType from jeepney.wrappers import Properties @@ -23,9 +23,8 @@ from openpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_80 NM_802_11_AP_FLAGS_PRIVACY, NM_802_11_AP_FLAGS_WPS, NM_PATH, NM_IFACE, NM_ACCESS_POINT_IFACE, NM_SETTINGS_PATH, NM_SETTINGS_IFACE, NM_CONNECTION_IFACE, NM_DEVICE_IFACE, - NM_DEVICE_TYPE_WIFI, NM_DEVICE_TYPE_MODEM, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, - NM_DEVICE_STATE_REASON_NEW_ACTIVATION, NM_ACTIVE_CONNECTION_IFACE, - NM_IP4_CONFIG_IFACE, NMDeviceState) + NM_DEVICE_TYPE_WIFI, NM_DEVICE_TYPE_MODEM, NM_ACTIVE_CONNECTION_IFACE, + NM_IP4_CONFIG_IFACE, NM_PROPERTIES_IFACE, NMDeviceState, NMDeviceStateReason) try: from openpilot.common.params import Params @@ -37,6 +36,27 @@ DEFAULT_TETHERING_PASSWORD = "swagswagcomma" SIGNAL_QUEUE_SIZE = 10 SCAN_PERIOD_SECONDS = 5 +DEBUG = False +_dbus_call_idx = 0 + + +def normalize_ssid(ssid: str) -> str: + return ssid.replace("’", "'") # for iPhone hotspots + + +def _wrap_router(router): + def _wrap(orig): + def wrapper(msg, **kw): + global _dbus_call_idx + _dbus_call_idx += 1 + if DEBUG: + h = msg.header.fields + print(f"[DBUS #{_dbus_call_idx}] {h.get(6, '?')} {h.get(3, '?')} {msg.body}") + return orig(msg, **kw) + return wrapper + router.send_and_get_reply = _wrap(router.send_and_get_reply) + router.send = _wrap(router.send) + class SecurityType(IntEnum): OPEN = 0 @@ -72,24 +92,20 @@ def get_security_type(flags: int, wpa_flags: int, rsn_flags: int) -> SecurityTyp class Network: ssid: str strength: int - is_connected: bool security_type: SecurityType - is_saved: bool - ip_address: str = "" # TODO: implement + is_tethering: bool @classmethod - def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_saved: bool) -> "Network": + def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_tethering: bool) -> "Network": # we only want to show the strongest AP for each Network/SSID strongest_ap = max(aps, key=lambda ap: ap.strength) - is_connected = any(ap.is_connected for ap in aps) security_type = get_security_type(strongest_ap.flags, strongest_ap.wpa_flags, strongest_ap.rsn_flags) return cls( ssid=ssid, - strength=strongest_ap.strength, - is_connected=is_connected and is_saved, + strength=100 if is_tethering else strongest_ap.strength, security_type=security_type, - is_saved=is_saved, + is_tethering=is_tethering, ) @@ -98,14 +114,13 @@ class AccessPoint: ssid: str bssid: str strength: int - is_connected: bool flags: int wpa_flags: int rsn_flags: int ap_path: str @classmethod - def from_dbus(cls, ap_props: dict[str, tuple[str, Any]], ap_path: str, active_ap_path: str) -> "AccessPoint": + def from_dbus(cls, ap_props: dict[str, tuple[str, Any]], ap_path: str) -> "AccessPoint": ssid = bytes(ap_props['Ssid'][1]).decode("utf-8", "replace") bssid = str(ap_props['HwAddress'][1]) strength = int(ap_props['Strength'][1]) @@ -117,7 +132,6 @@ class AccessPoint: ssid=ssid, bssid=bssid, strength=strength, - is_connected=ap_path == active_ap_path, flags=flags, wpa_flags=wpa_flags, rsn_flags=rsn_flags, @@ -125,15 +139,28 @@ class AccessPoint: ) +class ConnectStatus(IntEnum): + DISCONNECTED = 0 + CONNECTING = 1 + CONNECTED = 2 + + +@dataclass(frozen=True) +class WifiState: + ssid: str | None = None + status: ConnectStatus = ConnectStatus.DISCONNECTED + + class WifiManager: def __init__(self): - self._networks: list[Network] = [] # a network can be comprised of multiple APs + self._networks: list[Network] = [] # an unsorted list of available Networks. a Network can be comprised of multiple APs self._active = True # used to not run when not in settings self._exit = False # DBus connections try: self._router_main = DBusRouter(open_dbus_connection_threading(bus="SYSTEM")) # used by scanner / general method calls + _wrap_router(self._router_main) self._conn_monitor = open_dbus_connection_blocking(bus="SYSTEM") # used by state monitor thread self._nm = DBusAddress(NM_PATH, bus_name=NM, interface=NM_IFACE) except FileNotFoundError: @@ -146,13 +173,15 @@ class WifiManager: self._wifi_device: str | None = None # State - self._connecting_to_ssid: str = "" + self._connections: dict[str, str] = {} # ssid -> connection path, updated via NM signals + self._wifi_state: WifiState = WifiState() + self._user_epoch: int = 0 self._ipv4_address: str = "" self._current_network_metered: MeteredType = MeteredType.UNKNOWN self._tethering_password: str = "" self._ipv4_forward = False - self._last_network_update: float = 0.0 + self._last_network_scan: float = 0.0 self._callback_queue: list[Callable] = [] self._tethering_ssid = "weedle" @@ -164,11 +193,11 @@ class WifiManager: # Callbacks self._need_auth: list[Callable[[str], None]] = [] self._activated: list[Callable[[], None]] = [] - self._forgotten: list[Callable[[], None]] = [] + self._forgotten: list[Callable[[str | None], None]] = [] self._networks_updated: list[Callable[[list[Network]], None]] = [] self._disconnected: list[Callable[[], None]] = [] - self._lock = threading.Lock() + self._scan_lock = threading.Lock() self._scan_thread = threading.Thread(target=self._network_scanner, daemon=True) self._state_thread = threading.Thread(target=self._monitor_state, daemon=True) self._initialize() @@ -178,20 +207,56 @@ class WifiManager: def worker(): self._wait_for_wifi_device() + self._init_connections() + if Params is not None and self._tethering_ssid not in self._connections: + self._add_tethering_connection() + + self._init_wifi_state() + self._scan_thread.start() self._state_thread.start() - if Params is not None and self._tethering_ssid not in self._get_connections(): - self._add_tethering_connection() - self._tethering_password = self._get_tethering_password() cloudlog.debug("WifiManager initialized") threading.Thread(target=worker, daemon=True).start() + def _init_wifi_state(self, block: bool = True): + def worker(): + if self._wifi_device is None: + cloudlog.warning("No WiFi device found") + return + + epoch = self._user_epoch + + dev_addr = DBusAddress(self._wifi_device, bus_name=NM, interface=NM_DEVICE_IFACE) + dev_state = self._router_main.send_and_get_reply(Properties(dev_addr).get('State')).body[0][1] + + ssid: str | None = None + status = ConnectStatus.DISCONNECTED + if NMDeviceState.PREPARE <= dev_state <= NMDeviceState.SECONDARIES and dev_state != NMDeviceState.NEED_AUTH: + status = ConnectStatus.CONNECTING + elif dev_state == NMDeviceState.ACTIVATED: + status = ConnectStatus.CONNECTED + + conn_path, _ = self._get_active_wifi_connection() + if conn_path: + ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + + # Discard if user acted during DBus calls + if self._user_epoch != epoch: + return + + self._wifi_state = WifiState(ssid=ssid, status=status) + + if block: + worker() + else: + threading.Thread(target=worker, daemon=True).start() + def add_callbacks(self, need_auth: Callable[[str], None] | None = None, activated: Callable[[], None] | None = None, - forgotten: Callable[[], None] | None = None, + forgotten: Callable[[str], None] | None = None, networks_updated: Callable[[list[Network]], None] | None = None, disconnected: Callable[[], None] | None = None): if need_auth is not None: @@ -205,6 +270,15 @@ class WifiManager: if disconnected is not None: self._disconnected.append(disconnected) + @property + def networks(self) -> list[Network]: + # Sort by connected/connecting, then known, then strength, then alphabetically. This is a pure UI ordering and should not affect underlying state. + return sorted(self._networks, key=lambda n: (n.ssid != self._wifi_state.ssid, not self.is_connection_saved(n.ssid), -n.strength, n.ssid.lower())) + + @property + def wifi_state(self) -> WifiState: + return self._wifi_state + @property def ipv4_address(self) -> str: return self._ipv4_address @@ -213,10 +287,25 @@ class WifiManager: def current_network_metered(self) -> MeteredType: return self._current_network_metered + @property + def connecting_to_ssid(self) -> str | None: + wifi_state = self._wifi_state + return wifi_state.ssid if wifi_state.status == ConnectStatus.CONNECTING else None + + @property + def connected_ssid(self) -> str | None: + wifi_state = self._wifi_state + return wifi_state.ssid if wifi_state.status == ConnectStatus.CONNECTED else None + @property def tethering_password(self) -> str: return self._tethering_password + def _set_connecting(self, ssid: str | None): + # Called by user action, or sequentially from state change handler + self._user_epoch += 1 + self._wifi_state = WifiState(ssid=ssid, status=ConnectStatus.DISCONNECTED if ssid is None else ConnectStatus.CONNECTING) + def _enqueue_callbacks(self, cbs: list[Callable], *args): for cb in cbs: self._callback_queue.append(lambda _cb=cb: _cb(*args)) @@ -230,60 +319,185 @@ class WifiManager: def set_active(self, active: bool): self._active = active - # Scan immediately if we haven't scanned in a while - if active and time.monotonic() - self._last_network_update > SCAN_PERIOD_SECONDS / 2: - self._last_network_update = 0.0 + # Update networks and WiFi state (to self-heal) immediately when activating for UI + if active: + self._init_wifi_state(block=False) + self._update_networks(block=False) def _monitor_state(self): - rule = MatchRule( - type="signal", - interface=NM_DEVICE_IFACE, - member="StateChanged", - path=self._wifi_device, + # Filter for signals + rules = ( + MatchRule( + type="signal", + interface=NM_DEVICE_IFACE, + member="StateChanged", + path=self._wifi_device, + ), + MatchRule( + type="signal", + interface=NM_SETTINGS_IFACE, + member="NewConnection", + path=NM_SETTINGS_PATH, + ), + MatchRule( + type="signal", + interface=NM_SETTINGS_IFACE, + member="ConnectionRemoved", + path=NM_SETTINGS_PATH, + ), + MatchRule( + type="signal", + interface=NM_PROPERTIES_IFACE, + member="PropertiesChanged", + path=self._wifi_device, + ), ) - # Filter for StateChanged signal - self._conn_monitor.send_and_get_reply(message_bus.AddMatch(rule)) + for rule in rules: + self._conn_monitor.send_and_get_reply(message_bus.AddMatch(rule)) - with self._conn_monitor.filter(rule, bufsize=SIGNAL_QUEUE_SIZE) as q: + with (self._conn_monitor.filter(rules[0], bufsize=SIGNAL_QUEUE_SIZE) as state_q, + self._conn_monitor.filter(rules[1], bufsize=SIGNAL_QUEUE_SIZE) as new_conn_q, + self._conn_monitor.filter(rules[2], bufsize=SIGNAL_QUEUE_SIZE) as removed_conn_q, + self._conn_monitor.filter(rules[3], bufsize=SIGNAL_QUEUE_SIZE) as props_q): while not self._exit: - if not self._active: - time.sleep(1) - continue - - # Block until a matching signal arrives try: - msg = self._conn_monitor.recv_until_filtered(q, timeout=1) + self._conn_monitor.recv_messages(timeout=1) except TimeoutError: continue - new_state, previous_state, change_reason = msg.body + # Connection added/removed + while len(removed_conn_q): + conn_path = removed_conn_q.popleft().body[0] + self._connection_removed(conn_path) + while len(new_conn_q): + conn_path = new_conn_q.popleft().body[0] + self._new_connection(conn_path) - # BAD PASSWORD - if new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and len(self._connecting_to_ssid): - self.forget_connection(self._connecting_to_ssid, block=True) - self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid) - self._connecting_to_ssid = "" - - elif new_state == NMDeviceState.ACTIVATED: - if len(self._activated): + # PropertiesChanged on wifi device (LastScan = scan complete) + while len(props_q): + iface, changed, _ = props_q.popleft().body + if iface == NM_WIRELESS_IFACE and 'LastScan' in changed: self._update_networks() - self._enqueue_callbacks(self._activated) - self._connecting_to_ssid = "" - elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION: - self._connecting_to_ssid = "" - self._enqueue_callbacks(self._forgotten) + # Device state changes + while len(state_q): + new_state, previous_state, change_reason = state_q.popleft().body + + self._handle_state_change(new_state, previous_state, change_reason) + + def _handle_state_change(self, new_state: int, prev_state: int, change_reason: int): + # Thread safety: _wifi_state is read/written by both the monitor thread (this handler) + # and the main thread (_set_connecting via connect/activate). PREPARE/CONFIG and ACTIVATED + # have a read-then-write pattern with a slow DBus call in between — if _set_connecting + # runs mid-call, the handler would overwrite the user's newer state with stale data. + # + # The _user_epoch counter solves this without locks. _set_connecting increments the epoch + # on every user action. Handlers snapshot the epoch before their DBus call and compare + # after: if it changed, a user action occurred during the call and the stale result is + # discarded. Combined with deterministic fixes (skip DBus lookup when ssid already set, + # DEACTIVATING clears CONNECTED on CONNECTION_REMOVED, CONNECTION_REMOVED guard), + # all known race windows are closed. + + # TODO: Handle (FAILED, SSID_NOT_FOUND) and emit for UI to show error + # Happens when network drops off after starting connection + + if new_state == NMDeviceState.DISCONNECTED: + if change_reason == NMDeviceStateReason.NEW_ACTIVATION: + return + + # Guard: forget A while connecting to B fires CONNECTION_REMOVED. Don't clear B's state + # if B is still a known connection. If B hasn't arrived in _connections yet (late + # NewConnection), state clears here but PREPARE recovers via DBus lookup. + if (change_reason == NMDeviceStateReason.CONNECTION_REMOVED and self._wifi_state.ssid and + self._wifi_state.ssid in self._connections): + return + + self._set_connecting(None) + + elif new_state in (NMDeviceState.PREPARE, NMDeviceState.CONFIG): + epoch = self._user_epoch + + if self._wifi_state.ssid is not None: + self._wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTING) + return + + # Auto-connection when NetworkManager connects to known networks on its own (ssid=None): look up ssid from NM + wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTING) + + conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) + + # Discard if user acted during DBus call + if self._user_epoch != epoch: + return + + if conn_path is None: + cloudlog.warning("Failed to get active wifi connection during PREPARE/CONFIG state") + else: + wifi_state = replace(wifi_state, ssid=next((s for s, p in self._connections.items() if p == conn_path), None)) + + self._wifi_state = wifi_state + + # BAD PASSWORD + # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT + # - weak/gone network fails with FAILED+NO_SECRETS + # TODO: sometimes on PC it's observed no future signals are fired if mouse is held down blocking wrong password dialog + elif ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT + and prev_state == NMDeviceState.CONFIG) or + (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): + + # prev_state guard: real auth failures come from CONFIG (supplicant handshake). + # Stale NEED_AUTH from a prior connection during network switching arrives with + # prev_state=DISCONNECTED and must be ignored to avoid a false wrong-password callback. + if self._wifi_state.ssid: + self._enqueue_callbacks(self._need_auth, self._wifi_state.ssid) + self._set_connecting(None) + + elif new_state in (NMDeviceState.NEED_AUTH, NMDeviceState.IP_CONFIG, NMDeviceState.IP_CHECK, + NMDeviceState.SECONDARIES, NMDeviceState.FAILED): + pass + + elif new_state == NMDeviceState.ACTIVATED: + # Note that IP address from Ip4Config may not be propagated immediately and could take until the next scan results + epoch = self._user_epoch + wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTED) + + conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) + + # Discard if user acted during DBus call + if self._user_epoch != epoch: + return + + if conn_path is None: + cloudlog.warning("Failed to get active wifi connection during ACTIVATED state") + else: + wifi_state = replace(wifi_state, ssid=next((s for s, p in self._connections.items() if p == conn_path), None)) + + self._wifi_state = wifi_state + self._enqueue_callbacks(self._activated) + self._update_active_connection_info() + + # Persist volatile connections (created by AddAndActivateConnection2) to disk + if conn_path is not None: + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + save_reply = self._conn_monitor.send_and_get_reply(new_method_call(conn_addr, 'Save')) + if save_reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to persist connection to disk: {save_reply}") + + elif new_state == NMDeviceState.DEACTIVATING: + # Must clear state when forgetting the currently connected network so the UI + # doesn't flash "connected" after the eager "forgetting..." state resets + # (the forgotten callback fires between DEACTIVATING and DISCONNECTED). + # Only clear CONNECTED — CONNECTING must be preserved for forget-A-connect-B. + if change_reason == NMDeviceStateReason.CONNECTION_REMOVED and self._wifi_state.status == ConnectStatus.CONNECTED: + self._set_connecting(None) def _network_scanner(self): while not self._exit: if self._active: - if time.monotonic() - self._last_network_update > SCAN_PERIOD_SECONDS: - # Scan for networks every 10 seconds - # TODO: should update when scan is complete (PropertiesChanged), but this is more than good enough for now - self._update_networks() + if time.monotonic() - self._last_network_scan > SCAN_PERIOD_SECONDS: self._request_scan() - self._last_network_update = time.monotonic() + self._last_network_scan = time.monotonic() time.sleep(1 / 2.) def _wait_for_wifi_device(self): @@ -307,7 +521,7 @@ class WifiManager: cloudlog.exception(f"Error getting adapter type {adapter_type}: {e}") return None - def _get_connections(self) -> dict[str, str]: + def _init_connections(self) -> None: settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) known_connections = self._router_main.send_and_get_reply(new_method_call(settings_addr, 'ListConnections')).body[0] @@ -323,10 +537,46 @@ class WifiManager: ssid = settings['802-11-wireless']['ssid'][1].decode("utf-8", "replace") if ssid != "": conns[ssid] = conn_path - return conns + self._connections = conns - def _get_active_connections(self): - return self._router_main.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1] + def _new_connection(self, conn_path: str): + settings = self._get_connection_settings(conn_path) + + if "802-11-wireless" in settings: + ssid = settings['802-11-wireless']['ssid'][1].decode("utf-8", "replace") + if ssid != "": + self._connections[ssid] = conn_path + + def _connection_removed(self, conn_path: str): + self._connections = {ssid: path for ssid, path in self._connections.items() if path != conn_path} + + def _get_active_connections(self, router: DBusConnection | DBusRouter | None = None): + # Returns list of ActiveConnection + if router is None: + router = self._router_main + + return router.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1] + + def _get_active_wifi_connection(self, router: DBusConnection | DBusRouter | None = None) -> tuple[str | None, dict | None]: + # Returns first Connection settings path and ActiveConnection props from ActiveConnections with Type 802-11-wireless + if router is None: + router = self._router_main + + for active_conn in self._get_active_connections(router): + conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) + reply = router.send_and_get_reply(Properties(conn_addr).get_all()) + + if reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to get active connection properties for {active_conn}: {reply}") + continue + + props = reply.body[0] + + conn_path = props.get('Connection', ('o', '/'))[1] + if props.get('Type', ('s', ''))[1] == '802-11-wireless' and conn_path != '/': + return conn_path, props + + return None, None def _get_connection_settings(self, conn_path: str) -> dict: conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) @@ -374,9 +624,10 @@ class WifiManager: self._router_main.send_and_get_reply(new_method_call(settings_addr, 'AddConnection', 'a{sa{sv}}', (connection,))) def connect_to_network(self, ssid: str, password: str, hidden: bool = False): + self._set_connecting(ssid) + def worker(): # Clear all connections that may already exist to the network we are connecting to - self._connecting_to_ssid = ssid self.forget_connection(ssid, block=True) connection = { @@ -405,22 +656,34 @@ class WifiManager: 'psk': ('s', password), } - settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) - self._router_main.send_and_get_reply(new_method_call(settings_addr, 'AddConnection', 'a{sa{sv}}', (connection,))) - self.activate_connection(ssid, block=True) + # Volatile connection auto-deletes on disconnect (wrong password, user switches networks) + # Persisted to disk on ACTIVATED via Save() + if self._wifi_device is None: + cloudlog.warning("No WiFi device found") + # TODO: expose a failed connection state in the UI + self._init_wifi_state() + return + + reply = self._router_main.send_and_get_reply(new_method_call(self._nm, 'AddAndActivateConnection2', 'a{sa{sv}}ooa{sv}', + (connection, self._wifi_device, "/", {'persist': ('s', 'volatile')}))) + + if reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to add and activate connection for {ssid}: {reply}") + # TODO: expose a failed connection state in the UI + self._init_wifi_state() threading.Thread(target=worker, daemon=True).start() def forget_connection(self, ssid: str, block: bool = False): def worker(): - conn_path = self._get_connections().get(ssid, None) - if conn_path is not None: + conn_path = self._connections.get(ssid, None) + if conn_path is None: + cloudlog.warning(f"Trying to forget unknown connection: {ssid}") + else: conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) - if len(self._forgotten): - self._update_networks() - self._enqueue_callbacks(self._forgotten) + self._enqueue_callbacks(self._forgotten, ssid) if block: worker() @@ -428,16 +691,23 @@ class WifiManager: threading.Thread(target=worker, daemon=True).start() def activate_connection(self, ssid: str, block: bool = False): - def worker(): - conn_path = self._get_connections().get(ssid, None) - if conn_path is not None: - if self._wifi_device is None: - cloudlog.warning("No WiFi device found") - return + self._set_connecting(ssid) - self._connecting_to_ssid = ssid - self._router_main.send(new_method_call(self._nm, 'ActivateConnection', 'ooo', - (conn_path, self._wifi_device, "/"))) + def worker(): + conn_path = self._connections.get(ssid, None) + if conn_path is None or self._wifi_device is None: + cloudlog.warning(f"Failed to activate connection for {ssid}: conn_path={conn_path}, wifi_device={self._wifi_device}") + # TODO: expose a failed connection state in the UI + self._init_wifi_state() + return + + reply = self._router_main.send_and_get_reply(new_method_call(self._nm, 'ActivateConnection', 'ooo', + (conn_path, self._wifi_device, "/"))) + + if reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to activate connection for {ssid}: {reply}") + # TODO: expose a failed connection state in the UI + self._init_wifi_state() if block: worker() @@ -445,27 +715,36 @@ class WifiManager: threading.Thread(target=worker, daemon=True).start() def _deactivate_connection(self, ssid: str): - for conn_path in self._get_active_connections(): - conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - specific_obj_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('SpecificObject')).body[0][1] + for active_conn in self._get_active_connections(): + conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) + reply = self._router_main.send_and_get_reply(Properties(conn_addr).get('SpecificObject')) + if reply.header.message_type == MessageType.error: + continue # object gone (e.g. rapid connect/disconnect) + + specific_obj_path = reply.body[0][1] if specific_obj_path != "/": ap_addr = DBusAddress(specific_obj_path, bus_name=NM, interface=NM_ACCESS_POINT_IFACE) - ap_ssid = bytes(self._router_main.send_and_get_reply(Properties(ap_addr).get('Ssid')).body[0][1]).decode("utf-8", "replace") + ap_reply = self._router_main.send_and_get_reply(Properties(ap_addr).get('Ssid')) + if ap_reply.header.message_type == MessageType.error: + continue # AP gone (e.g. mode switch) + + ap_ssid = bytes(ap_reply.body[0][1]).decode("utf-8", "replace") if ap_ssid == ssid: - self._router_main.send_and_get_reply(new_method_call(self._nm, 'DeactivateConnection', 'o', (conn_path,))) + self._router_main.send_and_get_reply(new_method_call(self._nm, 'DeactivateConnection', 'o', (active_conn,))) return def is_tethering_active(self) -> bool: - for network in self._networks: - if network.is_connected: - return bool(network.ssid == self._tethering_ssid) - return False + # Check ssid, not connected_ssid, to also catch connecting state + return self._wifi_state.ssid == self._tethering_ssid + + def is_connection_saved(self, ssid: str) -> bool: + return ssid in self._connections def set_tethering_password(self, password: str): def worker(): - conn_path = self._get_connections().get(self._tethering_ssid, None) + conn_path = self._connections.get(self._tethering_ssid, None) if conn_path is None: cloudlog.warning('No tethering connection found') return @@ -490,7 +769,7 @@ class WifiManager: threading.Thread(target=worker, daemon=True).start() def _get_tethering_password(self) -> str: - conn_path = self._get_connections().get(self._tethering_ssid, None) + conn_path = self._connections.get(self._tethering_ssid, None) if conn_path is None: cloudlog.warning('No tethering connection found') return '' @@ -527,58 +806,28 @@ class WifiManager: threading.Thread(target=worker, daemon=True).start() - def _update_current_network_metered(self) -> None: - if self._wifi_device is None: - cloudlog.warning("No WiFi device found") - return - - self._current_network_metered = MeteredType.UNKNOWN - for active_conn in self._get_active_connections(): - conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] - - if conn_type == '802-11-wireless': - conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1] - if conn_path == "/": - continue - - settings = self._get_connection_settings(conn_path) - - if len(settings) == 0: - cloudlog.warning(f'Failed to get connection settings for {conn_path}') - continue - - metered_prop = settings['connection'].get('metered', ('i', 0))[1] - if metered_prop == MeteredType.YES: - self._current_network_metered = MeteredType.YES - elif metered_prop == MeteredType.NO: - self._current_network_metered = MeteredType.NO - return - def set_current_network_metered(self, metered: MeteredType): def worker(): - for active_conn in self._get_active_connections(): - conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] + if self.is_tethering_active(): + return - if conn_type == '802-11-wireless' and not self.is_tethering_active(): - conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1] - if conn_path == "/": - continue + conn_path, _ = self._get_active_wifi_connection() + if conn_path is None: + cloudlog.warning('No active WiFi connection found') + return - settings = self._get_connection_settings(conn_path) + settings = self._get_connection_settings(conn_path) - if len(settings) == 0: - cloudlog.warning(f'Failed to get connection settings for {conn_path}') - return + if len(settings) == 0: + cloudlog.warning(f'Failed to get connection settings for {conn_path}') + return - settings['connection']['metered'] = ('i', int(metered)) + settings['connection']['metered'] = ('i', int(metered)) - conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) - reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Update', 'a{sa{sv}}', (settings,))) - if reply.header.message_type == MessageType.error: - cloudlog.warning(f'Failed to update tethering settings: {reply}') - return + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Update', 'a{sa{sv}}', (settings,))) + if reply.header.message_type == MessageType.error: + cloudlog.warning(f'Failed to update metered settings: {reply}') threading.Thread(target=worker, daemon=True).start() @@ -593,73 +842,86 @@ class WifiManager: if reply.header.message_type == MessageType.error: cloudlog.warning(f"Failed to request scan: {reply}") - def _update_networks(self): - with self._lock: - if self._wifi_device is None: - cloudlog.warning("No WiFi device found") - return - - # returns '/' if no active AP - wifi_addr = DBusAddress(self._wifi_device, NM, interface=NM_WIRELESS_IFACE) - active_ap_path = self._router_main.send_and_get_reply(Properties(wifi_addr).get('ActiveAccessPoint')).body[0][1] - ap_paths = self._router_main.send_and_get_reply(new_method_call(wifi_addr, 'GetAllAccessPoints')).body[0] - - aps: dict[str, list[AccessPoint]] = {} - - for ap_path in ap_paths: - ap_addr = DBusAddress(ap_path, NM, interface=NM_ACCESS_POINT_IFACE) - ap_props = self._router_main.send_and_get_reply(Properties(ap_addr).get_all()) - - # some APs have been seen dropping off during iteration - if ap_props.header.message_type == MessageType.error: - cloudlog.warning(f"Failed to get AP properties for {ap_path}") - continue - - try: - ap = AccessPoint.from_dbus(ap_props.body[0], ap_path, active_ap_path) - if ap.ssid == "": - continue - - if ap.ssid not in aps: - aps[ap.ssid] = [] - - aps[ap.ssid].append(ap) - except Exception: - # catch all for parsing errors - cloudlog.exception(f"Failed to parse AP properties for {ap_path}") - - known_connections = self._get_connections() - networks = [Network.from_dbus(ssid, ap_list, ssid in known_connections) for ssid, ap_list in aps.items()] - # sort with quantized strength to reduce jumping - networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower())) - self._networks = networks - - self._update_ipv4_address() - self._update_current_network_metered() - - self._enqueue_callbacks(self._networks_updated, self._networks) - - def _update_ipv4_address(self): - if self._wifi_device is None: - cloudlog.warning("No WiFi device found") + def _update_networks(self, block: bool = True): + if not self._active: return - self._ipv4_address = "" + def worker(): + with self._scan_lock: + if self._wifi_device is None: + cloudlog.warning("No WiFi device found") + return - for conn_path in self._get_active_connections(): - conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] - if conn_type == '802-11-wireless': - ip4config_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Ip4Config')).body[0][1] + # NOTE: AccessPoints property may exclude hidden APs (use GetAllAccessPoints method if needed) + wifi_addr = DBusAddress(self._wifi_device, NM, interface=NM_WIRELESS_IFACE) + wifi_props = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()).body[0] + ap_paths = wifi_props.get('AccessPoints', ('ao', []))[1] - if ip4config_path != "/": - ip4config_addr = DBusAddress(ip4config_path, bus_name=NM, interface=NM_IP4_CONFIG_IFACE) - address_data = self._router_main.send_and_get_reply(Properties(ip4config_addr).get('AddressData')).body[0][1] + aps: dict[str, list[AccessPoint]] = {} - for entry in address_data: - if 'address' in entry: - self._ipv4_address = entry['address'][1] - return + for ap_path in ap_paths: + ap_addr = DBusAddress(ap_path, NM, interface=NM_ACCESS_POINT_IFACE) + ap_props = self._router_main.send_and_get_reply(Properties(ap_addr).get_all()) + + # some APs have been seen dropping off during iteration + if ap_props.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to get AP properties for {ap_path}") + continue + + try: + ap = AccessPoint.from_dbus(ap_props.body[0], ap_path) + if ap.ssid == "": + continue + + if ap.ssid not in aps: + aps[ap.ssid] = [] + + aps[ap.ssid].append(ap) + except Exception: + # catch all for parsing errors + cloudlog.exception(f"Failed to parse AP properties for {ap_path}") + + self._networks = [Network.from_dbus(ssid, ap_list, ssid == self._tethering_ssid) for ssid, ap_list in aps.items()] + self._update_active_connection_info() + self._enqueue_callbacks(self._networks_updated, self.networks) # sorted + + if block: + worker() + else: + threading.Thread(target=worker, daemon=True).start() + + def _update_active_connection_info(self): + ipv4_address = "" + metered = MeteredType.UNKNOWN + + conn_path, props = self._get_active_wifi_connection() + + if conn_path is not None and props is not None: + # IPv4 address + ip4config_path = props.get('Ip4Config', ('o', '/'))[1] + + if ip4config_path != "/": + ip4config_addr = DBusAddress(ip4config_path, bus_name=NM, interface=NM_IP4_CONFIG_IFACE) + address_data = self._router_main.send_and_get_reply(Properties(ip4config_addr).get('AddressData')).body[0][1] + + for entry in address_data: + if 'address' in entry: + ipv4_address = entry['address'][1] + break + + # Metered status + settings = self._get_connection_settings(conn_path) + + if len(settings) > 0: + metered_prop = settings['connection'].get('metered', ('i', 0))[1] + + if metered_prop == MeteredType.YES: + metered = MeteredType.YES + elif metered_prop == MeteredType.NO: + metered = MeteredType.NO + + self._ipv4_address = ipv4_address + self._current_network_metered = metered def __del__(self): self.stop() diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 925afd7d10..a459927eeb 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -38,18 +38,13 @@ class Reset(Widget): self._reset_state = ResetState.NONE self._cancel_button = SmallButton("cancel") - self._cancel_button.set_click_callback(self._cancel_callback) + self._cancel_button.set_click_callback(gui_app.request_close) self._reboot_button = FullRoundedButton("reboot") self._reboot_button.set_click_callback(self._do_reboot) self._confirm_slider = SmallSlider("reset", self._confirm) - self._render_status = True - - def _cancel_callback(self): - self._render_status = False - def _do_reboot(self): if PC: return @@ -121,8 +116,6 @@ class Reset(Widget): self._reboot_button.rect.width, self._reboot_button.rect.height)) - return self._render_status - def _confirm(self): self.start_reset() @@ -150,10 +143,10 @@ def main(): if mode == ResetMode.FORMAT: reset.start_reset() - for should_render in gui_app.render(): - if should_render: - if not reset.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)): - break + gui_app.push_widget(reset) + + for _ in gui_app.render(): + pass if __name__ == "__main__": diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 3242cc0dbe..76fdfd8c68 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -7,22 +7,24 @@ import time import urllib.request import urllib.error from urllib.parse import urlparse -from enum import IntEnum import shutil from collections.abc import Callable import pyray as rl from cereal import log +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.realtime import config_realtime_process, set_core_affinity +from openpilot.common.swaglog import cloudlog from openpilot.common.utils import run_cmd -from openpilot.system.hardware import HARDWARE +from openpilot.system.hardware import HARDWARE, TICI from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 -from openpilot.system.ui.widgets import Widget, DialogResult +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton, - SmallCircleIconButton, WidishRoundedButton, SmallRedPillButton, - FullRoundedButton) + SmallCircleIconButton, WidishRoundedButton, FullRoundedButton) from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiUIMici @@ -49,11 +51,10 @@ exec ./launch_openpilot.sh class NetworkConnectivityMonitor: - def __init__(self, should_check: Callable[[], bool] | None = None, check_interval: float = 1.0): + def __init__(self, should_check: Callable[[], bool] | None = None): self.network_connected = threading.Event() self.wifi_connected = threading.Event() self._should_check = should_check or (lambda: True) - self._check_interval = check_interval self._stop_event = threading.Event() self._thread: threading.Thread | None = None @@ -78,7 +79,7 @@ class NetworkConnectivityMonitor: if self._should_check(): try: request = urllib.request.Request(OPENPILOT_URL, method="HEAD") - urllib.request.urlopen(request, timeout=1.0) + urllib.request.urlopen(request, timeout=2.0) self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() @@ -87,21 +88,10 @@ class NetworkConnectivityMonitor: else: self.reset() - if self._stop_event.wait(timeout=self._check_interval): + if self._stop_event.wait(timeout=1.0): break -class SetupState(IntEnum): - GETTING_STARTED = 0 - NETWORK_SETUP = 1 - NETWORK_SETUP_CUSTOM_SOFTWARE = 2 - SOFTWARE_SELECTION = 3 - CUSTOM_SOFTWARE = 4 - DOWNLOADING = 5 - DOWNLOAD_FAILED = 6 - CUSTOM_SOFTWARE_WARNING = 7 - - class StartPage(Widget): def __init__(self): super().__init__() @@ -110,25 +100,41 @@ class StartPage(Widget): font_weight=FontWeight.DISPLAY, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) - self._start_bg_txt = gui_app.texture("icons_mici/setup/green_button.png", 520, 224) - self._start_bg_pressed_txt = gui_app.texture("icons_mici/setup/green_button_pressed.png", 520, 224) + self._start_bg_txt = gui_app.texture("icons_mici/setup/start_button.png", 500, 224, keep_aspect_ratio=False) + self._start_bg_pressed_txt = gui_app.texture("icons_mici/setup/start_button_pressed.png", 500, 224, keep_aspect_ratio=False) + self._scale_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._click_delay = 0.075 def _render(self, rect: rl.Rectangle): - draw_x = rect.x + (rect.width - self._start_bg_txt.width) / 2 - draw_y = rect.y + (rect.height - self._start_bg_txt.height) / 2 + scale = self._scale_filter.update(1.07 if self.is_pressed else 1.0) + base_draw_x = rect.x + (rect.width - self._start_bg_txt.width) / 2 + base_draw_y = rect.y + (rect.height - self._start_bg_txt.height) / 2 + draw_x = base_draw_x + (self._start_bg_txt.width * (1 - scale)) / 2 + draw_y = base_draw_y + (self._start_bg_txt.height * (1 - scale)) / 2 texture = self._start_bg_pressed_txt if self.is_pressed else self._start_bg_txt - rl.draw_texture(texture, int(draw_x), int(draw_y), rl.WHITE) + rl.draw_texture_ex(texture, (draw_x, draw_y), 0, scale, rl.WHITE) - self._title.render(rect) + self._title.render(rl.Rectangle(rect.x, rect.y + (draw_y - base_draw_y), rect.width, rect.height)) -class SoftwareSelectionPage(Widget): +class SoftwareSelectionPage(NavWidget): def __init__(self, use_openpilot_callback: Callable, use_custom_software_callback: Callable): super().__init__() self._openpilot_slider = LargerSlider("slide to use\nopenpilot", use_openpilot_callback) + self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) self._custom_software_slider = LargerSlider("slide to use\ncustom software", use_custom_software_callback, green=False) + self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) + + def show_event(self): + super().show_event() + self._nav_bar._alpha = 0.0 + + def _update_state(self): + super()._update_state() + if self.is_dismissing: + self.reset() def reset(self): self._openpilot_slider.reset() @@ -248,6 +254,7 @@ class TermsPage(Widget): pass def _render(self, _): + rl.draw_rectangle_rec(self._rect, rl.BLACK) scroll_offset = round(self._scroll_panel.update(self._rect, self._content_height + self._continue_button.rect.height + 16)) if scroll_offset <= self._scrolled_down_offset: @@ -353,50 +360,61 @@ class DownloadingPage(Widget): def __init__(self): super().__init__() - self._title_label = UnifiedLabel("downloading", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + self._title_label = UnifiedLabel("downloading...", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) - self._progress_label = UnifiedLabel("", 128, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.35)), + self._progress_label = UnifiedLabel("", 132, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) self._progress = 0 + def show_event(self): + super().show_event() + self.set_progress(0) + def set_progress(self, progress: int): self._progress = progress self._progress_label.set_text(f"{progress}%") def _render(self, rect: rl.Rectangle): + rl.draw_rectangle_rec(rect, rl.BLACK) self._title_label.render(rl.Rectangle( - rect.x + 20, - rect.y + 10, + rect.x + 12, + rect.y + 2, rect.width, 64, )) self._progress_label.render(rl.Rectangle( - rect.x + 20, - rect.y + 20, + rect.x + 12, + rect.y + 18, rect.width, rect.height, )) -class FailedPage(Widget): +class FailedPage(NavWidget): def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): super().__init__() + self.set_back_callback(retry_callback) self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), font_weight=FontWeight.ROMAN) - self._reboot_button = SmallRedPillButton("reboot") - self._reboot_button.set_click_callback(reboot_callback) + self._reboot_slider = SmallSlider("reboot", reboot_callback) + self._reboot_slider.set_enabled(lambda: self.enabled) # for nav stack - self._retry_button = WideRoundedButton("retry") + self._retry_button = SmallButton("retry") self._retry_button.set_click_callback(retry_callback) + self._retry_button.set_enabled(lambda: self.enabled) # for nav stack def set_reason(self, reason: str): self._reason_label.set_text(reason) + def show_event(self): + super().show_event() + self._reboot_slider.reset() + def _render(self, rect: rl.Rectangle): self._title_label.render(rl.Rectangle( rect.x + 8, @@ -412,30 +430,34 @@ class FailedPage(Widget): 36, )) - self._reboot_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._reboot_button.rect.height, - self._reboot_button.rect.width, - self._reboot_button.rect.height, - )) - + self._retry_button.set_opacity(1 - self._reboot_slider.slider_percentage) self._retry_button.render(rl.Rectangle( - rect.x + 8 + self._reboot_button.rect.width + 8, - rect.y + rect.height - self._retry_button.rect.height, + self._rect.x + 8, + self._rect.y + self._rect.height - self._retry_button.rect.height, self._retry_button.rect.width, self._retry_button.rect.height, )) - -class NetworkSetupState(IntEnum): - MAIN = 0 - WIFI_PANEL = 1 + self._reboot_slider.render(rl.Rectangle( + self._rect.x + self._rect.width - self._reboot_slider.rect.width, + self._rect.y + self._rect.height - self._reboot_slider.rect.height, + self._reboot_slider.rect.width, + self._reboot_slider.rect.height, + )) -class NetworkSetupPage(Widget): - def __init__(self, wifi_manager, continue_callback: Callable, back_callback: Callable): +class NetworkSetupPage(NavWidget): + def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None], + back_callback: Callable[[], None] | None): super().__init__() - self._wifi_ui = WifiUIMici(wifi_manager, back_callback=lambda: self.set_state(NetworkSetupState.MAIN)) + self.set_back_callback(back_callback) + + self._wifi_manager = WifiManager() + self._wifi_manager.set_active(True) + self._network_monitor = network_monitor + self._custom_software = False + self._prev_has_internet = False + self._wifi_ui = WifiUIMici(self._wifi_manager) self._no_wifi_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 58, 50) self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 58, 50) @@ -445,202 +467,129 @@ class NetworkSetupPage(Widget): back_txt = gui_app.texture("icons_mici/setup/back_new.png", 37, 32) self._back_button = SmallCircleIconButton(back_txt) self._back_button.set_click_callback(back_callback) + self._back_button.set_enabled(lambda: self.enabled) # for nav stack self._wifi_button = SmallerRoundedButton("wifi") - self._wifi_button.set_click_callback(lambda: self.set_state(NetworkSetupState.WIFI_PANEL)) + self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) + self._wifi_button.set_enabled(lambda: self.enabled) self._continue_button = WidishRoundedButton("continue") self._continue_button.set_enabled(False) - self._continue_button.set_click_callback(continue_callback) + self._continue_button.set_click_callback(lambda: continue_callback(self._custom_software)) - self._state = NetworkSetupState.MAIN + gui_app.add_nav_stack_tick(self._nav_stack_tick) + + def show_event(self): + super().show_event() self._prev_has_internet = False + self._network_monitor.reset() + self._set_has_internet(False) - def set_state(self, state: NetworkSetupState): - self._state = state - if state == NetworkSetupState.WIFI_PANEL: - self._wifi_ui.show_event() + def _nav_stack_tick(self): + self._wifi_manager.process_callbacks() - def set_has_internet(self, has_internet: bool): + has_internet = self._network_monitor.network_connected.is_set() + if has_internet != self._prev_has_internet: + self._set_has_internet(has_internet) + if has_internet: + gui_app.pop_widgets_to(self) + self._prev_has_internet = has_internet + + def _set_has_internet(self, has_internet: bool): if has_internet: self._network_header.set_title("connected to internet") self._network_header.set_icon(self._wifi_full_txt) - self._continue_button.set_enabled(True) + self._continue_button.set_enabled(lambda: self.enabled) else: self._network_header.set_title(self._waiting_text) self._network_header.set_icon(self._no_wifi_txt) self._continue_button.set_enabled(False) - if has_internet and not self._prev_has_internet: - self.set_state(NetworkSetupState.MAIN) - self._prev_has_internet = has_internet - - def show_event(self): - super().show_event() - self._state = NetworkSetupState.MAIN - self._wifi_ui.show_event() - - def hide_event(self): - super().hide_event() - self._wifi_ui.hide_event() + def set_custom_software(self, custom_software: bool): + self._custom_software = custom_software def _render(self, _): - if self._state == NetworkSetupState.MAIN: - self._network_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16, - self._rect.width - 32, - self._network_header.rect.height, - )) + self._network_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16, + self._rect.width - 32, + self._network_header.rect.height, + )) - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) + self._back_button.render(rl.Rectangle( + self._rect.x + 8, + self._rect.y + self._rect.height - self._back_button.rect.height, + self._back_button.rect.width, + self._back_button.rect.height, + )) - self._wifi_button.render(rl.Rectangle( - self._rect.x + 8 + self._back_button.rect.width + 10, - self._rect.y + self._rect.height - self._wifi_button.rect.height, - self._wifi_button.rect.width, - self._wifi_button.rect.height, - )) + self._wifi_button.render(rl.Rectangle( + self._rect.x + 8 + self._back_button.rect.width + 10, + self._rect.y + self._rect.height - self._wifi_button.rect.height, + self._wifi_button.rect.width, + self._wifi_button.rect.height, + )) - self._continue_button.render(rl.Rectangle( - self._rect.x + self._rect.width - self._continue_button.rect.width - 8, - self._rect.y + self._rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) - else: - self._wifi_ui.render(self._rect) + self._continue_button.render(rl.Rectangle( + self._rect.x + self._rect.width - self._continue_button.rect.width - 8, + self._rect.y + self._rect.height - self._continue_button.rect.height, + self._continue_button.rect.width, + self._continue_button.rect.height, + )) class Setup(Widget): def __init__(self): super().__init__() - self.state = SetupState.GETTING_STARTED - self.failed_url = "" - self.failed_reason = "" self.download_url = "" self.download_progress = 0 self.download_thread = None - self._wifi_manager = WifiManager() - self._wifi_manager.set_active(True) + self._download_failed_reason: str | None = None + self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() - self._prev_has_internet = False - gui_app.set_modal_overlay_tick(self._modal_overlay_tick) + + def getting_started_button_callback(): + self._software_selection_page.reset() + gui_app.push_widget(self._software_selection_page) self._start_page = StartPage() - self._start_page.set_click_callback(self._getting_started_button_callback) + self._start_page.set_click_callback(getting_started_button_callback) + self._start_page.set_enabled(lambda: self.enabled) # for nav stack - self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_button_callback, - self._network_setup_back_button_callback) + self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_button_callback, + self._pop_to_software_selection) + self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page)) - self._software_selection_page = SoftwareSelectionPage(self._software_selection_continue_button_callback, - self._software_selection_custom_software_button_callback) + self._download_failed_page = FailedPage(HARDWARE.reboot, self._pop_to_software_selection) - self._download_failed_page = FailedPage(HARDWARE.reboot, self._download_failed_startover_button_callback) - - self._custom_software_warning_page = CustomSoftwareWarningPage(self._software_selection_custom_software_continue, - self._custom_software_warning_back_button_callback) + self._custom_software_warning_page = CustomSoftwareWarningPage(self._software_selection_custom_software_continue, self._pop_to_software_selection) self._downloading_page = DownloadingPage() - def _modal_overlay_tick(self): - has_internet = self._network_monitor.network_connected.is_set() - if has_internet and not self._prev_has_internet: - gui_app.set_modal_overlay(None) - self._prev_has_internet = has_internet + gui_app.add_nav_stack_tick(self._nav_stack_tick) - def _update_state(self): - self._wifi_manager.process_callbacks() + def _nav_stack_tick(self): + self._downloading_page.set_progress(self.download_progress) - def _set_state(self, state: SetupState): - self.state = state - if self.state == SetupState.SOFTWARE_SELECTION: - self._software_selection_page.reset() - elif self.state == SetupState.CUSTOM_SOFTWARE_WARNING: - self._custom_software_warning_page.reset() - - if self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE): - self._network_setup_page.show_event() - self._network_monitor.reset() - else: - self._network_setup_page.hide_event() + if self._download_failed_reason is not None: + reason = self._download_failed_reason + self._download_failed_reason = None + self._download_failed_page.set_reason(reason) + gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders + gui_app.push_widget(self._download_failed_page) def _render(self, rect: rl.Rectangle): - if self.state == SetupState.GETTING_STARTED: - self._start_page.render(rect) - elif self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE): - self.render_network_setup(rect) - elif self.state == SetupState.SOFTWARE_SELECTION: - self._software_selection_page.render(rect) - elif self.state == SetupState.CUSTOM_SOFTWARE_WARNING: - self._custom_software_warning_page.render(rect) - elif self.state == SetupState.CUSTOM_SOFTWARE: - self.render_custom_software() - elif self.state == SetupState.DOWNLOADING: - self.render_downloading(rect) - elif self.state == SetupState.DOWNLOAD_FAILED: - self._download_failed_page.render(rect) - - def _custom_software_warning_back_button_callback(self): - self._set_state(SetupState.SOFTWARE_SELECTION) - - def _getting_started_button_callback(self): - self._set_state(SetupState.SOFTWARE_SELECTION) - - def _software_selection_continue_button_callback(self): - self.use_openpilot() - - def _software_selection_custom_software_button_callback(self): - self._set_state(SetupState.CUSTOM_SOFTWARE_WARNING) - - def _software_selection_custom_software_continue(self): - self._set_state(SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE) - - def _download_failed_startover_button_callback(self): - self._set_state(SetupState.GETTING_STARTED) - - def _network_setup_back_button_callback(self): - self._set_state(SetupState.SOFTWARE_SELECTION) - - def _network_setup_continue_button_callback(self): - if self.state == SetupState.NETWORK_SETUP: - self.download(OPENPILOT_URL) - elif self.state == SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE: - self._set_state(SetupState.CUSTOM_SOFTWARE) + self._start_page.render(rect) def close(self): self._network_monitor.stop() - def render_network_setup(self, rect: rl.Rectangle): - has_internet = self._network_monitor.network_connected.is_set() - self._prev_has_internet = has_internet - self._network_setup_page.set_has_internet(has_internet) - self._network_setup_page.render(rect) + def _pop_to_software_selection(self): + # reset sliders after dismiss completes + gui_app.pop_widgets_to(self._software_selection_page, self._software_selection_page.reset) - def render_downloading(self, rect: rl.Rectangle): - self._downloading_page.set_progress(self.download_progress) - self._downloading_page.render(rect) - - def render_custom_software(self): - def handle_keyboard_result(text): - url = text.strip() - if url: - self.download(url) - - def handle_keyboard_exit(result): - if result == DialogResult.CANCEL: - self._set_state(SetupState.SOFTWARE_SELECTION) - - keyboard = BigInputDialog("custom software URL", confirm_callback=handle_keyboard_result) - gui_app.set_modal_overlay(keyboard, callback=handle_keyboard_exit) - - def use_openpilot(self): + def _use_openpilot(self): if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): os.remove(VALID_CACHE_PATH) with open(TMP_CONTINUE_PATH, "w") as f: @@ -653,17 +602,40 @@ class Setup(Widget): time.sleep(0.1) gui_app.request_close() else: - self._set_state(SetupState.NETWORK_SETUP) + self._push_network_setup(custom_software=False) - def download(self, url: str): + def _push_network_setup(self, custom_software: bool): + self._network_setup_page.set_custom_software(custom_software) + gui_app.push_widget(self._network_setup_page) + + def _software_selection_custom_software_continue(self): + gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders + self._push_network_setup(custom_software=True) + + def _network_setup_continue_button_callback(self, custom_software): + if not custom_software: + gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders + self._download(OPENPILOT_URL) + else: + def handle_keyboard_result(text): + url = text.strip() + if url: + gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders + self._download(url) + + keyboard = BigInputDialog("custom software URL", confirm_callback=handle_keyboard_result) + gui_app.push_widget(keyboard) + + def _download(self, url: str): # autocomplete incomplete URLs if re.match("^([^/.]+)/([^/]+)$", url): url = f"https://installer.comma.ai/{url}" parsed = urlparse(url, scheme='https') self.download_url = (urlparse(f"https://{url}") if not parsed.netloc else parsed).geturl() + self.download_progress = 0 - self._set_state(SetupState.DOWNLOADING) + gui_app.push_widget(self._downloading_page) self.download_thread = threading.Thread(target=self._download_thread, daemon=True) self.download_thread.start() @@ -694,7 +666,6 @@ class Setup(Widget): if total_size: self.download_progress = int(downloaded * 100 / total_size) - self._downloading_page.set_progress(self.download_progress) is_elf = False with open(tmpfile, 'rb') as f: @@ -702,7 +673,7 @@ class Setup(Widget): is_elf = header == b'\x7fELF' if not is_elf: - self.download_failed(self.download_url, "No custom software found at this URL.") + self._download_failed_reason = "No custom software found at this URL." return # AGNOS might try to execute the installer before this process exits. @@ -719,26 +690,26 @@ class Setup(Widget): except urllib.error.HTTPError as e: if e.code == 409: - error_msg = "Incompatible sunnypilot version" - self.download_failed(self.download_url, error_msg) + self._download_failed_reason = "Incompatible sunnypilot version" except Exception: - error_msg = "Invalid URL" - self.download_failed(self.download_url, error_msg) - - def download_failed(self, url: str, reason: str): - self.failed_url = url - self.failed_reason = reason - self._download_failed_page.set_reason(reason) - self._set_state(SetupState.DOWNLOAD_FAILED) + self._download_failed_reason = "Invalid URL" def main(): + config_realtime_process(0, 51) + # attempt to affine. AGNOS will start setup with all cores, should only fail when manually launching with screen off + if TICI: + try: + set_core_affinity([5]) + except OSError: + cloudlog.exception("Failed to set core affinity for setup process") + try: gui_app.init_window("Setup") setup = Setup() - for should_render in gui_app.render(): - if should_render: - setup.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + gui_app.push_widget(setup) + for _ in gui_app.render(): + pass setup.close() except Exception as e: print(f"Setup error: {e}") diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 7ebb4262ff..c98b310709 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -7,10 +7,9 @@ from enum import IntEnum from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network +from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.label import gui_text_box, gui_label, UnifiedLabel +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.button import FullRoundedButton from openpilot.system.ui.mici_setup import NetworkSetupPage, FailedPage, NetworkConnectivityMonitor @@ -28,7 +27,6 @@ class Updater(Widget): self.updater = updater_path self.manifest = manifest_path self.current_screen = Screen.PROMPT - self._current_network_strength = -1 self.progress_value = 0 self.progress_text = "loading" @@ -39,8 +37,8 @@ class Updater(Widget): self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_callback, self._network_setup_back_callback) + self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack - self._wifi_manager.add_callbacks(networks_updated=self._on_network_updated) self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() @@ -48,7 +46,7 @@ class Updater(Widget): self._continue_button = FullRoundedButton("continue") self._continue_button.set_click_callback(lambda: self.set_current_screen(Screen.WIFI)) - self._title_label = UnifiedLabel("update required", 48, text_color=rl.Color(255, 115, 0, 255), + self._title_label = UnifiedLabel("update required", 48, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._subtitle_label = UnifiedLabel("The download size is approximately 1GB.", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), @@ -57,6 +55,12 @@ class Updater(Widget): self._update_failed_page = FailedPage(HARDWARE.reboot, self._update_failed_retry_callback, title="update failed") + self._progress_title_label = UnifiedLabel("", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + font_weight=FontWeight.DISPLAY, line_height=0.8) + self._progress_percent_label = UnifiedLabel("", 132, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), + font_weight=FontWeight.ROMAN, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + def _network_setup_back_callback(self): self.set_current_screen(Screen.PROMPT) @@ -66,9 +70,6 @@ class Updater(Widget): def _update_failed_retry_callback(self): self.set_current_screen(Screen.PROMPT) - def _on_network_updated(self, networks: list[Network]): - self._current_network_strength = next((net.strength for net in networks if net.is_connected), -1) - def set_current_screen(self, screen: Screen): if self.current_screen != screen: if screen == Screen.PROGRESS: @@ -143,20 +144,21 @@ class Updater(Widget): )) def render_progress_screen(self, rect: rl.Rectangle): - title_rect = rl.Rectangle(self._rect.x + 6, self._rect.y - 5, self._rect.width - 12, self._rect.height - 8) - if ' ' in self.progress_text: - font_size = 62 - else: - font_size = 82 - gui_text_box(title_rect, self.progress_text, font_size, font_weight=FontWeight.DISPLAY, - color=rl.Color(255, 255, 255, int(255 * 0.9))) + self._progress_title_label.set_text(self.progress_text.replace("_", "_\n") + "...") + self._progress_title_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 2, + rect.width, + self._progress_title_label.get_content_height(int(rect.width - 20)), + )) - progress_value = f"{self.progress_value}%" - text_height = measure_text_cached(gui_app.font(FontWeight.ROMAN), progress_value, 128).y - progress_rect = rl.Rectangle(self._rect.x + 6, self._rect.y + self._rect.height - text_height + 18, - self._rect.width - 12, text_height) - gui_label(progress_rect, progress_value, 128, font_weight=FontWeight.ROMAN, - color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.35))) + self._progress_percent_label.set_text(f"{self.progress_value}%") + self._progress_percent_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 18, + rect.width, + rect.height, + )) def _update_state(self): self._wifi_manager.process_callbacks() @@ -187,9 +189,9 @@ def main(): try: gui_app.init_window("System Update") updater = Updater(updater_path, manifest_path) - for should_render in gui_app.render(): - if should_render: - updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + gui_app.push_widget(updater) + for _ in gui_app.render(): + pass updater.close() except Exception as e: print(f"Updater error: {e}") diff --git a/system/ui/sunnypilot/lib/utils.py b/system/ui/sunnypilot/lib/utils.py index 09230da14b..ecaba86f4b 100644 --- a/system/ui/sunnypilot/lib/utils.py +++ b/system/ui/sunnypilot/lib/utils.py @@ -10,3 +10,27 @@ from openpilot.system.ui.sunnypilot.widgets.list_view import ButtonActionSP class NoElideButtonAction(ButtonActionSP): def get_width_hint(self): return super().get_width_hint() + 1 + + +class AlertFadeAnimator: + def __init__(self, target_fps: int, duration_on: float = 0.75, rc: float = 0.05): + from openpilot.common.filter_simple import FirstOrderFilter + self._filter = FirstOrderFilter(1.0, rc, 1 / target_fps) + self._frame = 0 + self._target_fps = target_fps + self._duration_on = duration_on + + def update(self, active: bool): + if active: + self._frame += 1 + if (self._frame % self._target_fps) < (self._target_fps * self._duration_on): + self._filter.x = 1.0 + else: + self._filter.update(0.0) + else: + self._frame = 0 + self._filter.update(1.0) + + @property + def alpha(self) -> float: + return self._filter.x diff --git a/system/ui/sunnypilot/widgets/html_render.py b/system/ui/sunnypilot/widgets/html_render.py index 259067723e..a0018f5be7 100644 --- a/system/ui/sunnypilot/widgets/html_render.py +++ b/system/ui/sunnypilot/widgets/html_render.py @@ -18,7 +18,7 @@ class HtmlModalSP(HtmlModal): def _on_ok_clicked(self): self._dialog_result = DialogResult.CONFIRM - gui_app.set_modal_overlay(None) + gui_app.pop_widget() if self._callback: self._callback(self._dialog_result) diff --git a/system/ui/sunnypilot/widgets/input_dialog.py b/system/ui/sunnypilot/widgets/input_dialog.py index ed67302fcb..83b7edacf2 100644 --- a/system/ui/sunnypilot/widgets/input_dialog.py +++ b/system/ui/sunnypilot/widgets/input_dialog.py @@ -39,4 +39,5 @@ class InputDialogSP: if self.callback: self.callback(result, text) - gui_app.set_modal_overlay(self.keyboard, internal_callback) + self.keyboard.set_callback(internal_callback) + gui_app.push_widget(self.keyboard) diff --git a/system/ui/sunnypilot/widgets/option_control.py b/system/ui/sunnypilot/widgets/option_control.py index 91e9650ebd..291d8f6ff0 100644 --- a/system/ui/sunnypilot/widgets/option_control.py +++ b/system/ui/sunnypilot/widgets/option_control.py @@ -44,7 +44,8 @@ class OptionControlSP(ItemAction): self.current_value = int(key) break else: - self.current_value = int(self.params.get(self.param_key, return_default=True)) + value = self.params.get(self.param_key, return_default=True) + self.current_value = int(float(value) * 100.0) if self.use_float_scaling else int(value) # Initialize font and button styles self._font = gui_app.font(FontWeight.MEDIUM) diff --git a/system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py b/system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py index af6b9cf45d..77e8b5fd2c 100644 --- a/system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py +++ b/system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py @@ -45,7 +45,7 @@ class SunnylinkPairingDialog(PairingDialog): def _update_state(self): is_paired = ui_state.sunnylink_state.is_paired() if not self._is_paired_prev and is_paired: - gui_app.set_modal_overlay(None) + gui_app.pop_widget() def _render(self, rect: rl.Rectangle) -> int: rl.clear_background(rl.Color(224, 224, 224, 255)) diff --git a/system/ui/sunnypilot/widgets/tree_dialog.py b/system/ui/sunnypilot/widgets/tree_dialog.py index 2dd5e3a2dc..c34db092e8 100644 --- a/system/ui/sunnypilot/widgets/tree_dialog.py +++ b/system/ui/sunnypilot/widgets/tree_dialog.py @@ -8,7 +8,7 @@ from dataclasses import dataclass, field import pyray as rl from openpilot.common.params import Params -from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.lib.application import FontWeight from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.button import Button, ButtonStyle, BUTTON_PRESSED_BACKGROUND_COLORS @@ -76,6 +76,14 @@ class TreeItemWidget(Button): class TreeOptionDialog(MultiOptionDialog): + @property + def on_exit(self): + return self._callback + + @on_exit.setter + def on_exit(self, value): + self._callback = value + def __init__(self, title, folders, current_ref="", fav_param="", option_font_weight=FontWeight.MEDIUM, search_prompt=None, get_folders_fn=None, on_exit=None, display_func=None, search_funcs=None, search_title=None, search_subtitle=None): super().__init__(title, [], current_ref, option_font_weight) @@ -124,7 +132,6 @@ class TreeOptionDialog(MultiOptionDialog): if result == DialogResult.CONFIRM: self.query = text self._build_visible_items() - gui_app.set_modal_overlay(self, callback=self.on_exit) def _on_search_clicked(self): self.search_dialog = InputDialogSP( @@ -266,8 +273,6 @@ class TreeOptionDialog(MultiOptionDialog): self.select_button.set_enabled(self.selection != self.current) self.select_button.render(select_rect) - return self._result - def _handle_mouse_press(self, mouse_pos): if self._search_rect and rl.check_collision_point_rec(mouse_pos, self._search_rect): self._search_pressed = True diff --git a/system/ui/tici_reset.py b/system/ui/tici_reset.py index 3922c27aac..23f6b344ec 100755 --- a/system/ui/tici_reset.py +++ b/system/ui/tici_reset.py @@ -36,13 +36,9 @@ class Reset(Widget): self._mode = mode self._previous_reset_state = None self._reset_state = ResetState.NONE - self._cancel_button = Button("Cancel", self._cancel_callback) + self._cancel_button = Button("Cancel", gui_app.request_close) self._confirm_button = Button("Confirm", self._confirm, button_style=ButtonStyle.PRIMARY) self._reboot_button = Button("Reboot", lambda: os.system("sudo reboot")) - self._render_status = True - - def _cancel_callback(self): - self._render_status = False def _do_erase(self): if PC: @@ -69,30 +65,30 @@ class Reset(Widget): elif self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: exit(0) - def _render(self, rect: rl.Rectangle): - label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100 * FONT_SCALE) + def _render(self, _): + content_rect = rl.Rectangle(45, 200, self._rect.width - 90, self._rect.height - 245) + + label_rect = rl.Rectangle(content_rect.x + 140, content_rect.y, content_rect.width - 280, 100 * FONT_SCALE) gui_label(label_rect, "System Reset", 100, font_weight=FontWeight.BOLD) - text_rect = rl.Rectangle(rect.x + 140, rect.y + 140, rect.width - 280, rect.height - 90 - 100 * FONT_SCALE) + text_rect = rl.Rectangle(content_rect.x + 140, content_rect.y + 140, content_rect.width - 280, content_rect.height - 90 - 100 * FONT_SCALE) gui_text_box(text_rect, self._get_body_text(), 90) button_height = 160 button_spacing = 50 - button_top = rect.y + rect.height - button_height - button_width = (rect.width - button_spacing) / 2.0 + button_top = content_rect.y + content_rect.height - button_height + button_width = (content_rect.width - button_spacing) / 2.0 if self._reset_state != ResetState.RESETTING: if self._mode == ResetMode.RECOVER: - self._reboot_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height)) + self._reboot_button.render(rl.Rectangle(content_rect.x, button_top, button_width, button_height)) elif self._mode == ResetMode.USER_RESET: - self._cancel_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height)) + self._cancel_button.render(rl.Rectangle(content_rect.x, button_top, button_width, button_height)) if self._reset_state != ResetState.FAILED: - self._confirm_button.render(rl.Rectangle(rect.x + button_width + 50, button_top, button_width, button_height)) + self._confirm_button.render(rl.Rectangle(content_rect.x + button_width + 50, button_top, button_width, button_height)) else: - self._reboot_button.render(rl.Rectangle(rect.x, button_top, rect.width, button_height)) - - return self._render_status + self._reboot_button.render(rl.Rectangle(content_rect.x, button_top, content_rect.width, button_height)) def _confirm(self): if self._reset_state == ResetState.CONFIRM: @@ -126,10 +122,10 @@ def main(): if mode == ResetMode.FORMAT: reset.start_reset() - for should_render in gui_app.render(): - if should_render: - if not reset.render(rl.Rectangle(45, 200, gui_app.width - 90, gui_app.height - 245)): - break + gui_app.push_widget(reset) + + for _ in gui_app.render(): + pass if __name__ == "__main__": diff --git a/system/ui/tici_setup.py b/system/ui/tici_setup.py index bf64361bed..8098e9ea27 100755 --- a/system/ui/tici_setup.py +++ b/system/ui/tici_setup.py @@ -16,7 +16,7 @@ from openpilot.common.utils import run_cmd from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets import DialogResult, Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle, ButtonRadio from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.label import Label @@ -218,7 +218,7 @@ class Setup(Widget): while not self.stop_network_check_thread.is_set(): if self.state == SetupState.NETWORK_SETUP: try: - urllib.request.urlopen(OPENPILOT_URL, timeout=2) + urllib.request.urlopen(OPENPILOT_URL, timeout=2.0) self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() @@ -226,7 +226,7 @@ class Setup(Widget): self.wifi_connected.clear() except Exception: self.network_connected.clear() - time.sleep(1) + time.sleep(1.0) def start_network_check(self): if self.network_check_thread is None or not self.network_check_thread.is_alive(): @@ -327,19 +327,20 @@ class Setup(Widget): def render_custom_software(self): def handle_keyboard_result(result): # Enter pressed - if result == 1: + if result == DialogResult.CONFIRM: url = self.keyboard.text self.keyboard.clear() if url: self.download(url) # Cancel pressed - elif result == 0: + elif result == DialogResult.CANCEL: self.state = SetupState.SOFTWARE_SELECTION self.keyboard.reset(min_text_size=1) self.keyboard.set_title("Enter URL", "for Custom Software") - gui_app.set_modal_overlay(self.keyboard, callback=handle_keyboard_result) + self.keyboard.set_callback(handle_keyboard_result) + gui_app.push_widget(self.keyboard) def use_openpilot(self): if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): @@ -437,9 +438,9 @@ def main(): try: gui_app.init_window("Setup", 20) setup = Setup() - for should_render in gui_app.render(): - if should_render: - setup.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + gui_app.push_widget(setup) + for _ in gui_app.render(): + pass setup.close() except Exception as e: print(f"Setup error: {e}") diff --git a/system/ui/tici_updater.py b/system/ui/tici_updater.py index ebf4b3bec3..9824638cd0 100755 --- a/system/ui/tici_updater.py +++ b/system/ui/tici_updater.py @@ -161,10 +161,9 @@ def main(): try: gui_app.init_window("System Update") - updater = Updater(updater_path, manifest_path) - for should_render in gui_app.render(): - if should_render: - updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + gui_app.push_widget(Updater(updater_path, manifest_path)) + for _ in gui_app.render(): + pass finally: # Make sure we clean up even if there's an error gui_app.close() diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index b3b1276a58..568f58b985 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -4,7 +4,6 @@ import abc import pyray as rl from enum import IntEnum from collections.abc import Callable -from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter from openpilot.system.ui.lib.application import gui_app, MousePos, MAX_TOUCH_SLOTS, MouseEvent try: @@ -31,6 +30,8 @@ class Widget(abc.ABC): self._enabled: bool | Callable[[], bool] = True self._is_visible: bool | Callable[[], bool] = True self._touch_valid_callback: Callable[[], bool] | None = None + self._click_delay: float | None = None # seconds to hold is_pressed after release + self._click_release_time: float | None = None self._click_callback: Callable[[], None] | None = None self._multi_touch = False self.__was_awake = True @@ -52,7 +53,8 @@ class Widget(abc.ABC): @property def is_pressed(self) -> bool: - return any(self.__is_pressed) + # if actually pressed or holding after release + return any(self.__is_pressed) or self._click_release_time is not None @property def enabled(self) -> bool: @@ -99,20 +101,34 @@ class Widget(abc.ABC): self._update_state() + if self._click_release_time is not None and rl.get_time() >= self._click_release_time: + self._click_release_time = None + if not self.is_visible: return None self._layout() ret = self._render(self._rect) + if gui_app.show_touches: + self._draw_debug_rect() + # Keep track of whether mouse down started within the widget's rectangle if self.enabled and self.__was_awake: self._process_mouse_events() + else: + # TODO: ideally we emit release events when going disabled + self.__is_pressed = [False] * MAX_TOUCH_SLOTS + self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS self.__was_awake = device.awake return ret + def _draw_debug_rect(self) -> None: + rl.draw_rectangle_lines(int(self._rect.x), int(self._rect.y), + max(int(self._rect.width), 1), max(int(self._rect.height), 1), rl.RED) + def _process_mouse_events(self) -> None: hit_rect = self._hit_rect touch_valid = self._touch_valid() @@ -172,6 +188,8 @@ class Widget(abc.ABC): def _handle_mouse_release(self, mouse_pos: MousePos) -> None: """Optionally handle mouse release events.""" + if self._click_delay is not None: + self._click_release_time = rl.get_time() + self._click_delay if self._click_callback: self._click_callback() @@ -181,216 +199,13 @@ class Widget(abc.ABC): def show_event(self): """Optionally handle show event. Parent must manually call this""" + # TODO: iterate through all child objects, check for subclassing from Widget/Layout (Scroller) def hide_event(self): """Optionally handle hide event. Parent must manually call this""" - -SWIPE_AWAY_THRESHOLD = 80 # px to dismiss after releasing -START_DISMISSING_THRESHOLD = 40 # px to start dismissing while dragging -BLOCK_SWIPE_AWAY_THRESHOLD = 60 # px horizontal movement to block swipe away - -NAV_BAR_MARGIN = 6 -NAV_BAR_WIDTH = 205 -NAV_BAR_HEIGHT = 8 - -DISMISS_PUSH_OFFSET = 50 + NAV_BAR_MARGIN + NAV_BAR_HEIGHT # px extra to push down when dismissing -DISMISS_TIME_SECONDS = 2.0 - - -class NavBar(Widget): - def __init__(self): - super().__init__() - self.set_rect(rl.Rectangle(0, 0, NAV_BAR_WIDTH, NAV_BAR_HEIGHT)) - self._alpha = 1.0 - self._alpha_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) - self._fade_time = 0.0 - - def set_alpha(self, alpha: float) -> None: - self._alpha = alpha - self._fade_time = rl.get_time() - - def show_event(self): - super().show_event() - self._alpha = 1.0 - self._alpha_filter.x = 1.0 - self._fade_time = rl.get_time() - - def _render(self, _): - if rl.get_time() - self._fade_time > DISMISS_TIME_SECONDS: - self._alpha = 0.0 - alpha = self._alpha_filter.update(self._alpha) - - # white bar with black border - rl.draw_rectangle_rounded(self._rect, 1.0, 6, rl.Color(255, 255, 255, int(255 * 0.9 * alpha))) - rl.draw_rectangle_rounded_lines_ex(self._rect, 1.0, 6, 2, rl.Color(0, 0, 0, int(255 * 0.3 * alpha))) - - -class NavWidget(Widget, abc.ABC): - """ - A full screen widget that supports back navigation by swiping down from the top. - """ - BACK_TOUCH_AREA_PERCENTAGE = 0.65 - - def __init__(self): - super().__init__() - self._back_callback: Callable[[], None] | None = None - self._back_button_start_pos: MousePos | None = None - self._swiping_away = False # currently swiping away - self._can_swipe_away = True # swipe away is blocked after certain horizontal movement - - self._pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) - self._playing_dismiss_animation = False - self._trigger_animate_in = False - self._nav_bar_show_time = 0.0 - self._back_enabled: bool | Callable[[], bool] = True - self._nav_bar = NavBar() - - self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) - - self._set_up = False - - @property - def back_enabled(self) -> bool: - return self._back_enabled() if callable(self._back_enabled) else self._back_enabled - - def set_back_enabled(self, enabled: bool | Callable[[], bool]) -> None: - self._back_enabled = enabled - - def set_back_callback(self, callback: Callable[[], None]) -> None: - self._back_callback = callback - - def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: - super()._handle_mouse_event(mouse_event) - - if not self.back_enabled: - self._back_button_start_pos = None - self._swiping_away = False - self._can_swipe_away = True - return - - if mouse_event.left_pressed: - # user is able to swipe away if starting near top of screen, or anywhere if scroller is at top - self._pos_filter.update_alpha(0.04) - in_dismiss_area = mouse_event.pos.y < self._rect.height * self.BACK_TOUCH_AREA_PERCENTAGE - - scroller_at_top = False - vertical_scroller = False - # TODO: -20? snapping in WiFi dialog can make offset not be positive at the top - if hasattr(self, '_scroller'): - scroller_at_top = self._scroller.scroll_panel.get_offset() >= -20 and not self._scroller._horizontal - vertical_scroller = not self._scroller._horizontal - elif hasattr(self, '_scroll_panel'): - scroller_at_top = self._scroll_panel.get_offset() >= -20 and not self._scroll_panel._horizontal - vertical_scroller = not self._scroll_panel._horizontal - - # Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes - if (not vertical_scroller and in_dismiss_area) or scroller_at_top: - self._can_swipe_away = True - self._back_button_start_pos = mouse_event.pos - - elif mouse_event.left_down: - if self._back_button_start_pos is not None: - # block swiping away if too much horizontal or upward movement - horizontal_movement = abs(mouse_event.pos.x - self._back_button_start_pos.x) > BLOCK_SWIPE_AWAY_THRESHOLD - upward_movement = mouse_event.pos.y - self._back_button_start_pos.y < -BLOCK_SWIPE_AWAY_THRESHOLD - if not self._swiping_away and (horizontal_movement or upward_movement): - self._can_swipe_away = False - self._back_button_start_pos = None - - # block horizontal swiping if now swiping away - if self._can_swipe_away: - if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: - self._swiping_away = True - - elif mouse_event.left_released: - self._pos_filter.update_alpha(0.1) - # if far enough, trigger back navigation callback - if self._back_button_start_pos is not None: - if mouse_event.pos.y - self._back_button_start_pos.y > SWIPE_AWAY_THRESHOLD: - self._playing_dismiss_animation = True - - self._back_button_start_pos = None - self._swiping_away = False - - def _update_state(self): - super()._update_state() - - # Disable self's scroller while swiping away - if not self._set_up: - self._set_up = True - if hasattr(self, '_scroller'): - original_enabled = self._scroller._enabled - self._scroller.set_enabled(lambda: not self._swiping_away and (original_enabled() if callable(original_enabled) else - original_enabled)) - elif hasattr(self, '_scroll_panel'): - original_enabled = self._scroll_panel.enabled - self._scroll_panel.set_enabled(lambda: not self._swiping_away and (original_enabled() if callable(original_enabled) else - original_enabled)) - - if self._trigger_animate_in: - self._pos_filter.x = self._rect.height - self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT - self._nav_bar_show_time = rl.get_time() - self._trigger_animate_in = False - - new_y = 0.0 - - if self._back_button_start_pos is not None: - last_mouse_event = gui_app.last_mouse_event - # push entire widget as user drags it away - new_y = max(last_mouse_event.pos.y - self._back_button_start_pos.y, 0) - if new_y < SWIPE_AWAY_THRESHOLD: - new_y /= 2 # resistance until mouse release would dismiss widget - - if self._swiping_away: - self._nav_bar.set_alpha(1.0) - - if self._playing_dismiss_animation: - new_y = self._rect.height + DISMISS_PUSH_OFFSET - - new_y = round(self._pos_filter.update(new_y)) - if abs(new_y) < 1 and self._pos_filter.velocity.x == 0.0: - new_y = self._pos_filter.x = 0.0 - - if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: - if self._back_callback is not None: - self._back_callback() - - self._playing_dismiss_animation = False - self._back_button_start_pos = None - self._swiping_away = False - - self.set_position(self._rect.x, new_y) - - def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: - ret = super().render(rect) - - if self.back_enabled: - bar_x = self._rect.x + (self._rect.width - self._nav_bar.rect.width) / 2 - nav_bar_delayed = rl.get_time() - self._nav_bar_show_time < 0.4 - # User dragging or dismissing, nav bar follows NavWidget - if self._back_button_start_pos is not None or self._playing_dismiss_animation: - self._nav_bar_y_filter.x = NAV_BAR_MARGIN + self._pos_filter.x - # Waiting to show - elif nav_bar_delayed: - self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT - # Animate back to top - else: - self._nav_bar_y_filter.update(NAV_BAR_MARGIN) - - # draw black above widget when dismissing - if self._rect.y > 0: - rl.draw_rectangle(int(self._rect.x), 0, int(self._rect.width), int(self._rect.y), rl.BLACK) - - self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) - self._nav_bar.render() - - return ret - - def show_event(self): - super().show_event() - # FIXME: we don't know the height of the rect at first show_event since it's before the first render :( - # so we need this hacky bool for now - self._trigger_animate_in = True - self._nav_bar.show_event() + def dismiss(self, callback: Callable[[], None] | None = None): + """Immediately dismiss the widget, firing the callback after.""" + gui_app.pop_widget() + if callback: + callback() diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 9c0ea75b42..67125d7091 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -232,7 +232,7 @@ class SmallButton(Widget): self._load_assets() - self._label = UnifiedLabel(text, 36, font_weight=FontWeight.MEDIUM, + self._label = UnifiedLabel(text, 36, font_weight=FontWeight.SEMI_BOLD, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index 97618660bd..3544836761 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -1,4 +1,5 @@ import pyray as rl +from collections.abc import Callable from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult @@ -17,7 +18,7 @@ BACKGROUND_COLOR = rl.Color(27, 27, 27, 255) class ConfirmDialog(Widget): - def __init__(self, text: str, confirm_text: str, cancel_text: str | None = None, rich: bool = False): + def __init__(self, text: str, confirm_text: str, cancel_text: str | None = None, rich: bool = False, callback: Callable[[DialogResult], None] | None = None): super().__init__() if cancel_text is None: cancel_text = tr("Cancel") @@ -26,7 +27,7 @@ class ConfirmDialog(Widget): self._cancel_button = Button(cancel_text, self._cancel_button_callback) self._confirm_button = Button(confirm_text, self._confirm_button_callback, button_style=ButtonStyle.PRIMARY) self._rich = rich - self._dialog_result = DialogResult.NO_ACTION + self._callback = callback self._cancel_text = cancel_text self._scroller = Scroller([self._html_renderer], line_separator=False, spacing=0) @@ -36,14 +37,15 @@ class ConfirmDialog(Widget): else: self._html_renderer.parse_html_content(text) - def reset(self): - self._dialog_result = DialogResult.NO_ACTION - def _cancel_button_callback(self): - self._dialog_result = DialogResult.CANCEL + gui_app.pop_widget() + if self._callback: + self._callback(DialogResult.CANCEL) def _confirm_button_callback(self): - self._dialog_result = DialogResult.CONFIRM + gui_app.pop_widget() + if self._callback: + self._callback(DialogResult.CONFIRM) def _render(self, rect: rl.Rectangle): dialog_x = OUTER_MARGIN if not self._rich else RICH_OUTER_MARGIN @@ -73,9 +75,9 @@ class ConfirmDialog(Widget): self._scroller.render(text_rect) if rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER): - self._dialog_result = DialogResult.CONFIRM + self._confirm_button_callback() elif rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE): - self._dialog_result = DialogResult.CANCEL + self._cancel_button_callback() if self._cancel_text: self._confirm_button.render(confirm_button) @@ -85,8 +87,6 @@ class ConfirmDialog(Widget): full_confirm_button = rl.Rectangle(dialog_rect.x + MARGIN, button_y, full_button_width, BUTTON_HEIGHT) self._confirm_button.render(full_confirm_button) - return self._dialog_result - def alert_dialog(message: str, button_text: str | None = None): if button_text is None: diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index 7d90d56925..77fca9fe34 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -260,7 +260,7 @@ class HtmlModal(Widget): super().__init__() self._content = HtmlRenderer(file_path=file_path, text=text) self._scroll_panel = GuiScrollPanel() - self._ok_button = Button(tr("OK"), click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY) + self._ok_button = Button(tr("OK"), click_callback=gui_app.pop_widget, button_style=ButtonStyle.PRIMARY) def _render(self, rect: rl.Rectangle): margin = 50 diff --git a/system/ui/widgets/icon_widget.py b/system/ui/widgets/icon_widget.py new file mode 100644 index 0000000000..bf7790b937 --- /dev/null +++ b/system/ui/widgets/icon_widget.py @@ -0,0 +1,16 @@ +import pyray as rl +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import Widget + + +class IconWidget(Widget): + def __init__(self, image_path: str, size: tuple[int, int], opacity: float = 1.0): + super().__init__() + self._texture = gui_app.texture(image_path, size[0], size[1]) + self._opacity = opacity + self.set_rect(rl.Rectangle(0, 0, float(size[0]), float(size[1]))) + self.set_enabled(False) + + def _render(self, _) -> None: + color = rl.Color(255, 255, 255, int(self._opacity * 255)) + rl.draw_texture_ex(self._texture, rl.Vector2(self._rect.x, self._rect.y), 0.0, 1.0, color) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 4ec92f507a..49c59a431f 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -1,12 +1,13 @@ from functools import partial import time from typing import Literal +from collections.abc import Callable import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets import DialogResult, Widget from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.inputbox import InputBox from openpilot.system.ui.widgets.label import Label @@ -58,7 +59,8 @@ KEYBOARD_LAYOUTS = { class Keyboard(Widget): - def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False): + def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False, + callback: Callable[[DialogResult], None] | None = None): super().__init__() self._layout_name: Literal["lowercase", "uppercase", "numbers", "specials"] = "lowercase" self._caps_lock = False @@ -71,13 +73,13 @@ class Keyboard(Widget): self._input_box = InputBox(max_text_size) self._password_mode = password_mode self._show_password_toggle = show_password_toggle + self._callback = callback # Backspace key repeat tracking self._backspace_pressed: bool = False self._backspace_press_time: float = 0.0 self._backspace_last_repeat: float = 0.0 - self._render_return_status = -1 self._cancel_button = Button(lambda: tr("Cancel"), self._cancel_button_callback) self._eye_button = Button("", self._eye_button_callback, button_style=ButtonStyle.TRANSPARENT) @@ -122,16 +124,23 @@ class Keyboard(Widget): self._title.set_text(title) self._sub_title.set_text(sub_title) + def set_callback(self, callback: Callable[[DialogResult], None] | None): + self._callback = callback + def _eye_button_callback(self): self._password_mode = not self._password_mode def _cancel_button_callback(self): self.clear() - self._render_return_status = 0 + gui_app.pop_widget() + if self._callback: + self._callback(DialogResult.CANCEL) def _key_callback(self, k): if k == ENTER_KEY: - self._render_return_status = 1 + gui_app.pop_widget() + if self._callback: + self._callback(DialogResult.CONFIRM) else: self.handle_key_press(k) @@ -197,8 +206,6 @@ class Keyboard(Widget): self._all_keys[key].set_enabled(is_enabled) self._all_keys[key].render(key_rect) - return self._render_return_status - def _render_input_area(self, input_rect: rl.Rectangle): if self._show_password_toggle: self._input_box.set_password_mode(self._password_mode) @@ -250,7 +257,6 @@ class Keyboard(Widget): def reset(self, min_text_size: int | None = None): if min_text_size is not None: self._min_text_size = min_text_size - self._render_return_status = -1 self._last_shift_press_time = 0 self._backspace_pressed = False self._backspace_press_time = 0.0 @@ -259,15 +265,18 @@ class Keyboard(Widget): if __name__ == "__main__": - gui_app.init_window("Keyboard") - keyboard = Keyboard(min_text_size=8, show_password_toggle=True) - for _ in gui_app.render(): - keyboard.set_title("Keyboard Input", "Type your text below") - result = keyboard.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - if result == 1: + def callback(result: DialogResult): + if result == DialogResult.CONFIRM: print(f"You typed: {keyboard.text}") - gui_app.request_close() - elif result == 0: + elif result == DialogResult.CANCEL: print("Canceled") - gui_app.request_close() + gui_app.request_close() + + gui_app.init_window("Keyboard") + keyboard = Keyboard(min_text_size=8, show_password_toggle=True, callback=callback) + keyboard.set_title("Keyboard Input", "Type your text below") + + gui_app.push_widget(keyboard) + for _ in gui_app.render(): + pass gui_app.close() diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index cb0cf66b14..8ed9ec62f5 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -489,6 +489,13 @@ class UnifiedLabel(Widget): self._spacing_pixels = self._font_size * letter_spacing self._cached_text = None # Invalidate cache + def set_line_height(self, line_height: float): + """Update line height (multiplier, e.g., 1.0 = default).""" + new_line_height = line_height * 0.9 + if self._line_height != new_line_height: + self._line_height = new_line_height + self._cached_text = None # Invalidate cache (affects total height) + def set_font_weight(self, font_weight: FontWeight): """Update the font weight.""" if self._font_weight != font_weight: @@ -753,7 +760,12 @@ class UnifiedLabel(Widget): # draw black fade on left and right fade_width = 20 rl.draw_rectangle_gradient_h(int(self._rect.x + self._rect.width - fade_width), int(self._rect.y), fade_width, int(self._rect.height), rl.BLANK, rl.BLACK) - if self._scroll_state != ScrollState.STARTING: + + # stop drawing left fade once text scrolls past + text_width = visible_sizes[0].x if visible_sizes else 0 + first_copy_in_view = self._scroll_offset + text_width > 0 + draw_left_fade = self._scroll_state != ScrollState.STARTING and first_copy_in_view + if draw_left_fade: rl.draw_rectangle_gradient_h(int(self._rect.x), int(self._rect.y), fade_width, int(self._rect.height), rl.BLACK, rl.BLANK) rl.end_scissor_mode() diff --git a/system/ui/widgets/layouts.py b/system/ui/widgets/layouts.py new file mode 100644 index 0000000000..6f97fe5ed8 --- /dev/null +++ b/system/ui/widgets/layouts.py @@ -0,0 +1,60 @@ +from enum import IntFlag +from openpilot.system.ui.widgets import Widget + + +class Alignment(IntFlag): + LEFT = 0 + # TODO: implement + # H_CENTER = 2 + # RIGHT = 4 + + TOP = 8 + V_CENTER = 16 + BOTTOM = 32 + + +class HBoxLayout(Widget): + """ + A Widget that lays out child Widgets horizontally. + """ + + def __init__(self, widgets: list[Widget] | None = None, spacing: int = 0, + alignment: Alignment = Alignment.LEFT | Alignment.V_CENTER): + super().__init__() + self._widgets: list[Widget] = [] + self._spacing = spacing + self._alignment = alignment + + if widgets is not None: + for widget in widgets: + self.add_widget(widget) + + @property + def widgets(self) -> list[Widget]: + return self._widgets + + def add_widget(self, widget: Widget) -> None: + self._widgets.append(widget) + + def _render(self, _): + visible_widgets = [w for w in self._widgets if w.is_visible] + + cur_offset_x = 0 + + for idx, widget in enumerate(visible_widgets): + spacing = self._spacing if (idx > 0) else 0 + + x = self._rect.x + cur_offset_x + spacing + cur_offset_x += widget.rect.width + spacing + + if self._alignment & Alignment.TOP: + y = self._rect.y + elif self._alignment & Alignment.BOTTOM: + y = self._rect.y + self._rect.height - widget.rect.height + else: # center + y = self._rect.y + (self._rect.height - widget.rect.height) / 2 + + # Update widget position and render + widget.set_position(round(x), round(y)) + widget.set_parent_rect(self._rect) + widget.render() diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 6d2e08e053..18384fd905 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -61,7 +61,7 @@ class Key(Widget): self._x_filter.x = x self._y_filter.x = local_y # keep track of original position so dragging around feels consistent. also move touch area down a bit - self.original_position = rl.Vector2(x, y + KEY_TOUCH_AREA_OFFSET) + self.original_position = rl.Vector2(x, local_y + KEY_TOUCH_AREA_OFFSET) self._position_initialized = True if not smooth: @@ -227,6 +227,8 @@ class MiciKeyboard(Widget): for current_row, row in zip(self._current_keys, keys, strict=False): # not all layouts have the same number of keys for current_key, key in zip_repeat(current_row, row): + # reset parent rect for new keys + key.set_parent_rect(self._rect) current_pos = current_key.get_position() key.set_position(current_pos[0], current_pos[1], smooth=False) @@ -264,7 +266,8 @@ class MiciKeyboard(Widget): for key in row: mouse_pos = gui_app.last_mouse_event.pos # approximate distance for comparison is accurate enough - dist = abs(key.original_position.x - mouse_pos.x) + abs(key.original_position.y - mouse_pos.y) + # use local y coords so parent widget offset (e.g. during NavWidget animate-in) doesn't affect hit testing + dist = abs(key.original_position.x - mouse_pos.x) + abs(key.original_position.y - (mouse_pos.y - self._rect.y)) if dist < closest_key[1]: if self._closest_key[0] is None or key is self._closest_key[0] or dist < self._closest_key[1] - KEY_DRAG_HYSTERESIS: closest_key = (key, dist) @@ -319,7 +322,7 @@ class MiciKeyboard(Widget): self._selected_key_filter.update(self._closest_key[0] is not None) # unselect key after animation plays - if self._unselect_key_t is not None and rl.get_time() > self._unselect_key_t: + if (self._unselect_key_t is not None and rl.get_time() > self._unselect_key_t) or not self.enabled: self._closest_key = (None, float('inf')) self._unselect_key_t = None self._selected_key_t = None diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py new file mode 100644 index 0000000000..67203d53f4 --- /dev/null +++ b/system/ui/widgets/nav_widget.py @@ -0,0 +1,219 @@ +from __future__ import annotations + +import abc +import pyray as rl +from collections.abc import Callable +from openpilot.system.ui.widgets import Widget +from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter +from openpilot.system.ui.lib.application import gui_app, MousePos, MouseEvent + +SWIPE_AWAY_THRESHOLD = 80 # px to dismiss after releasing +START_DISMISSING_THRESHOLD = 40 # px to start dismissing while dragging +BLOCK_SWIPE_AWAY_THRESHOLD = 60 # px horizontal movement to block swipe away + +NAV_BAR_MARGIN = 6 +NAV_BAR_WIDTH = 205 +NAV_BAR_HEIGHT = 8 + +DISMISS_PUSH_OFFSET = NAV_BAR_MARGIN + NAV_BAR_HEIGHT + 50 # px extra to push down when dismissing +DISMISS_ANIMATION_RC = 0.2 # slightly slower for non-user triggered dismiss animation + + +class NavBar(Widget): + FADE_AFTER_SECONDS = 2.0 + + def __init__(self): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, NAV_BAR_WIDTH, NAV_BAR_HEIGHT)) + self._alpha = 1.0 + self._alpha_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._fade_time = 0.0 + + def set_alpha(self, alpha: float) -> None: + self._alpha = alpha + self._fade_time = rl.get_time() + + def show_event(self): + super().show_event() + self._alpha = 1.0 + self._alpha_filter.x = 1.0 + self._fade_time = rl.get_time() + + def _render(self, _): + if rl.get_time() - self._fade_time > self.FADE_AFTER_SECONDS: + self._alpha = 0.0 + alpha = self._alpha_filter.update(self._alpha) + + # white bar with black border + rl.draw_rectangle_rounded(self._rect, 1.0, 6, rl.Color(255, 255, 255, int(255 * 0.9 * alpha))) + rl.draw_rectangle_rounded_lines_ex(self._rect, 1.0, 6, 2, rl.Color(0, 0, 0, int(255 * 0.3 * alpha))) + + +class NavWidget(Widget, abc.ABC): + """ + A full screen widget that supports back navigation by swiping down from the top. + """ + BACK_TOUCH_AREA_PERCENTAGE = 0.65 + + def __init__(self): + super().__init__() + # State + self._drag_start_pos: MousePos | None = None # cleared after certain amount of horizontal movement + self._dragging_down = False # swiped down enough to trigger dismissing on release + self._playing_dismiss_animation = False # released and animating away + self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) + + self._back_callback: Callable[[], None] | None = None # persistent callback for any back navigation + self._dismiss_callback: Callable[[], None] | None = None # transient callback for programmatic dismiss + + # TODO: move this state into NavBar + self._nav_bar = NavBar() + self._nav_bar_show_time = 0.0 + self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + + def _back_enabled(self) -> bool: + # Children can override this to block swipe away, like when not at + # the top of a vertical scroll panel to prevent erroneous swipes + return True + + def set_back_callback(self, callback: Callable[[], None]) -> None: + self._back_callback = callback + + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: + super()._handle_mouse_event(mouse_event) + + # Don't let touch events change filter state during dismiss animation + if self._playing_dismiss_animation: + return + + if mouse_event.left_pressed: + # user is able to swipe away if starting near top of screen + self._y_pos_filter.update_alpha(0.04) + in_dismiss_area = mouse_event.pos.y < self._rect.height * self.BACK_TOUCH_AREA_PERCENTAGE + + if in_dismiss_area and self._back_enabled(): + self._drag_start_pos = mouse_event.pos + + elif mouse_event.left_down: + if self._drag_start_pos is not None: + # block swiping away if too much horizontal or upward movement + # block (lock-in) threshold is higher than start dismissing + horizontal_movement = abs(mouse_event.pos.x - self._drag_start_pos.x) > BLOCK_SWIPE_AWAY_THRESHOLD + upward_movement = mouse_event.pos.y - self._drag_start_pos.y < -BLOCK_SWIPE_AWAY_THRESHOLD + + if not (horizontal_movement or upward_movement): + # no blocking movement, check if we should start dismissing + if mouse_event.pos.y - self._drag_start_pos.y > START_DISMISSING_THRESHOLD: + self._dragging_down = True + else: + if not self._dragging_down: + self._drag_start_pos = None + + elif mouse_event.left_released: + # reset rc for either slide up or down animation + self._y_pos_filter.update_alpha(0.1) + + # if far enough, trigger back navigation callback + if self._drag_start_pos is not None: + if mouse_event.pos.y - self._drag_start_pos.y > SWIPE_AWAY_THRESHOLD: + self._playing_dismiss_animation = True + + self._drag_start_pos = None + self._dragging_down = False + + def _update_state(self): + super()._update_state() + + new_y = 0.0 + + if self._dragging_down: + self._nav_bar.set_alpha(1.0) + + # FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down + if not self.enabled: + self._drag_start_pos = None + + if self._drag_start_pos is not None: + last_mouse_event = gui_app.last_mouse_event + # push entire widget as user drags it away + new_y = max(last_mouse_event.pos.y - self._drag_start_pos.y, 0) + if new_y < SWIPE_AWAY_THRESHOLD: + new_y /= 2 # resistance until mouse release would dismiss widget + + if self._playing_dismiss_animation: + new_y = self._rect.height + DISMISS_PUSH_OFFSET + + new_y = round(self._y_pos_filter.update(new_y)) + if abs(new_y) < 1 and self._y_pos_filter.velocity.x == 0.0: + new_y = self._y_pos_filter.x = 0.0 + + if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: + gui_app.pop_widget() + + if self._back_callback is not None: + self._back_callback() + + if self._dismiss_callback is not None: + self._dismiss_callback() + self._dismiss_callback = None + + self._playing_dismiss_animation = False + self._drag_start_pos = None + self._dragging_down = False + + self.set_position(self._rect.x, new_y) + + def _layout(self): + # Dim whatever is behind this widget, fading with position (runs after _update_state so position is correct) + overlay_alpha = int(200 * max(0.0, min(1.0, 1.0 - self._rect.y / self._rect.height))) if self._rect.height > 0 else 0 + rl.draw_rectangle(0, 0, int(self._rect.width), int(self._rect.height), rl.Color(0, 0, 0, overlay_alpha)) + + bounce_height = 20 + rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height + bounce_height), rl.BLACK) + + def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: + ret = super().render(rect) + + bar_x = self._rect.x + (self._rect.width - self._nav_bar.rect.width) / 2 + nav_bar_delayed = rl.get_time() - self._nav_bar_show_time < 0.4 + # User dragging or dismissing, nav bar follows NavWidget + if self._drag_start_pos is not None or self._playing_dismiss_animation: + self._nav_bar_y_filter.x = NAV_BAR_MARGIN + self._y_pos_filter.x + # Waiting to show + elif nav_bar_delayed: + self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT + # Animate back to top + else: + self._nav_bar_y_filter.update(NAV_BAR_MARGIN) + + self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) + self._nav_bar.render() + + return ret + + @property + def is_dismissing(self) -> bool: + return self._dragging_down or self._playing_dismiss_animation + + def dismiss(self, callback: Callable[[], None] | None = None): + """Programmatically trigger the dismiss animation. Calls pop_widget when done, then callback.""" + if not self._playing_dismiss_animation: + self._playing_dismiss_animation = True + self._y_pos_filter.update_alpha(DISMISS_ANIMATION_RC) + self._dismiss_callback = callback + + def show_event(self): + super().show_event() + self._nav_bar.show_event() + + # Reset state + self._drag_start_pos = None + self._dragging_down = False + self._playing_dismiss_animation = False + self._dismiss_callback = None + # Start NavWidget off-screen, no matter how tall it is + self._y_pos_filter.update_alpha(0.1) + self._y_pos_filter.x = gui_app.height + + self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT + self._nav_bar_show_time = rl.get_time() diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index dd0a540648..6427a2f203 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -6,8 +6,8 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel -from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType, normalize_ssid +from openpilot.system.ui.widgets import DialogResult, Widget from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.keyboard import Keyboard @@ -193,8 +193,8 @@ class AdvancedNetworkSettings(Widget): self._wifi_manager.update_gsm_settings(roaming_state, self._params.get("GsmApn") or "", self._params.get_bool("GsmMetered")) def _edit_apn(self): - def update_apn(result): - if result != 1: + def update_apn(result: DialogResult): + if result != DialogResult.CONFIRM: return apn = self._keyboard.text.strip() @@ -209,7 +209,8 @@ class AdvancedNetworkSettings(Widget): self._keyboard.reset(min_text_size=0) self._keyboard.set_title(tr("Enter APN"), tr("leave blank for automatic configuration")) self._keyboard.set_text(current_apn) - gui_app.set_modal_overlay(self._keyboard, update_apn) + self._keyboard.set_callback(update_apn) + gui_app.push_widget(self._keyboard) def _toggle_cellular_metered(self): metered = self._cellular_metered_action.get_state() @@ -222,15 +223,18 @@ class AdvancedNetworkSettings(Widget): self._wifi_manager.set_current_network_metered(metered_type) def _connect_to_hidden_network(self): - def connect_hidden(result): - if result != 1: + def connect_hidden(result: DialogResult): + if result != DialogResult.CONFIRM: return ssid = self._keyboard.text if not ssid: return - def enter_password(result): + def enter_password(result: DialogResult): + if result != DialogResult.CONFIRM: + return + password = self._keyboard.text if password == "": # connect without password @@ -241,15 +245,17 @@ class AdvancedNetworkSettings(Widget): self._keyboard.reset(min_text_size=0) self._keyboard.set_title(tr("Enter password"), tr("for \"{}\"").format(ssid)) - gui_app.set_modal_overlay(self._keyboard, enter_password) + self._keyboard.set_callback(enter_password) + gui_app.push_widget(self._keyboard) self._keyboard.reset(min_text_size=1) self._keyboard.set_title(tr("Enter SSID"), "") - gui_app.set_modal_overlay(self._keyboard, connect_hidden) + self._keyboard.set_callback(connect_hidden) + gui_app.push_widget(self._keyboard) def _edit_tethering_password(self): - def update_password(result): - if result != 1: + def update_password(result: DialogResult): + if result != DialogResult.CONFIRM: return password = self._keyboard.text @@ -259,7 +265,8 @@ class AdvancedNetworkSettings(Widget): self._keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) self._keyboard.set_title(tr("Enter new tethering password"), "") self._keyboard.set_text(self._wifi_manager.tethering_password) - gui_app.set_modal_overlay(self._keyboard, update_password) + self._keyboard.set_callback(update_password) + gui_app.push_widget(self._keyboard) def _update_state(self): self._wifi_manager.process_callbacks() @@ -317,31 +324,32 @@ class WifiManagerUI(Widget): return if self.state == UIState.NEEDS_AUTH and self._state_network: - self.keyboard.set_title(tr("Wrong password") if self._password_retry else tr("Enter password"), tr("for \"{}\"").format(self._state_network.ssid)) + self.keyboard.set_title(tr("Wrong password") if self._password_retry else tr("Enter password"), + tr("for \"{}\"").format(normalize_ssid(self._state_network.ssid))) self.keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) - gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(cast(Network, self._state_network), result)) + self.keyboard.set_callback(lambda result: self._on_password_entered(cast(Network, self._state_network), result)) + gui_app.push_widget(self.keyboard) elif self.state == UIState.SHOW_FORGET_CONFIRM and self._state_network: - confirm_dialog = ConfirmDialog("", tr("Forget"), tr("Cancel")) - confirm_dialog.set_text(tr("Forget Wi-Fi Network \"{}\"?").format(self._state_network.ssid)) - confirm_dialog.reset() - gui_app.set_modal_overlay(confirm_dialog, callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result)) + confirm_dialog = ConfirmDialog("", tr("Forget"), tr("Cancel"), callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result)) + confirm_dialog.set_text(tr("Forget Wi-Fi Network \"{}\"?").format(normalize_ssid(self._state_network.ssid))) + gui_app.push_widget(confirm_dialog) else: self._draw_network_list(rect) - def _on_password_entered(self, network: Network, result: int): - if result == 1: + def _on_password_entered(self, network: Network, result: DialogResult): + if result == DialogResult.CONFIRM: password = self.keyboard.text self.keyboard.clear() if len(password) >= MIN_PASSWORD_LENGTH: self.connect_to_network(network, password) - elif result == 0: + elif result == DialogResult.CANCEL: self.state = UIState.IDLE - def on_forgot_confirm_finished(self, network, result: int): - if result == 1: + def on_forgot_confirm_finished(self, network, result: DialogResult): + if result == DialogResult.CONFIRM: self.forget_network(network) - elif result == 0: + elif result == DialogResult.CANCEL: self.state = UIState.IDLE def _draw_network_list(self, rect: rl.Rectangle): @@ -389,7 +397,7 @@ class WifiManagerUI(Widget): gui_label(status_text_rect, status_text, font_size=48, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) else: # If the network is saved, show the "Forget" button - if network.is_saved: + if self._wifi_manager.is_connection_saved(network.ssid): forget_btn_rect = rl.Rectangle( security_icon_rect.x - self.btn_width - spacing, rect.y + (ITEM_HEIGHT - 80) / 2, @@ -402,11 +410,11 @@ class WifiManagerUI(Widget): self._draw_signal_strength_icon(signal_icon_rect, network) def _networks_buttons_callback(self, network): - if not network.is_saved and network.security_type != SecurityType.OPEN: + if not self._wifi_manager.is_connection_saved(network.ssid) and network.security_type != SecurityType.OPEN: self.state = UIState.NEEDS_AUTH self._state_network = network self._password_retry = False - elif not network.is_connected: + elif self._wifi_manager.wifi_state.ssid != network.ssid: self.connect_to_network(network) def _forget_networks_buttons_callback(self, network): @@ -416,7 +424,7 @@ class WifiManagerUI(Widget): def _draw_status_icon(self, rect, network: Network): """Draw the status icon based on network's connection state""" icon_file = None - if network.is_connected and self.state != UIState.CONNECTING: + if self._wifi_manager.connected_ssid == network.ssid and self.state != UIState.CONNECTING: icon_file = "icons/checkmark.png" elif network.security_type == SecurityType.UNSUPPORTED: icon_file = "icons/circled_slash.png" @@ -438,7 +446,7 @@ class WifiManagerUI(Widget): def connect_to_network(self, network: Network, password=''): self.state = UIState.CONNECTING self._state_network = network - if network.is_saved and not password: + if self._wifi_manager.is_connection_saved(network.ssid) and not password: self._wifi_manager.activate_connection(network.ssid) else: self._wifi_manager.connect_to_network(network.ssid, password) @@ -451,7 +459,7 @@ class WifiManagerUI(Widget): def _on_network_updated(self, networks: list[Network]): self._networks = networks for n in self._networks: - self._networks_buttons[n.ssid] = Button(n.ssid, partial(self._networks_buttons_callback, n), font_size=55, + self._networks_buttons[n.ssid] = Button(normalize_ssid(n.ssid), partial(self._networks_buttons_callback, n), font_size=55, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.TRANSPARENT_WHITE_TEXT) self._networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) self._forget_networks_buttons[n.ssid] = Button(tr("Forget"), partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI, @@ -469,7 +477,7 @@ class WifiManagerUI(Widget): if self.state == UIState.CONNECTING: self.state = UIState.IDLE - def _on_forgotten(self): + def _on_forgotten(self, _): if self.state == UIState.FORGETTING: self.state = UIState.IDLE @@ -480,10 +488,10 @@ class WifiManagerUI(Widget): def main(): gui_app.init_window("Wi-Fi Manager") - wifi_ui = WifiManagerUI(WifiManager()) + gui_app.push_widget(WifiManagerUI(WifiManager())) for _ in gui_app.render(): - wifi_ui.render(rl.Rectangle(50, 50, gui_app.width - 100, gui_app.height - 100)) + pass gui_app.close() diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index 62578d1cfb..206400a74f 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -1,5 +1,6 @@ import pyray as rl -from openpilot.system.ui.lib.application import FontWeight +from collections.abc import Callable +from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.button import Button, ButtonStyle @@ -17,13 +18,13 @@ LIST_ITEM_SPACING = 25 class MultiOptionDialog(Widget): - def __init__(self, title, options, current="", option_font_weight=FontWeight.MEDIUM): + def __init__(self, title, options, current="", option_font_weight=FontWeight.MEDIUM, callback: Callable[[DialogResult], None] | None = None): super().__init__() self.title = title self.options = options self.current = current self.selection = current - self._result: DialogResult = DialogResult.NO_ACTION + self._callback = callback # Create scroller with option buttons self.option_buttons = [Button(option, click_callback=lambda opt=option: self._on_option_clicked(opt), @@ -36,7 +37,9 @@ class MultiOptionDialog(Widget): self.select_button = Button(lambda: tr("Select"), click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY) def _set_result(self, result: DialogResult): - self._result = result + gui_app.pop_widget() + if self._callback: + self._callback(result) def _on_option_clicked(self, option): self.selection = option @@ -74,5 +77,3 @@ class MultiOptionDialog(Widget): select_rect = rl.Rectangle(content_rect.x + button_w + BUTTON_SPACING, button_y, button_w, BUTTON_HEIGHT) self.select_button.set_enabled(self.selection != self.current) self.select_button.render(select_rect) - - return self._result diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 03c49fca65..c48be6b80b 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -3,35 +3,28 @@ import numpy as np from collections.abc import Callable from openpilot.common.filter_simple import FirstOrderFilter, BounceFilter +from openpilot.common.swaglog import cloudlog from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2, ScrollState from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget ITEM_SPACING = 20 LINE_COLOR = rl.GRAY LINE_PADDING = 40 ANIMATION_SCALE = 0.6 +MOVE_LIFT = 20 +MOVE_OVERLAY_ALPHA = 0.65 +SCROLL_RC = 0.15 + +EDGE_SHADOW_WIDTH = 20 + MIN_ZOOM_ANIMATION_TIME = 0.075 # seconds DO_ZOOM = False DO_JELLO = False -class LineSeparator(Widget): - def __init__(self, height: int = 1): - super().__init__() - self._rect = rl.Rectangle(0, 0, 0, height) - - def set_parent_rect(self, parent_rect: rl.Rectangle) -> None: - super().set_parent_rect(parent_rect) - self._rect.width = parent_rect.width - - def _render(self, _): - rl.draw_line(int(self._rect.x) + LINE_PADDING, int(self._rect.y), - int(self._rect.x + self._rect.width) - LINE_PADDING, int(self._rect.y), - LINE_COLOR) - - class ScrollIndicator(Widget): HORIZONTAL_MARGIN = 4 @@ -74,23 +67,21 @@ class ScrollIndicator(Widget): rl.Color(255, 255, 255, int(255 * 0.45))) -class Scroller(Widget): - def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = True, spacing: int = ITEM_SPACING, - line_separator: bool = False, pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING, - scroll_indicator: bool = True): +class _Scroller(Widget): + """Should use wrapper below to reduce boilerplate""" + def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = False, spacing: int = ITEM_SPACING, + pad: int = ITEM_SPACING, scroll_indicator: bool = True, edge_shadows: bool = True): super().__init__() self._items: list[Widget] = [] self._horizontal = horizontal self._snap_items = snap_items self._spacing = spacing - self._line_separator = LineSeparator() if line_separator else None - self._pad_start = pad_start - self._pad_end = pad_end + self._pad = pad self._reset_scroll_at_show = True - self._scrolling_to: float | None = None - self._scroll_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + self._scrolling_to: tuple[float | None, bool] = (None, False) # target offset, block_interaction + self._scrolling_to_filter = FirstOrderFilter(0.0, SCROLL_RC, 1 / gui_app.target_fps) self._zoom_filter = FirstOrderFilter(1.0, 0.2, 1 / gui_app.target_fps) self._zoom_out_t: float = 0.0 @@ -107,16 +98,27 @@ class Scroller(Widget): self.scroll_panel = GuiScrollPanel2(self._horizontal, handle_out_of_bounds=not self._snap_items) self._scroll_enabled: bool | Callable[[], bool] = True - self._show_scroll_indicator = scroll_indicator + self._show_scroll_indicator = scroll_indicator and self._horizontal self._scroll_indicator = ScrollIndicator() + self._edge_shadows = edge_shadows and self._horizontal - for item in items: - self.add_widget(item) + # move animation state + # on move; lift src widget -> wait -> move all -> wait -> drop src widget + self._overlay_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps) + self._move_animations: dict[Widget, FirstOrderFilter] = {} + self._move_lift: dict[Widget, FirstOrderFilter] = {} + # these are used to wait before moving/dropping, also to move onto next part of the animation earlier for timing + self._pending_lift: set[Widget] = set() + self._pending_move: set[Widget] = set() + + self.add_widgets(items) def set_reset_scroll_at_show(self, scroll: bool): self._reset_scroll_at_show = scroll - def scroll_to(self, pos: float, smooth: bool = False): + def scroll_to(self, pos: float, smooth: bool = False, block_interaction: bool = False): + assert not block_interaction or smooth, "Instant scroll cannot block user interaction" + # already there if abs(pos) < 1: return @@ -124,17 +126,35 @@ class Scroller(Widget): # FIXME: the padding correction doesn't seem correct scroll_offset = self.scroll_panel.get_offset() - pos if smooth: - self._scrolling_to = scroll_offset + self._scrolling_to_filter.x = self.scroll_panel.get_offset() + self._scrolling_to = scroll_offset, block_interaction else: self.scroll_panel.set_offset(scroll_offset) @property def is_auto_scrolling(self) -> bool: - return self._scrolling_to is not None + return self._scrolling_to[0] is not None + + @property + def items(self) -> list[Widget]: + return self._items + + @property + def content_size(self) -> float: + return self._content_size def add_widget(self, item: Widget) -> None: self._items.append(item) - item.set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid() and self.enabled) + + # preserve original touch valid callback + original_touch_valid_callback = item._touch_valid_callback + item.set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid() and self.enabled and self._scrolling_to[0] is None + and not self.moving_items and (original_touch_valid_callback() if + original_touch_valid_callback else True)) + + def add_widgets(self, items: list[Widget]) -> None: + for item in items: + self.add_widget(item) def set_scrolling_enabled(self, enabled: bool | Callable[[], bool]) -> None: """Set whether scrolling is enabled (does not affect widget enabled state).""" @@ -142,7 +162,7 @@ class Scroller(Widget): def _update_state(self): if DO_ZOOM: - if self._scrolling_to is not None or self.scroll_panel.state != ScrollState.STEADY: + if self._scrolling_to[0] is not None or self.scroll_panel.state != ScrollState.STEADY: self._zoom_out_t = rl.get_time() + MIN_ZOOM_ANIMATION_TIME self._zoom_filter.update(0.85) else: @@ -152,24 +172,22 @@ class Scroller(Widget): else: self._zoom_filter.update(0.85) - # Cancel auto-scroll if user starts manually scrolling - if self._scrolling_to is not None and (self.scroll_panel.state == ScrollState.PRESSED or self.scroll_panel.state == ScrollState.MANUAL_SCROLL): - self._scrolling_to = None + # Cancel auto-scroll if user starts manually scrolling (unless block_interaction) + if (self.scroll_panel.state in (ScrollState.PRESSED, ScrollState.MANUAL_SCROLL) and + self._scrolling_to[0] is not None and not self._scrolling_to[1]): + self._scrolling_to = None, False - if self._scrolling_to is not None: - self._scroll_filter.update(self._scrolling_to) - self.scroll_panel.set_offset(self._scroll_filter.x) + if self._scrolling_to[0] is not None and len(self._pending_lift) == 0: + self._scrolling_to_filter.update(self._scrolling_to[0]) + self.scroll_panel.set_offset(self._scrolling_to_filter.x) - if abs(self._scroll_filter.x - self._scrolling_to) < 1: - self.scroll_panel.set_offset(self._scrolling_to) - self._scrolling_to = None - else: - # keep current scroll position up to date - self._scroll_filter.x = self.scroll_panel.get_offset() + if abs(self._scrolling_to_filter.x - self._scrolling_to[0]) < 1: + self.scroll_panel.set_offset(self._scrolling_to[0]) + self._scrolling_to = None, False def _get_scroll(self, visible_items: list[Widget], content_size: float) -> float: scroll_enabled = self._scroll_enabled() if callable(self._scroll_enabled) else self._scroll_enabled - self.scroll_panel.set_enabled(scroll_enabled and self.enabled) + self.scroll_panel.set_enabled(scroll_enabled and self.enabled and not self._scrolling_to[1]) self.scroll_panel.update(self._rect, content_size) if not self._snap_items: return round(self.scroll_panel.get_offset()) @@ -208,29 +226,86 @@ class Scroller(Widget): return self.scroll_panel.get_offset() + @property + def moving_items(self) -> bool: + return len(self._move_animations) > 0 or len(self._move_lift) > 0 + + def move_item(self, from_idx: int, to_idx: int): + assert self._horizontal + if from_idx == to_idx: + return + + if self.moving_items: + cloudlog.warning(f"Already moving items, cannot move from {from_idx} to {to_idx}") + return + + item = self._items.pop(from_idx) + self._items.insert(to_idx, item) + + # store original position in content space of all affected widgets to animate from + for idx in range(min(from_idx, to_idx), max(from_idx, to_idx) + 1): + affected_item = self._items[idx] + self._move_animations[affected_item] = FirstOrderFilter(affected_item.rect.x - self._scroll_offset, SCROLL_RC, 1 / gui_app.target_fps) + self._pending_move.add(affected_item) + + # lift only src widget to make it more clear which one is moving + self._move_lift[item] = FirstOrderFilter(0.0, SCROLL_RC, 1 / gui_app.target_fps) + self._pending_lift.add(item) + + def _do_move_animation(self, item: Widget, target_x: float, target_y: float) -> tuple[float, float]: + # wait a frame before moving so we match potential pending scroll animation + can_start_move = len(self._pending_lift) == 0 + + if item in self._move_lift: + lift_filter = self._move_lift[item] + + # Animate lift + if len(self._pending_move) > 0: + lift_filter.update(MOVE_LIFT) + # start moving when almost lifted + if abs(lift_filter.x - MOVE_LIFT) < 2: + self._pending_lift.discard(item) + else: + # if done moving, animate down + lift_filter.update(0) + if abs(lift_filter.x) < 1: + del self._move_lift[item] + target_y -= lift_filter.x + + # Animate move + if item in self._move_animations: + move_filter = self._move_animations[item] + + # compare/update in content space to match filter + content_x = target_x - self._scroll_offset + if can_start_move: + move_filter.update(content_x) + + # drop when close to target + if abs(move_filter.x - content_x) < 10: + self._pending_move.discard(item) + + # finished moving + if abs(move_filter.x - content_x) < 1: + del self._move_animations[item] + target_x = move_filter.x + self._scroll_offset + + return target_x, target_y + def _layout(self): self._visible_items = [item for item in self._items if item.is_visible] - # Add line separator between items - if self._line_separator is not None: - l = len(self._visible_items) - for i in range(1, len(self._visible_items)): - self._visible_items.insert(l - i, self._line_separator) - self._content_size = sum(item.rect.width if self._horizontal else item.rect.height for item in self._visible_items) self._content_size += self._spacing * (len(self._visible_items) - 1) - self._content_size += self._pad_start + self._pad_end + self._content_size += self._pad * 2 self._scroll_offset = self._get_scroll(self._visible_items, self._content_size) - rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), - int(self._rect.width), int(self._rect.height)) - self._item_pos_filter.update(self._scroll_offset) cur_pos = 0 for idx, item in enumerate(self._visible_items): - spacing = self._spacing if (idx > 0) else self._pad_start + spacing = self._spacing if (idx > 0) else self._pad # Nicely lay out items horizontally/vertically if self._horizontal: x = self._rect.x + cur_pos + spacing @@ -262,44 +337,133 @@ class Scroller(Widget): [self._item_pos_filter.x, self._scroll_offset, self._item_pos_filter.x]) y -= np.clip(jello_offset, -20, 20) + # Animate moves if needed + x, y = self._do_move_animation(item, x, y) + # Update item state item.set_position(round(x), round(y)) # round to prevent jumping when settling item.set_parent_rect(self._rect) - def _render(self, _): - for item in self._visible_items: - # Skip rendering if not in viewport - if not rl.check_collision_recs(item.rect, self._rect): - continue + def _render_item(self, item: Widget): + # Skip rendering if not in viewport + if not rl.check_collision_recs(item.rect, self._rect): + return - # Scale each element around its own origin when scrolling - scale = self._zoom_filter.x - if scale != 1.0: - rl.rl_push_matrix() - rl.rl_scalef(scale, scale, 1.0) - rl.rl_translatef((1 - scale) * (item.rect.x + item.rect.width / 2) / scale, - (1 - scale) * (item.rect.y + item.rect.height / 2) / scale, 0) - item.render() - rl.rl_pop_matrix() - else: - item.render() + # Scale each element around its own origin when scrolling + scale = self._zoom_filter.x + if scale != 1.0: + rl.rl_push_matrix() + rl.rl_scalef(scale, scale, 1.0) + rl.rl_translatef((1 - scale) * (item.rect.x + item.rect.width / 2) / scale, + (1 - scale) * (item.rect.y + item.rect.height / 2) / scale, 0) + item.render() + rl.rl_pop_matrix() + else: + item.render() + + def _render(self, _): + rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), + int(self._rect.width), int(self._rect.height)) + + for item in reversed(self._visible_items): + if item in self._move_lift: + continue + self._render_item(item) + + # Dim background if moving items, lifted items are above + self._overlay_filter.update(MOVE_OVERLAY_ALPHA if len(self._pending_move) else 0.0) + if self._overlay_filter.x > 0.01: + rl.draw_rectangle_rec(self._rect, rl.Color(0, 0, 0, int(255 * self._overlay_filter.x))) + + for item in self._move_lift: + self._render_item(item) rl.end_scissor_mode() - # Draw scroll indicator - if self._show_scroll_indicator and self._horizontal and len(self._visible_items) > 0: + # Draw edge shadows on top of scroller content + if self._edge_shadows: + rl.draw_rectangle_gradient_h(int(self._rect.x), int(self._rect.y), + EDGE_SHADOW_WIDTH, int(self._rect.height), + rl.Color(0, 0, 0, 204), rl.BLANK) + + right_x = int(self._rect.x + self._rect.width - EDGE_SHADOW_WIDTH) + rl.draw_rectangle_gradient_h(right_x, int(self._rect.y), + EDGE_SHADOW_WIDTH, int(self._rect.height), + rl.BLANK, rl.Color(0, 0, 0, 204)) + + # Draw scroll indicator on top of edge shadows + if self._show_scroll_indicator and len(self._visible_items) > 0: self._scroll_indicator.update(self._scroll_offset, self._content_size, self._rect) self._scroll_indicator.render() def show_event(self): super().show_event() + for item in self._items: + item.show_event() + if self._reset_scroll_at_show: self.scroll_panel.set_offset(0.0) - for item in self._items: - item.show_event() + self._overlay_filter.x = 0.0 + self._move_animations.clear() + self._move_lift.clear() + self._pending_lift.clear() + self._pending_move.clear() + self._scrolling_to = None, False + self._scrolling_to_filter.x = 0.0 def hide_event(self): super().hide_event() for item in self._items: item.hide_event() + + +class Scroller(Widget): + """Wrapper for _Scroller so that children do not need to call events or pass down enabled for nav stack.""" + def __init__(self, **kwargs): + super().__init__() + self._scroller = _Scroller([], **kwargs) + # pass down enabled to child widget for nav stack + self._scroller.set_enabled(lambda: self.enabled) + + def show_event(self): + super().show_event() + self._scroller.show_event() + + def hide_event(self): + super().hide_event() + self._scroller.hide_event() + + def _render(self, _): + self._scroller.render(self._rect) + + +class NavScroller(NavWidget, Scroller): + """Full screen Scroller that properly supports nav stack w/ animations""" + def __init__(self, **kwargs): + super().__init__(**kwargs) + # pass down enabled to child widget for nav stack + disable while swiping away NavWidget + self._scroller.set_enabled(lambda: self.enabled and not self.is_dismissing) + + def _back_enabled(self) -> bool: + # Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes + # TODO: only used for offroad alerts, remove when horizontal + return self._scroller._horizontal or self._scroller.scroll_panel.get_offset() >= -20 # some tolerance + + +# TODO: only used for a few vertical scrollers, remove when horizontal +class NavRawScrollPanel(NavWidget): + # can swipe anywhere, only when at top + BACK_TOUCH_AREA_PERCENTAGE = 1.0 + + def __init__(self): + super().__init__() + self._scroll_panel = GuiScrollPanel2(horizontal=False) + self._scroll_panel.set_enabled(lambda: self.enabled and not self.is_dismissing) + + def show_event(self): + super().show_event() + self._scroll_panel.set_offset(0) + + def _back_enabled(self) -> bool: + return self._scroll_panel.get_offset() >= -20 diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 455cdeef71..8f4bbfc011 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -33,7 +33,7 @@ class SmallSlider(Widget): self._is_dragging_circle = False - self._label = UnifiedLabel(title, font_size=36, font_weight=FontWeight.MEDIUM, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), + self._label = UnifiedLabel(title, font_size=36, font_weight=FontWeight.SEMI_BOLD, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9) @@ -42,6 +42,7 @@ class SmallSlider(Widget): self._bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_bg.png", 316, 100) self._circle_bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle.png", 100, 100) + self._circle_bg_pressed_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle_pressed.png", 100, 100) self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 37, 32) @property @@ -107,8 +108,8 @@ class SmallSlider(Widget): # activate once animation completes, small threshold for small floats if self._scroll_x_circle_filter.x < (activated_pos + 1): if not self._confirm_callback_called and (rl.get_time() - self._confirmed_time) >= self.CONFIRM_DELAY: - self._on_confirm() self._confirm_callback_called = True + self._on_confirm() elif not self._is_dragging_circle: # reset back to right @@ -140,7 +141,8 @@ class SmallSlider(Widget): self._label.render(label_rect) # circle and arrow - rl.draw_texture_ex(self._circle_bg_txt, rl.Vector2(btn_x, btn_y), 0.0, 1.0, white) + circle_bg_txt = self._circle_bg_pressed_txt if self._is_dragging_circle or self._confirmed_time > 0 else self._circle_bg_txt + rl.draw_texture_ex(circle_bg_txt, rl.Vector2(btn_x, btn_y), 0.0, 1.0, white) arrow_x = btn_x + (self._circle_bg_txt.width - self._circle_arrow_txt.width) / 2 arrow_y = btn_y + (self._circle_bg_txt.height - self._circle_arrow_txt.height) / 2 @@ -158,6 +160,7 @@ class LargerSlider(SmallSlider): self._bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_bg_larger.png", 520, 115) circle_fn = "slider_green_rounded_rectangle" if self._green else "slider_black_rounded_rectangle" self._circle_bg_txt = gui_app.texture(f"icons_mici/setup/small_slider/{circle_fn}.png", 180, 115) + self._circle_bg_pressed_txt = gui_app.texture(f"icons_mici/setup/small_slider/{circle_fn}_pressed.png", 180, 115) self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 55) @@ -174,6 +177,7 @@ class BigSlider(SmallSlider): self._bg_txt = gui_app.texture("icons_mici/buttons/slider_bg.png", 520, 180) self._circle_bg_txt = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180) + self._circle_bg_pressed_txt = gui_app.texture("icons_mici/buttons/button_circle_pressed.png", 180, 180) self._circle_arrow_txt = self._icon @@ -183,4 +187,5 @@ class RedBigSlider(BigSlider): self._bg_txt = gui_app.texture("icons_mici/buttons/slider_bg.png", 520, 180) self._circle_bg_txt = gui_app.texture("icons_mici/buttons/button_circle_red.png", 180, 180) + self._circle_bg_pressed_txt = gui_app.texture("icons_mici/buttons/button_circle_red_pressed.png", 180, 180) self._circle_arrow_txt = self._icon diff --git a/system/webrtc/device/audio.py b/system/webrtc/device/audio.py deleted file mode 100644 index b1859518a1..0000000000 --- a/system/webrtc/device/audio.py +++ /dev/null @@ -1,109 +0,0 @@ -import asyncio -import io - -import aiortc -import av -import numpy as np -import pyaudio - - -class AudioInputStreamTrack(aiortc.mediastreams.AudioStreamTrack): - PYAUDIO_TO_AV_FORMAT_MAP = { - pyaudio.paUInt8: 'u8', - pyaudio.paInt16: 's16', - pyaudio.paInt24: 's24', - pyaudio.paInt32: 's32', - pyaudio.paFloat32: 'flt', - } - - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: int | None = None): - super().__init__() - - self.p = pyaudio.PyAudio() - chunk_size = int(packet_time * rate) - self.stream = self.p.open(format=audio_format, - channels=channels, - rate=rate, - frames_per_buffer=chunk_size, - input=True, - input_device_index=device_index) - self.format = audio_format - self.rate = rate - self.channels = channels - self.packet_time = packet_time - self.chunk_size = chunk_size - self.pts = 0 - - async def recv(self): - mic_data = self.stream.read(self.chunk_size) - mic_array = np.frombuffer(mic_data, dtype=np.int16) - mic_array = np.expand_dims(mic_array, axis=0) - layout = 'stereo' if self.channels > 1 else 'mono' - frame = av.AudioFrame.from_ndarray(mic_array, format=self.PYAUDIO_TO_AV_FORMAT_MAP[self.format], layout=layout) - frame.rate = self.rate - frame.pts = self.pts - self.pts += frame.samples - - return frame - - -class AudioOutputSpeaker: - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: int | None = None): - - chunk_size = int(packet_time * rate) - self.p = pyaudio.PyAudio() - self.buffer = io.BytesIO() - self.channels = channels - self.stream = self.p.open(format=audio_format, - channels=channels, - rate=rate, - frames_per_buffer=chunk_size, - output=True, - output_device_index=device_index, - stream_callback=self.__pyaudio_callback) - self.tracks_and_tasks: list[tuple[aiortc.MediaStreamTrack, asyncio.Task | None]] = [] - - def __pyaudio_callback(self, in_data, frame_count, time_info, status): - if self.buffer.getbuffer().nbytes < frame_count * self.channels * 2: - buff = b'\x00\x00' * frame_count * self.channels - elif self.buffer.getbuffer().nbytes > 115200: # 3x the usual read size - self.buffer.seek(0) - buff = self.buffer.read(frame_count * self.channels * 4) - buff = buff[:frame_count * self.channels * 2] - self.buffer.seek(2) - else: - self.buffer.seek(0) - buff = self.buffer.read(frame_count * self.channels * 2) - self.buffer.seek(2) - return (buff, pyaudio.paContinue) - - async def __consume(self, track): - while True: - try: - frame = await track.recv() - except aiortc.MediaStreamError: - return - - self.buffer.write(bytes(frame.planes[0])) - - def hasTrack(self, track: aiortc.MediaStreamTrack) -> bool: - return any(t == track for t, _ in self.tracks_and_tasks) - - def addTrack(self, track: aiortc.MediaStreamTrack): - if not self.hasTrack(track): - self.tracks_and_tasks.append((track, None)) - - def start(self): - for index, (track, task) in enumerate(self.tracks_and_tasks): - if task is None: - self.tracks_and_tasks[index] = (track, asyncio.create_task(self.__consume(track))) - - def stop(self): - for _, task in self.tracks_and_tasks: - if task is not None: - task.cancel() - - self.tracks_and_tasks = [] - self.stream.stop_stream() - self.stream.close() - self.p.terminate() diff --git a/system/webrtc/tests/test_stream_session.py b/system/webrtc/tests/test_stream_session.py index e31fda3728..f44d217d58 100644 --- a/system/webrtc/tests/test_stream_session.py +++ b/system/webrtc/tests/test_stream_session.py @@ -9,12 +9,10 @@ warnings.filterwarnings("ignore", category=RuntimeWarning) # TODO: remove this w 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 from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack -from openpilot.system.webrtc.device.audio import AudioInputStreamTrack class TestStreamSession: @@ -87,18 +85,3 @@ class TestStreamSession: assert abs(i + packet.pts - (start_pts + (((time.monotonic_ns() - start_ns) * VIDEO_CLOCK_RATE) // 1_000_000_000))) < 450 #5ms assert packet.size == 0 - def test_input_audio_track(self, mocker): - packet_time, rate = 0.02, 16000 - sample_count = int(packet_time * rate) - 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} - 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()) - 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 deleted file mode 100644 index 4fa6d8953f..0000000000 --- a/system/webrtc/tests/test_webrtcd.py +++ /dev/null @@ -1,65 +0,0 @@ -import pytest -import asyncio -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 openpilot.system.webrtc.webrtcd import get_stream - -import aiortc -from teleoprtc import WebRTCOfferBuilder -from parameterized import parameterized_class - - -@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: - pytest.fail("Timeout while waiting for awaitable to complete") - - async def test_webrtcd(self, mocker): - mock_request = mocker.MagicMock() - async def connect(offer): - 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) - - builder = WebRTCOfferBuilder(connect) - builder.offer_to_receive_video_stream("road") - builder.offer_to_receive_audio_stream() - 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()) - - 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()) - await self.assertCompletesWithTimeout(audio_track.recv()) - - await self.assertCompletesWithTimeout(stream.stop()) - - # cleanup, very implementation specific, test may break if it changes - 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()) - diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index c19f1bf9dd..d2c90cafb5 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -119,10 +119,8 @@ 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 + from aiortc.mediastreams import VideoStreamTrack from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack - from openpilot.system.webrtc.device.audio import AudioInputStreamTrack, AudioOutputSpeaker from teleoprtc import WebRTCAnswerBuilder from teleoprtc.info import parse_info_from_offer @@ -132,11 +130,6 @@ class StreamSession: assert len(cameras) == config.n_expected_camera_tracks, "Incoming stream has misconfigured number of video tracks" for cam in cameras: builder.add_video_stream(cam, LiveStreamVideoStreamTrack(cam) if not debug_mode else VideoStreamTrack()) - if config.expected_audio_track: - builder.add_audio_stream(AudioInputStreamTrack() if not debug_mode else AudioStreamTrack()) - if config.incoming_audio_track: - self.audio_output_cls = AudioOutputSpeaker if not debug_mode else MediaBlackhole - builder.offer_to_receive_audio_stream() self.stream = builder.stream() self.identifier = str(uuid.uuid4()) @@ -151,11 +144,10 @@ class StreamSession: 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 self.logger = logging.getLogger("webrtcd") - self.logger.info("New stream session (%s), cameras %s, audio in %s out %s, incoming services %s, outgoing services %s", - self.identifier, cameras, config.incoming_audio_track, config.expected_audio_track, incoming_services, outgoing_services) + self.logger.info("New stream session (%s), cameras %s, incoming services %s, outgoing services %s", + self.identifier, cameras, incoming_services, outgoing_services) def start(self): self.run_task = asyncio.create_task(self.run()) @@ -188,11 +180,6 @@ class StreamSession: 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() - self.audio_output.addTrack(track) - self.audio_output.start() self.logger.info("Stream session (%s) connected", self.identifier) await self.stream.wait_for_disconnection() @@ -206,8 +193,6 @@ class StreamSession: await self.stream.stop() if self.outgoing_bridge is not None: self.outgoing_bridge_runner.stop() - if self.audio_output: - self.audio_output.stop() @dataclass diff --git a/third_party/acados/build.sh b/third_party/acados/build.sh index 2c4d839ae7..95b3913c4a 100755 --- a/third_party/acados/build.sh +++ b/third_party/acados/build.sh @@ -16,7 +16,7 @@ fi ACADOS_FLAGS="-DACADOS_WITH_QPOASES=ON -UBLASFEO_TARGET -DBLASFEO_TARGET=$BLAS_TARGET" if [[ "$OSTYPE" == "darwin"* ]]; then - ACADOS_FLAGS="$ACADOS_FLAGS -DCMAKE_OSX_ARCHITECTURES=arm64;x86_64 -DCMAKE_MACOSX_RPATH=1" + ACADOS_FLAGS="$ACADOS_FLAGS -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_MACOSX_RPATH=1" ARCHNAME="Darwin" fi @@ -57,8 +57,7 @@ fi cd $DIR/acados_repo/interfaces/acados_template/tera_renderer/ if [[ "$OSTYPE" == "darwin"* ]]; then cargo build --verbose --release --target aarch64-apple-darwin - cargo build --verbose --release --target x86_64-apple-darwin - lipo -create -output target/release/t_renderer target/x86_64-apple-darwin/release/t_renderer target/aarch64-apple-darwin/release/t_renderer + cp target/aarch64-apple-darwin/release/t_renderer target/release/t_renderer else cargo build --verbose --release fi diff --git a/third_party/libyuv/.gitignore b/third_party/libyuv/.gitignore deleted file mode 100644 index 1e943ae6c6..0000000000 --- a/third_party/libyuv/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/libyuv/ -!*.a diff --git a/third_party/libyuv/Darwin/lib/libyuv.a b/third_party/libyuv/Darwin/lib/libyuv.a deleted file mode 100644 index b72979ef19..0000000000 --- a/third_party/libyuv/Darwin/lib/libyuv.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:497e01c39e1629a89afa730341fe066c2e926966c5f050003e7fde2ce46d9da3 -size 863648 diff --git a/third_party/libyuv/LICENSE b/third_party/libyuv/LICENSE deleted file mode 100644 index c911747a6b..0000000000 --- a/third_party/libyuv/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -Copyright 2011 The LibYuv Project Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/libyuv/aarch64 b/third_party/libyuv/aarch64 deleted file mode 120000 index 062c65e8d9..0000000000 --- a/third_party/libyuv/aarch64 +++ /dev/null @@ -1 +0,0 @@ -larch64/ \ No newline at end of file diff --git a/third_party/libyuv/build.sh b/third_party/libyuv/build.sh deleted file mode 100755 index 35a7d947a2..0000000000 --- a/third_party/libyuv/build.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -set -e - -export SOURCE_DATE_EPOCH=0 -export ZERO_AR_DATE=1 - -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 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 -## ``` diff --git a/third_party/libyuv/include/libyuv.h b/third_party/libyuv/include/libyuv.h deleted file mode 100644 index aeffd5ef7a..0000000000 --- a/third_party/libyuv/include/libyuv.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_H_ -#define INCLUDE_LIBYUV_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/compare.h" -#include "libyuv/convert.h" -#include "libyuv/convert_argb.h" -#include "libyuv/convert_from.h" -#include "libyuv/convert_from_argb.h" -#include "libyuv/cpu_id.h" -#include "libyuv/mjpeg_decoder.h" -#include "libyuv/planar_functions.h" -#include "libyuv/rotate.h" -#include "libyuv/rotate_argb.h" -#include "libyuv/row.h" -#include "libyuv/scale.h" -#include "libyuv/scale_argb.h" -#include "libyuv/scale_row.h" -#include "libyuv/version.h" -#include "libyuv/video_common.h" - -#endif // INCLUDE_LIBYUV_H_ diff --git a/third_party/libyuv/include/libyuv/basic_types.h b/third_party/libyuv/include/libyuv/basic_types.h deleted file mode 100644 index 5b760ee0d4..0000000000 --- a/third_party/libyuv/include/libyuv/basic_types.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_BASIC_TYPES_H_ -#define INCLUDE_LIBYUV_BASIC_TYPES_H_ - -#include // for NULL, size_t - -#if defined(_MSC_VER) && (_MSC_VER < 1600) -#include // for uintptr_t on x86 -#else -#include // for uintptr_t -#endif - -#ifndef GG_LONGLONG -#ifndef INT_TYPES_DEFINED -#define INT_TYPES_DEFINED -#ifdef COMPILER_MSVC -typedef unsigned __int64 uint64; -typedef __int64 int64; -#ifndef INT64_C -#define INT64_C(x) x ## I64 -#endif -#ifndef UINT64_C -#define UINT64_C(x) x ## UI64 -#endif -#define INT64_F "I64" -#else // COMPILER_MSVC -#if defined(__LP64__) && !defined(__OpenBSD__) && !defined(__APPLE__) -typedef unsigned long uint64; // NOLINT -typedef long int64; // NOLINT -#ifndef INT64_C -#define INT64_C(x) x ## L -#endif -#ifndef UINT64_C -#define UINT64_C(x) x ## UL -#endif -#define INT64_F "l" -#else // defined(__LP64__) && !defined(__OpenBSD__) && !defined(__APPLE__) -typedef unsigned long long uint64; // NOLINT -typedef long long int64; // NOLINT -#ifndef INT64_C -#define INT64_C(x) x ## LL -#endif -#ifndef UINT64_C -#define UINT64_C(x) x ## ULL -#endif -#define INT64_F "ll" -#endif // __LP64__ -#endif // COMPILER_MSVC -typedef unsigned int uint32; -typedef int int32; -typedef unsigned short uint16; // NOLINT -typedef short int16; // NOLINT -typedef unsigned char uint8; -typedef signed char int8; -#endif // INT_TYPES_DEFINED -#endif // GG_LONGLONG - -// Detect compiler is for x86 or x64. -#if defined(__x86_64__) || defined(_M_X64) || \ - defined(__i386__) || defined(_M_IX86) -#define CPU_X86 1 -#endif -// Detect compiler is for ARM. -#if defined(__arm__) || defined(_M_ARM) -#define CPU_ARM 1 -#endif - -#ifndef ALIGNP -#ifdef __cplusplus -#define ALIGNP(p, t) \ - (reinterpret_cast(((reinterpret_cast(p) + \ - ((t) - 1)) & ~((t) - 1)))) -#else -#define ALIGNP(p, t) \ - ((uint8*)((((uintptr_t)(p) + ((t) - 1)) & ~((t) - 1)))) /* NOLINT */ -#endif -#endif - -#if !defined(LIBYUV_API) -#if defined(_WIN32) || defined(__CYGWIN__) -#if defined(LIBYUV_BUILDING_SHARED_LIBRARY) -#define LIBYUV_API __declspec(dllexport) -#elif defined(LIBYUV_USING_SHARED_LIBRARY) -#define LIBYUV_API __declspec(dllimport) -#else -#define LIBYUV_API -#endif // LIBYUV_BUILDING_SHARED_LIBRARY -#elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__APPLE__) && \ - (defined(LIBYUV_BUILDING_SHARED_LIBRARY) || \ - defined(LIBYUV_USING_SHARED_LIBRARY)) -#define LIBYUV_API __attribute__ ((visibility ("default"))) -#else -#define LIBYUV_API -#endif // __GNUC__ -#endif // LIBYUV_API - -#define LIBYUV_BOOL int -#define LIBYUV_FALSE 0 -#define LIBYUV_TRUE 1 - -// Visual C x86 or GCC little endian. -#if defined(__x86_64__) || defined(_M_X64) || \ - defined(__i386__) || defined(_M_IX86) || \ - defined(__arm__) || defined(_M_ARM) || \ - (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) -#define LIBYUV_LITTLE_ENDIAN -#endif - -#endif // INCLUDE_LIBYUV_BASIC_TYPES_H_ diff --git a/third_party/libyuv/include/libyuv/compare.h b/third_party/libyuv/include/libyuv/compare.h deleted file mode 100644 index 550712de6e..0000000000 --- a/third_party/libyuv/include/libyuv/compare.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_COMPARE_H_ -#define INCLUDE_LIBYUV_COMPARE_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Compute a hash for specified memory. Seed of 5381 recommended. -LIBYUV_API -uint32 HashDjb2(const uint8* src, uint64 count, uint32 seed); - -// Scan an opaque argb image and return fourcc based on alpha offset. -// Returns FOURCC_ARGB, FOURCC_BGRA, or 0 if unknown. -LIBYUV_API -uint32 ARGBDetect(const uint8* argb, int stride_argb, int width, int height); - -// Sum Square Error - used to compute Mean Square Error or PSNR. -LIBYUV_API -uint64 ComputeSumSquareError(const uint8* src_a, - const uint8* src_b, int count); - -LIBYUV_API -uint64 ComputeSumSquareErrorPlane(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height); - -static const int kMaxPsnr = 128; - -LIBYUV_API -double SumSquareErrorToPsnr(uint64 sse, uint64 count); - -LIBYUV_API -double CalcFramePsnr(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height); - -LIBYUV_API -double I420Psnr(const uint8* src_y_a, int stride_y_a, - const uint8* src_u_a, int stride_u_a, - const uint8* src_v_a, int stride_v_a, - const uint8* src_y_b, int stride_y_b, - const uint8* src_u_b, int stride_u_b, - const uint8* src_v_b, int stride_v_b, - int width, int height); - -LIBYUV_API -double CalcFrameSsim(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height); - -LIBYUV_API -double I420Ssim(const uint8* src_y_a, int stride_y_a, - const uint8* src_u_a, int stride_u_a, - const uint8* src_v_a, int stride_v_a, - const uint8* src_y_b, int stride_y_b, - const uint8* src_u_b, int stride_u_b, - const uint8* src_v_b, int stride_v_b, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_COMPARE_H_ diff --git a/third_party/libyuv/include/libyuv/compare_row.h b/third_party/libyuv/include/libyuv/compare_row.h deleted file mode 100644 index 781cad3e65..0000000000 --- a/third_party/libyuv/include/libyuv/compare_row.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_COMPARE_ROW_H_ -#define INCLUDE_LIBYUV_COMPARE_ROW_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif - -// Visual C 2012 required for AVX2. -#if defined(_M_IX86) && !defined(__clang__) && \ - defined(_MSC_VER) && _MSC_VER >= 1700 -#define VISUALC_HAS_AVX2 1 -#endif // VisualStudio >= 2012 - -// clang >= 3.4.0 required for AVX2. -#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) -#if (__clang_major__ > 3) || (__clang_major__ == 3 && (__clang_minor__ >= 4)) -#define CLANG_HAS_AVX2 1 -#endif // clang >= 3.4 -#endif // __clang__ - -#if !defined(LIBYUV_DISABLE_X86) && \ - defined(_M_IX86) && (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) -#define HAS_HASHDJB2_AVX2 -#endif - -// The following are available for Visual C and GCC: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__x86_64__) || (defined(__i386__) || defined(_M_IX86))) -#define HAS_HASHDJB2_SSE41 -#define HAS_SUMSQUAREERROR_SSE2 -#endif - -// The following are available for Visual C and clangcl 32 bit: -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) && \ - (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) -#define HAS_HASHDJB2_AVX2 -#define HAS_SUMSQUAREERROR_AVX2 -#endif - -// The following are available for Neon: -#if !defined(LIBYUV_DISABLE_NEON) && \ - (defined(__ARM_NEON__) || defined(LIBYUV_NEON) || defined(__aarch64__)) -#define HAS_SUMSQUAREERROR_NEON -#endif - -uint32 SumSquareError_C(const uint8* src_a, const uint8* src_b, int count); -uint32 SumSquareError_SSE2(const uint8* src_a, const uint8* src_b, int count); -uint32 SumSquareError_AVX2(const uint8* src_a, const uint8* src_b, int count); -uint32 SumSquareError_NEON(const uint8* src_a, const uint8* src_b, int count); - -uint32 HashDjb2_C(const uint8* src, int count, uint32 seed); -uint32 HashDjb2_SSE41(const uint8* src, int count, uint32 seed); -uint32 HashDjb2_AVX2(const uint8* src, int count, uint32 seed); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_COMPARE_ROW_H_ diff --git a/third_party/libyuv/include/libyuv/convert.h b/third_party/libyuv/include/libyuv/convert.h deleted file mode 100644 index d44485847b..0000000000 --- a/third_party/libyuv/include/libyuv/convert.h +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_H_ -#define INCLUDE_LIBYUV_CONVERT_H_ - -#include "libyuv/basic_types.h" - -#include "libyuv/rotate.h" // For enum RotationMode. - -// TODO(fbarchard): fix WebRTC source to include following libyuv headers: -#include "libyuv/convert_argb.h" // For WebRTC I420ToARGB. b/620 -#include "libyuv/convert_from.h" // For WebRTC ConvertFromI420. b/620 -#include "libyuv/planar_functions.h" // For WebRTC I420Rect, CopyPlane. b/618 - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Convert I444 to I420. -LIBYUV_API -int I444ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert I422 to I420. -LIBYUV_API -int I422ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert I411 to I420. -LIBYUV_API -int I411ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Copy I420 to I420. -#define I420ToI420 I420Copy -LIBYUV_API -int I420Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert I400 (grey) to I420. -LIBYUV_API -int I400ToI420(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -#define J400ToJ420 I400ToI420 - -// Convert NV12 to I420. -LIBYUV_API -int NV12ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert NV21 to I420. -LIBYUV_API -int NV21ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_vu, int src_stride_vu, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert YUY2 to I420. -LIBYUV_API -int YUY2ToI420(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert UYVY to I420. -LIBYUV_API -int UYVYToI420(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert M420 to I420. -LIBYUV_API -int M420ToI420(const uint8* src_m420, int src_stride_m420, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert Android420 to I420. -LIBYUV_API -int Android420ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - int pixel_stride_uv, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// ARGB little endian (bgra in memory) to I420. -LIBYUV_API -int ARGBToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// BGRA little endian (argb in memory) to I420. -LIBYUV_API -int BGRAToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// ABGR little endian (rgba in memory) to I420. -LIBYUV_API -int ABGRToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGBA little endian (abgr in memory) to I420. -LIBYUV_API -int RGBAToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB little endian (bgr in memory) to I420. -LIBYUV_API -int RGB24ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB big endian (rgb in memory) to I420. -LIBYUV_API -int RAWToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB16 (RGBP fourcc) little endian to I420. -LIBYUV_API -int RGB565ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB15 (RGBO fourcc) little endian to I420. -LIBYUV_API -int ARGB1555ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB12 (R444 fourcc) little endian to I420. -LIBYUV_API -int ARGB4444ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -#ifdef HAVE_JPEG -// src_width/height provided by capture. -// dst_width/height for clipping determine final size. -LIBYUV_API -int MJPGToI420(const uint8* sample, size_t sample_size, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int src_width, int src_height, - int dst_width, int dst_height); - -// Query size of MJPG in pixels. -LIBYUV_API -int MJPGSize(const uint8* sample, size_t sample_size, - int* width, int* height); -#endif - -// Convert camera sample to I420 with cropping, rotation and vertical flip. -// "src_size" is needed to parse MJPG. -// "dst_stride_y" number of bytes in a row of the dst_y plane. -// Normally this would be the same as dst_width, with recommended alignment -// to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. The caller should -// allocate the I420 buffer according to rotation. -// "dst_stride_u" number of bytes in a row of the dst_u plane. -// Normally this would be the same as (dst_width + 1) / 2, with -// recommended alignment to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. -// "crop_x" and "crop_y" are starting position for cropping. -// To center, crop_x = (src_width - dst_width) / 2 -// crop_y = (src_height - dst_height) / 2 -// "src_width" / "src_height" is size of src_frame in pixels. -// "src_height" can be negative indicating a vertically flipped image source. -// "crop_width" / "crop_height" is the size to crop the src to. -// Must be less than or equal to src_width/src_height -// Cropping parameters are pre-rotation. -// "rotation" can be 0, 90, 180 or 270. -// "format" is a fourcc. ie 'I420', 'YUY2' -// Returns 0 for successful; -1 for invalid parameter. Non-zero for failure. -LIBYUV_API -int ConvertToI420(const uint8* src_frame, size_t src_size, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int crop_x, int crop_y, - int src_width, int src_height, - int crop_width, int crop_height, - enum RotationMode rotation, - uint32 format); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_H_ diff --git a/third_party/libyuv/include/libyuv/convert_argb.h b/third_party/libyuv/include/libyuv/convert_argb.h deleted file mode 100644 index dc03ac8d5d..0000000000 --- a/third_party/libyuv/include/libyuv/convert_argb.h +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_ARGB_H_ -#define INCLUDE_LIBYUV_CONVERT_ARGB_H_ - -#include "libyuv/basic_types.h" - -#include "libyuv/rotate.h" // For enum RotationMode. - -// TODO(fbarchard): This set of functions should exactly match convert.h -// TODO(fbarchard): Add tests. Create random content of right size and convert -// with C vs Opt and or to I420 and compare. -// TODO(fbarchard): Some of these functions lack parameter setting. - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Alias. -#define ARGBToARGB ARGBCopy - -// Copy ARGB to ARGB. -LIBYUV_API -int ARGBCopy(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I420 to ARGB. -LIBYUV_API -int I420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Duplicate prototype for function in convert_from.h for remoting. -LIBYUV_API -int I420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I422 to ARGB. -LIBYUV_API -int I422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I444 to ARGB. -LIBYUV_API -int I444ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J444 to ARGB. -LIBYUV_API -int J444ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I444 to ABGR. -LIBYUV_API -int I444ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert I411 to ARGB. -LIBYUV_API -int I411ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I420 with Alpha to preattenuated ARGB. -LIBYUV_API -int I420AlphaToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - const uint8* src_a, int src_stride_a, - uint8* dst_argb, int dst_stride_argb, - int width, int height, int attenuate); - -// Convert I420 with Alpha to preattenuated ABGR. -LIBYUV_API -int I420AlphaToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - const uint8* src_a, int src_stride_a, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height, int attenuate); - -// Convert I400 (grey) to ARGB. Reverse of ARGBToI400. -LIBYUV_API -int I400ToARGB(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J400 (jpeg grey) to ARGB. -LIBYUV_API -int J400ToARGB(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Alias. -#define YToARGB I400ToARGB - -// Convert NV12 to ARGB. -LIBYUV_API -int NV12ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert NV21 to ARGB. -LIBYUV_API -int NV21ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_vu, int src_stride_vu, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert M420 to ARGB. -LIBYUV_API -int M420ToARGB(const uint8* src_m420, int src_stride_m420, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert YUY2 to ARGB. -LIBYUV_API -int YUY2ToARGB(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert UYVY to ARGB. -LIBYUV_API -int UYVYToARGB(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J420 to ARGB. -LIBYUV_API -int J420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J422 to ARGB. -LIBYUV_API -int J422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J420 to ABGR. -LIBYUV_API -int J420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert J422 to ABGR. -LIBYUV_API -int J422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert H420 to ARGB. -LIBYUV_API -int H420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert H422 to ARGB. -LIBYUV_API -int H422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert H420 to ABGR. -LIBYUV_API -int H420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert H422 to ABGR. -LIBYUV_API -int H422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// BGRA little endian (argb in memory) to ARGB. -LIBYUV_API -int BGRAToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// ABGR little endian (rgba in memory) to ARGB. -LIBYUV_API -int ABGRToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGBA little endian (abgr in memory) to ARGB. -LIBYUV_API -int RGBAToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Deprecated function name. -#define BG24ToARGB RGB24ToARGB - -// RGB little endian (bgr in memory) to ARGB. -LIBYUV_API -int RGB24ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB big endian (rgb in memory) to ARGB. -LIBYUV_API -int RAWToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB16 (RGBP fourcc) little endian to ARGB. -LIBYUV_API -int RGB565ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB15 (RGBO fourcc) little endian to ARGB. -LIBYUV_API -int ARGB1555ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB12 (R444 fourcc) little endian to ARGB. -LIBYUV_API -int ARGB4444ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -#ifdef HAVE_JPEG -// src_width/height provided by capture -// dst_width/height for clipping determine final size. -LIBYUV_API -int MJPGToARGB(const uint8* sample, size_t sample_size, - uint8* dst_argb, int dst_stride_argb, - int src_width, int src_height, - int dst_width, int dst_height); -#endif - -// Convert camera sample to ARGB with cropping, rotation and vertical flip. -// "src_size" is needed to parse MJPG. -// "dst_stride_argb" number of bytes in a row of the dst_argb plane. -// Normally this would be the same as dst_width, with recommended alignment -// to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. The caller should -// allocate the I420 buffer according to rotation. -// "dst_stride_u" number of bytes in a row of the dst_u plane. -// Normally this would be the same as (dst_width + 1) / 2, with -// recommended alignment to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. -// "crop_x" and "crop_y" are starting position for cropping. -// To center, crop_x = (src_width - dst_width) / 2 -// crop_y = (src_height - dst_height) / 2 -// "src_width" / "src_height" is size of src_frame in pixels. -// "src_height" can be negative indicating a vertically flipped image source. -// "crop_width" / "crop_height" is the size to crop the src to. -// Must be less than or equal to src_width/src_height -// Cropping parameters are pre-rotation. -// "rotation" can be 0, 90, 180 or 270. -// "format" is a fourcc. ie 'I420', 'YUY2' -// Returns 0 for successful; -1 for invalid parameter. Non-zero for failure. -LIBYUV_API -int ConvertToARGB(const uint8* src_frame, size_t src_size, - uint8* dst_argb, int dst_stride_argb, - int crop_x, int crop_y, - int src_width, int src_height, - int crop_width, int crop_height, - enum RotationMode rotation, - uint32 format); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_ARGB_H_ diff --git a/third_party/libyuv/include/libyuv/convert_from.h b/third_party/libyuv/include/libyuv/convert_from.h deleted file mode 100644 index 59c40474f1..0000000000 --- a/third_party/libyuv/include/libyuv/convert_from.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_FROM_H_ -#define INCLUDE_LIBYUV_CONVERT_FROM_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/rotate.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// See Also convert.h for conversions from formats to I420. - -// I420Copy in convert to I420ToI420. - -LIBYUV_API -int I420ToI422(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -LIBYUV_API -int I420ToI444(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -LIBYUV_API -int I420ToI411(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Copy to I400. Source can be I420, I422, I444, I400, NV12 or NV21. -LIBYUV_API -int I400Copy(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -LIBYUV_API -int I420ToNV12(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -LIBYUV_API -int I420ToNV21(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_vu, int dst_stride_vu, - int width, int height); - -LIBYUV_API -int I420ToYUY2(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToUYVY(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -LIBYUV_API -int I420ToBGRA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -LIBYUV_API -int I420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -LIBYUV_API -int I420ToRGBA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height); - -LIBYUV_API -int I420ToRGB24(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToRAW(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToRGB565(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert I420 To RGB565 with 4x4 dither matrix (16 bytes). -// Values in dither matrix from 0 to 7 recommended. -// The order of the dither matrix is first byte is upper left. - -LIBYUV_API -int I420ToRGB565Dither(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - const uint8* dither4x4, int width, int height); - -LIBYUV_API -int I420ToARGB1555(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToARGB4444(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert I420 to specified format. -// "dst_sample_stride" is bytes in a row for the destination. Pass 0 if the -// buffer has contiguous rows. Can be negative. A multiple of 16 is optimal. -LIBYUV_API -int ConvertFromI420(const uint8* y, int y_stride, - const uint8* u, int u_stride, - const uint8* v, int v_stride, - uint8* dst_sample, int dst_sample_stride, - int width, int height, - uint32 format); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_FROM_H_ diff --git a/third_party/libyuv/include/libyuv/convert_from_argb.h b/third_party/libyuv/include/libyuv/convert_from_argb.h deleted file mode 100644 index 8d7f02f8c4..0000000000 --- a/third_party/libyuv/include/libyuv/convert_from_argb.h +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_FROM_ARGB_H_ -#define INCLUDE_LIBYUV_CONVERT_FROM_ARGB_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Copy ARGB to ARGB. -#define ARGBToARGB ARGBCopy -LIBYUV_API -int ARGBCopy(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert ARGB To BGRA. -LIBYUV_API -int ARGBToBGRA(const uint8* src_argb, int src_stride_argb, - uint8* dst_bgra, int dst_stride_bgra, - int width, int height); - -// Convert ARGB To ABGR. -LIBYUV_API -int ARGBToABGR(const uint8* src_argb, int src_stride_argb, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert ARGB To RGBA. -LIBYUV_API -int ARGBToRGBA(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height); - -// Convert ARGB To RGB24. -LIBYUV_API -int ARGBToRGB24(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb24, int dst_stride_rgb24, - int width, int height); - -// Convert ARGB To RAW. -LIBYUV_API -int ARGBToRAW(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb, int dst_stride_rgb, - int width, int height); - -// Convert ARGB To RGB565. -LIBYUV_API -int ARGBToRGB565(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb565, int dst_stride_rgb565, - int width, int height); - -// Convert ARGB To RGB565 with 4x4 dither matrix (16 bytes). -// Values in dither matrix from 0 to 7 recommended. -// The order of the dither matrix is first byte is upper left. -// TODO(fbarchard): Consider pointer to 2d array for dither4x4. -// const uint8(*dither)[4][4]; -LIBYUV_API -int ARGBToRGB565Dither(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb565, int dst_stride_rgb565, - const uint8* dither4x4, int width, int height); - -// Convert ARGB To ARGB1555. -LIBYUV_API -int ARGBToARGB1555(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb1555, int dst_stride_argb1555, - int width, int height); - -// Convert ARGB To ARGB4444. -LIBYUV_API -int ARGBToARGB4444(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb4444, int dst_stride_argb4444, - int width, int height); - -// Convert ARGB To I444. -LIBYUV_API -int ARGBToI444(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB To I422. -LIBYUV_API -int ARGBToI422(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB To I420. (also in convert.h) -LIBYUV_API -int ARGBToI420(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB to J420. (JPeg full range I420). -LIBYUV_API -int ARGBToJ420(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB to J422. -LIBYUV_API -int ARGBToJ422(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB To I411. -LIBYUV_API -int ARGBToI411(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB to J400. (JPeg full range). -LIBYUV_API -int ARGBToJ400(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - int width, int height); - -// Convert ARGB to I400. -LIBYUV_API -int ARGBToI400(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Convert ARGB to G. (Reverse of J400toARGB, which replicates G back to ARGB) -LIBYUV_API -int ARGBToG(const uint8* src_argb, int src_stride_argb, - uint8* dst_g, int dst_stride_g, - int width, int height); - -// Convert ARGB To NV12. -LIBYUV_API -int ARGBToNV12(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -// Convert ARGB To NV21. -LIBYUV_API -int ARGBToNV21(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_vu, int dst_stride_vu, - int width, int height); - -// Convert ARGB To NV21. -LIBYUV_API -int ARGBToNV21(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_vu, int dst_stride_vu, - int width, int height); - -// Convert ARGB To YUY2. -LIBYUV_API -int ARGBToYUY2(const uint8* src_argb, int src_stride_argb, - uint8* dst_yuy2, int dst_stride_yuy2, - int width, int height); - -// Convert ARGB To UYVY. -LIBYUV_API -int ARGBToUYVY(const uint8* src_argb, int src_stride_argb, - uint8* dst_uyvy, int dst_stride_uyvy, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_FROM_ARGB_H_ diff --git a/third_party/libyuv/include/libyuv/cpu_id.h b/third_party/libyuv/include/libyuv/cpu_id.h deleted file mode 100644 index 7c6c9aeb00..0000000000 --- a/third_party/libyuv/include/libyuv/cpu_id.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CPU_ID_H_ -#define INCLUDE_LIBYUV_CPU_ID_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Internal flag to indicate cpuid requires initialization. -static const int kCpuInitialized = 0x1; - -// These flags are only valid on ARM processors. -static const int kCpuHasARM = 0x2; -static const int kCpuHasNEON = 0x4; -// 0x8 reserved for future ARM flag. - -// These flags are only valid on x86 processors. -static const int kCpuHasX86 = 0x10; -static const int kCpuHasSSE2 = 0x20; -static const int kCpuHasSSSE3 = 0x40; -static const int kCpuHasSSE41 = 0x80; -static const int kCpuHasSSE42 = 0x100; -static const int kCpuHasAVX = 0x200; -static const int kCpuHasAVX2 = 0x400; -static const int kCpuHasERMS = 0x800; -static const int kCpuHasFMA3 = 0x1000; -static const int kCpuHasAVX3 = 0x2000; -// 0x2000, 0x4000, 0x8000 reserved for future X86 flags. - -// These flags are only valid on MIPS processors. -static const int kCpuHasMIPS = 0x10000; -static const int kCpuHasDSPR2 = 0x20000; -static const int kCpuHasMSA = 0x40000; - -// Internal function used to auto-init. -LIBYUV_API -int InitCpuFlags(void); - -// Internal function for parsing /proc/cpuinfo. -LIBYUV_API -int ArmCpuCaps(const char* cpuinfo_name); - -// Detect CPU has SSE2 etc. -// Test_flag parameter should be one of kCpuHas constants above. -// returns non-zero if instruction set is detected -static __inline int TestCpuFlag(int test_flag) { - LIBYUV_API extern int cpu_info_; - return (!cpu_info_ ? InitCpuFlags() : cpu_info_) & test_flag; -} - -// For testing, allow CPU flags to be disabled. -// ie MaskCpuFlags(~kCpuHasSSSE3) to disable SSSE3. -// MaskCpuFlags(-1) to enable all cpu specific optimizations. -// MaskCpuFlags(1) to disable all cpu specific optimizations. -LIBYUV_API -void MaskCpuFlags(int enable_flags); - -// Low level cpuid for X86. Returns zeros on other CPUs. -// eax is the info type that you want. -// ecx is typically the cpu number, and should normally be zero. -LIBYUV_API -void CpuId(uint32 eax, uint32 ecx, uint32* cpu_info); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CPU_ID_H_ diff --git a/third_party/libyuv/include/libyuv/macros_msa.h b/third_party/libyuv/include/libyuv/macros_msa.h deleted file mode 100644 index 92ed21c385..0000000000 --- a/third_party/libyuv/include/libyuv/macros_msa.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2016 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_MACROS_MSA_H_ -#define INCLUDE_LIBYUV_MACROS_MSA_H_ - -#if !defined(LIBYUV_DISABLE_MSA) && defined(__mips_msa) -#include -#include - -#define LD_B(RTYPE, psrc) *((RTYPE*)(psrc)) /* NOLINT */ -#define LD_UB(...) LD_B(v16u8, __VA_ARGS__) - -#define ST_B(RTYPE, in, pdst) *((RTYPE*)(pdst)) = (in) /* NOLINT */ -#define ST_UB(...) ST_B(v16u8, __VA_ARGS__) - -/* Description : Load two vectors with 16 'byte' sized elements - Arguments : Inputs - psrc, stride - Outputs - out0, out1 - Return Type - as per RTYPE - Details : Load 16 byte elements in 'out0' from (psrc) - Load 16 byte elements in 'out1' from (psrc + stride) -*/ -#define LD_B2(RTYPE, psrc, stride, out0, out1) { \ - out0 = LD_B(RTYPE, (psrc)); \ - out1 = LD_B(RTYPE, (psrc) + stride); \ -} -#define LD_UB2(...) LD_B2(v16u8, __VA_ARGS__) - -#define LD_B4(RTYPE, psrc, stride, out0, out1, out2, out3) { \ - LD_B2(RTYPE, (psrc), stride, out0, out1); \ - LD_B2(RTYPE, (psrc) + 2 * stride , stride, out2, out3); \ -} -#define LD_UB4(...) LD_B4(v16u8, __VA_ARGS__) - -/* Description : Store two vectors with stride each having 16 'byte' sized - elements - Arguments : Inputs - in0, in1, pdst, stride - Details : Store 16 byte elements from 'in0' to (pdst) - Store 16 byte elements from 'in1' to (pdst + stride) -*/ -#define ST_B2(RTYPE, in0, in1, pdst, stride) { \ - ST_B(RTYPE, in0, (pdst)); \ - ST_B(RTYPE, in1, (pdst) + stride); \ -} -#define ST_UB2(...) ST_B2(v16u8, __VA_ARGS__) -# -#define ST_B4(RTYPE, in0, in1, in2, in3, pdst, stride) { \ - ST_B2(RTYPE, in0, in1, (pdst), stride); \ - ST_B2(RTYPE, in2, in3, (pdst) + 2 * stride, stride); \ -} -#define ST_UB4(...) ST_B4(v16u8, __VA_ARGS__) -# -/* Description : Shuffle byte vector elements as per mask vector - Arguments : Inputs - in0, in1, in2, in3, mask0, mask1 - Outputs - out0, out1 - Return Type - as per RTYPE - Details : Byte elements from 'in0' & 'in1' are copied selectively to - 'out0' as per control vector 'mask0' -*/ -#define VSHF_B2(RTYPE, in0, in1, in2, in3, mask0, mask1, out0, out1) { \ - out0 = (RTYPE) __msa_vshf_b((v16i8) mask0, (v16i8) in1, (v16i8) in0); \ - out1 = (RTYPE) __msa_vshf_b((v16i8) mask1, (v16i8) in3, (v16i8) in2); \ -} -#define VSHF_B2_UB(...) VSHF_B2(v16u8, __VA_ARGS__) - -#endif /* !defined(LIBYUV_DISABLE_MSA) && defined(__mips_msa) */ - -#endif // INCLUDE_LIBYUV_MACROS_MSA_H_ diff --git a/third_party/libyuv/include/libyuv/mjpeg_decoder.h b/third_party/libyuv/include/libyuv/mjpeg_decoder.h deleted file mode 100644 index 4975bae5b7..0000000000 --- a/third_party/libyuv/include/libyuv/mjpeg_decoder.h +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_MJPEG_DECODER_H_ -#define INCLUDE_LIBYUV_MJPEG_DECODER_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -// NOTE: For a simplified public API use convert.h MJPGToI420(). - -struct jpeg_common_struct; -struct jpeg_decompress_struct; -struct jpeg_source_mgr; - -namespace libyuv { - -#ifdef __cplusplus -extern "C" { -#endif - -LIBYUV_BOOL ValidateJpeg(const uint8* sample, size_t sample_size); - -#ifdef __cplusplus -} // extern "C" -#endif - -static const uint32 kUnknownDataSize = 0xFFFFFFFF; - -enum JpegSubsamplingType { - kJpegYuv420, - kJpegYuv422, - kJpegYuv411, - kJpegYuv444, - kJpegYuv400, - kJpegUnknown -}; - -struct Buffer { - const uint8* data; - int len; -}; - -struct BufferVector { - Buffer* buffers; - int len; - int pos; -}; - -struct SetJmpErrorMgr; - -// MJPEG ("Motion JPEG") is a pseudo-standard video codec where the frames are -// simply independent JPEG images with a fixed huffman table (which is omitted). -// It is rarely used in video transmission, but is common as a camera capture -// format, especially in Logitech devices. This class implements a decoder for -// MJPEG frames. -// -// See http://tools.ietf.org/html/rfc2435 -class LIBYUV_API MJpegDecoder { - public: - typedef void (*CallbackFunction)(void* opaque, - const uint8* const* data, - const int* strides, - int rows); - - static const int kColorSpaceUnknown; - static const int kColorSpaceGrayscale; - static const int kColorSpaceRgb; - static const int kColorSpaceYCbCr; - static const int kColorSpaceCMYK; - static const int kColorSpaceYCCK; - - MJpegDecoder(); - ~MJpegDecoder(); - - // Loads a new frame, reads its headers, and determines the uncompressed - // image format. - // Returns LIBYUV_TRUE if image looks valid and format is supported. - // If return value is LIBYUV_TRUE, then the values for all the following - // getters are populated. - // src_len is the size of the compressed mjpeg frame in bytes. - LIBYUV_BOOL LoadFrame(const uint8* src, size_t src_len); - - // Returns width of the last loaded frame in pixels. - int GetWidth(); - - // Returns height of the last loaded frame in pixels. - int GetHeight(); - - // Returns format of the last loaded frame. The return value is one of the - // kColorSpace* constants. - int GetColorSpace(); - - // Number of color components in the color space. - int GetNumComponents(); - - // Sample factors of the n-th component. - int GetHorizSampFactor(int component); - - int GetVertSampFactor(int component); - - int GetHorizSubSampFactor(int component); - - int GetVertSubSampFactor(int component); - - // Public for testability. - int GetImageScanlinesPerImcuRow(); - - // Public for testability. - int GetComponentScanlinesPerImcuRow(int component); - - // Width of a component in bytes. - int GetComponentWidth(int component); - - // Height of a component. - int GetComponentHeight(int component); - - // Width of a component in bytes with padding for DCTSIZE. Public for testing. - int GetComponentStride(int component); - - // Size of a component in bytes. - int GetComponentSize(int component); - - // Call this after LoadFrame() if you decide you don't want to decode it - // after all. - LIBYUV_BOOL UnloadFrame(); - - // Decodes the entire image into a one-buffer-per-color-component format. - // dst_width must match exactly. dst_height must be <= to image height; if - // less, the image is cropped. "planes" must have size equal to at least - // GetNumComponents() and they must point to non-overlapping buffers of size - // at least GetComponentSize(i). The pointers in planes are incremented - // to point to after the end of the written data. - // TODO(fbarchard): Add dst_x, dst_y to allow specific rect to be decoded. - LIBYUV_BOOL DecodeToBuffers(uint8** planes, int dst_width, int dst_height); - - // Decodes the entire image and passes the data via repeated calls to a - // callback function. Each call will get the data for a whole number of - // image scanlines. - // TODO(fbarchard): Add dst_x, dst_y to allow specific rect to be decoded. - LIBYUV_BOOL DecodeToCallback(CallbackFunction fn, void* opaque, - int dst_width, int dst_height); - - // The helper function which recognizes the jpeg sub-sampling type. - static JpegSubsamplingType JpegSubsamplingTypeHelper( - int* subsample_x, int* subsample_y, int number_of_components); - - private: - void AllocOutputBuffers(int num_outbufs); - void DestroyOutputBuffers(); - - LIBYUV_BOOL StartDecode(); - LIBYUV_BOOL FinishDecode(); - - void SetScanlinePointers(uint8** data); - LIBYUV_BOOL DecodeImcuRow(); - - int GetComponentScanlinePadding(int component); - - // A buffer holding the input data for a frame. - Buffer buf_; - BufferVector buf_vec_; - - jpeg_decompress_struct* decompress_struct_; - jpeg_source_mgr* source_mgr_; - SetJmpErrorMgr* error_mgr_; - - // LIBYUV_TRUE iff at least one component has scanline padding. (i.e., - // GetComponentScanlinePadding() != 0.) - LIBYUV_BOOL has_scanline_padding_; - - // Temporaries used to point to scanline outputs. - int num_outbufs_; // Outermost size of all arrays below. - uint8*** scanlines_; - int* scanlines_sizes_; - // Temporary buffer used for decoding when we can't decode directly to the - // output buffers. Large enough for just one iMCU row. - uint8** databuf_; - int* databuf_strides_; -}; - -} // namespace libyuv - -#endif // __cplusplus -#endif // INCLUDE_LIBYUV_MJPEG_DECODER_H_ diff --git a/third_party/libyuv/include/libyuv/planar_functions.h b/third_party/libyuv/include/libyuv/planar_functions.h deleted file mode 100644 index 1b57b29261..0000000000 --- a/third_party/libyuv/include/libyuv/planar_functions.h +++ /dev/null @@ -1,529 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_PLANAR_FUNCTIONS_H_ -#define INCLUDE_LIBYUV_PLANAR_FUNCTIONS_H_ - -#include "libyuv/basic_types.h" - -// TODO(fbarchard): Remove the following headers includes. -#include "libyuv/convert.h" -#include "libyuv/convert_argb.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Copy a plane of data. -LIBYUV_API -void CopyPlane(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -LIBYUV_API -void CopyPlane_16(const uint16* src_y, int src_stride_y, - uint16* dst_y, int dst_stride_y, - int width, int height); - -// Set a plane of data to a 32 bit value. -LIBYUV_API -void SetPlane(uint8* dst_y, int dst_stride_y, - int width, int height, - uint32 value); - -// Split interleaved UV plane into separate U and V planes. -LIBYUV_API -void SplitUVPlane(const uint8* src_uv, int src_stride_uv, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Merge separate U and V planes into one interleaved UV plane. -LIBYUV_API -void MergeUVPlane(const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -// Copy I400. Supports inverting. -LIBYUV_API -int I400ToI400(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -#define J400ToJ400 I400ToI400 - -// Copy I422 to I422. -#define I422ToI422 I422Copy -LIBYUV_API -int I422Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Copy I444 to I444. -#define I444ToI444 I444Copy -LIBYUV_API -int I444Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert YUY2 to I422. -LIBYUV_API -int YUY2ToI422(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert UYVY to I422. -LIBYUV_API -int UYVYToI422(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -LIBYUV_API -int YUY2ToNV12(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -LIBYUV_API -int UYVYToNV12(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -// Convert I420 to I400. (calls CopyPlane ignoring u/v). -LIBYUV_API -int I420ToI400(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Alias -#define J420ToJ400 I420ToI400 -#define I420ToI420Mirror I420Mirror - -// I420 mirror. -LIBYUV_API -int I420Mirror(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Alias -#define I400ToI400Mirror I400Mirror - -// I400 mirror. A single plane is mirrored horizontally. -// Pass negative height to achieve 180 degree rotation. -LIBYUV_API -int I400Mirror(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Alias -#define ARGBToARGBMirror ARGBMirror - -// ARGB mirror. -LIBYUV_API -int ARGBMirror(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert NV12 to RGB565. -LIBYUV_API -int NV12ToRGB565(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_rgb565, int dst_stride_rgb565, - int width, int height); - -// I422ToARGB is in convert_argb.h -// Convert I422 to BGRA. -LIBYUV_API -int I422ToBGRA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_bgra, int dst_stride_bgra, - int width, int height); - -// Convert I422 to ABGR. -LIBYUV_API -int I422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert I422 to RGBA. -LIBYUV_API -int I422ToRGBA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height); - -// Alias -#define RGB24ToRAW RAWToRGB24 - -LIBYUV_API -int RAWToRGB24(const uint8* src_raw, int src_stride_raw, - uint8* dst_rgb24, int dst_stride_rgb24, - int width, int height); - -// Draw a rectangle into I420. -LIBYUV_API -int I420Rect(uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int x, int y, int width, int height, - int value_y, int value_u, int value_v); - -// Draw a rectangle into ARGB. -LIBYUV_API -int ARGBRect(uint8* dst_argb, int dst_stride_argb, - int x, int y, int width, int height, uint32 value); - -// Convert ARGB to gray scale ARGB. -LIBYUV_API -int ARGBGrayTo(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Make a rectangle of ARGB gray scale. -LIBYUV_API -int ARGBGray(uint8* dst_argb, int dst_stride_argb, - int x, int y, int width, int height); - -// Make a rectangle of ARGB Sepia tone. -LIBYUV_API -int ARGBSepia(uint8* dst_argb, int dst_stride_argb, - int x, int y, int width, int height); - -// Apply a matrix rotation to each ARGB pixel. -// matrix_argb is 4 signed ARGB values. -128 to 127 representing -2 to 2. -// The first 4 coefficients apply to B, G, R, A and produce B of the output. -// The next 4 coefficients apply to B, G, R, A and produce G of the output. -// The next 4 coefficients apply to B, G, R, A and produce R of the output. -// The last 4 coefficients apply to B, G, R, A and produce A of the output. -LIBYUV_API -int ARGBColorMatrix(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const int8* matrix_argb, - int width, int height); - -// Deprecated. Use ARGBColorMatrix instead. -// Apply a matrix rotation to each ARGB pixel. -// matrix_argb is 3 signed ARGB values. -128 to 127 representing -1 to 1. -// The first 4 coefficients apply to B, G, R, A and produce B of the output. -// The next 4 coefficients apply to B, G, R, A and produce G of the output. -// The last 4 coefficients apply to B, G, R, A and produce R of the output. -LIBYUV_API -int RGBColorMatrix(uint8* dst_argb, int dst_stride_argb, - const int8* matrix_rgb, - int x, int y, int width, int height); - -// Apply a color table each ARGB pixel. -// Table contains 256 ARGB values. -LIBYUV_API -int ARGBColorTable(uint8* dst_argb, int dst_stride_argb, - const uint8* table_argb, - int x, int y, int width, int height); - -// Apply a color table each ARGB pixel but preserve destination alpha. -// Table contains 256 ARGB values. -LIBYUV_API -int RGBColorTable(uint8* dst_argb, int dst_stride_argb, - const uint8* table_argb, - int x, int y, int width, int height); - -// Apply a luma/color table each ARGB pixel but preserve destination alpha. -// Table contains 32768 values indexed by [Y][C] where 7 it 7 bit luma from -// RGB (YJ style) and C is an 8 bit color component (R, G or B). -LIBYUV_API -int ARGBLumaColorTable(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const uint8* luma_rgb_table, - int width, int height); - -// Apply a 3 term polynomial to ARGB values. -// poly points to a 4x4 matrix. The first row is constants. The 2nd row is -// coefficients for b, g, r and a. The 3rd row is coefficients for b squared, -// g squared, r squared and a squared. The 4rd row is coefficients for b to -// the 3, g to the 3, r to the 3 and a to the 3. The values are summed and -// result clamped to 0 to 255. -// A polynomial approximation can be dirived using software such as 'R'. - -LIBYUV_API -int ARGBPolynomial(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const float* poly, - int width, int height); - -// Convert plane of 16 bit shorts to half floats. -// Source values are multiplied by scale before storing as half float. -LIBYUV_API -int HalfFloatPlane(const uint16* src_y, int src_stride_y, - uint16* dst_y, int dst_stride_y, - float scale, - int width, int height); - -// Quantize a rectangle of ARGB. Alpha unaffected. -// scale is a 16 bit fractional fixed point scaler between 0 and 65535. -// interval_size should be a value between 1 and 255. -// interval_offset should be a value between 0 and 255. -LIBYUV_API -int ARGBQuantize(uint8* dst_argb, int dst_stride_argb, - int scale, int interval_size, int interval_offset, - int x, int y, int width, int height); - -// Copy ARGB to ARGB. -LIBYUV_API -int ARGBCopy(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Copy Alpha channel of ARGB to alpha of ARGB. -LIBYUV_API -int ARGBCopyAlpha(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Extract the alpha channel from ARGB. -LIBYUV_API -int ARGBExtractAlpha(const uint8* src_argb, int src_stride_argb, - uint8* dst_a, int dst_stride_a, - int width, int height); - -// Copy Y channel to Alpha of ARGB. -LIBYUV_API -int ARGBCopyYToAlpha(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -typedef void (*ARGBBlendRow)(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width); - -// Get function to Alpha Blend ARGB pixels and store to destination. -LIBYUV_API -ARGBBlendRow GetARGBBlend(); - -// Alpha Blend ARGB images and store to destination. -// Source is pre-multiplied by alpha using ARGBAttenuate. -// Alpha of destination is set to 255. -LIBYUV_API -int ARGBBlend(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Alpha Blend plane and store to destination. -// Source is not pre-multiplied by alpha. -LIBYUV_API -int BlendPlane(const uint8* src_y0, int src_stride_y0, - const uint8* src_y1, int src_stride_y1, - const uint8* alpha, int alpha_stride, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Alpha Blend YUV images and store to destination. -// Source is not pre-multiplied by alpha. -// Alpha is full width x height and subsampled to half size to apply to UV. -LIBYUV_API -int I420Blend(const uint8* src_y0, int src_stride_y0, - const uint8* src_u0, int src_stride_u0, - const uint8* src_v0, int src_stride_v0, - const uint8* src_y1, int src_stride_y1, - const uint8* src_u1, int src_stride_u1, - const uint8* src_v1, int src_stride_v1, - const uint8* alpha, int alpha_stride, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Multiply ARGB image by ARGB image. Shifted down by 8. Saturates to 255. -LIBYUV_API -int ARGBMultiply(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Add ARGB image with ARGB image. Saturates to 255. -LIBYUV_API -int ARGBAdd(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Subtract ARGB image (argb1) from ARGB image (argb0). Saturates to 0. -LIBYUV_API -int ARGBSubtract(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I422 to YUY2. -LIBYUV_API -int I422ToYUY2(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert I422 to UYVY. -LIBYUV_API -int I422ToUYVY(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert unattentuated ARGB to preattenuated ARGB. -LIBYUV_API -int ARGBAttenuate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert preattentuated ARGB to unattenuated ARGB. -LIBYUV_API -int ARGBUnattenuate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Internal function - do not call directly. -// Computes table of cumulative sum for image where the value is the sum -// of all values above and to the left of the entry. Used by ARGBBlur. -LIBYUV_API -int ARGBComputeCumulativeSum(const uint8* src_argb, int src_stride_argb, - int32* dst_cumsum, int dst_stride32_cumsum, - int width, int height); - -// Blur ARGB image. -// dst_cumsum table of width * (height + 1) * 16 bytes aligned to -// 16 byte boundary. -// dst_stride32_cumsum is number of ints in a row (width * 4). -// radius is number of pixels around the center. e.g. 1 = 3x3. 2=5x5. -// Blur is optimized for radius of 5 (11x11) or less. -LIBYUV_API -int ARGBBlur(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int32* dst_cumsum, int dst_stride32_cumsum, - int width, int height, int radius); - -// Multiply ARGB image by ARGB value. -LIBYUV_API -int ARGBShade(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height, uint32 value); - -// Interpolate between two images using specified amount of interpolation -// (0 to 255) and store to destination. -// 'interpolation' is specified as 8 bit fraction where 0 means 100% src0 -// and 255 means 1% src0 and 99% src1. -LIBYUV_API -int InterpolatePlane(const uint8* src0, int src_stride0, - const uint8* src1, int src_stride1, - uint8* dst, int dst_stride, - int width, int height, int interpolation); - -// Interpolate between two ARGB images using specified amount of interpolation -// Internally calls InterpolatePlane with width * 4 (bpp). -LIBYUV_API -int ARGBInterpolate(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height, int interpolation); - -// Interpolate between two YUV images using specified amount of interpolation -// Internally calls InterpolatePlane on each plane where the U and V planes -// are half width and half height. -LIBYUV_API -int I420Interpolate(const uint8* src0_y, int src0_stride_y, - const uint8* src0_u, int src0_stride_u, - const uint8* src0_v, int src0_stride_v, - const uint8* src1_y, int src1_stride_y, - const uint8* src1_u, int src1_stride_u, - const uint8* src1_v, int src1_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height, int interpolation); - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif -// The following are available on all x86 platforms: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)) -#define HAS_ARGBAFFINEROW_SSE2 -#endif - -// Row function for copying pixels from a source with a slope to a row -// of destination. Useful for scaling, rotation, mirror, texture mapping. -LIBYUV_API -void ARGBAffineRow_C(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); -LIBYUV_API -void ARGBAffineRow_SSE2(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); - -// Shuffle ARGB channel order. e.g. BGRA to ARGB. -// shuffler is 16 bytes and must be aligned. -LIBYUV_API -int ARGBShuffle(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_argb, int dst_stride_argb, - const uint8* shuffler, int width, int height); - -// Sobel ARGB effect with planar output. -LIBYUV_API -int ARGBSobelToPlane(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Sobel ARGB effect. -LIBYUV_API -int ARGBSobel(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Sobel ARGB effect w/ Sobel X, Sobel, Sobel Y in ARGB. -LIBYUV_API -int ARGBSobelXY(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_PLANAR_FUNCTIONS_H_ diff --git a/third_party/libyuv/include/libyuv/rotate.h b/third_party/libyuv/include/libyuv/rotate.h deleted file mode 100644 index 8a2da9a5aa..0000000000 --- a/third_party/libyuv/include/libyuv/rotate.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROTATE_H_ -#define INCLUDE_LIBYUV_ROTATE_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Supported rotation. -typedef enum RotationMode { - kRotate0 = 0, // No rotation. - kRotate90 = 90, // Rotate 90 degrees clockwise. - kRotate180 = 180, // Rotate 180 degrees. - kRotate270 = 270, // Rotate 270 degrees clockwise. - - // Deprecated. - kRotateNone = 0, - kRotateClockwise = 90, - kRotateCounterClockwise = 270, -} RotationModeEnum; - -// Rotate I420 frame. -LIBYUV_API -int I420Rotate(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int src_width, int src_height, enum RotationMode mode); - -// Rotate NV12 input and store in I420. -LIBYUV_API -int NV12ToI420Rotate(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int src_width, int src_height, enum RotationMode mode); - -// Rotate a plane by 0, 90, 180, or 270. -LIBYUV_API -int RotatePlane(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int src_width, int src_height, enum RotationMode mode); - -// Rotate planes by 90, 180, 270. Deprecated. -LIBYUV_API -void RotatePlane90(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void RotatePlane180(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void RotatePlane270(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void RotateUV90(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -// Rotations for when U and V are interleaved. -// These functions take one input pointer and -// split the data into two buffers while -// rotating them. Deprecated. -LIBYUV_API -void RotateUV180(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -LIBYUV_API -void RotateUV270(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -// The 90 and 270 functions are based on transposes. -// Doing a transpose with reversing the read/write -// order will result in a rotation by +- 90 degrees. -// Deprecated. -LIBYUV_API -void TransposePlane(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void TransposeUV(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROTATE_H_ diff --git a/third_party/libyuv/include/libyuv/rotate_argb.h b/third_party/libyuv/include/libyuv/rotate_argb.h deleted file mode 100644 index 21fe7e1807..0000000000 --- a/third_party/libyuv/include/libyuv/rotate_argb.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROTATE_ARGB_H_ -#define INCLUDE_LIBYUV_ROTATE_ARGB_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/rotate.h" // For RotationMode. - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Rotate ARGB frame -LIBYUV_API -int ARGBRotate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int src_width, int src_height, enum RotationMode mode); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROTATE_ARGB_H_ diff --git a/third_party/libyuv/include/libyuv/rotate_row.h b/third_party/libyuv/include/libyuv/rotate_row.h deleted file mode 100644 index 6abd201677..0000000000 --- a/third_party/libyuv/include/libyuv/rotate_row.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROTATE_ROW_H_ -#define INCLUDE_LIBYUV_ROTATE_ROW_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif -// The following are available for Visual C and clangcl 32 bit: -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) -#define HAS_TRANSPOSEWX8_SSSE3 -#define HAS_TRANSPOSEUVWX8_SSE2 -#endif - -// The following are available for GCC 32 or 64 bit but not NaCL for 64 bit: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__i386__) || (defined(__x86_64__) && !defined(__native_client__))) -#define HAS_TRANSPOSEWX8_SSSE3 -#endif - -// The following are available for 64 bit GCC but not NaCL: -#if !defined(LIBYUV_DISABLE_X86) && !defined(__native_client__) && \ - defined(__x86_64__) -#define HAS_TRANSPOSEWX8_FAST_SSSE3 -#define HAS_TRANSPOSEUVWX8_SSE2 -#endif - -#if !defined(LIBYUV_DISABLE_NEON) && !defined(__native_client__) && \ - (defined(__ARM_NEON__) || defined(LIBYUV_NEON) || defined(__aarch64__)) -#define HAS_TRANSPOSEWX8_NEON -#define HAS_TRANSPOSEUVWX8_NEON -#endif - -#if !defined(LIBYUV_DISABLE_MIPS) && !defined(__native_client__) && \ - defined(__mips__) && \ - defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_TRANSPOSEWX8_DSPR2 -#define HAS_TRANSPOSEUVWX8_DSPR2 -#endif // defined(__mips__) - -void TransposeWxH_C(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width, int height); - -void TransposeWx8_C(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_NEON(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Fast_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Fast_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); - -void TransposeWx8_Any_NEON(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Any_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Fast_Any_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Any_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); - -void TransposeUVWxH_C(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -void TransposeUVWx8_C(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_SSE2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_NEON(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_DSPR2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); - -void TransposeUVWx8_Any_SSE2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_Any_NEON(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_Any_DSPR2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROTATE_ROW_H_ diff --git a/third_party/libyuv/include/libyuv/row.h b/third_party/libyuv/include/libyuv/row.h deleted file mode 100644 index b810221ec7..0000000000 --- a/third_party/libyuv/include/libyuv/row.h +++ /dev/null @@ -1,1963 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROW_H_ -#define INCLUDE_LIBYUV_ROW_H_ - -#include // For malloc. - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#define IS_ALIGNED(p, a) (!((uintptr_t)(p) & ((a) - 1))) - -#define align_buffer_64(var, size) \ - uint8* var##_mem = (uint8*)(malloc((size) + 63)); /* NOLINT */ \ - uint8* var = (uint8*)(((intptr_t)(var##_mem) + 63) & ~63) /* NOLINT */ - -#define free_aligned_buffer_64(var) \ - free(var##_mem); \ - var = 0 - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif -// True if compiling for SSSE3 as a requirement. -#if defined(__SSSE3__) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 3)) -#define LIBYUV_SSSE3_ONLY -#endif - -#if defined(__native_client__) -#define LIBYUV_DISABLE_NEON -#endif -// clang >= 3.5.0 required for Arm64. -#if defined(__clang__) && defined(__aarch64__) && !defined(LIBYUV_DISABLE_NEON) -#if (__clang_major__ < 3) || (__clang_major__ == 3 && (__clang_minor__ < 5)) -#define LIBYUV_DISABLE_NEON -#endif // clang >= 3.5 -#endif // __clang__ - -// GCC >= 4.7.0 required for AVX2. -#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) -#if (__GNUC__ > 4) || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 7)) -#define GCC_HAS_AVX2 1 -#endif // GNUC >= 4.7 -#endif // __GNUC__ - -// clang >= 3.4.0 required for AVX2. -#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) -#if (__clang_major__ > 3) || (__clang_major__ == 3 && (__clang_minor__ >= 4)) -#define CLANG_HAS_AVX2 1 -#endif // clang >= 3.4 -#endif // __clang__ - -// Visual C 2012 required for AVX2. -#if defined(_M_IX86) && !defined(__clang__) && \ - defined(_MSC_VER) && _MSC_VER >= 1700 -#define VISUALC_HAS_AVX2 1 -#endif // VisualStudio >= 2012 - -// The following are available on all x86 platforms: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)) -// Conversions: -#define HAS_ABGRTOUVROW_SSSE3 -#define HAS_ABGRTOYROW_SSSE3 -#define HAS_ARGB1555TOARGBROW_SSE2 -#define HAS_ARGB4444TOARGBROW_SSE2 -#define HAS_ARGBSETROW_X86 -#define HAS_ARGBSHUFFLEROW_SSE2 -#define HAS_ARGBSHUFFLEROW_SSSE3 -#define HAS_ARGBTOARGB1555ROW_SSE2 -#define HAS_ARGBTOARGB4444ROW_SSE2 -#define HAS_ARGBTORAWROW_SSSE3 -#define HAS_ARGBTORGB24ROW_SSSE3 -#define HAS_ARGBTORGB565DITHERROW_SSE2 -#define HAS_ARGBTORGB565ROW_SSE2 -#define HAS_ARGBTOUV444ROW_SSSE3 -#define HAS_ARGBTOUVJROW_SSSE3 -#define HAS_ARGBTOUVROW_SSSE3 -#define HAS_ARGBTOYJROW_SSSE3 -#define HAS_ARGBTOYROW_SSSE3 -#define HAS_ARGBEXTRACTALPHAROW_SSE2 -#define HAS_BGRATOUVROW_SSSE3 -#define HAS_BGRATOYROW_SSSE3 -#define HAS_COPYROW_ERMS -#define HAS_COPYROW_SSE2 -#define HAS_H422TOARGBROW_SSSE3 -#define HAS_I400TOARGBROW_SSE2 -#define HAS_I422TOARGB1555ROW_SSSE3 -#define HAS_I422TOARGB4444ROW_SSSE3 -#define HAS_I422TOARGBROW_SSSE3 -#define HAS_I422TORGB24ROW_SSSE3 -#define HAS_I422TORGB565ROW_SSSE3 -#define HAS_I422TORGBAROW_SSSE3 -#define HAS_I422TOUYVYROW_SSE2 -#define HAS_I422TOYUY2ROW_SSE2 -#define HAS_I444TOARGBROW_SSSE3 -#define HAS_J400TOARGBROW_SSE2 -#define HAS_J422TOARGBROW_SSSE3 -#define HAS_MERGEUVROW_SSE2 -#define HAS_MIRRORROW_SSSE3 -#define HAS_MIRRORUVROW_SSSE3 -#define HAS_NV12TOARGBROW_SSSE3 -#define HAS_NV12TORGB565ROW_SSSE3 -#define HAS_NV21TOARGBROW_SSSE3 -#define HAS_RAWTOARGBROW_SSSE3 -#define HAS_RAWTORGB24ROW_SSSE3 -#define HAS_RAWTOYROW_SSSE3 -#define HAS_RGB24TOARGBROW_SSSE3 -#define HAS_RGB24TOYROW_SSSE3 -#define HAS_RGB565TOARGBROW_SSE2 -#define HAS_RGBATOUVROW_SSSE3 -#define HAS_RGBATOYROW_SSSE3 -#define HAS_SETROW_ERMS -#define HAS_SETROW_X86 -#define HAS_SPLITUVROW_SSE2 -#define HAS_UYVYTOARGBROW_SSSE3 -#define HAS_UYVYTOUV422ROW_SSE2 -#define HAS_UYVYTOUVROW_SSE2 -#define HAS_UYVYTOYROW_SSE2 -#define HAS_YUY2TOARGBROW_SSSE3 -#define HAS_YUY2TOUV422ROW_SSE2 -#define HAS_YUY2TOUVROW_SSE2 -#define HAS_YUY2TOYROW_SSE2 - -// Effects: -#define HAS_ARGBADDROW_SSE2 -#define HAS_ARGBAFFINEROW_SSE2 -#define HAS_ARGBATTENUATEROW_SSSE3 -#define HAS_ARGBBLENDROW_SSSE3 -#define HAS_ARGBCOLORMATRIXROW_SSSE3 -#define HAS_ARGBCOLORTABLEROW_X86 -#define HAS_ARGBCOPYALPHAROW_SSE2 -#define HAS_ARGBCOPYYTOALPHAROW_SSE2 -#define HAS_ARGBGRAYROW_SSSE3 -#define HAS_ARGBLUMACOLORTABLEROW_SSSE3 -#define HAS_ARGBMIRRORROW_SSE2 -#define HAS_ARGBMULTIPLYROW_SSE2 -#define HAS_ARGBPOLYNOMIALROW_SSE2 -#define HAS_ARGBQUANTIZEROW_SSE2 -#define HAS_ARGBSEPIAROW_SSSE3 -#define HAS_ARGBSHADEROW_SSE2 -#define HAS_ARGBSUBTRACTROW_SSE2 -#define HAS_ARGBUNATTENUATEROW_SSE2 -#define HAS_BLENDPLANEROW_SSSE3 -#define HAS_COMPUTECUMULATIVESUMROW_SSE2 -#define HAS_CUMULATIVESUMTOAVERAGEROW_SSE2 -#define HAS_INTERPOLATEROW_SSSE3 -#define HAS_RGBCOLORTABLEROW_X86 -#define HAS_SOBELROW_SSE2 -#define HAS_SOBELTOPLANEROW_SSE2 -#define HAS_SOBELXROW_SSE2 -#define HAS_SOBELXYROW_SSE2 -#define HAS_SOBELYROW_SSE2 - -// The following functions fail on gcc/clang 32 bit with fpic and framepointer. -// caveat: clangcl uses row_win.cc which works. -#if defined(NDEBUG) || !(defined(_DEBUG) && defined(__i386__)) || \ - !defined(__i386__) || defined(_MSC_VER) -// TODO(fbarchard): fix build error on x86 debug -// https://code.google.com/p/libyuv/issues/detail?id=524 -#define HAS_I411TOARGBROW_SSSE3 -// TODO(fbarchard): fix build error on android_full_debug=1 -// https://code.google.com/p/libyuv/issues/detail?id=517 -#define HAS_I422ALPHATOARGBROW_SSSE3 -#endif -#endif - -// The following are available on all x86 platforms, but -// require VS2012, clang 3.4 or gcc 4.7. -// The code supports NaCL but requires a new compiler and validator. -#if !defined(LIBYUV_DISABLE_X86) && (defined(VISUALC_HAS_AVX2) || \ - defined(CLANG_HAS_AVX2) || defined(GCC_HAS_AVX2)) -#define HAS_ARGBCOPYALPHAROW_AVX2 -#define HAS_ARGBCOPYYTOALPHAROW_AVX2 -#define HAS_ARGBMIRRORROW_AVX2 -#define HAS_ARGBPOLYNOMIALROW_AVX2 -#define HAS_ARGBSHUFFLEROW_AVX2 -#define HAS_ARGBTORGB565DITHERROW_AVX2 -#define HAS_ARGBTOUVJROW_AVX2 -#define HAS_ARGBTOUVROW_AVX2 -#define HAS_ARGBTOYJROW_AVX2 -#define HAS_ARGBTOYROW_AVX2 -#define HAS_COPYROW_AVX -#define HAS_H422TOARGBROW_AVX2 -#define HAS_I400TOARGBROW_AVX2 -#if !(defined(_DEBUG) && defined(__i386__)) -// TODO(fbarchard): fix build error on android_full_debug=1 -// https://code.google.com/p/libyuv/issues/detail?id=517 -#define HAS_I422ALPHATOARGBROW_AVX2 -#endif -#define HAS_I411TOARGBROW_AVX2 -#define HAS_I422TOARGB1555ROW_AVX2 -#define HAS_I422TOARGB4444ROW_AVX2 -#define HAS_I422TOARGBROW_AVX2 -#define HAS_I422TORGB24ROW_AVX2 -#define HAS_I422TORGB565ROW_AVX2 -#define HAS_I422TORGBAROW_AVX2 -#define HAS_I444TOARGBROW_AVX2 -#define HAS_INTERPOLATEROW_AVX2 -#define HAS_J422TOARGBROW_AVX2 -#define HAS_MERGEUVROW_AVX2 -#define HAS_MIRRORROW_AVX2 -#define HAS_NV12TOARGBROW_AVX2 -#define HAS_NV12TORGB565ROW_AVX2 -#define HAS_NV21TOARGBROW_AVX2 -#define HAS_SPLITUVROW_AVX2 -#define HAS_UYVYTOARGBROW_AVX2 -#define HAS_UYVYTOUV422ROW_AVX2 -#define HAS_UYVYTOUVROW_AVX2 -#define HAS_UYVYTOYROW_AVX2 -#define HAS_YUY2TOARGBROW_AVX2 -#define HAS_YUY2TOUV422ROW_AVX2 -#define HAS_YUY2TOUVROW_AVX2 -#define HAS_YUY2TOYROW_AVX2 -#define HAS_HALFFLOATROW_AVX2 - -// Effects: -#define HAS_ARGBADDROW_AVX2 -#define HAS_ARGBATTENUATEROW_AVX2 -#define HAS_ARGBMULTIPLYROW_AVX2 -#define HAS_ARGBSUBTRACTROW_AVX2 -#define HAS_ARGBUNATTENUATEROW_AVX2 -#define HAS_BLENDPLANEROW_AVX2 -#endif - -// The following are available for AVX2 Visual C and clangcl 32 bit: -// TODO(fbarchard): Port to gcc. -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) && \ - (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) -#define HAS_ARGB1555TOARGBROW_AVX2 -#define HAS_ARGB4444TOARGBROW_AVX2 -#define HAS_ARGBTOARGB1555ROW_AVX2 -#define HAS_ARGBTOARGB4444ROW_AVX2 -#define HAS_ARGBTORGB565ROW_AVX2 -#define HAS_J400TOARGBROW_AVX2 -#define HAS_RGB565TOARGBROW_AVX2 -#endif - -// The following are also available on x64 Visual C. -#if !defined(LIBYUV_DISABLE_X86) && defined(_MSC_VER) && defined(_M_X64) && \ - (!defined(__clang__) || defined(__SSSE3__)) -#define HAS_I422ALPHATOARGBROW_SSSE3 -#define HAS_I422TOARGBROW_SSSE3 -#endif - -// The following are available on gcc x86 platforms: -// TODO(fbarchard): Port to Visual C. -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__x86_64__) || (defined(__i386__) && !defined(_MSC_VER))) -#define HAS_HALFFLOATROW_SSE2 -#endif - -// The following are available on Neon platforms: -#if !defined(LIBYUV_DISABLE_NEON) && \ - (defined(__aarch64__) || defined(__ARM_NEON__) || defined(LIBYUV_NEON)) -#define HAS_ABGRTOUVROW_NEON -#define HAS_ABGRTOYROW_NEON -#define HAS_ARGB1555TOARGBROW_NEON -#define HAS_ARGB1555TOUVROW_NEON -#define HAS_ARGB1555TOYROW_NEON -#define HAS_ARGB4444TOARGBROW_NEON -#define HAS_ARGB4444TOUVROW_NEON -#define HAS_ARGB4444TOYROW_NEON -#define HAS_ARGBSETROW_NEON -#define HAS_ARGBTOARGB1555ROW_NEON -#define HAS_ARGBTOARGB4444ROW_NEON -#define HAS_ARGBTORAWROW_NEON -#define HAS_ARGBTORGB24ROW_NEON -#define HAS_ARGBTORGB565DITHERROW_NEON -#define HAS_ARGBTORGB565ROW_NEON -#define HAS_ARGBTOUV411ROW_NEON -#define HAS_ARGBTOUV444ROW_NEON -#define HAS_ARGBTOUVJROW_NEON -#define HAS_ARGBTOUVROW_NEON -#define HAS_ARGBTOYJROW_NEON -#define HAS_ARGBTOYROW_NEON -#define HAS_ARGBEXTRACTALPHAROW_NEON -#define HAS_BGRATOUVROW_NEON -#define HAS_BGRATOYROW_NEON -#define HAS_COPYROW_NEON -#define HAS_I400TOARGBROW_NEON -#define HAS_I411TOARGBROW_NEON -#define HAS_I422ALPHATOARGBROW_NEON -#define HAS_I422TOARGB1555ROW_NEON -#define HAS_I422TOARGB4444ROW_NEON -#define HAS_I422TOARGBROW_NEON -#define HAS_I422TORGB24ROW_NEON -#define HAS_I422TORGB565ROW_NEON -#define HAS_I422TORGBAROW_NEON -#define HAS_I422TOUYVYROW_NEON -#define HAS_I422TOYUY2ROW_NEON -#define HAS_I444TOARGBROW_NEON -#define HAS_J400TOARGBROW_NEON -#define HAS_MERGEUVROW_NEON -#define HAS_MIRRORROW_NEON -#define HAS_MIRRORUVROW_NEON -#define HAS_NV12TOARGBROW_NEON -#define HAS_NV12TORGB565ROW_NEON -#define HAS_NV21TOARGBROW_NEON -#define HAS_RAWTOARGBROW_NEON -#define HAS_RAWTORGB24ROW_NEON -#define HAS_RAWTOUVROW_NEON -#define HAS_RAWTOYROW_NEON -#define HAS_RGB24TOARGBROW_NEON -#define HAS_RGB24TOUVROW_NEON -#define HAS_RGB24TOYROW_NEON -#define HAS_RGB565TOARGBROW_NEON -#define HAS_RGB565TOUVROW_NEON -#define HAS_RGB565TOYROW_NEON -#define HAS_RGBATOUVROW_NEON -#define HAS_RGBATOYROW_NEON -#define HAS_SETROW_NEON -#define HAS_SPLITUVROW_NEON -#define HAS_UYVYTOARGBROW_NEON -#define HAS_UYVYTOUV422ROW_NEON -#define HAS_UYVYTOUVROW_NEON -#define HAS_UYVYTOYROW_NEON -#define HAS_YUY2TOARGBROW_NEON -#define HAS_YUY2TOUV422ROW_NEON -#define HAS_YUY2TOUVROW_NEON -#define HAS_YUY2TOYROW_NEON - -// Effects: -#define HAS_ARGBADDROW_NEON -#define HAS_ARGBATTENUATEROW_NEON -#define HAS_ARGBBLENDROW_NEON -#define HAS_ARGBCOLORMATRIXROW_NEON -#define HAS_ARGBGRAYROW_NEON -#define HAS_ARGBMIRRORROW_NEON -#define HAS_ARGBMULTIPLYROW_NEON -#define HAS_ARGBQUANTIZEROW_NEON -#define HAS_ARGBSEPIAROW_NEON -#define HAS_ARGBSHADEROW_NEON -#define HAS_ARGBSHUFFLEROW_NEON -#define HAS_ARGBSUBTRACTROW_NEON -#define HAS_INTERPOLATEROW_NEON -#define HAS_SOBELROW_NEON -#define HAS_SOBELTOPLANEROW_NEON -#define HAS_SOBELXROW_NEON -#define HAS_SOBELXYROW_NEON -#define HAS_SOBELYROW_NEON -#endif - -// The following are available on Mips platforms: -#if !defined(LIBYUV_DISABLE_MIPS) && defined(__mips__) && \ - (_MIPS_SIM == _MIPS_SIM_ABI32) && (__mips_isa_rev < 6) -#define HAS_COPYROW_MIPS -#if defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_I422TOARGBROW_DSPR2 -#define HAS_INTERPOLATEROW_DSPR2 -#define HAS_MIRRORROW_DSPR2 -#define HAS_MIRRORUVROW_DSPR2 -#define HAS_SPLITUVROW_DSPR2 -#endif -#endif - -#if !defined(LIBYUV_DISABLE_MSA) && defined(__mips_msa) -#define HAS_MIRRORROW_MSA -#define HAS_ARGBMIRRORROW_MSA -#endif - -#if defined(_MSC_VER) && !defined(__CLR_VER) && !defined(__clang__) -#if defined(VISUALC_HAS_AVX2) -#define SIMD_ALIGNED(var) __declspec(align(32)) var -#else -#define SIMD_ALIGNED(var) __declspec(align(16)) var -#endif -typedef __declspec(align(16)) int16 vec16[8]; -typedef __declspec(align(16)) int32 vec32[4]; -typedef __declspec(align(16)) int8 vec8[16]; -typedef __declspec(align(16)) uint16 uvec16[8]; -typedef __declspec(align(16)) uint32 uvec32[4]; -typedef __declspec(align(16)) uint8 uvec8[16]; -typedef __declspec(align(32)) int16 lvec16[16]; -typedef __declspec(align(32)) int32 lvec32[8]; -typedef __declspec(align(32)) int8 lvec8[32]; -typedef __declspec(align(32)) uint16 ulvec16[16]; -typedef __declspec(align(32)) uint32 ulvec32[8]; -typedef __declspec(align(32)) uint8 ulvec8[32]; -#elif !defined(__pnacl__) && (defined(__GNUC__) || defined(__clang__)) -// Caveat GCC 4.2 to 4.7 have a known issue using vectors with const. -#if defined(CLANG_HAS_AVX2) || defined(GCC_HAS_AVX2) -#define SIMD_ALIGNED(var) var __attribute__((aligned(32))) -#else -#define SIMD_ALIGNED(var) var __attribute__((aligned(16))) -#endif -typedef int16 __attribute__((vector_size(16))) vec16; -typedef int32 __attribute__((vector_size(16))) vec32; -typedef int8 __attribute__((vector_size(16))) vec8; -typedef uint16 __attribute__((vector_size(16))) uvec16; -typedef uint32 __attribute__((vector_size(16))) uvec32; -typedef uint8 __attribute__((vector_size(16))) uvec8; -typedef int16 __attribute__((vector_size(32))) lvec16; -typedef int32 __attribute__((vector_size(32))) lvec32; -typedef int8 __attribute__((vector_size(32))) lvec8; -typedef uint16 __attribute__((vector_size(32))) ulvec16; -typedef uint32 __attribute__((vector_size(32))) ulvec32; -typedef uint8 __attribute__((vector_size(32))) ulvec8; -#else -#define SIMD_ALIGNED(var) var -typedef int16 vec16[8]; -typedef int32 vec32[4]; -typedef int8 vec8[16]; -typedef uint16 uvec16[8]; -typedef uint32 uvec32[4]; -typedef uint8 uvec8[16]; -typedef int16 lvec16[16]; -typedef int32 lvec32[8]; -typedef int8 lvec8[32]; -typedef uint16 ulvec16[16]; -typedef uint32 ulvec32[8]; -typedef uint8 ulvec8[32]; -#endif - -#if defined(__aarch64__) -// This struct is for Arm64 color conversion. -struct YuvConstants { - uvec16 kUVToRB; - uvec16 kUVToRB2; - uvec16 kUVToG; - uvec16 kUVToG2; - vec16 kUVBiasBGR; - vec32 kYToRgb; -}; -#elif defined(__arm__) -// This struct is for ArmV7 color conversion. -struct YuvConstants { - uvec8 kUVToRB; - uvec8 kUVToG; - vec16 kUVBiasBGR; - vec32 kYToRgb; -}; -#else -// This struct is for Intel color conversion. -struct YuvConstants { - int8 kUVToB[32]; - int8 kUVToG[32]; - int8 kUVToR[32]; - int16 kUVBiasB[16]; - int16 kUVBiasG[16]; - int16 kUVBiasR[16]; - int16 kYToRgb[16]; -}; - -// Offsets into YuvConstants structure -#define KUVTOB 0 -#define KUVTOG 32 -#define KUVTOR 64 -#define KUVBIASB 96 -#define KUVBIASG 128 -#define KUVBIASR 160 -#define KYTORGB 192 -#endif - -// Conversion matrix for YUV to RGB -extern const struct YuvConstants SIMD_ALIGNED(kYuvI601Constants); // BT.601 -extern const struct YuvConstants SIMD_ALIGNED(kYuvJPEGConstants); // JPeg -extern const struct YuvConstants SIMD_ALIGNED(kYuvH709Constants); // BT.709 - -// Conversion matrix for YVU to BGR -extern const struct YuvConstants SIMD_ALIGNED(kYvuI601Constants); // BT.601 -extern const struct YuvConstants SIMD_ALIGNED(kYvuJPEGConstants); // JPeg -extern const struct YuvConstants SIMD_ALIGNED(kYvuH709Constants); // BT.709 - -#if defined(__APPLE__) || defined(__x86_64__) || defined(__llvm__) -#define OMITFP -#else -#define OMITFP __attribute__((optimize("omit-frame-pointer"))) -#endif - -// NaCL macros for GCC x86 and x64. -#if defined(__native_client__) -#define LABELALIGN ".p2align 5\n" -#else -#define LABELALIGN -#endif -#if defined(__native_client__) && defined(__x86_64__) -// r14 is used for MEMOP macros. -#define NACL_R14 "r14", -#define BUNDLELOCK ".bundle_lock\n" -#define BUNDLEUNLOCK ".bundle_unlock\n" -#define MEMACCESS(base) "%%nacl:(%%r15,%q" #base ")" -#define MEMACCESS2(offset, base) "%%nacl:" #offset "(%%r15,%q" #base ")" -#define MEMLEA(offset, base) #offset "(%q" #base ")" -#define MEMLEA3(offset, index, scale) \ - #offset "(,%q" #index "," #scale ")" -#define MEMLEA4(offset, base, index, scale) \ - #offset "(%q" #base ",%q" #index "," #scale ")" -#define MEMMOVESTRING(s, d) "%%nacl:(%q" #s "),%%nacl:(%q" #d "), %%r15" -#define MEMSTORESTRING(reg, d) "%%" #reg ",%%nacl:(%q" #d "), %%r15" -#define MEMOPREG(opcode, offset, base, index, scale, reg) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " (%%r15,%%r14),%%" #reg "\n" \ - BUNDLEUNLOCK -#define MEMOPMEM(opcode, reg, offset, base, index, scale) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " %%" #reg ",(%%r15,%%r14)\n" \ - BUNDLEUNLOCK -#define MEMOPARG(opcode, offset, base, index, scale, arg) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " (%%r15,%%r14),%" #arg "\n" \ - BUNDLEUNLOCK -#define VMEMOPREG(opcode, offset, base, index, scale, reg1, reg2) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " (%%r15,%%r14),%%" #reg1 ",%%" #reg2 "\n" \ - BUNDLEUNLOCK -#define VEXTOPMEM(op, sel, reg, offset, base, index, scale) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #op " $" #sel ",%%" #reg ",(%%r15,%%r14)\n" \ - BUNDLEUNLOCK -#else // defined(__native_client__) && defined(__x86_64__) -#define NACL_R14 -#define BUNDLEALIGN -#define MEMACCESS(base) "(%" #base ")" -#define MEMACCESS2(offset, base) #offset "(%" #base ")" -#define MEMLEA(offset, base) #offset "(%" #base ")" -#define MEMLEA3(offset, index, scale) \ - #offset "(,%" #index "," #scale ")" -#define MEMLEA4(offset, base, index, scale) \ - #offset "(%" #base ",%" #index "," #scale ")" -#define MEMMOVESTRING(s, d) -#define MEMSTORESTRING(reg, d) -#define MEMOPREG(opcode, offset, base, index, scale, reg) \ - #opcode " " #offset "(%" #base ",%" #index "," #scale "),%%" #reg "\n" -#define MEMOPMEM(opcode, reg, offset, base, index, scale) \ - #opcode " %%" #reg ","#offset "(%" #base ",%" #index "," #scale ")\n" -#define MEMOPARG(opcode, offset, base, index, scale, arg) \ - #opcode " " #offset "(%" #base ",%" #index "," #scale "),%" #arg "\n" -#define VMEMOPREG(opcode, offset, base, index, scale, reg1, reg2) \ - #opcode " " #offset "(%" #base ",%" #index "," #scale "),%%" #reg1 ",%%" \ - #reg2 "\n" -#define VEXTOPMEM(op, sel, reg, offset, base, index, scale) \ - #op " $" #sel ",%%" #reg ","#offset "(%" #base ",%" #index "," #scale ")\n" -#endif // defined(__native_client__) && defined(__x86_64__) - -#if defined(__arm__) || defined(__aarch64__) -#undef MEMACCESS -#if defined(__native_client__) -#define MEMACCESS(base) ".p2align 3\nbic %" #base ", #0xc0000000\n" -#else -#define MEMACCESS(base) -#endif -#endif - -void I444ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_NEON(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb1555, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_NEON(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_NEON(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_NEON(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); - -void ARGBToYRow_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYRow_Any_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_Any_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_SSSE3(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_SSSE3(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_SSSE3(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_SSSE3(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_SSSE3(const uint8* src_raw, uint8* dst_y, int width); -void ARGBToYRow_NEON(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_NEON(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToUV444Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUV411Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUVRow_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_NEON(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_NEON(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_NEON(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void RGB24ToUVRow_NEON(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width); -void RAWToUVRow_NEON(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width); -void RGB565ToUVRow_NEON(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width); -void ARGB1555ToUVRow_NEON(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width); -void ARGB4444ToUVRow_NEON(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToYRow_NEON(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_NEON(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_NEON(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_NEON(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_NEON(const uint8* src_raw, uint8* dst_y, int width); -void RGB565ToYRow_NEON(const uint8* src_rgb565, uint8* dst_y, int width); -void ARGB1555ToYRow_NEON(const uint8* src_argb1555, uint8* dst_y, int width); -void ARGB4444ToYRow_NEON(const uint8* src_argb4444, uint8* dst_y, int width); -void ARGBToYRow_C(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_C(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_C(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_C(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_C(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_C(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_C(const uint8* src_raw, uint8* dst_y, int width); -void RGB565ToYRow_C(const uint8* src_rgb565, uint8* dst_y, int width); -void ARGB1555ToYRow_C(const uint8* src_argb1555, uint8* dst_y, int width); -void ARGB4444ToYRow_C(const uint8* src_argb4444, uint8* dst_y, int width); -void ARGBToYRow_Any_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_Any_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_Any_SSSE3(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_Any_SSSE3(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_Any_SSSE3(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_Any_SSSE3(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_Any_SSSE3(const uint8* src_raw, uint8* dst_y, int width); -void ARGBToYRow_Any_NEON(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_Any_NEON(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_Any_NEON(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_Any_NEON(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_Any_NEON(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_Any_NEON(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_Any_NEON(const uint8* src_raw, uint8* dst_y, int width); -void RGB565ToYRow_Any_NEON(const uint8* src_rgb565, uint8* dst_y, int width); -void ARGB1555ToYRow_Any_NEON(const uint8* src_argb1555, uint8* dst_y, - int width); -void ARGB4444ToYRow_Any_NEON(const uint8* src_argb4444, uint8* dst_y, - int width); - -void ARGBToUVRow_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_SSSE3(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_SSSE3(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_SSSE3(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_Any_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_Any_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_Any_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_Any_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_Any_SSSE3(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_Any_SSSE3(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_Any_SSSE3(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV444Row_Any_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUV411Row_Any_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUVRow_Any_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_Any_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_Any_NEON(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_Any_NEON(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_Any_NEON(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void RGB24ToUVRow_Any_NEON(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width); -void RAWToUVRow_Any_NEON(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width); -void RGB565ToUVRow_Any_NEON(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width); -void ARGB1555ToUVRow_Any_NEON(const uint8* src_argb1555, - int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width); -void ARGB4444ToUVRow_Any_NEON(const uint8* src_argb4444, - int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_C(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_C(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_C(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_C(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_C(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void RGB24ToUVRow_C(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width); -void RAWToUVRow_C(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width); -void RGB565ToUVRow_C(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width); -void ARGB1555ToUVRow_C(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width); -void ARGB4444ToUVRow_C(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width); - -void ARGBToUV444Row_SSSE3(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV444Row_Any_SSSE3(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); - -void ARGBToUV444Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV411Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); - -void MirrorRow_AVX2(const uint8* src, uint8* dst, int width); -void MirrorRow_SSSE3(const uint8* src, uint8* dst, int width); -void MirrorRow_NEON(const uint8* src, uint8* dst, int width); -void MirrorRow_DSPR2(const uint8* src, uint8* dst, int width); -void MirrorRow_MSA(const uint8* src, uint8* dst, int width); -void MirrorRow_C(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_AVX2(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_SSSE3(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_SSE2(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_NEON(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_MSA(const uint8* src, uint8* dst, int width); - -void MirrorUVRow_SSSE3(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void MirrorUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void MirrorUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void MirrorUVRow_C(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); - -void ARGBMirrorRow_AVX2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_SSE2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_NEON(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_MSA(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_C(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_Any_AVX2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_Any_SSE2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_Any_NEON(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_Any_MSA(const uint8* src, uint8* dst, int width); - -void SplitUVRow_C(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); -void SplitUVRow_SSE2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_AVX2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_SSE2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_AVX2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); - -void MergeUVRow_C(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_SSE2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_AVX2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_NEON(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_Any_SSE2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_Any_AVX2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_Any_NEON(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); - -void CopyRow_SSE2(const uint8* src, uint8* dst, int count); -void CopyRow_AVX(const uint8* src, uint8* dst, int count); -void CopyRow_ERMS(const uint8* src, uint8* dst, int count); -void CopyRow_NEON(const uint8* src, uint8* dst, int count); -void CopyRow_MIPS(const uint8* src, uint8* dst, int count); -void CopyRow_C(const uint8* src, uint8* dst, int count); -void CopyRow_Any_SSE2(const uint8* src, uint8* dst, int count); -void CopyRow_Any_AVX(const uint8* src, uint8* dst, int count); -void CopyRow_Any_NEON(const uint8* src, uint8* dst, int count); - -void CopyRow_16_C(const uint16* src, uint16* dst, int count); - -void ARGBCopyAlphaRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBCopyAlphaRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBCopyAlphaRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBCopyAlphaRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBCopyAlphaRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - int width); - -void ARGBExtractAlphaRow_C(const uint8* src_argb, uint8* dst_a, int width); -void ARGBExtractAlphaRow_SSE2(const uint8* src_argb, uint8* dst_a, int width); -void ARGBExtractAlphaRow_NEON(const uint8* src_argb, uint8* dst_a, int width); -void ARGBExtractAlphaRow_Any_SSE2(const uint8* src_argb, uint8* dst_a, - int width); -void ARGBExtractAlphaRow_Any_NEON(const uint8* src_argb, uint8* dst_a, - int width); - -void ARGBCopyYToAlphaRow_C(const uint8* src_y, uint8* dst_argb, int width); -void ARGBCopyYToAlphaRow_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void ARGBCopyYToAlphaRow_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void ARGBCopyYToAlphaRow_Any_SSE2(const uint8* src_y, uint8* dst_argb, - int width); -void ARGBCopyYToAlphaRow_Any_AVX2(const uint8* src_y, uint8* dst_argb, - int width); - -void SetRow_C(uint8* dst, uint8 v8, int count); -void SetRow_X86(uint8* dst, uint8 v8, int count); -void SetRow_ERMS(uint8* dst, uint8 v8, int count); -void SetRow_NEON(uint8* dst, uint8 v8, int count); -void SetRow_Any_X86(uint8* dst, uint8 v8, int count); -void SetRow_Any_NEON(uint8* dst, uint8 v8, int count); - -void ARGBSetRow_C(uint8* dst_argb, uint32 v32, int count); -void ARGBSetRow_X86(uint8* dst_argb, uint32 v32, int count); -void ARGBSetRow_NEON(uint8* dst_argb, uint32 v32, int count); -void ARGBSetRow_Any_NEON(uint8* dst_argb, uint32 v32, int count); - -// ARGBShufflers for BGRAToARGB etc. -void ARGBShuffleRow_C(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_SSE2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_AVX2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_NEON(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_SSSE3(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_NEON(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); - -void RGB24ToARGBRow_SSSE3(const uint8* src_rgb24, uint8* dst_argb, int width); -void RAWToARGBRow_SSSE3(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_SSSE3(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_SSE2(const uint8* src_rgb565, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_SSE2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_SSE2(const uint8* src_argb4444, uint8* dst_argb, - int width); -void RGB565ToARGBRow_AVX2(const uint8* src_rgb565, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_AVX2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_AVX2(const uint8* src_argb4444, uint8* dst_argb, - int width); - -void RGB24ToARGBRow_NEON(const uint8* src_rgb24, uint8* dst_argb, int width); -void RAWToARGBRow_NEON(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_NEON(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_NEON(const uint8* src_rgb565, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_NEON(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_NEON(const uint8* src_argb4444, uint8* dst_argb, - int width); -void RGB24ToARGBRow_C(const uint8* src_rgb24, uint8* dst_argb, int width); -void RAWToARGBRow_C(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_C(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_C(const uint8* src_rgb, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGB4444ToARGBRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void RGB24ToARGBRow_Any_SSSE3(const uint8* src_rgb24, uint8* dst_argb, - int width); -void RAWToARGBRow_Any_SSSE3(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_Any_SSSE3(const uint8* src_raw, uint8* dst_rgb24, int width); - -void RGB565ToARGBRow_Any_SSE2(const uint8* src_rgb565, uint8* dst_argb, - int width); -void ARGB1555ToARGBRow_Any_SSE2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_Any_SSE2(const uint8* src_argb4444, uint8* dst_argb, - int width); -void RGB565ToARGBRow_Any_AVX2(const uint8* src_rgb565, uint8* dst_argb, - int width); -void ARGB1555ToARGBRow_Any_AVX2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_Any_AVX2(const uint8* src_argb4444, uint8* dst_argb, - int width); - -void RGB24ToARGBRow_Any_NEON(const uint8* src_rgb24, uint8* dst_argb, - int width); -void RAWToARGBRow_Any_NEON(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_Any_NEON(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_Any_NEON(const uint8* src_rgb565, uint8* dst_argb, - int width); -void ARGB1555ToARGBRow_Any_NEON(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_Any_NEON(const uint8* src_argb4444, uint8* dst_argb, - int width); - -void ARGBToRGB24Row_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); - -void ARGBToRGB565DitherRow_C(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); -void ARGBToRGB565DitherRow_SSE2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); -void ARGBToRGB565DitherRow_AVX2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void ARGBToRGB565Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); - -void ARGBToRGB24Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565DitherRow_NEON(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void ARGBToRGBARow_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB24Row_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_C(const uint8* src_argb, uint8* dst_rgb, int width); - -void J400ToARGBRow_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_NEON(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_C(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_Any_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_Any_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_Any_NEON(const uint8* src_y, uint8* dst_argb, int width); - -void I444ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_C(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_C(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_C(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_C(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_C(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_C(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_SSSE3(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_SSSE3(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_AVX2(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_AVX2(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_Any_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_Any_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_Any_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_Any_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_Any_SSSE3(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_Any_SSSE3(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_Any_AVX2(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_Any_AVX2(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); - -void I400ToARGBRow_C(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_NEON(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_Any_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_Any_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_Any_NEON(const uint8* src_y, uint8* dst_argb, int width); - -// ARGB preattenuated alpha blend. -void ARGBBlendRow_SSSE3(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBBlendRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBBlendRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -// Unattenuated planar alpha blend. -void BlendPlaneRow_SSSE3(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_Any_SSSE3(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_AVX2(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_Any_AVX2(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_C(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); - -// ARGB multiply images. Same API as Blend, but these require -// pointer and width alignment for SSE2. -void ARGBMultiplyRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_Any_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_Any_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_Any_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -// ARGB add images. -void ARGBAddRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_Any_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_Any_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_Any_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -// ARGB subtract images. Same API as Blend, but these require -// pointer and width alignment for SSE2. -void ARGBSubtractRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_Any_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_Any_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_Any_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -void ARGBToRGB24Row_Any_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_Any_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToARGB4444Row_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, - int width); - -void ARGBToRGB565DitherRow_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); -void ARGBToRGB565DitherRow_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void ARGBToRGB565Row_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToARGB4444Row_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, - int width); - -void ARGBToRGB24Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_Any_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToARGB4444Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToRGB565DitherRow_Any_NEON(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void I444ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - const uint8* src_a, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_Any_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_Any_NEON(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_Any_NEON(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_DSPR2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_DSPR2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); - -void YUY2ToYRow_AVX2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_AVX2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_AVX2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_SSE2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_SSE2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_SSE2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_NEON(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_NEON(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_NEON(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_C(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_C(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_C(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_Any_AVX2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_Any_AVX2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_Any_AVX2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_Any_SSE2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_Any_SSE2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_Any_SSE2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_Any_NEON(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_Any_NEON(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_Any_NEON(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_AVX2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_AVX2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_AVX2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_SSE2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_SSE2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_SSE2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_AVX2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_AVX2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_AVX2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_NEON(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_NEON(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_NEON(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); - -void UYVYToYRow_C(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_C(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_C(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_Any_AVX2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_Any_AVX2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_Any_AVX2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_Any_SSE2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_Any_SSE2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_Any_SSE2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_Any_NEON(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_Any_NEON(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_Any_NEON(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); - -void I422ToYUY2Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_Any_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_Any_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); - -// Effects related row functions. -void ARGBAttenuateRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_SSSE3(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_NEON(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBAttenuateRow_Any_SSSE3(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBAttenuateRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBAttenuateRow_Any_NEON(const uint8* src_argb, uint8* dst_argb, - int width); - -// Inverse table for unattenuate, shared by C and SSE2. -extern const uint32 fixed_invtbl8[256]; -void ARGBUnattenuateRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBUnattenuateRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBUnattenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBUnattenuateRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBUnattenuateRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - int width); - -void ARGBGrayRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBGrayRow_SSSE3(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBGrayRow_NEON(const uint8* src_argb, uint8* dst_argb, int width); - -void ARGBSepiaRow_C(uint8* dst_argb, int width); -void ARGBSepiaRow_SSSE3(uint8* dst_argb, int width); -void ARGBSepiaRow_NEON(uint8* dst_argb, int width); - -void ARGBColorMatrixRow_C(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width); -void ARGBColorMatrixRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width); -void ARGBColorMatrixRow_NEON(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width); - -void ARGBColorTableRow_C(uint8* dst_argb, const uint8* table_argb, int width); -void ARGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width); - -void RGBColorTableRow_C(uint8* dst_argb, const uint8* table_argb, int width); -void RGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width); - -void ARGBQuantizeRow_C(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width); -void ARGBQuantizeRow_SSE2(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width); -void ARGBQuantizeRow_NEON(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width); - -void ARGBShadeRow_C(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value); -void ARGBShadeRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value); -void ARGBShadeRow_NEON(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value); - -// Used for blur. -void CumulativeSumToAverageRow_SSE2(const int32* topleft, const int32* botleft, - int width, int area, uint8* dst, int count); -void ComputeCumulativeSumRow_SSE2(const uint8* row, int32* cumsum, - const int32* previous_cumsum, int width); - -void CumulativeSumToAverageRow_C(const int32* topleft, const int32* botleft, - int width, int area, uint8* dst, int count); -void ComputeCumulativeSumRow_C(const uint8* row, int32* cumsum, - const int32* previous_cumsum, int width); - -LIBYUV_API -void ARGBAffineRow_C(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); -LIBYUV_API -void ARGBAffineRow_SSE2(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); - -// Used for I420Scale, ARGBScale, and ARGBInterpolate. -void InterpolateRow_C(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, - int width, int source_y_fraction); -void InterpolateRow_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_AVX2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_NEON(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_DSPR2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_NEON(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_AVX2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_DSPR2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); - -void InterpolateRow_16_C(uint16* dst_ptr, const uint16* src_ptr, - ptrdiff_t src_stride_ptr, - int width, int source_y_fraction); - -// Sobel images. -void SobelXRow_C(const uint8* src_y0, const uint8* src_y1, const uint8* src_y2, - uint8* dst_sobelx, int width); -void SobelXRow_SSE2(const uint8* src_y0, const uint8* src_y1, - const uint8* src_y2, uint8* dst_sobelx, int width); -void SobelXRow_NEON(const uint8* src_y0, const uint8* src_y1, - const uint8* src_y2, uint8* dst_sobelx, int width); -void SobelYRow_C(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width); -void SobelYRow_SSE2(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width); -void SobelYRow_NEON(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width); -void SobelRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelToPlaneRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelToPlaneRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelToPlaneRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelXYRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelXYRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelXYRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_Any_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_Any_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelToPlaneRow_Any_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelToPlaneRow_Any_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelXYRow_Any_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelXYRow_Any_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); - -void ARGBPolynomialRow_C(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width); -void ARGBPolynomialRow_SSE2(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width); -void ARGBPolynomialRow_AVX2(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width); - -// Scale and convert to half float. -void HalfFloatRow_C(const uint16* src, uint16* dst, float scale, int width); -void HalfFloatRow_AVX2(const uint16* src, uint16* dst, float scale, int width); -void HalfFloatRow_Any_AVX2(const uint16* src, uint16* dst, float scale, - int width); -void HalfFloatRow_SSE2(const uint16* src, uint16* dst, float scale, int width); -void HalfFloatRow_Any_SSE2(const uint16* src, uint16* dst, float scale, - int width); - -void ARGBLumaColorTableRow_C(const uint8* src_argb, uint8* dst_argb, int width, - const uint8* luma, uint32 lumacoeff); -void ARGBLumaColorTableRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - int width, - const uint8* luma, uint32 lumacoeff); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROW_H_ diff --git a/third_party/libyuv/include/libyuv/scale.h b/third_party/libyuv/include/libyuv/scale.h deleted file mode 100644 index ae14694598..0000000000 --- a/third_party/libyuv/include/libyuv/scale.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_SCALE_H_ -#define INCLUDE_LIBYUV_SCALE_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Supported filtering. -typedef enum FilterMode { - kFilterNone = 0, // Point sample; Fastest. - kFilterLinear = 1, // Filter horizontally only. - kFilterBilinear = 2, // Faster than box, but lower quality scaling down. - kFilterBox = 3 // Highest quality. -} FilterModeEnum; - -// Scale a YUV plane. -LIBYUV_API -void ScalePlane(const uint8* src, int src_stride, - int src_width, int src_height, - uint8* dst, int dst_stride, - int dst_width, int dst_height, - enum FilterMode filtering); - -LIBYUV_API -void ScalePlane_16(const uint16* src, int src_stride, - int src_width, int src_height, - uint16* dst, int dst_stride, - int dst_width, int dst_height, - enum FilterMode filtering); - -// Scales a YUV 4:2:0 image from the src width and height to the -// dst width and height. -// If filtering is kFilterNone, a simple nearest-neighbor algorithm is -// used. This produces basic (blocky) quality at the fastest speed. -// If filtering is kFilterBilinear, interpolation is used to produce a better -// quality image, at the expense of speed. -// If filtering is kFilterBox, averaging is used to produce ever better -// quality image, at further expense of speed. -// Returns 0 if successful. - -LIBYUV_API -int I420Scale(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - int src_width, int src_height, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int dst_width, int dst_height, - enum FilterMode filtering); - -LIBYUV_API -int I420Scale_16(const uint16* src_y, int src_stride_y, - const uint16* src_u, int src_stride_u, - const uint16* src_v, int src_stride_v, - int src_width, int src_height, - uint16* dst_y, int dst_stride_y, - uint16* dst_u, int dst_stride_u, - uint16* dst_v, int dst_stride_v, - int dst_width, int dst_height, - enum FilterMode filtering); - -#ifdef __cplusplus -// Legacy API. Deprecated. -LIBYUV_API -int Scale(const uint8* src_y, const uint8* src_u, const uint8* src_v, - int src_stride_y, int src_stride_u, int src_stride_v, - int src_width, int src_height, - uint8* dst_y, uint8* dst_u, uint8* dst_v, - int dst_stride_y, int dst_stride_u, int dst_stride_v, - int dst_width, int dst_height, - LIBYUV_BOOL interpolate); - -// Legacy API. Deprecated. -LIBYUV_API -int ScaleOffset(const uint8* src_i420, int src_width, int src_height, - uint8* dst_i420, int dst_width, int dst_height, int dst_yoffset, - LIBYUV_BOOL interpolate); - -// For testing, allow disabling of specialized scalers. -LIBYUV_API -void SetUseReferenceImpl(LIBYUV_BOOL use); -#endif // __cplusplus - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_SCALE_H_ diff --git a/third_party/libyuv/include/libyuv/scale_argb.h b/third_party/libyuv/include/libyuv/scale_argb.h deleted file mode 100644 index 35cd191c0f..0000000000 --- a/third_party/libyuv/include/libyuv/scale_argb.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_SCALE_ARGB_H_ -#define INCLUDE_LIBYUV_SCALE_ARGB_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/scale.h" // For FilterMode - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -LIBYUV_API -int ARGBScale(const uint8* src_argb, int src_stride_argb, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - int dst_width, int dst_height, - enum FilterMode filtering); - -// Clipped scale takes destination rectangle coordinates for clip values. -LIBYUV_API -int ARGBScaleClip(const uint8* src_argb, int src_stride_argb, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - int dst_width, int dst_height, - int clip_x, int clip_y, int clip_width, int clip_height, - enum FilterMode filtering); - -// Scale with YUV conversion to ARGB and clipping. -LIBYUV_API -int YUVToARGBScaleClip(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint32 src_fourcc, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - uint32 dst_fourcc, - int dst_width, int dst_height, - int clip_x, int clip_y, int clip_width, int clip_height, - enum FilterMode filtering); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_SCALE_ARGB_H_ diff --git a/third_party/libyuv/include/libyuv/scale_row.h b/third_party/libyuv/include/libyuv/scale_row.h deleted file mode 100644 index 791fbf7d05..0000000000 --- a/third_party/libyuv/include/libyuv/scale_row.h +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_SCALE_ROW_H_ -#define INCLUDE_LIBYUV_SCALE_ROW_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/scale.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif - -// GCC >= 4.7.0 required for AVX2. -#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) -#if (__GNUC__ > 4) || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 7)) -#define GCC_HAS_AVX2 1 -#endif // GNUC >= 4.7 -#endif // __GNUC__ - -// clang >= 3.4.0 required for AVX2. -#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) -#if (__clang_major__ > 3) || (__clang_major__ == 3 && (__clang_minor__ >= 4)) -#define CLANG_HAS_AVX2 1 -#endif // clang >= 3.4 -#endif // __clang__ - -// Visual C 2012 required for AVX2. -#if defined(_M_IX86) && !defined(__clang__) && \ - defined(_MSC_VER) && _MSC_VER >= 1700 -#define VISUALC_HAS_AVX2 1 -#endif // VisualStudio >= 2012 - -// The following are available on all x86 platforms: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)) -#define HAS_FIXEDDIV1_X86 -#define HAS_FIXEDDIV_X86 -#define HAS_SCALEARGBCOLS_SSE2 -#define HAS_SCALEARGBCOLSUP2_SSE2 -#define HAS_SCALEARGBFILTERCOLS_SSSE3 -#define HAS_SCALEARGBROWDOWN2_SSE2 -#define HAS_SCALEARGBROWDOWNEVEN_SSE2 -#define HAS_SCALECOLSUP2_SSE2 -#define HAS_SCALEFILTERCOLS_SSSE3 -#define HAS_SCALEROWDOWN2_SSSE3 -#define HAS_SCALEROWDOWN34_SSSE3 -#define HAS_SCALEROWDOWN38_SSSE3 -#define HAS_SCALEROWDOWN4_SSSE3 -#define HAS_SCALEADDROW_SSE2 -#endif - -// The following are available on all x86 platforms, but -// require VS2012, clang 3.4 or gcc 4.7. -// The code supports NaCL but requires a new compiler and validator. -#if !defined(LIBYUV_DISABLE_X86) && (defined(VISUALC_HAS_AVX2) || \ - defined(CLANG_HAS_AVX2) || defined(GCC_HAS_AVX2)) -#define HAS_SCALEADDROW_AVX2 -#define HAS_SCALEROWDOWN2_AVX2 -#define HAS_SCALEROWDOWN4_AVX2 -#endif - -// The following are available on Neon platforms: -#if !defined(LIBYUV_DISABLE_NEON) && !defined(__native_client__) && \ - (defined(__ARM_NEON__) || defined(LIBYUV_NEON) || defined(__aarch64__)) -#define HAS_SCALEARGBCOLS_NEON -#define HAS_SCALEARGBROWDOWN2_NEON -#define HAS_SCALEARGBROWDOWNEVEN_NEON -#define HAS_SCALEFILTERCOLS_NEON -#define HAS_SCALEROWDOWN2_NEON -#define HAS_SCALEROWDOWN34_NEON -#define HAS_SCALEROWDOWN38_NEON -#define HAS_SCALEROWDOWN4_NEON -#define HAS_SCALEARGBFILTERCOLS_NEON -#endif - -// The following are available on Mips platforms: -#if !defined(LIBYUV_DISABLE_MIPS) && !defined(__native_client__) && \ - defined(__mips__) && defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_SCALEROWDOWN2_DSPR2 -#define HAS_SCALEROWDOWN4_DSPR2 -#define HAS_SCALEROWDOWN34_DSPR2 -#define HAS_SCALEROWDOWN38_DSPR2 -#endif - -// Scale ARGB vertically with bilinear interpolation. -void ScalePlaneVertical(int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_argb, uint8* dst_argb, - int x, int y, int dy, - int bpp, enum FilterMode filtering); - -void ScalePlaneVertical_16(int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_argb, uint16* dst_argb, - int x, int y, int dy, - int wpp, enum FilterMode filtering); - -// Simplify the filtering based on scale factors. -enum FilterMode ScaleFilterReduce(int src_width, int src_height, - int dst_width, int dst_height, - enum FilterMode filtering); - -// Divide num by div and return as 16.16 fixed point result. -int FixedDiv_C(int num, int div); -int FixedDiv_X86(int num, int div); -// Divide num - 1 by div - 1 and return as 16.16 fixed point result. -int FixedDiv1_C(int num, int div); -int FixedDiv1_X86(int num, int div); -#ifdef HAS_FIXEDDIV_X86 -#define FixedDiv FixedDiv_X86 -#define FixedDiv1 FixedDiv1_X86 -#else -#define FixedDiv FixedDiv_C -#define FixedDiv1 FixedDiv1_C -#endif - -// Compute slope values for stepping. -void ScaleSlope(int src_width, int src_height, - int dst_width, int dst_height, - enum FilterMode filtering, - int* x, int* y, int* dx, int* dy); - -void ScaleRowDown2_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown2Linear_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Linear_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown2Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_Odd_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown4_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown4Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown34_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown34_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown34_0_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown34_0_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* d, int dst_width); -void ScaleRowDown34_1_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown34_1_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* d, int dst_width); -void ScaleCols_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleCols_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx); -void ScaleColsUp2_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int, int); -void ScaleColsUp2_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int, int); -void ScaleFilterCols_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleFilterCols_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx); -void ScaleFilterCols64_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleFilterCols64_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx); -void ScaleRowDown38_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown38_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown38_3_Box_C(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_16_C(const uint16* src_ptr, - ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width); -void ScaleAddRow_C(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_16_C(const uint16* src_ptr, uint32* dst_ptr, int src_width); -void ScaleARGBRowDown2_C(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Linear_C(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_C(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_C(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_C(const uint8* src_argb, - ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBCols_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBCols64_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBColsUp2_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int, int); -void ScaleARGBFilterCols_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols64_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); - -// Specialized scalers for x86. -void ScaleRowDown2_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleRowDown34_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Odd_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Odd_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleRowDown34_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleAddRow_SSE2(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_AVX2(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_Any_SSE2(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_Any_AVX2(const uint8* src_ptr, uint16* dst_ptr, int src_width); - -void ScaleFilterCols_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleColsUp2_SSE2(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); - - -// ARGB Column functions -void ScaleARGBCols_SSE2(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols_SSSE3(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBColsUp2_SSE2(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBCols_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols_Any_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBCols_Any_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); - -// ARGB Row functions -void ScaleARGBRowDown2_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Linear_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleARGBRowDown2Linear_NEON(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleARGBRowDown2_Any_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Linear_Any_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_Any_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleARGBRowDown2Linear_Any_NEON(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); - -void ScaleARGBRowDownEven_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_Any_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_Any_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_Any_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_Any_NEON(const uint8* src_argb, - ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); - -// ScaleRowDown2Box also used by planar functions -// NEON downscalers with interpolation. - -// Note - not static due to reuse in convert for 444 to 420. -void ScaleRowDown2_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Linear_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); - -void ScaleRowDown4_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -// Down scale from 4 to 3 pixels. Use the neon multilane read/write -// to load up the every 4th pixel into a 4 different registers. -// Point samples 32 pixels to 24 pixels. -void ScaleRowDown34_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -// 32 -> 12 -void ScaleRowDown38_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x3 -> 12x1 -void ScaleRowDown38_3_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x2 -> 12x1 -void ScaleRowDown38_2_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleRowDown2_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Linear_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_Odd_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32 -> 12 -void ScaleRowDown38_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x3 -> 12x1 -void ScaleRowDown38_3_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x2 -> 12x1 -void ScaleRowDown38_2_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleAddRow_NEON(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_Any_NEON(const uint8* src_ptr, uint16* dst_ptr, int src_width); - -void ScaleFilterCols_NEON(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); - -void ScaleFilterCols_Any_NEON(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); - -void ScaleRowDown2_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown34_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown34_0_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown34_1_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown38_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown38_2_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_SCALE_ROW_H_ diff --git a/third_party/libyuv/include/libyuv/version.h b/third_party/libyuv/include/libyuv/version.h deleted file mode 100644 index 3a8f6337ca..0000000000 --- a/third_party/libyuv/include/libyuv/version.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_VERSION_H_ -#define INCLUDE_LIBYUV_VERSION_H_ - -#define LIBYUV_VERSION 1622 - -#endif // INCLUDE_LIBYUV_VERSION_H_ diff --git a/third_party/libyuv/include/libyuv/video_common.h b/third_party/libyuv/include/libyuv/video_common.h deleted file mode 100644 index cb425426a2..0000000000 --- a/third_party/libyuv/include/libyuv/video_common.h +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -// Common definitions for video, including fourcc and VideoFormat. - -#ifndef INCLUDE_LIBYUV_VIDEO_COMMON_H_ -#define INCLUDE_LIBYUV_VIDEO_COMMON_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -////////////////////////////////////////////////////////////////////////////// -// Definition of FourCC codes -////////////////////////////////////////////////////////////////////////////// - -// Convert four characters to a FourCC code. -// Needs to be a macro otherwise the OS X compiler complains when the kFormat* -// constants are used in a switch. -#ifdef __cplusplus -#define FOURCC(a, b, c, d) ( \ - (static_cast(a)) | (static_cast(b) << 8) | \ - (static_cast(c) << 16) | (static_cast(d) << 24)) -#else -#define FOURCC(a, b, c, d) ( \ - ((uint32)(a)) | ((uint32)(b) << 8) | /* NOLINT */ \ - ((uint32)(c) << 16) | ((uint32)(d) << 24)) /* NOLINT */ -#endif - -// Some pages discussing FourCC codes: -// http://www.fourcc.org/yuv.php -// http://v4l2spec.bytesex.org/spec/book1.htm -// http://developer.apple.com/quicktime/icefloe/dispatch020.html -// http://msdn.microsoft.com/library/windows/desktop/dd206750.aspx#nv12 -// http://people.xiph.org/~xiphmont/containers/nut/nut4cc.txt - -// FourCC codes grouped according to implementation efficiency. -// Primary formats should convert in 1 efficient step. -// Secondary formats are converted in 2 steps. -// Auxilliary formats call primary converters. -enum FourCC { - // 9 Primary YUV formats: 5 planar, 2 biplanar, 2 packed. - FOURCC_I420 = FOURCC('I', '4', '2', '0'), - FOURCC_I422 = FOURCC('I', '4', '2', '2'), - FOURCC_I444 = FOURCC('I', '4', '4', '4'), - FOURCC_I411 = FOURCC('I', '4', '1', '1'), - FOURCC_I400 = FOURCC('I', '4', '0', '0'), - FOURCC_NV21 = FOURCC('N', 'V', '2', '1'), - FOURCC_NV12 = FOURCC('N', 'V', '1', '2'), - FOURCC_YUY2 = FOURCC('Y', 'U', 'Y', '2'), - FOURCC_UYVY = FOURCC('U', 'Y', 'V', 'Y'), - - // 2 Secondary YUV formats: row biplanar. - FOURCC_M420 = FOURCC('M', '4', '2', '0'), - FOURCC_Q420 = FOURCC('Q', '4', '2', '0'), // deprecated. - - // 9 Primary RGB formats: 4 32 bpp, 2 24 bpp, 3 16 bpp. - FOURCC_ARGB = FOURCC('A', 'R', 'G', 'B'), - FOURCC_BGRA = FOURCC('B', 'G', 'R', 'A'), - FOURCC_ABGR = FOURCC('A', 'B', 'G', 'R'), - FOURCC_24BG = FOURCC('2', '4', 'B', 'G'), - FOURCC_RAW = FOURCC('r', 'a', 'w', ' '), - FOURCC_RGBA = FOURCC('R', 'G', 'B', 'A'), - FOURCC_RGBP = FOURCC('R', 'G', 'B', 'P'), // rgb565 LE. - FOURCC_RGBO = FOURCC('R', 'G', 'B', 'O'), // argb1555 LE. - FOURCC_R444 = FOURCC('R', '4', '4', '4'), // argb4444 LE. - - // 4 Secondary RGB formats: 4 Bayer Patterns. deprecated. - FOURCC_RGGB = FOURCC('R', 'G', 'G', 'B'), - FOURCC_BGGR = FOURCC('B', 'G', 'G', 'R'), - FOURCC_GRBG = FOURCC('G', 'R', 'B', 'G'), - FOURCC_GBRG = FOURCC('G', 'B', 'R', 'G'), - - // 1 Primary Compressed YUV format. - FOURCC_MJPG = FOURCC('M', 'J', 'P', 'G'), - - // 5 Auxiliary YUV variations: 3 with U and V planes are swapped, 1 Alias. - FOURCC_YV12 = FOURCC('Y', 'V', '1', '2'), - FOURCC_YV16 = FOURCC('Y', 'V', '1', '6'), - FOURCC_YV24 = FOURCC('Y', 'V', '2', '4'), - FOURCC_YU12 = FOURCC('Y', 'U', '1', '2'), // Linux version of I420. - FOURCC_J420 = FOURCC('J', '4', '2', '0'), - FOURCC_J400 = FOURCC('J', '4', '0', '0'), // unofficial fourcc - FOURCC_H420 = FOURCC('H', '4', '2', '0'), // unofficial fourcc - - // 14 Auxiliary aliases. CanonicalFourCC() maps these to canonical fourcc. - FOURCC_IYUV = FOURCC('I', 'Y', 'U', 'V'), // Alias for I420. - FOURCC_YU16 = FOURCC('Y', 'U', '1', '6'), // Alias for I422. - FOURCC_YU24 = FOURCC('Y', 'U', '2', '4'), // Alias for I444. - FOURCC_YUYV = FOURCC('Y', 'U', 'Y', 'V'), // Alias for YUY2. - FOURCC_YUVS = FOURCC('y', 'u', 'v', 's'), // Alias for YUY2 on Mac. - FOURCC_HDYC = FOURCC('H', 'D', 'Y', 'C'), // Alias for UYVY. - FOURCC_2VUY = FOURCC('2', 'v', 'u', 'y'), // Alias for UYVY on Mac. - FOURCC_JPEG = FOURCC('J', 'P', 'E', 'G'), // Alias for MJPG. - FOURCC_DMB1 = FOURCC('d', 'm', 'b', '1'), // Alias for MJPG on Mac. - FOURCC_BA81 = FOURCC('B', 'A', '8', '1'), // Alias for BGGR. - FOURCC_RGB3 = FOURCC('R', 'G', 'B', '3'), // Alias for RAW. - FOURCC_BGR3 = FOURCC('B', 'G', 'R', '3'), // Alias for 24BG. - FOURCC_CM32 = FOURCC(0, 0, 0, 32), // Alias for BGRA kCMPixelFormat_32ARGB - FOURCC_CM24 = FOURCC(0, 0, 0, 24), // Alias for RAW kCMPixelFormat_24RGB - FOURCC_L555 = FOURCC('L', '5', '5', '5'), // Alias for RGBO. - FOURCC_L565 = FOURCC('L', '5', '6', '5'), // Alias for RGBP. - FOURCC_5551 = FOURCC('5', '5', '5', '1'), // Alias for RGBO. - - // 1 Auxiliary compressed YUV format set aside for capturer. - FOURCC_H264 = FOURCC('H', '2', '6', '4'), - - // Match any fourcc. - FOURCC_ANY = -1, -}; - -enum FourCCBpp { - // Canonical fourcc codes used in our code. - FOURCC_BPP_I420 = 12, - FOURCC_BPP_I422 = 16, - FOURCC_BPP_I444 = 24, - FOURCC_BPP_I411 = 12, - FOURCC_BPP_I400 = 8, - FOURCC_BPP_NV21 = 12, - FOURCC_BPP_NV12 = 12, - FOURCC_BPP_YUY2 = 16, - FOURCC_BPP_UYVY = 16, - FOURCC_BPP_M420 = 12, - FOURCC_BPP_Q420 = 12, - FOURCC_BPP_ARGB = 32, - FOURCC_BPP_BGRA = 32, - FOURCC_BPP_ABGR = 32, - FOURCC_BPP_RGBA = 32, - FOURCC_BPP_24BG = 24, - FOURCC_BPP_RAW = 24, - FOURCC_BPP_RGBP = 16, - FOURCC_BPP_RGBO = 16, - FOURCC_BPP_R444 = 16, - FOURCC_BPP_RGGB = 8, - FOURCC_BPP_BGGR = 8, - FOURCC_BPP_GRBG = 8, - FOURCC_BPP_GBRG = 8, - FOURCC_BPP_YV12 = 12, - FOURCC_BPP_YV16 = 16, - FOURCC_BPP_YV24 = 24, - FOURCC_BPP_YU12 = 12, - FOURCC_BPP_J420 = 12, - FOURCC_BPP_J400 = 8, - FOURCC_BPP_H420 = 12, - FOURCC_BPP_MJPG = 0, // 0 means unknown. - FOURCC_BPP_H264 = 0, - FOURCC_BPP_IYUV = 12, - FOURCC_BPP_YU16 = 16, - FOURCC_BPP_YU24 = 24, - FOURCC_BPP_YUYV = 16, - FOURCC_BPP_YUVS = 16, - FOURCC_BPP_HDYC = 16, - FOURCC_BPP_2VUY = 16, - FOURCC_BPP_JPEG = 1, - FOURCC_BPP_DMB1 = 1, - FOURCC_BPP_BA81 = 8, - FOURCC_BPP_RGB3 = 24, - FOURCC_BPP_BGR3 = 24, - FOURCC_BPP_CM32 = 32, - FOURCC_BPP_CM24 = 24, - - // Match any fourcc. - FOURCC_BPP_ANY = 0, // 0 means unknown. -}; - -// Converts fourcc aliases into canonical ones. -LIBYUV_API uint32 CanonicalFourCC(uint32 fourcc); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_VIDEO_COMMON_H_ diff --git a/third_party/libyuv/larch64/lib/libyuv.a b/third_party/libyuv/larch64/lib/libyuv.a deleted file mode 100644 index 9c4a32bcdb..0000000000 --- a/third_party/libyuv/larch64/lib/libyuv.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:adafce26582e425164df7af36253ce58e3ed1dba9965650745c93bd96e42e976 -size 462482 diff --git a/third_party/libyuv/x86_64/lib/libyuv.a b/third_party/libyuv/x86_64/lib/libyuv.a deleted file mode 100644 index 391b1c8769..0000000000 --- a/third_party/libyuv/x86_64/lib/libyuv.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00f9759c67c6fa21657fabde9e096478ea5809716989599f673f638f039431e5 -size 504790 diff --git a/third_party/opencl/include/CL/cl.h b/third_party/opencl/include/CL/cl.h deleted file mode 100644 index 0086319f5f..0000000000 --- a/third_party/opencl/include/CL/cl.h +++ /dev/null @@ -1,1452 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are 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 Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE 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 - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_H -#define __OPENCL_CL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ - -typedef struct _cl_platform_id * cl_platform_id; -typedef struct _cl_device_id * cl_device_id; -typedef struct _cl_context * cl_context; -typedef struct _cl_command_queue * cl_command_queue; -typedef struct _cl_mem * cl_mem; -typedef struct _cl_program * cl_program; -typedef struct _cl_kernel * cl_kernel; -typedef struct _cl_event * cl_event; -typedef struct _cl_sampler * cl_sampler; - -typedef cl_uint cl_bool; /* WARNING! Unlike cl_ types in cl_platform.h, cl_bool is not guaranteed to be the same size as the bool in kernels. */ -typedef cl_ulong cl_bitfield; -typedef cl_bitfield cl_device_type; -typedef cl_uint cl_platform_info; -typedef cl_uint cl_device_info; -typedef cl_bitfield cl_device_fp_config; -typedef cl_uint cl_device_mem_cache_type; -typedef cl_uint cl_device_local_mem_type; -typedef cl_bitfield cl_device_exec_capabilities; -typedef cl_bitfield cl_device_svm_capabilities; -typedef cl_bitfield cl_command_queue_properties; -typedef intptr_t cl_device_partition_property; -typedef cl_bitfield cl_device_affinity_domain; - -typedef intptr_t cl_context_properties; -typedef cl_uint cl_context_info; -typedef cl_bitfield cl_queue_properties; -typedef cl_uint cl_command_queue_info; -typedef cl_uint cl_channel_order; -typedef cl_uint cl_channel_type; -typedef cl_bitfield cl_mem_flags; -typedef cl_bitfield cl_svm_mem_flags; -typedef cl_uint cl_mem_object_type; -typedef cl_uint cl_mem_info; -typedef cl_bitfield cl_mem_migration_flags; -typedef cl_uint cl_image_info; -typedef cl_uint cl_buffer_create_type; -typedef cl_uint cl_addressing_mode; -typedef cl_uint cl_filter_mode; -typedef cl_uint cl_sampler_info; -typedef cl_bitfield cl_map_flags; -typedef intptr_t cl_pipe_properties; -typedef cl_uint cl_pipe_info; -typedef cl_uint cl_program_info; -typedef cl_uint cl_program_build_info; -typedef cl_uint cl_program_binary_type; -typedef cl_int cl_build_status; -typedef cl_uint cl_kernel_info; -typedef cl_uint cl_kernel_arg_info; -typedef cl_uint cl_kernel_arg_address_qualifier; -typedef cl_uint cl_kernel_arg_access_qualifier; -typedef cl_bitfield cl_kernel_arg_type_qualifier; -typedef cl_uint cl_kernel_work_group_info; -typedef cl_uint cl_kernel_sub_group_info; -typedef cl_uint cl_event_info; -typedef cl_uint cl_command_type; -typedef cl_uint cl_profiling_info; -typedef cl_bitfield cl_sampler_properties; -typedef cl_uint cl_kernel_exec_info; - -typedef struct _cl_image_format { - cl_channel_order image_channel_order; - cl_channel_type image_channel_data_type; -} cl_image_format; - -typedef struct _cl_image_desc { - cl_mem_object_type image_type; - size_t image_width; - size_t image_height; - size_t image_depth; - size_t image_array_size; - size_t image_row_pitch; - size_t image_slice_pitch; - cl_uint num_mip_levels; - cl_uint num_samples; -#ifdef __GNUC__ - __extension__ /* Prevents warnings about anonymous union in -pedantic builds */ -#endif - union { - cl_mem buffer; - cl_mem mem_object; - }; -} cl_image_desc; - -typedef struct _cl_buffer_region { - size_t origin; - size_t size; -} cl_buffer_region; - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_SUCCESS 0 -#define CL_DEVICE_NOT_FOUND -1 -#define CL_DEVICE_NOT_AVAILABLE -2 -#define CL_COMPILER_NOT_AVAILABLE -3 -#define CL_MEM_OBJECT_ALLOCATION_FAILURE -4 -#define CL_OUT_OF_RESOURCES -5 -#define CL_OUT_OF_HOST_MEMORY -6 -#define CL_PROFILING_INFO_NOT_AVAILABLE -7 -#define CL_MEM_COPY_OVERLAP -8 -#define CL_IMAGE_FORMAT_MISMATCH -9 -#define CL_IMAGE_FORMAT_NOT_SUPPORTED -10 -#define CL_BUILD_PROGRAM_FAILURE -11 -#define CL_MAP_FAILURE -12 -#define CL_MISALIGNED_SUB_BUFFER_OFFSET -13 -#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14 -#define CL_COMPILE_PROGRAM_FAILURE -15 -#define CL_LINKER_NOT_AVAILABLE -16 -#define CL_LINK_PROGRAM_FAILURE -17 -#define CL_DEVICE_PARTITION_FAILED -18 -#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE -19 - -#define CL_INVALID_VALUE -30 -#define CL_INVALID_DEVICE_TYPE -31 -#define CL_INVALID_PLATFORM -32 -#define CL_INVALID_DEVICE -33 -#define CL_INVALID_CONTEXT -34 -#define CL_INVALID_QUEUE_PROPERTIES -35 -#define CL_INVALID_COMMAND_QUEUE -36 -#define CL_INVALID_HOST_PTR -37 -#define CL_INVALID_MEM_OBJECT -38 -#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR -39 -#define CL_INVALID_IMAGE_SIZE -40 -#define CL_INVALID_SAMPLER -41 -#define CL_INVALID_BINARY -42 -#define CL_INVALID_BUILD_OPTIONS -43 -#define CL_INVALID_PROGRAM -44 -#define CL_INVALID_PROGRAM_EXECUTABLE -45 -#define CL_INVALID_KERNEL_NAME -46 -#define CL_INVALID_KERNEL_DEFINITION -47 -#define CL_INVALID_KERNEL -48 -#define CL_INVALID_ARG_INDEX -49 -#define CL_INVALID_ARG_VALUE -50 -#define CL_INVALID_ARG_SIZE -51 -#define CL_INVALID_KERNEL_ARGS -52 -#define CL_INVALID_WORK_DIMENSION -53 -#define CL_INVALID_WORK_GROUP_SIZE -54 -#define CL_INVALID_WORK_ITEM_SIZE -55 -#define CL_INVALID_GLOBAL_OFFSET -56 -#define CL_INVALID_EVENT_WAIT_LIST -57 -#define CL_INVALID_EVENT -58 -#define CL_INVALID_OPERATION -59 -#define CL_INVALID_GL_OBJECT -60 -#define CL_INVALID_BUFFER_SIZE -61 -#define CL_INVALID_MIP_LEVEL -62 -#define CL_INVALID_GLOBAL_WORK_SIZE -63 -#define CL_INVALID_PROPERTY -64 -#define CL_INVALID_IMAGE_DESCRIPTOR -65 -#define CL_INVALID_COMPILER_OPTIONS -66 -#define CL_INVALID_LINKER_OPTIONS -67 -#define CL_INVALID_DEVICE_PARTITION_COUNT -68 -#define CL_INVALID_PIPE_SIZE -69 -#define CL_INVALID_DEVICE_QUEUE -70 - -/* OpenCL Version */ -#define CL_VERSION_1_0 1 -#define CL_VERSION_1_1 1 -#define CL_VERSION_1_2 1 -#define CL_VERSION_2_0 1 -#define CL_VERSION_2_1 1 - -/* cl_bool */ -#define CL_FALSE 0 -#define CL_TRUE 1 -#define CL_BLOCKING CL_TRUE -#define CL_NON_BLOCKING CL_FALSE - -/* cl_platform_info */ -#define CL_PLATFORM_PROFILE 0x0900 -#define CL_PLATFORM_VERSION 0x0901 -#define CL_PLATFORM_NAME 0x0902 -#define CL_PLATFORM_VENDOR 0x0903 -#define CL_PLATFORM_EXTENSIONS 0x0904 -#define CL_PLATFORM_HOST_TIMER_RESOLUTION 0x0905 - -/* cl_device_type - bitfield */ -#define CL_DEVICE_TYPE_DEFAULT (1 << 0) -#define CL_DEVICE_TYPE_CPU (1 << 1) -#define CL_DEVICE_TYPE_GPU (1 << 2) -#define CL_DEVICE_TYPE_ACCELERATOR (1 << 3) -#define CL_DEVICE_TYPE_CUSTOM (1 << 4) -#define CL_DEVICE_TYPE_ALL 0xFFFFFFFF - -/* cl_device_info */ -#define CL_DEVICE_TYPE 0x1000 -#define CL_DEVICE_VENDOR_ID 0x1001 -#define CL_DEVICE_MAX_COMPUTE_UNITS 0x1002 -#define CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS 0x1003 -#define CL_DEVICE_MAX_WORK_GROUP_SIZE 0x1004 -#define CL_DEVICE_MAX_WORK_ITEM_SIZES 0x1005 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR 0x1006 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT 0x1007 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT 0x1008 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG 0x1009 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT 0x100A -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE 0x100B -#define CL_DEVICE_MAX_CLOCK_FREQUENCY 0x100C -#define CL_DEVICE_ADDRESS_BITS 0x100D -#define CL_DEVICE_MAX_READ_IMAGE_ARGS 0x100E -#define CL_DEVICE_MAX_WRITE_IMAGE_ARGS 0x100F -#define CL_DEVICE_MAX_MEM_ALLOC_SIZE 0x1010 -#define CL_DEVICE_IMAGE2D_MAX_WIDTH 0x1011 -#define CL_DEVICE_IMAGE2D_MAX_HEIGHT 0x1012 -#define CL_DEVICE_IMAGE3D_MAX_WIDTH 0x1013 -#define CL_DEVICE_IMAGE3D_MAX_HEIGHT 0x1014 -#define CL_DEVICE_IMAGE3D_MAX_DEPTH 0x1015 -#define CL_DEVICE_IMAGE_SUPPORT 0x1016 -#define CL_DEVICE_MAX_PARAMETER_SIZE 0x1017 -#define CL_DEVICE_MAX_SAMPLERS 0x1018 -#define CL_DEVICE_MEM_BASE_ADDR_ALIGN 0x1019 -#define CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE 0x101A -#define CL_DEVICE_SINGLE_FP_CONFIG 0x101B -#define CL_DEVICE_GLOBAL_MEM_CACHE_TYPE 0x101C -#define CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE 0x101D -#define CL_DEVICE_GLOBAL_MEM_CACHE_SIZE 0x101E -#define CL_DEVICE_GLOBAL_MEM_SIZE 0x101F -#define CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE 0x1020 -#define CL_DEVICE_MAX_CONSTANT_ARGS 0x1021 -#define CL_DEVICE_LOCAL_MEM_TYPE 0x1022 -#define CL_DEVICE_LOCAL_MEM_SIZE 0x1023 -#define CL_DEVICE_ERROR_CORRECTION_SUPPORT 0x1024 -#define CL_DEVICE_PROFILING_TIMER_RESOLUTION 0x1025 -#define CL_DEVICE_ENDIAN_LITTLE 0x1026 -#define CL_DEVICE_AVAILABLE 0x1027 -#define CL_DEVICE_COMPILER_AVAILABLE 0x1028 -#define CL_DEVICE_EXECUTION_CAPABILITIES 0x1029 -#define CL_DEVICE_QUEUE_PROPERTIES 0x102A /* deprecated */ -#define CL_DEVICE_QUEUE_ON_HOST_PROPERTIES 0x102A -#define CL_DEVICE_NAME 0x102B -#define CL_DEVICE_VENDOR 0x102C -#define CL_DRIVER_VERSION 0x102D -#define CL_DEVICE_PROFILE 0x102E -#define CL_DEVICE_VERSION 0x102F -#define CL_DEVICE_EXTENSIONS 0x1030 -#define CL_DEVICE_PLATFORM 0x1031 -#define CL_DEVICE_DOUBLE_FP_CONFIG 0x1032 -/* 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG */ -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF 0x1034 -#define CL_DEVICE_HOST_UNIFIED_MEMORY 0x1035 /* deprecated */ -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR 0x1036 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT 0x1037 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_INT 0x1038 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG 0x1039 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT 0x103A -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE 0x103B -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF 0x103C -#define CL_DEVICE_OPENCL_C_VERSION 0x103D -#define CL_DEVICE_LINKER_AVAILABLE 0x103E -#define CL_DEVICE_BUILT_IN_KERNELS 0x103F -#define CL_DEVICE_IMAGE_MAX_BUFFER_SIZE 0x1040 -#define CL_DEVICE_IMAGE_MAX_ARRAY_SIZE 0x1041 -#define CL_DEVICE_PARENT_DEVICE 0x1042 -#define CL_DEVICE_PARTITION_MAX_SUB_DEVICES 0x1043 -#define CL_DEVICE_PARTITION_PROPERTIES 0x1044 -#define CL_DEVICE_PARTITION_AFFINITY_DOMAIN 0x1045 -#define CL_DEVICE_PARTITION_TYPE 0x1046 -#define CL_DEVICE_REFERENCE_COUNT 0x1047 -#define CL_DEVICE_PREFERRED_INTEROP_USER_SYNC 0x1048 -#define CL_DEVICE_PRINTF_BUFFER_SIZE 0x1049 -#define CL_DEVICE_IMAGE_PITCH_ALIGNMENT 0x104A -#define CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT 0x104B -#define CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS 0x104C -#define CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE 0x104D -#define CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES 0x104E -#define CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE 0x104F -#define CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE 0x1050 -#define CL_DEVICE_MAX_ON_DEVICE_QUEUES 0x1051 -#define CL_DEVICE_MAX_ON_DEVICE_EVENTS 0x1052 -#define CL_DEVICE_SVM_CAPABILITIES 0x1053 -#define CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE 0x1054 -#define CL_DEVICE_MAX_PIPE_ARGS 0x1055 -#define CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS 0x1056 -#define CL_DEVICE_PIPE_MAX_PACKET_SIZE 0x1057 -#define CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT 0x1058 -#define CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT 0x1059 -#define CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT 0x105A -#define CL_DEVICE_IL_VERSION 0x105B -#define CL_DEVICE_MAX_NUM_SUB_GROUPS 0x105C -#define CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS 0x105D - -/* cl_device_fp_config - bitfield */ -#define CL_FP_DENORM (1 << 0) -#define CL_FP_INF_NAN (1 << 1) -#define CL_FP_ROUND_TO_NEAREST (1 << 2) -#define CL_FP_ROUND_TO_ZERO (1 << 3) -#define CL_FP_ROUND_TO_INF (1 << 4) -#define CL_FP_FMA (1 << 5) -#define CL_FP_SOFT_FLOAT (1 << 6) -#define CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT (1 << 7) - -/* cl_device_mem_cache_type */ -#define CL_NONE 0x0 -#define CL_READ_ONLY_CACHE 0x1 -#define CL_READ_WRITE_CACHE 0x2 - -/* cl_device_local_mem_type */ -#define CL_LOCAL 0x1 -#define CL_GLOBAL 0x2 - -/* cl_device_exec_capabilities - bitfield */ -#define CL_EXEC_KERNEL (1 << 0) -#define CL_EXEC_NATIVE_KERNEL (1 << 1) - -/* cl_command_queue_properties - bitfield */ -#define CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE (1 << 0) -#define CL_QUEUE_PROFILING_ENABLE (1 << 1) -#define CL_QUEUE_ON_DEVICE (1 << 2) -#define CL_QUEUE_ON_DEVICE_DEFAULT (1 << 3) - -/* cl_context_info */ -#define CL_CONTEXT_REFERENCE_COUNT 0x1080 -#define CL_CONTEXT_DEVICES 0x1081 -#define CL_CONTEXT_PROPERTIES 0x1082 -#define CL_CONTEXT_NUM_DEVICES 0x1083 - -/* cl_context_properties */ -#define CL_CONTEXT_PLATFORM 0x1084 -#define CL_CONTEXT_INTEROP_USER_SYNC 0x1085 - -/* cl_device_partition_property */ -#define CL_DEVICE_PARTITION_EQUALLY 0x1086 -#define CL_DEVICE_PARTITION_BY_COUNTS 0x1087 -#define CL_DEVICE_PARTITION_BY_COUNTS_LIST_END 0x0 -#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN 0x1088 - -/* cl_device_affinity_domain */ -#define CL_DEVICE_AFFINITY_DOMAIN_NUMA (1 << 0) -#define CL_DEVICE_AFFINITY_DOMAIN_L4_CACHE (1 << 1) -#define CL_DEVICE_AFFINITY_DOMAIN_L3_CACHE (1 << 2) -#define CL_DEVICE_AFFINITY_DOMAIN_L2_CACHE (1 << 3) -#define CL_DEVICE_AFFINITY_DOMAIN_L1_CACHE (1 << 4) -#define CL_DEVICE_AFFINITY_DOMAIN_NEXT_PARTITIONABLE (1 << 5) - -/* cl_device_svm_capabilities */ -#define CL_DEVICE_SVM_COARSE_GRAIN_BUFFER (1 << 0) -#define CL_DEVICE_SVM_FINE_GRAIN_BUFFER (1 << 1) -#define CL_DEVICE_SVM_FINE_GRAIN_SYSTEM (1 << 2) -#define CL_DEVICE_SVM_ATOMICS (1 << 3) - -/* cl_command_queue_info */ -#define CL_QUEUE_CONTEXT 0x1090 -#define CL_QUEUE_DEVICE 0x1091 -#define CL_QUEUE_REFERENCE_COUNT 0x1092 -#define CL_QUEUE_PROPERTIES 0x1093 -#define CL_QUEUE_SIZE 0x1094 -#define CL_QUEUE_DEVICE_DEFAULT 0x1095 - -/* cl_mem_flags and cl_svm_mem_flags - bitfield */ -#define CL_MEM_READ_WRITE (1 << 0) -#define CL_MEM_WRITE_ONLY (1 << 1) -#define CL_MEM_READ_ONLY (1 << 2) -#define CL_MEM_USE_HOST_PTR (1 << 3) -#define CL_MEM_ALLOC_HOST_PTR (1 << 4) -#define CL_MEM_COPY_HOST_PTR (1 << 5) -/* reserved (1 << 6) */ -#define CL_MEM_HOST_WRITE_ONLY (1 << 7) -#define CL_MEM_HOST_READ_ONLY (1 << 8) -#define CL_MEM_HOST_NO_ACCESS (1 << 9) -#define CL_MEM_SVM_FINE_GRAIN_BUFFER (1 << 10) /* used by cl_svm_mem_flags only */ -#define CL_MEM_SVM_ATOMICS (1 << 11) /* used by cl_svm_mem_flags only */ -#define CL_MEM_KERNEL_READ_AND_WRITE (1 << 12) - -/* cl_mem_migration_flags - bitfield */ -#define CL_MIGRATE_MEM_OBJECT_HOST (1 << 0) -#define CL_MIGRATE_MEM_OBJECT_CONTENT_UNDEFINED (1 << 1) - -/* cl_channel_order */ -#define CL_R 0x10B0 -#define CL_A 0x10B1 -#define CL_RG 0x10B2 -#define CL_RA 0x10B3 -#define CL_RGB 0x10B4 -#define CL_RGBA 0x10B5 -#define CL_BGRA 0x10B6 -#define CL_ARGB 0x10B7 -#define CL_INTENSITY 0x10B8 -#define CL_LUMINANCE 0x10B9 -#define CL_Rx 0x10BA -#define CL_RGx 0x10BB -#define CL_RGBx 0x10BC -#define CL_DEPTH 0x10BD -#define CL_DEPTH_STENCIL 0x10BE -#define CL_sRGB 0x10BF -#define CL_sRGBx 0x10C0 -#define CL_sRGBA 0x10C1 -#define CL_sBGRA 0x10C2 -#define CL_ABGR 0x10C3 - -/* cl_channel_type */ -#define CL_SNORM_INT8 0x10D0 -#define CL_SNORM_INT16 0x10D1 -#define CL_UNORM_INT8 0x10D2 -#define CL_UNORM_INT16 0x10D3 -#define CL_UNORM_SHORT_565 0x10D4 -#define CL_UNORM_SHORT_555 0x10D5 -#define CL_UNORM_INT_101010 0x10D6 -#define CL_SIGNED_INT8 0x10D7 -#define CL_SIGNED_INT16 0x10D8 -#define CL_SIGNED_INT32 0x10D9 -#define CL_UNSIGNED_INT8 0x10DA -#define CL_UNSIGNED_INT16 0x10DB -#define CL_UNSIGNED_INT32 0x10DC -#define CL_HALF_FLOAT 0x10DD -#define CL_FLOAT 0x10DE -#define CL_UNORM_INT24 0x10DF -#define CL_UNORM_INT_101010_2 0x10E0 - -/* cl_mem_object_type */ -#define CL_MEM_OBJECT_BUFFER 0x10F0 -#define CL_MEM_OBJECT_IMAGE2D 0x10F1 -#define CL_MEM_OBJECT_IMAGE3D 0x10F2 -#define CL_MEM_OBJECT_IMAGE2D_ARRAY 0x10F3 -#define CL_MEM_OBJECT_IMAGE1D 0x10F4 -#define CL_MEM_OBJECT_IMAGE1D_ARRAY 0x10F5 -#define CL_MEM_OBJECT_IMAGE1D_BUFFER 0x10F6 -#define CL_MEM_OBJECT_PIPE 0x10F7 - -/* cl_mem_info */ -#define CL_MEM_TYPE 0x1100 -#define CL_MEM_FLAGS 0x1101 -#define CL_MEM_SIZE 0x1102 -#define CL_MEM_HOST_PTR 0x1103 -#define CL_MEM_MAP_COUNT 0x1104 -#define CL_MEM_REFERENCE_COUNT 0x1105 -#define CL_MEM_CONTEXT 0x1106 -#define CL_MEM_ASSOCIATED_MEMOBJECT 0x1107 -#define CL_MEM_OFFSET 0x1108 -#define CL_MEM_USES_SVM_POINTER 0x1109 - -/* cl_image_info */ -#define CL_IMAGE_FORMAT 0x1110 -#define CL_IMAGE_ELEMENT_SIZE 0x1111 -#define CL_IMAGE_ROW_PITCH 0x1112 -#define CL_IMAGE_SLICE_PITCH 0x1113 -#define CL_IMAGE_WIDTH 0x1114 -#define CL_IMAGE_HEIGHT 0x1115 -#define CL_IMAGE_DEPTH 0x1116 -#define CL_IMAGE_ARRAY_SIZE 0x1117 -#define CL_IMAGE_BUFFER 0x1118 -#define CL_IMAGE_NUM_MIP_LEVELS 0x1119 -#define CL_IMAGE_NUM_SAMPLES 0x111A - -/* cl_pipe_info */ -#define CL_PIPE_PACKET_SIZE 0x1120 -#define CL_PIPE_MAX_PACKETS 0x1121 - -/* cl_addressing_mode */ -#define CL_ADDRESS_NONE 0x1130 -#define CL_ADDRESS_CLAMP_TO_EDGE 0x1131 -#define CL_ADDRESS_CLAMP 0x1132 -#define CL_ADDRESS_REPEAT 0x1133 -#define CL_ADDRESS_MIRRORED_REPEAT 0x1134 - -/* cl_filter_mode */ -#define CL_FILTER_NEAREST 0x1140 -#define CL_FILTER_LINEAR 0x1141 - -/* cl_sampler_info */ -#define CL_SAMPLER_REFERENCE_COUNT 0x1150 -#define CL_SAMPLER_CONTEXT 0x1151 -#define CL_SAMPLER_NORMALIZED_COORDS 0x1152 -#define CL_SAMPLER_ADDRESSING_MODE 0x1153 -#define CL_SAMPLER_FILTER_MODE 0x1154 -#define CL_SAMPLER_MIP_FILTER_MODE 0x1155 -#define CL_SAMPLER_LOD_MIN 0x1156 -#define CL_SAMPLER_LOD_MAX 0x1157 - -/* cl_map_flags - bitfield */ -#define CL_MAP_READ (1 << 0) -#define CL_MAP_WRITE (1 << 1) -#define CL_MAP_WRITE_INVALIDATE_REGION (1 << 2) - -/* cl_program_info */ -#define CL_PROGRAM_REFERENCE_COUNT 0x1160 -#define CL_PROGRAM_CONTEXT 0x1161 -#define CL_PROGRAM_NUM_DEVICES 0x1162 -#define CL_PROGRAM_DEVICES 0x1163 -#define CL_PROGRAM_SOURCE 0x1164 -#define CL_PROGRAM_BINARY_SIZES 0x1165 -#define CL_PROGRAM_BINARIES 0x1166 -#define CL_PROGRAM_NUM_KERNELS 0x1167 -#define CL_PROGRAM_KERNEL_NAMES 0x1168 -#define CL_PROGRAM_IL 0x1169 - -/* cl_program_build_info */ -#define CL_PROGRAM_BUILD_STATUS 0x1181 -#define CL_PROGRAM_BUILD_OPTIONS 0x1182 -#define CL_PROGRAM_BUILD_LOG 0x1183 -#define CL_PROGRAM_BINARY_TYPE 0x1184 -#define CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE 0x1185 - -/* cl_program_binary_type */ -#define CL_PROGRAM_BINARY_TYPE_NONE 0x0 -#define CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT 0x1 -#define CL_PROGRAM_BINARY_TYPE_LIBRARY 0x2 -#define CL_PROGRAM_BINARY_TYPE_EXECUTABLE 0x4 - -/* cl_build_status */ -#define CL_BUILD_SUCCESS 0 -#define CL_BUILD_NONE -1 -#define CL_BUILD_ERROR -2 -#define CL_BUILD_IN_PROGRESS -3 - -/* cl_kernel_info */ -#define CL_KERNEL_FUNCTION_NAME 0x1190 -#define CL_KERNEL_NUM_ARGS 0x1191 -#define CL_KERNEL_REFERENCE_COUNT 0x1192 -#define CL_KERNEL_CONTEXT 0x1193 -#define CL_KERNEL_PROGRAM 0x1194 -#define CL_KERNEL_ATTRIBUTES 0x1195 -#define CL_KERNEL_MAX_NUM_SUB_GROUPS 0x11B9 -#define CL_KERNEL_COMPILE_NUM_SUB_GROUPS 0x11BA - -/* cl_kernel_arg_info */ -#define CL_KERNEL_ARG_ADDRESS_QUALIFIER 0x1196 -#define CL_KERNEL_ARG_ACCESS_QUALIFIER 0x1197 -#define CL_KERNEL_ARG_TYPE_NAME 0x1198 -#define CL_KERNEL_ARG_TYPE_QUALIFIER 0x1199 -#define CL_KERNEL_ARG_NAME 0x119A - -/* cl_kernel_arg_address_qualifier */ -#define CL_KERNEL_ARG_ADDRESS_GLOBAL 0x119B -#define CL_KERNEL_ARG_ADDRESS_LOCAL 0x119C -#define CL_KERNEL_ARG_ADDRESS_CONSTANT 0x119D -#define CL_KERNEL_ARG_ADDRESS_PRIVATE 0x119E - -/* cl_kernel_arg_access_qualifier */ -#define CL_KERNEL_ARG_ACCESS_READ_ONLY 0x11A0 -#define CL_KERNEL_ARG_ACCESS_WRITE_ONLY 0x11A1 -#define CL_KERNEL_ARG_ACCESS_READ_WRITE 0x11A2 -#define CL_KERNEL_ARG_ACCESS_NONE 0x11A3 - -/* cl_kernel_arg_type_qualifer */ -#define CL_KERNEL_ARG_TYPE_NONE 0 -#define CL_KERNEL_ARG_TYPE_CONST (1 << 0) -#define CL_KERNEL_ARG_TYPE_RESTRICT (1 << 1) -#define CL_KERNEL_ARG_TYPE_VOLATILE (1 << 2) -#define CL_KERNEL_ARG_TYPE_PIPE (1 << 3) - -/* cl_kernel_work_group_info */ -#define CL_KERNEL_WORK_GROUP_SIZE 0x11B0 -#define CL_KERNEL_COMPILE_WORK_GROUP_SIZE 0x11B1 -#define CL_KERNEL_LOCAL_MEM_SIZE 0x11B2 -#define CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE 0x11B3 -#define CL_KERNEL_PRIVATE_MEM_SIZE 0x11B4 -#define CL_KERNEL_GLOBAL_WORK_SIZE 0x11B5 - -/* cl_kernel_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE 0x2034 -#define CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT 0x11B8 - -/* cl_kernel_exec_info */ -#define CL_KERNEL_EXEC_INFO_SVM_PTRS 0x11B6 -#define CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM 0x11B7 - -/* cl_event_info */ -#define CL_EVENT_COMMAND_QUEUE 0x11D0 -#define CL_EVENT_COMMAND_TYPE 0x11D1 -#define CL_EVENT_REFERENCE_COUNT 0x11D2 -#define CL_EVENT_COMMAND_EXECUTION_STATUS 0x11D3 -#define CL_EVENT_CONTEXT 0x11D4 - -/* cl_command_type */ -#define CL_COMMAND_NDRANGE_KERNEL 0x11F0 -#define CL_COMMAND_TASK 0x11F1 -#define CL_COMMAND_NATIVE_KERNEL 0x11F2 -#define CL_COMMAND_READ_BUFFER 0x11F3 -#define CL_COMMAND_WRITE_BUFFER 0x11F4 -#define CL_COMMAND_COPY_BUFFER 0x11F5 -#define CL_COMMAND_READ_IMAGE 0x11F6 -#define CL_COMMAND_WRITE_IMAGE 0x11F7 -#define CL_COMMAND_COPY_IMAGE 0x11F8 -#define CL_COMMAND_COPY_IMAGE_TO_BUFFER 0x11F9 -#define CL_COMMAND_COPY_BUFFER_TO_IMAGE 0x11FA -#define CL_COMMAND_MAP_BUFFER 0x11FB -#define CL_COMMAND_MAP_IMAGE 0x11FC -#define CL_COMMAND_UNMAP_MEM_OBJECT 0x11FD -#define CL_COMMAND_MARKER 0x11FE -#define CL_COMMAND_ACQUIRE_GL_OBJECTS 0x11FF -#define CL_COMMAND_RELEASE_GL_OBJECTS 0x1200 -#define CL_COMMAND_READ_BUFFER_RECT 0x1201 -#define CL_COMMAND_WRITE_BUFFER_RECT 0x1202 -#define CL_COMMAND_COPY_BUFFER_RECT 0x1203 -#define CL_COMMAND_USER 0x1204 -#define CL_COMMAND_BARRIER 0x1205 -#define CL_COMMAND_MIGRATE_MEM_OBJECTS 0x1206 -#define CL_COMMAND_FILL_BUFFER 0x1207 -#define CL_COMMAND_FILL_IMAGE 0x1208 -#define CL_COMMAND_SVM_FREE 0x1209 -#define CL_COMMAND_SVM_MEMCPY 0x120A -#define CL_COMMAND_SVM_MEMFILL 0x120B -#define CL_COMMAND_SVM_MAP 0x120C -#define CL_COMMAND_SVM_UNMAP 0x120D - -/* command execution status */ -#define CL_COMPLETE 0x0 -#define CL_RUNNING 0x1 -#define CL_SUBMITTED 0x2 -#define CL_QUEUED 0x3 - -/* cl_buffer_create_type */ -#define CL_BUFFER_CREATE_TYPE_REGION 0x1220 - -/* cl_profiling_info */ -#define CL_PROFILING_COMMAND_QUEUED 0x1280 -#define CL_PROFILING_COMMAND_SUBMIT 0x1281 -#define CL_PROFILING_COMMAND_START 0x1282 -#define CL_PROFILING_COMMAND_END 0x1283 -#define CL_PROFILING_COMMAND_COMPLETE 0x1284 - -/********************************************************************************************************/ - -/* Platform API */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformIDs(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformInfo(cl_platform_id /* platform */, - cl_platform_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Device APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceIDs(cl_platform_id /* platform */, - cl_device_type /* device_type */, - cl_uint /* num_entries */, - cl_device_id * /* devices */, - cl_uint * /* num_devices */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceInfo(cl_device_id /* device */, - cl_device_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateSubDevices(cl_device_id /* in_device */, - const cl_device_partition_property * /* properties */, - cl_uint /* num_devices */, - cl_device_id * /* out_devices */, - cl_uint * /* num_devices_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetDefaultDeviceCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceAndHostTimer(cl_device_id /* device */, - cl_ulong* /* device_timestamp */, - cl_ulong* /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetHostTimer(cl_device_id /* device */, - cl_ulong * /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - - -/* Context APIs */ -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContext(const cl_context_properties * /* properties */, - cl_uint /* num_devices */, - const cl_device_id * /* devices */, - void (CL_CALLBACK * /* pfn_notify */)(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContextFromType(const cl_context_properties * /* properties */, - cl_device_type /* device_type */, - void (CL_CALLBACK * /* pfn_notify*/ )(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetContextInfo(cl_context /* context */, - cl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Command Queue APIs */ -extern CL_API_ENTRY cl_command_queue CL_API_CALL -clCreateCommandQueueWithProperties(cl_context /* context */, - cl_device_id /* device */, - const cl_queue_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetCommandQueueInfo(cl_command_queue /* command_queue */, - cl_command_queue_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Memory Object APIs */ -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - size_t /* size */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateSubBuffer(cl_mem /* buffer */, - cl_mem_flags /* flags */, - cl_buffer_create_type /* buffer_create_type */, - const void * /* buffer_create_info */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateImage(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - const cl_image_desc * /* image_desc */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreatePipe(cl_context /* context */, - cl_mem_flags /* flags */, - cl_uint /* pipe_packet_size */, - cl_uint /* pipe_max_packets */, - const cl_pipe_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSupportedImageFormats(cl_context /* context */, - cl_mem_flags /* flags */, - cl_mem_object_type /* image_type */, - cl_uint /* num_entries */, - cl_image_format * /* image_formats */, - cl_uint * /* num_image_formats */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetMemObjectInfo(cl_mem /* memobj */, - cl_mem_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetImageInfo(cl_mem /* image */, - cl_image_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPipeInfo(cl_mem /* pipe */, - cl_pipe_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_2_0; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetMemObjectDestructorCallback(cl_mem /* memobj */, - void (CL_CALLBACK * /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_API_SUFFIX__VERSION_1_1; - -/* SVM Allocation APIs */ -extern CL_API_ENTRY void * CL_API_CALL -clSVMAlloc(cl_context /* context */, - cl_svm_mem_flags /* flags */, - size_t /* size */, - cl_uint /* alignment */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY void CL_API_CALL -clSVMFree(cl_context /* context */, - void * /* svm_pointer */) CL_API_SUFFIX__VERSION_2_0; - -/* Sampler APIs */ -extern CL_API_ENTRY cl_sampler CL_API_CALL -clCreateSamplerWithProperties(cl_context /* context */, - const cl_sampler_properties * /* normalized_coords */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSamplerInfo(cl_sampler /* sampler */, - cl_sampler_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Program Object APIs */ -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithSource(cl_context /* context */, - cl_uint /* count */, - const char ** /* strings */, - const size_t * /* lengths */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBinary(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const size_t * /* lengths */, - const unsigned char ** /* binaries */, - cl_int * /* binary_status */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBuiltInKernels(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* kernel_names */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithIL(cl_context /* context */, - const void* /* il */, - size_t /* length */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clBuildProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCompileProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_headers */, - const cl_program * /* input_headers */, - const char ** /* header_include_names */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clLinkProgram(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_programs */, - const cl_program * /* input_programs */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */, - cl_int * /* errcode_ret */ ) CL_API_SUFFIX__VERSION_1_2; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clUnloadPlatformCompiler(cl_platform_id /* platform */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramInfo(cl_program /* program */, - cl_program_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramBuildInfo(cl_program /* program */, - cl_device_id /* device */, - cl_program_build_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Kernel Object APIs */ -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCreateKernel(cl_program /* program */, - const char * /* kernel_name */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateKernelsInProgram(cl_program /* program */, - cl_uint /* num_kernels */, - cl_kernel * /* kernels */, - cl_uint * /* num_kernels_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCloneKernel(cl_kernel /* source_kernel */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArg(cl_kernel /* kernel */, - cl_uint /* arg_index */, - size_t /* arg_size */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArgSVMPointer(cl_kernel /* kernel */, - cl_uint /* arg_index */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelExecInfo(cl_kernel /* kernel */, - cl_kernel_exec_info /* param_name */, - size_t /* param_value_size */, - const void * /* param_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelInfo(cl_kernel /* kernel */, - cl_kernel_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelArgInfo(cl_kernel /* kernel */, - cl_uint /* arg_indx */, - cl_kernel_arg_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelWorkGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_work_group_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_sub_group_info /* param_name */, - size_t /* input_value_size */, - const void* /*input_value */, - size_t /* param_value_size */, - void* /* param_value */, - size_t* /* param_value_size_ret */ ) CL_API_SUFFIX__VERSION_2_1; - - -/* Event Object APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clWaitForEvents(cl_uint /* num_events */, - const cl_event * /* event_list */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventInfo(cl_event /* event */, - cl_event_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateUserEvent(cl_context /* context */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetUserEventStatus(cl_event /* event */, - cl_int /* execution_status */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetEventCallback( cl_event /* event */, - cl_int /* command_exec_callback_type */, - void (CL_CALLBACK * /* pfn_notify */)(cl_event, cl_int, void *), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_1; - -/* Profiling APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventProfilingInfo(cl_event /* event */, - cl_profiling_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Flush and Finish APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clFlush(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clFinish(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -/* Enqueued Commands APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - size_t /* offset */, - size_t /* size */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - size_t /* offset */, - size_t /* size */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - size_t /* src_offset */, - size_t /* dst_offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferRect(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin */, - const size_t * /* dst_origin */, - const size_t * /* region */, - size_t /* src_row_pitch */, - size_t /* src_slice_pitch */, - size_t /* dst_row_pitch */, - size_t /* dst_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_read */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* row_pitch */, - size_t /* slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_write */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* input_row_pitch */, - size_t /* input_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - const void * /* fill_color */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImage(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_image */, - const size_t * /* src_origin[3] */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImageToBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin[3] */, - const size_t * /* region[3] */, - size_t /* dst_offset */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferToImage(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_image */, - size_t /* src_offset */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t * /* image_row_pitch */, - size_t * /* image_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueUnmapMemObject(cl_command_queue /* command_queue */, - cl_mem /* memobj */, - void * /* mapped_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMigrateMemObjects(cl_command_queue /* command_queue */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_objects */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNDRangeKernel(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* work_dim */, - const size_t * /* global_work_offset */, - const size_t * /* global_work_size */, - const size_t * /* local_work_size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNativeKernel(cl_command_queue /* command_queue */, - void (CL_CALLBACK * /*user_func*/)(void *), - void * /* args */, - size_t /* cb_args */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_list */, - const void ** /* args_mem_loc */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMarkerWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueBarrierWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMFree(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void (CL_CALLBACK * /*pfn_free_func*/)(cl_command_queue /* queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void * /* user_data */), - void * /* user_data */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemcpy(cl_command_queue /* command_queue */, - cl_bool /* blocking_copy */, - void * /* dst_ptr */, - const void * /* src_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemFill(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMap(cl_command_queue /* command_queue */, - cl_bool /* blocking_map */, - cl_map_flags /* flags */, - void * /* svm_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMUnmap(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMigrateMem(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - const void ** /* svm_pointers */, - const size_t * /* sizes */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_1; - - -/* Extension function access - * - * Returns the extension function address for the given function name, - * or NULL if a valid function can not be found. The client must - * check to make sure the address is not NULL, before using or - * calling the returned function address. - */ -extern CL_API_ENTRY void * CL_API_CALL -clGetExtensionFunctionAddressForPlatform(cl_platform_id /* platform */, - const char * /* func_name */) CL_API_SUFFIX__VERSION_1_2; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage2D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_row_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage3D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_depth */, - size_t /* image_row_pitch */, - size_t /* image_slice_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueMarker(cl_command_queue /* command_queue */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueWaitForEvents(cl_command_queue /* command_queue */, - cl_uint /* num_events */, - const cl_event * /* event_list */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueBarrier(cl_command_queue /* command_queue */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clUnloadCompiler(void) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED void * CL_API_CALL -clGetExtensionFunctionAddress(const char * /* func_name */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* Deprecated OpenCL 2.0 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_command_queue CL_API_CALL -clCreateCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue_properties /* properties */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_sampler CL_API_CALL -clCreateSampler(cl_context /* context */, - cl_bool /* normalized_coords */, - cl_addressing_mode /* addressing_mode */, - cl_filter_mode /* filter_mode */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_int CL_API_CALL -clEnqueueTask(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d10.h b/third_party/opencl/include/CL/cl_d3d10.h deleted file mode 100644 index d5960a43f7..0000000000 --- a/third_party/opencl/include/CL/cl_d3d10.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are 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 Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE 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 - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D10_H -#define __OPENCL_CL_D3D10_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d10_sharing */ -#define cl_khr_d3d10_sharing 1 - -typedef cl_uint cl_d3d10_device_source_khr; -typedef cl_uint cl_d3d10_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D10_DEVICE_KHR -1002 -#define CL_INVALID_D3D10_RESOURCE_KHR -1003 -#define CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR -1004 -#define CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR -1005 - -/* cl_d3d10_device_source_nv */ -#define CL_D3D10_DEVICE_KHR 0x4010 -#define CL_D3D10_DXGI_ADAPTER_KHR 0x4011 - -/* cl_d3d10_device_set_nv */ -#define CL_PREFERRED_DEVICES_FOR_D3D10_KHR 0x4012 -#define CL_ALL_DEVICES_FOR_D3D10_KHR 0x4013 - -/* cl_context_info */ -#define CL_CONTEXT_D3D10_DEVICE_KHR 0x4014 -#define CL_CONTEXT_D3D10_PREFER_SHARED_RESOURCES_KHR 0x402C - -/* cl_mem_info */ -#define CL_MEM_D3D10_RESOURCE_KHR 0x4015 - -/* cl_image_info */ -#define CL_IMAGE_D3D10_SUBRESOURCE_KHR 0x4016 - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR 0x4017 -#define CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR 0x4018 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D10KHR_fn)( - cl_platform_id platform, - cl_d3d10_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d10_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D10_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d11.h b/third_party/opencl/include/CL/cl_d3d11.h deleted file mode 100644 index 39f9072398..0000000000 --- a/third_party/opencl/include/CL/cl_d3d11.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are 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 Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE 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 - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D11_H -#define __OPENCL_CL_D3D11_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d11_sharing */ -#define cl_khr_d3d11_sharing 1 - -typedef cl_uint cl_d3d11_device_source_khr; -typedef cl_uint cl_d3d11_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D11_DEVICE_KHR -1006 -#define CL_INVALID_D3D11_RESOURCE_KHR -1007 -#define CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR -1008 -#define CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR -1009 - -/* cl_d3d11_device_source */ -#define CL_D3D11_DEVICE_KHR 0x4019 -#define CL_D3D11_DXGI_ADAPTER_KHR 0x401A - -/* cl_d3d11_device_set */ -#define CL_PREFERRED_DEVICES_FOR_D3D11_KHR 0x401B -#define CL_ALL_DEVICES_FOR_D3D11_KHR 0x401C - -/* cl_context_info */ -#define CL_CONTEXT_D3D11_DEVICE_KHR 0x401D -#define CL_CONTEXT_D3D11_PREFER_SHARED_RESOURCES_KHR 0x402D - -/* cl_mem_info */ -#define CL_MEM_D3D11_RESOURCE_KHR 0x401E - -/* cl_image_info */ -#define CL_IMAGE_D3D11_SUBRESOURCE_KHR 0x401F - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D11_OBJECTS_KHR 0x4020 -#define CL_COMMAND_RELEASE_D3D11_OBJECTS_KHR 0x4021 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D11KHR_fn)( - cl_platform_id platform, - cl_d3d11_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d11_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D11_H */ - diff --git a/third_party/opencl/include/CL/cl_dx9_media_sharing.h b/third_party/opencl/include/CL/cl_dx9_media_sharing.h deleted file mode 100644 index 2729e8b9e8..0000000000 --- a/third_party/opencl/include/CL/cl_dx9_media_sharing.h +++ /dev/null @@ -1,132 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are 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 Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE 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 - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_DX9_MEDIA_SHARING_H -#define __OPENCL_CL_DX9_MEDIA_SHARING_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ -/* cl_khr_dx9_media_sharing */ -#define cl_khr_dx9_media_sharing 1 - -typedef cl_uint cl_dx9_media_adapter_type_khr; -typedef cl_uint cl_dx9_media_adapter_set_khr; - -#if defined(_WIN32) -#include -typedef struct _cl_dx9_surface_info_khr -{ - IDirect3DSurface9 *resource; - HANDLE shared_handle; -} cl_dx9_surface_info_khr; -#endif - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_DX9_MEDIA_ADAPTER_KHR -1010 -#define CL_INVALID_DX9_MEDIA_SURFACE_KHR -1011 -#define CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR -1012 -#define CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR -1013 - -/* cl_media_adapter_type_khr */ -#define CL_ADAPTER_D3D9_KHR 0x2020 -#define CL_ADAPTER_D3D9EX_KHR 0x2021 -#define CL_ADAPTER_DXVA_KHR 0x2022 - -/* cl_media_adapter_set_khr */ -#define CL_PREFERRED_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2023 -#define CL_ALL_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2024 - -/* cl_context_info */ -#define CL_CONTEXT_ADAPTER_D3D9_KHR 0x2025 -#define CL_CONTEXT_ADAPTER_D3D9EX_KHR 0x2026 -#define CL_CONTEXT_ADAPTER_DXVA_KHR 0x2027 - -/* cl_mem_info */ -#define CL_MEM_DX9_MEDIA_ADAPTER_TYPE_KHR 0x2028 -#define CL_MEM_DX9_MEDIA_SURFACE_INFO_KHR 0x2029 - -/* cl_image_info */ -#define CL_IMAGE_DX9_MEDIA_PLANE_KHR 0x202A - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_DX9_MEDIA_SURFACES_KHR 0x202B -#define CL_COMMAND_RELEASE_DX9_MEDIA_SURFACES_KHR 0x202C - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromDX9MediaAdapterKHR_fn)( - cl_platform_id platform, - cl_uint num_media_adapters, - cl_dx9_media_adapter_type_khr * media_adapter_type, - void * media_adapters, - cl_dx9_media_adapter_set_khr media_adapter_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromDX9MediaSurfaceKHR_fn)( - cl_context context, - cl_mem_flags flags, - cl_dx9_media_adapter_type_khr adapter_type, - void * surface_info, - cl_uint plane, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_DX9_MEDIA_SHARING_H */ - diff --git a/third_party/opencl/include/CL/cl_egl.h b/third_party/opencl/include/CL/cl_egl.h deleted file mode 100644 index a765bd5266..0000000000 --- a/third_party/opencl/include/CL/cl_egl.h +++ /dev/null @@ -1,136 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are 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 Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE 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 - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_EGL_H -#define __OPENCL_CL_EGL_H - -#ifdef __APPLE__ - -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - - -/* Command type for events created with clEnqueueAcquireEGLObjectsKHR */ -#define CL_COMMAND_EGL_FENCE_SYNC_OBJECT_KHR 0x202F -#define CL_COMMAND_ACQUIRE_EGL_OBJECTS_KHR 0x202D -#define CL_COMMAND_RELEASE_EGL_OBJECTS_KHR 0x202E - -/* Error type for clCreateFromEGLImageKHR */ -#define CL_INVALID_EGL_OBJECT_KHR -1093 -#define CL_EGL_RESOURCE_NOT_ACQUIRED_KHR -1092 - -/* CLeglImageKHR is an opaque handle to an EGLImage */ -typedef void* CLeglImageKHR; - -/* CLeglDisplayKHR is an opaque handle to an EGLDisplay */ -typedef void* CLeglDisplayKHR; - -/* CLeglSyncKHR is an opaque handle to an EGLSync object */ -typedef void* CLeglSyncKHR; - -/* properties passed to clCreateFromEGLImageKHR */ -typedef intptr_t cl_egl_image_properties_khr; - - -#define cl_khr_egl_image 1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageKHR(cl_context /* context */, - CLeglDisplayKHR /* egldisplay */, - CLeglImageKHR /* eglimage */, - cl_mem_flags /* flags */, - const cl_egl_image_properties_khr * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromEGLImageKHR_fn)( - cl_context context, - CLeglDisplayKHR egldisplay, - CLeglImageKHR eglimage, - cl_mem_flags flags, - const cl_egl_image_properties_khr * properties, - cl_int * errcode_ret); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -#define cl_khr_egl_event 1 - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromEGLSyncKHR(cl_context /* context */, - CLeglSyncKHR /* sync */, - CLeglDisplayKHR /* display */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_event (CL_API_CALL *clCreateEventFromEGLSyncKHR_fn)( - cl_context context, - CLeglSyncKHR sync, - CLeglDisplayKHR display, - cl_int * errcode_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EGL_H */ diff --git a/third_party/opencl/include/CL/cl_ext.h b/third_party/opencl/include/CL/cl_ext.h deleted file mode 100644 index 7941583895..0000000000 --- a/third_party/opencl/include/CL/cl_ext.h +++ /dev/null @@ -1,391 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are 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 Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE 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 - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */ - -/* cl_ext.h contains OpenCL extensions which don't have external */ -/* (OpenGL, D3D) dependencies. */ - -#ifndef __CL_EXT_H -#define __CL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include - #include -#else - #include -#endif - -/* cl_khr_fp16 extension - no extension #define since it has no functions */ -#define CL_DEVICE_HALF_FP_CONFIG 0x1033 - -/* Memory object destruction - * - * Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR - * - * Registers a user callback function that will be called when the memory object is deleted and its resources - * freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback - * stack associated with memobj. The registered user callback functions are called in the reverse order in - * which they were registered. The user callback functions are called and then the memory object is deleted - * and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be - * notified when the memory referenced by host_ptr, specified when the memory object is created and used as - * the storage bits for the memory object, can be reused or freed. - * - * The application may not call CL api's with the cl_mem object passed to the pfn_notify. - * - * Please check for the "cl_APPLE_SetMemObjectDestructor" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - */ -#define cl_APPLE_SetMemObjectDestructor 1 -cl_int CL_API_ENTRY clSetMemObjectDestructorAPPLE( cl_mem /* memobj */, - void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/* Context Logging Functions - * - * The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext(). - * Please check for the "cl_APPLE_ContextLoggingFunctions" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - * - * clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger - */ -#define cl_APPLE_ContextLoggingFunctions 1 -extern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */ -extern void CL_API_ENTRY clLogMessagesToStdoutAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */ -extern void CL_API_ENTRY clLogMessagesToStderrAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/************************ -* cl_khr_icd extension * -************************/ -#define cl_khr_icd 1 - -/* cl_platform_info */ -#define CL_PLATFORM_ICD_SUFFIX_KHR 0x0920 - -/* Additional Error Codes */ -#define CL_PLATFORM_NOT_FOUND_KHR -1001 - -extern CL_API_ENTRY cl_int CL_API_CALL -clIcdGetPlatformIDsKHR(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)( - cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - - -/* Extension: cl_khr_image2D_buffer - * - * This extension allows a 2D image to be created from a cl_mem buffer without a copy. - * The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t. - * Both the sampler and sampler-less read_image built-in functions are supported for 2D images - * and 2D images created from a buffer. Similarly, the write_image built-ins are also supported - * for 2D images created from a buffer. - * - * When the 2D image from buffer is created, the client must specify the width, - * height, image format (i.e. channel order and channel data type) and optionally the row pitch - * - * The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels. - * The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels. - */ - -/************************************* - * cl_khr_initalize_memory extension * - *************************************/ - -#define CL_CONTEXT_MEMORY_INITIALIZE_KHR 0x2030 - - -/************************************** - * cl_khr_terminate_context extension * - **************************************/ - -#define CL_DEVICE_TERMINATE_CAPABILITY_KHR 0x2031 -#define CL_CONTEXT_TERMINATE_KHR 0x2032 - -#define cl_khr_terminate_context 1 -extern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - - -/* - * Extension: cl_khr_spir - * - * This extension adds support to create an OpenCL program object from a - * Standard Portable Intermediate Representation (SPIR) instance - */ - -#define CL_DEVICE_SPIR_VERSIONS 0x40E0 -#define CL_PROGRAM_BINARY_TYPE_INTERMEDIATE 0x40E1 - - -/****************************************** -* cl_nv_device_attribute_query extension * -******************************************/ -/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */ -#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV 0x4000 -#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV 0x4001 -#define CL_DEVICE_REGISTERS_PER_BLOCK_NV 0x4002 -#define CL_DEVICE_WARP_SIZE_NV 0x4003 -#define CL_DEVICE_GPU_OVERLAP_NV 0x4004 -#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV 0x4005 -#define CL_DEVICE_INTEGRATED_MEMORY_NV 0x4006 - -/********************************* -* cl_amd_device_attribute_query * -*********************************/ -#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD 0x4036 - -/********************************* -* cl_arm_printf extension -*********************************/ -#define CL_PRINTF_CALLBACK_ARM 0x40B0 -#define CL_PRINTF_BUFFERSIZE_ARM 0x40B1 - -#ifdef CL_VERSION_1_1 - /*********************************** - * cl_ext_device_fission extension * - ***********************************/ - #define cl_ext_device_fission 1 - - extern CL_API_ENTRY cl_int CL_API_CALL - clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - extern CL_API_ENTRY cl_int CL_API_CALL - clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef cl_ulong cl_device_partition_property_ext; - extern CL_API_ENTRY cl_int CL_API_CALL - clCreateSubDevicesEXT( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clCreateSubDevicesEXT_fn)( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - /* cl_device_partition_property_ext */ - #define CL_DEVICE_PARTITION_EQUALLY_EXT 0x4050 - #define CL_DEVICE_PARTITION_BY_COUNTS_EXT 0x4051 - #define CL_DEVICE_PARTITION_BY_NAMES_EXT 0x4052 - #define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT 0x4053 - - /* clDeviceGetInfo selectors */ - #define CL_DEVICE_PARENT_DEVICE_EXT 0x4054 - #define CL_DEVICE_PARTITION_TYPES_EXT 0x4055 - #define CL_DEVICE_AFFINITY_DOMAINS_EXT 0x4056 - #define CL_DEVICE_REFERENCE_COUNT_EXT 0x4057 - #define CL_DEVICE_PARTITION_STYLE_EXT 0x4058 - - /* error codes */ - #define CL_DEVICE_PARTITION_FAILED_EXT -1057 - #define CL_INVALID_PARTITION_COUNT_EXT -1058 - #define CL_INVALID_PARTITION_NAME_EXT -1059 - - /* CL_AFFINITY_DOMAINs */ - #define CL_AFFINITY_DOMAIN_L1_CACHE_EXT 0x1 - #define CL_AFFINITY_DOMAIN_L2_CACHE_EXT 0x2 - #define CL_AFFINITY_DOMAIN_L3_CACHE_EXT 0x3 - #define CL_AFFINITY_DOMAIN_L4_CACHE_EXT 0x4 - #define CL_AFFINITY_DOMAIN_NUMA_EXT 0x10 - #define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT 0x100 - - /* cl_device_partition_property_ext list terminators */ - #define CL_PROPERTIES_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_COUNTS_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_NAMES_LIST_END_EXT ((cl_device_partition_property_ext) 0 - 1) - -/********************************* -* cl_qcom_ext_host_ptr extension -*********************************/ - -#define CL_MEM_EXT_HOST_PTR_QCOM (1 << 29) - -#define CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM 0x40A0 -#define CL_DEVICE_PAGE_SIZE_QCOM 0x40A1 -#define CL_IMAGE_ROW_ALIGNMENT_QCOM 0x40A2 -#define CL_IMAGE_SLICE_ALIGNMENT_QCOM 0x40A3 -#define CL_MEM_HOST_UNCACHED_QCOM 0x40A4 -#define CL_MEM_HOST_WRITEBACK_QCOM 0x40A5 -#define CL_MEM_HOST_WRITETHROUGH_QCOM 0x40A6 -#define CL_MEM_HOST_WRITE_COMBINING_QCOM 0x40A7 - -typedef cl_uint cl_image_pitch_info_qcom; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceImageInfoQCOM(cl_device_id device, - size_t image_width, - size_t image_height, - const cl_image_format *image_format, - cl_image_pitch_info_qcom param_name, - size_t param_value_size, - void *param_value, - size_t *param_value_size_ret); - -typedef struct _cl_mem_ext_host_ptr -{ - /* Type of external memory allocation. */ - /* Legal values will be defined in layered extensions. */ - cl_uint allocation_type; - - /* Host cache policy for this external memory allocation. */ - cl_uint host_cache_policy; - -} cl_mem_ext_host_ptr; - -/********************************* -* cl_qcom_ion_host_ptr extension -*********************************/ - -#define CL_MEM_ION_HOST_PTR_QCOM 0x40A8 - -typedef struct _cl_mem_ion_host_ptr -{ - /* Type of external memory allocation. */ - /* Must be CL_MEM_ION_HOST_PTR_QCOM for ION allocations. */ - cl_mem_ext_host_ptr ext_host_ptr; - - /* ION file descriptor */ - int ion_filedesc; - - /* Host pointer to the ION allocated memory */ - void* ion_hostptr; - -} cl_mem_ion_host_ptr; - -#endif /* CL_VERSION_1_1 */ - - -#ifdef CL_VERSION_2_0 -/********************************* -* cl_khr_sub_groups extension -*********************************/ -#define cl_khr_sub_groups 1 - -typedef cl_uint cl_kernel_sub_group_info_khr; - -/* cl_khr_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE_KHR 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE_KHR 0x2034 - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfoKHR(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; - -typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clGetKernelSubGroupInfoKHR_fn)(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; -#endif /* CL_VERSION_2_0 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_priority_hints extension -*********************************/ -#define cl_khr_priority_hints 1 - -typedef cl_uint cl_queue_priority_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_PRIORITY_KHR 0x1096 - -/* cl_queue_priority_khr */ -#define CL_QUEUE_PRIORITY_HIGH_KHR (1<<0) -#define CL_QUEUE_PRIORITY_MED_KHR (1<<1) -#define CL_QUEUE_PRIORITY_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_throttle_hints extension -*********************************/ -#define cl_khr_throttle_hints 1 - -typedef cl_uint cl_queue_throttle_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_THROTTLE_KHR 0x1097 - -/* cl_queue_throttle_khr */ -#define CL_QUEUE_THROTTLE_HIGH_KHR (1<<0) -#define CL_QUEUE_THROTTLE_MED_KHR (1<<1) -#define CL_QUEUE_THROTTLE_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef __cplusplus -} -#endif - - -#endif /* __CL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_ext_qcom.h b/third_party/opencl/include/CL/cl_ext_qcom.h deleted file mode 100644 index 6328a1cd93..0000000000 --- a/third_party/opencl/include/CL/cl_ext_qcom.h +++ /dev/null @@ -1,255 +0,0 @@ -/* Copyright (c) 2009-2017 Qualcomm Technologies, Inc. All Rights Reserved. - * Qualcomm Technologies Proprietary and Confidential. - */ - -#ifndef __OPENCL_CL_EXT_QCOM_H -#define __OPENCL_CL_EXT_QCOM_H - -// Needed by cl_khr_egl_event extension -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -/************************************ - * cl_qcom_create_buffer_from_image * - ************************************/ - -#define CL_BUFFER_FROM_IMAGE_ROW_PITCH_QCOM 0x40C0 -#define CL_BUFFER_FROM_IMAGE_SLICE_PITCH_QCOM 0x40C1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBufferFromImageQCOM(cl_mem image, - cl_mem_flags flags, - cl_int *errcode_ret); - - -/************************************ - * cl_qcom_limited_printf extension * - ************************************/ - -/* Builtin printf function buffer size in bytes. */ -#define CL_DEVICE_PRINTF_BUFFER_SIZE_QCOM 0x1049 - - -/************************************* - * cl_qcom_extended_images extension * - *************************************/ - -#define CL_CONTEXT_ENABLE_EXTENDED_IMAGES_QCOM 0x40AA -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_WIDTH_QCOM 0x40AB -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_HEIGHT_QCOM 0x40AC -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_WIDTH_QCOM 0x40AD -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_HEIGHT_QCOM 0x40AE -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_DEPTH_QCOM 0x40AF - -/************************************* - * cl_qcom_perf_hint extension * - *************************************/ - -typedef cl_uint cl_perf_hint; - -#define CL_CONTEXT_PERF_HINT_QCOM 0x40C2 - -/*cl_perf_hint*/ -#define CL_PERF_HINT_HIGH_QCOM 0x40C3 -#define CL_PERF_HINT_NORMAL_QCOM 0x40C4 -#define CL_PERF_HINT_LOW_QCOM 0x40C5 - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetPerfHintQCOM(cl_context context, - cl_perf_hint perf_hint); - -// This extension is published at Khronos, so its definitions are made in cl_ext.h. -// This duplication is for backward compatibility. - -#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/********************************* -* cl_qcom_android_native_buffer_host_ptr extension -*********************************/ - -#define CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM 0x40C6 - - -typedef struct _cl_mem_android_native_buffer_host_ptr -{ - // Type of external memory allocation. - // Must be CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM for Android native buffers. - cl_mem_ext_host_ptr ext_host_ptr; - - // Virtual pointer to the android native buffer - void* anb_ptr; - -} cl_mem_android_native_buffer_host_ptr; - -#endif //#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/*********************************** -* cl_img_egl_image extension * -************************************/ -typedef void* CLeglImageIMG; -typedef void* CLeglDisplayIMG; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageIMG(cl_context context, - cl_mem_flags flags, - CLeglImageIMG image, - CLeglDisplayIMG display, - cl_int *errcode_ret); - - -/********************************* -* cl_qcom_other_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-standard images -#define CL_MEM_OTHER_IMAGE_QCOM (1<<25) - -// cl_channel_type -#define CL_QCOM_UNORM_MIPI10 0x4159 -#define CL_QCOM_UNORM_MIPI12 0x415A -#define CL_QCOM_UNSIGNED_MIPI10 0x415B -#define CL_QCOM_UNSIGNED_MIPI12 0x415C -#define CL_QCOM_UNORM_INT10 0x415D -#define CL_QCOM_UNORM_INT12 0x415E -#define CL_QCOM_UNSIGNED_INT16 0x415F - -// cl_channel_order -// Dedicate 0x4130-0x415F range for QCOM extended image formats -// 0x4130 - 0x4132 range is assigned to pixel-oriented compressed format -#define CL_QCOM_BAYER 0x414E - -#define CL_QCOM_NV12 0x4133 -#define CL_QCOM_NV12_Y 0x4134 -#define CL_QCOM_NV12_UV 0x4135 - -#define CL_QCOM_TILED_NV12 0x4136 -#define CL_QCOM_TILED_NV12_Y 0x4137 -#define CL_QCOM_TILED_NV12_UV 0x4138 - -#define CL_QCOM_P010 0x413C -#define CL_QCOM_P010_Y 0x413D -#define CL_QCOM_P010_UV 0x413E - -#define CL_QCOM_TILED_P010 0x413F -#define CL_QCOM_TILED_P010_Y 0x4140 -#define CL_QCOM_TILED_P010_UV 0x4141 - - -#define CL_QCOM_TP10 0x4145 -#define CL_QCOM_TP10_Y 0x4146 -#define CL_QCOM_TP10_UV 0x4147 - -#define CL_QCOM_TILED_TP10 0x4148 -#define CL_QCOM_TILED_TP10_Y 0x4149 -#define CL_QCOM_TILED_TP10_UV 0x414A - -/********************************* -* cl_qcom_compressed_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-planar compressed images -#define CL_MEM_COMPRESSED_IMAGE_QCOM (1<<27) - -// Extended image format -// cl_channel_order -#define CL_QCOM_COMPRESSED_RGBA 0x4130 -#define CL_QCOM_COMPRESSED_RGBx 0x4131 - -#define CL_QCOM_COMPRESSED_NV12_Y 0x413A -#define CL_QCOM_COMPRESSED_NV12_UV 0x413B - -#define CL_QCOM_COMPRESSED_P010 0x4142 -#define CL_QCOM_COMPRESSED_P010_Y 0x4143 -#define CL_QCOM_COMPRESSED_P010_UV 0x4144 - -#define CL_QCOM_COMPRESSED_TP10 0x414B -#define CL_QCOM_COMPRESSED_TP10_Y 0x414C -#define CL_QCOM_COMPRESSED_TP10_UV 0x414D - -#define CL_QCOM_COMPRESSED_NV12_4R 0x414F -#define CL_QCOM_COMPRESSED_NV12_4R_Y 0x4150 -#define CL_QCOM_COMPRESSED_NV12_4R_UV 0x4151 -/********************************* -* cl_qcom_compressed_yuv_image_read extension -*********************************/ - -// Extended flag for creating/querying QCOM compressed images -#define CL_MEM_COMPRESSED_YUV_IMAGE_QCOM (1<<28) - -// Extended image format -#define CL_QCOM_COMPRESSED_NV12 0x10C4 - -// Extended flag for setting ION buffer allocation type -#define CL_MEM_ION_HOST_PTR_COMPRESSED_YUV_QCOM 0x40CD -#define CL_MEM_ION_HOST_PTR_PROTECTED_COMPRESSED_YUV_QCOM 0x40CE - -/********************************* -* cl_qcom_accelerated_image_ops -*********************************/ -#define CL_MEM_OBJECT_WEIGHT_IMAGE_QCOM 0x4110 -#define CL_DEVICE_HOF_MAX_NUM_PHASES_QCOM 0x4111 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_X_QCOM 0x4112 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_Y_QCOM 0x4113 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_X_QCOM 0x4114 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_Y_QCOM 0x4115 - -//Extended flag for specifying weight image type -#define CL_WEIGHT_IMAGE_SEPARABLE_QCOM (1<<0) - -// Box Filter -typedef struct _cl_box_filter_size_qcom -{ - // Width of box filter on X direction. - float box_filter_width; - - // Height of box filter on Y direction. - float box_filter_height; -} cl_box_filter_size_qcom; - -// HOF Weight Image Desc -typedef struct _cl_weight_desc_qcom -{ - /** Coordinate of the "center" point of the weight image, - based on the weight image's top-left corner as the origin. */ - size_t center_coord_x; - size_t center_coord_y; - cl_bitfield flags; -} cl_weight_desc_qcom; - -typedef struct _cl_weight_image_desc_qcom -{ - cl_image_desc image_desc; - cl_weight_desc_qcom weight_desc; -} cl_weight_image_desc_qcom; - -/************************************* - * cl_qcom_protected_context extension * - *************************************/ - -#define CL_CONTEXT_PROTECTED_QCOM 0x40C7 -#define CL_MEM_ION_HOST_PTR_PROTECTED_QCOM 0x40C8 - -/************************************* - * cl_qcom_priority_hint extension * - *************************************/ -#define CL_PRIORITY_HINT_NONE_QCOM 0 -typedef cl_uint cl_priority_hint; - -#define CL_CONTEXT_PRIORITY_HINT_QCOM 0x40C9 - -/*cl_priority_hint*/ -#define CL_PRIORITY_HINT_HIGH_QCOM 0x40CA -#define CL_PRIORITY_HINT_NORMAL_QCOM 0x40CB -#define CL_PRIORITY_HINT_LOW_QCOM 0x40CC - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EXT_QCOM_H */ diff --git a/third_party/opencl/include/CL/cl_gl.h b/third_party/opencl/include/CL/cl_gl.h deleted file mode 100644 index 945daa83d7..0000000000 --- a/third_party/opencl/include/CL/cl_gl.h +++ /dev/null @@ -1,167 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are 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 Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE 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 - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -#ifndef __OPENCL_CL_GL_H -#define __OPENCL_CL_GL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef cl_uint cl_gl_object_type; -typedef cl_uint cl_gl_texture_info; -typedef cl_uint cl_gl_platform_info; -typedef struct __GLsync *cl_GLsync; - -/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken */ -#define CL_GL_OBJECT_BUFFER 0x2000 -#define CL_GL_OBJECT_TEXTURE2D 0x2001 -#define CL_GL_OBJECT_TEXTURE3D 0x2002 -#define CL_GL_OBJECT_RENDERBUFFER 0x2003 -#define CL_GL_OBJECT_TEXTURE2D_ARRAY 0x200E -#define CL_GL_OBJECT_TEXTURE1D 0x200F -#define CL_GL_OBJECT_TEXTURE1D_ARRAY 0x2010 -#define CL_GL_OBJECT_TEXTURE_BUFFER 0x2011 - -/* cl_gl_texture_info */ -#define CL_GL_TEXTURE_TARGET 0x2004 -#define CL_GL_MIPMAP_LEVEL 0x2005 -#define CL_GL_NUM_SAMPLES 0x2012 - - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* bufobj */, - int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLTexture(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLRenderbuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* renderbuffer */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLObjectInfo(cl_mem /* memobj */, - cl_gl_object_type * /* gl_object_type */, - cl_GLuint * /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLTextureInfo(cl_mem /* memobj */, - cl_gl_texture_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture2D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture3D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* cl_khr_gl_sharing extension */ - -#define cl_khr_gl_sharing 1 - -typedef cl_uint cl_gl_context_info; - -/* Additional Error Codes */ -#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR -1000 - -/* cl_gl_context_info */ -#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR 0x2006 -#define CL_DEVICES_FOR_GL_CONTEXT_KHR 0x2007 - -/* Additional cl_context_properties */ -#define CL_GL_CONTEXT_KHR 0x2008 -#define CL_EGL_DISPLAY_KHR 0x2009 -#define CL_GLX_DISPLAY_KHR 0x200A -#define CL_WGL_HDC_KHR 0x200B -#define CL_CGL_SHAREGROUP_KHR 0x200C - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLContextInfoKHR(const cl_context_properties * /* properties */, - cl_gl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)( - const cl_context_properties * properties, - cl_gl_context_info param_name, - size_t param_value_size, - void * param_value, - size_t * param_value_size_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_H */ diff --git a/third_party/opencl/include/CL/cl_gl_ext.h b/third_party/opencl/include/CL/cl_gl_ext.h deleted file mode 100644 index e3c14c6408..0000000000 --- a/third_party/opencl/include/CL/cl_gl_ext.h +++ /dev/null @@ -1,74 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are 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 Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE 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 - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have */ -/* OpenGL dependencies. */ - -#ifndef __OPENCL_CL_GL_EXT_H -#define __OPENCL_CL_GL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include -#else - #include -#endif - -/* - * For each extension, follow this template - * cl_VEN_extname extension */ -/* #define cl_VEN_extname 1 - * ... define new types, if any - * ... define new tokens, if any - * ... define new APIs, if any - * - * If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header - * This allows us to avoid having to decide whether to include GL headers or GLES here. - */ - -/* - * cl_khr_gl_event extension - * See section 9.9 in the OpenCL 1.1 spec for more information - */ -#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR 0x200D - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromGLsyncKHR(cl_context /* context */, - cl_GLsync /* cl_GLsync */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_platform.h b/third_party/opencl/include/CL/cl_platform.h deleted file mode 100644 index 4e334a2918..0000000000 --- a/third_party/opencl/include/CL/cl_platform.h +++ /dev/null @@ -1,1333 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are 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 Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE 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 - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11803 $ on $Date: 2010-06-25 10:02:12 -0700 (Fri, 25 Jun 2010) $ */ - -#ifndef __CL_PLATFORM_H -#define __CL_PLATFORM_H - -#ifdef __APPLE__ - /* Contains #defines for AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER below */ - #include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(_WIN32) - #define CL_API_ENTRY - #define CL_API_CALL __stdcall - #define CL_CALLBACK __stdcall -#else - #define CL_API_ENTRY - #define CL_API_CALL - #define CL_CALLBACK -#endif - -/* - * Deprecation flags refer to the last version of the header in which the - * feature was not deprecated. - * - * E.g. VERSION_1_1_DEPRECATED means the feature is present in 1.1 without - * deprecation but is deprecated in versions later than 1.1. - */ - -#ifdef __APPLE__ - #define CL_EXTENSION_WEAK_LINK __attribute__((weak_import)) - #define CL_API_SUFFIX__VERSION_1_0 AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_7 - - #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_8 - #else - #warning This path should never happen outside of internal operating system development. AvailabilityMacros do not function correctly here! - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #endif -#else - #define CL_EXTENSION_WEAK_LINK - #define CL_API_SUFFIX__VERSION_1_0 - #define CL_EXT_SUFFIX__VERSION_1_0 - #define CL_API_SUFFIX__VERSION_1_1 - #define CL_EXT_SUFFIX__VERSION_1_1 - #define CL_API_SUFFIX__VERSION_1_2 - #define CL_EXT_SUFFIX__VERSION_1_2 - #define CL_API_SUFFIX__VERSION_2_0 - #define CL_EXT_SUFFIX__VERSION_2_0 - #define CL_API_SUFFIX__VERSION_2_1 - #define CL_EXT_SUFFIX__VERSION_2_1 - - #ifdef __GNUC__ - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif - #elif _WIN32 - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED __declspec(deprecated) - #endif - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif -#endif - -#if (defined (_WIN32) && defined(_MSC_VER)) - -/* scalar types */ -typedef signed __int8 cl_char; -typedef unsigned __int8 cl_uchar; -typedef signed __int16 cl_short; -typedef unsigned __int16 cl_ushort; -typedef signed __int32 cl_int; -typedef unsigned __int32 cl_uint; -typedef signed __int64 cl_long; -typedef unsigned __int64 cl_ulong; - -typedef unsigned __int16 cl_half; -typedef float cl_float; -typedef double cl_double; - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 340282346638528859811704183484516925440.0f -#define CL_FLT_MIN 1.175494350822287507969e-38f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0 -#define CL_DBL_MIN 2.225073858507201383090e-308 -#define CL_DBL_EPSILON 2.220446049250313080847e-16 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#define CL_NAN (CL_INFINITY - CL_INFINITY) -#define CL_HUGE_VALF ((cl_float) 1e50) -#define CL_HUGE_VAL ((cl_double) 1e500) -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#else - -#include - -/* scalar types */ -typedef int8_t cl_char; -typedef uint8_t cl_uchar; -typedef int16_t cl_short __attribute__((aligned(2))); -typedef uint16_t cl_ushort __attribute__((aligned(2))); -typedef int32_t cl_int __attribute__((aligned(4))); -typedef uint32_t cl_uint __attribute__((aligned(4))); -typedef int64_t cl_long __attribute__((aligned(8))); -typedef uint64_t cl_ulong __attribute__((aligned(8))); - -typedef uint16_t cl_half __attribute__((aligned(2))); -typedef float cl_float __attribute__((aligned(4))); -typedef double cl_double __attribute__((aligned(8))); - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 0x1.fffffep127f -#define CL_FLT_MIN 0x1.0p-126f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 0x1.fffffffffffffp1023 -#define CL_DBL_MIN 0x1.0p-1022 -#define CL_DBL_EPSILON 0x1.0p-52 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#if defined( __GNUC__ ) - #define CL_HUGE_VALF __builtin_huge_valf() - #define CL_HUGE_VAL __builtin_huge_val() - #define CL_NAN __builtin_nanf( "" ) -#else - #define CL_HUGE_VALF ((cl_float) 1e50) - #define CL_HUGE_VAL ((cl_double) 1e500) - float nanf( const char * ); - #define CL_NAN nanf( "" ) -#endif -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#endif - -#include - -/* Mirror types to GL types. Mirror types allow us to avoid deciding which 87s to load based on whether we are using GL or GLES here. */ -typedef unsigned int cl_GLuint; -typedef int cl_GLint; -typedef unsigned int cl_GLenum; - -/* - * Vector types - * - * Note: OpenCL requires that all types be naturally aligned. - * This means that vector types must be naturally aligned. - * For example, a vector of four floats must be aligned to - * a 16 byte boundary (calculated as 4 * the natural 4-byte - * alignment of the float). The alignment qualifiers here - * will only function properly if your compiler supports them - * and if you don't actively work to defeat them. For example, - * in order for a cl_float4 to be 16 byte aligned in a struct, - * the start of the struct must itself be 16-byte aligned. - * - * Maintaining proper alignment is the user's responsibility. - */ - -/* Define basic vector types */ -#if defined( __VEC__ ) - #include /* may be omitted depending on compiler. AltiVec spec provides no way to detect whether the header is required. */ - typedef vector unsigned char __cl_uchar16; - typedef vector signed char __cl_char16; - typedef vector unsigned short __cl_ushort8; - typedef vector signed short __cl_short8; - typedef vector unsigned int __cl_uint4; - typedef vector signed int __cl_int4; - typedef vector float __cl_float4; - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_UINT4__ 1 - #define __CL_INT4__ 1 - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef float __cl_float4 __attribute__((vector_size(16))); - #else - typedef __m128 __cl_float4; - #endif - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE2__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar16 __attribute__((vector_size(16))); - typedef cl_char __cl_char16 __attribute__((vector_size(16))); - typedef cl_ushort __cl_ushort8 __attribute__((vector_size(16))); - typedef cl_short __cl_short8 __attribute__((vector_size(16))); - typedef cl_uint __cl_uint4 __attribute__((vector_size(16))); - typedef cl_int __cl_int4 __attribute__((vector_size(16))); - typedef cl_ulong __cl_ulong2 __attribute__((vector_size(16))); - typedef cl_long __cl_long2 __attribute__((vector_size(16))); - typedef cl_double __cl_double2 __attribute__((vector_size(16))); - #else - typedef __m128i __cl_uchar16; - typedef __m128i __cl_char16; - typedef __m128i __cl_ushort8; - typedef __m128i __cl_short8; - typedef __m128i __cl_uint4; - typedef __m128i __cl_int4; - typedef __m128i __cl_ulong2; - typedef __m128i __cl_long2; - typedef __m128d __cl_double2; - #endif - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_INT4__ 1 - #define __CL_UINT4__ 1 - #define __CL_ULONG2__ 1 - #define __CL_LONG2__ 1 - #define __CL_DOUBLE2__ 1 -#endif - -#if defined( __MMX__ ) - #include - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar8 __attribute__((vector_size(8))); - typedef cl_char __cl_char8 __attribute__((vector_size(8))); - typedef cl_ushort __cl_ushort4 __attribute__((vector_size(8))); - typedef cl_short __cl_short4 __attribute__((vector_size(8))); - typedef cl_uint __cl_uint2 __attribute__((vector_size(8))); - typedef cl_int __cl_int2 __attribute__((vector_size(8))); - typedef cl_ulong __cl_ulong1 __attribute__((vector_size(8))); - typedef cl_long __cl_long1 __attribute__((vector_size(8))); - typedef cl_float __cl_float2 __attribute__((vector_size(8))); - #else - typedef __m64 __cl_uchar8; - typedef __m64 __cl_char8; - typedef __m64 __cl_ushort4; - typedef __m64 __cl_short4; - typedef __m64 __cl_uint2; - typedef __m64 __cl_int2; - typedef __m64 __cl_ulong1; - typedef __m64 __cl_long1; - typedef __m64 __cl_float2; - #endif - #define __CL_UCHAR8__ 1 - #define __CL_CHAR8__ 1 - #define __CL_USHORT4__ 1 - #define __CL_SHORT4__ 1 - #define __CL_INT2__ 1 - #define __CL_UINT2__ 1 - #define __CL_ULONG1__ 1 - #define __CL_LONG1__ 1 - #define __CL_FLOAT2__ 1 -#endif - -#if defined( __AVX__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_float __cl_float8 __attribute__((vector_size(32))); - typedef cl_double __cl_double4 __attribute__((vector_size(32))); - #else - typedef __m256 __cl_float8; - typedef __m256d __cl_double4; - #endif - #define __CL_FLOAT8__ 1 - #define __CL_DOUBLE4__ 1 -#endif - -/* Define capabilities for anonymous struct members. */ -#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ ) -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ __extension__ -#elif defined( _WIN32) && (_MSC_VER >= 1500) - /* Microsoft Developer Studio 2008 supports anonymous structs, but - * complains by default. */ -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ - /* Disable warning C4201: nonstandard extension used : nameless - * struct/union */ -#pragma warning( push ) -#pragma warning( disable : 4201 ) -#else -#define __CL_HAS_ANON_STRUCT__ 0 -#define __CL_ANON_STRUCT__ -#endif - -/* Define alignment keys */ -#if defined( __GNUC__ ) - #define CL_ALIGNED(_x) __attribute__ ((aligned(_x))) -#elif defined( _WIN32) && (_MSC_VER) - /* Alignment keys neutered on windows because MSVC can't swallow function arguments with alignment requirements */ - /* http://msdn.microsoft.com/en-us/library/373ak2y1%28VS.71%29.aspx */ - /* #include */ - /* #define CL_ALIGNED(_x) _CRT_ALIGN(_x) */ - #define CL_ALIGNED(_x) -#else - #warning Need to implement some method to align data here - #define CL_ALIGNED(_x) -#endif - -/* Indicate whether .xyzw, .s0123 and .hi.lo are supported */ -#if __CL_HAS_ANON_STRUCT__ - /* .xyzw and .s0123...{f|F} are supported */ - #define CL_HAS_NAMED_VECTOR_FIELDS 1 - /* .hi and .lo are supported */ - #define CL_HAS_HI_LO_VECTOR_FIELDS 1 -#endif - -/* Define cl_vector types */ - -/* ---- cl_charn ---- */ -typedef union -{ - cl_char CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_char lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2; -#endif -}cl_char2; - -typedef union -{ - cl_char CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_char2 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[2]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4; -#endif -}cl_char4; - -/* cl_char3 is identical in size, alignment and behavior to cl_char4. See section 6.1.5. */ -typedef cl_char4 cl_char3; - -typedef union -{ - cl_char CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_char4 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[4]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[2]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8; -#endif -}cl_char8; - -typedef union -{ - cl_char CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_char8 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[8]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[4]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8[2]; -#endif -#if defined( __CL_CHAR16__ ) - __cl_char16 v16; -#endif -}cl_char16; - - -/* ---- cl_ucharn ---- */ -typedef union -{ - cl_uchar CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uchar lo, hi; }; -#endif -#if defined( __cl_uchar2__) - __cl_uchar2 v2; -#endif -}cl_uchar2; - -typedef union -{ - cl_uchar CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uchar2 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[2]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4; -#endif -}cl_uchar4; - -/* cl_uchar3 is identical in size, alignment and behavior to cl_uchar4. See section 6.1.5. */ -typedef cl_uchar4 cl_uchar3; - -typedef union -{ - cl_uchar CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uchar4 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[4]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[2]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8; -#endif -}cl_uchar8; - -typedef union -{ - cl_uchar CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uchar8 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[8]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[4]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8[2]; -#endif -#if defined( __CL_UCHAR16__ ) - __cl_uchar16 v16; -#endif -}cl_uchar16; - - -/* ---- cl_shortn ---- */ -typedef union -{ - cl_short CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_short lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2; -#endif -}cl_short2; - -typedef union -{ - cl_short CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_short2 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[2]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4; -#endif -}cl_short4; - -/* cl_short3 is identical in size, alignment and behavior to cl_short4. See section 6.1.5. */ -typedef cl_short4 cl_short3; - -typedef union -{ - cl_short CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_short4 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[4]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[2]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8; -#endif -}cl_short8; - -typedef union -{ - cl_short CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_short8 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[8]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[4]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8[2]; -#endif -#if defined( __CL_SHORT16__ ) - __cl_short16 v16; -#endif -}cl_short16; - - -/* ---- cl_ushortn ---- */ -typedef union -{ - cl_ushort CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ushort lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2; -#endif -}cl_ushort2; - -typedef union -{ - cl_ushort CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ushort2 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[2]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4; -#endif -}cl_ushort4; - -/* cl_ushort3 is identical in size, alignment and behavior to cl_ushort4. See section 6.1.5. */ -typedef cl_ushort4 cl_ushort3; - -typedef union -{ - cl_ushort CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ushort4 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[4]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[2]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8; -#endif -}cl_ushort8; - -typedef union -{ - cl_ushort CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ushort8 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[8]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[4]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8[2]; -#endif -#if defined( __CL_USHORT16__ ) - __cl_ushort16 v16; -#endif -}cl_ushort16; - -/* ---- cl_intn ---- */ -typedef union -{ - cl_int CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_int lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2; -#endif -}cl_int2; - -typedef union -{ - cl_int CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_int2 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[2]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4; -#endif -}cl_int4; - -/* cl_int3 is identical in size, alignment and behavior to cl_int4. See section 6.1.5. */ -typedef cl_int4 cl_int3; - -typedef union -{ - cl_int CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_int4 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[4]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[2]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8; -#endif -}cl_int8; - -typedef union -{ - cl_int CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_int8 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[8]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[4]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8[2]; -#endif -#if defined( __CL_INT16__ ) - __cl_int16 v16; -#endif -}cl_int16; - - -/* ---- cl_uintn ---- */ -typedef union -{ - cl_uint CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uint lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2; -#endif -}cl_uint2; - -typedef union -{ - cl_uint CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uint2 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[2]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4; -#endif -}cl_uint4; - -/* cl_uint3 is identical in size, alignment and behavior to cl_uint4. See section 6.1.5. */ -typedef cl_uint4 cl_uint3; - -typedef union -{ - cl_uint CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uint4 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[4]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[2]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8; -#endif -}cl_uint8; - -typedef union -{ - cl_uint CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uint8 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[8]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[4]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8[2]; -#endif -#if defined( __CL_UINT16__ ) - __cl_uint16 v16; -#endif -}cl_uint16; - -/* ---- cl_longn ---- */ -typedef union -{ - cl_long CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_long lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2; -#endif -}cl_long2; - -typedef union -{ - cl_long CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_long2 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[2]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4; -#endif -}cl_long4; - -/* cl_long3 is identical in size, alignment and behavior to cl_long4. See section 6.1.5. */ -typedef cl_long4 cl_long3; - -typedef union -{ - cl_long CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_long4 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[4]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[2]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8; -#endif -}cl_long8; - -typedef union -{ - cl_long CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_long8 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[8]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[4]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8[2]; -#endif -#if defined( __CL_LONG16__ ) - __cl_long16 v16; -#endif -}cl_long16; - - -/* ---- cl_ulongn ---- */ -typedef union -{ - cl_ulong CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ulong lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2; -#endif -}cl_ulong2; - -typedef union -{ - cl_ulong CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ulong2 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[2]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4; -#endif -}cl_ulong4; - -/* cl_ulong3 is identical in size, alignment and behavior to cl_ulong4. See section 6.1.5. */ -typedef cl_ulong4 cl_ulong3; - -typedef union -{ - cl_ulong CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ulong4 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[4]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[2]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8; -#endif -}cl_ulong8; - -typedef union -{ - cl_ulong CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ulong8 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[8]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[4]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8[2]; -#endif -#if defined( __CL_ULONG16__ ) - __cl_ulong16 v16; -#endif -}cl_ulong16; - - -/* --- cl_floatn ---- */ - -typedef union -{ - cl_float CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_float lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2; -#endif -}cl_float2; - -typedef union -{ - cl_float CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_float2 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[2]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4; -#endif -}cl_float4; - -/* cl_float3 is identical in size, alignment and behavior to cl_float4. See section 6.1.5. */ -typedef cl_float4 cl_float3; - -typedef union -{ - cl_float CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_float4 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[4]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[2]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8; -#endif -}cl_float8; - -typedef union -{ - cl_float CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_float8 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[8]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[4]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8[2]; -#endif -#if defined( __CL_FLOAT16__ ) - __cl_float16 v16; -#endif -}cl_float16; - -/* --- cl_doublen ---- */ - -typedef union -{ - cl_double CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_double lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2; -#endif -}cl_double2; - -typedef union -{ - cl_double CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_double2 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[2]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4; -#endif -}cl_double4; - -/* cl_double3 is identical in size, alignment and behavior to cl_double4. See section 6.1.5. */ -typedef cl_double4 cl_double3; - -typedef union -{ - cl_double CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_double4 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[4]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[2]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8; -#endif -}cl_double8; - -typedef union -{ - cl_double CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_double8 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[8]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[4]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8[2]; -#endif -#if defined( __CL_DOUBLE16__ ) - __cl_double16 v16; -#endif -}cl_double16; - -/* Macro to facilitate debugging - * Usage: - * Place CL_PROGRAM_STRING_DEBUG_INFO on the line before the first line of your source. - * The first line ends with: CL_PROGRAM_STRING_DEBUG_INFO \" - * Each line thereafter of OpenCL C source must end with: \n\ - * The last line ends in "; - * - * Example: - * - * const char *my_program = CL_PROGRAM_STRING_DEBUG_INFO "\ - * kernel void foo( int a, float * b ) \n\ - * { \n\ - * // my comment \n\ - * *b[ get_global_id(0)] = a; \n\ - * } \n\ - * "; - * - * This should correctly set up the line, (column) and file information for your source - * string so you can do source level debugging. - */ -#define __CL_STRINGIFY( _x ) # _x -#define _CL_STRINGIFY( _x ) __CL_STRINGIFY( _x ) -#define CL_PROGRAM_STRING_DEBUG_INFO "#line " _CL_STRINGIFY(__LINE__) " \"" __FILE__ "\" \n\n" - -#ifdef __cplusplus -} -#endif - -#undef __CL_HAS_ANON_STRUCT__ -#undef __CL_ANON_STRUCT__ -#if defined( _WIN32) && (_MSC_VER >= 1500) -#pragma warning( pop ) -#endif - -#endif /* __CL_PLATFORM_H */ diff --git a/third_party/opencl/include/CL/opencl.h b/third_party/opencl/include/CL/opencl.h deleted file mode 100644 index 9855cd75e7..0000000000 --- a/third_party/opencl/include/CL/opencl.h +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are 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 Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE 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 - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_H -#define __OPENCL_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - -#include -#include -#include -#include - -#else - -#include -#include -#include -#include - -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_H */ - diff --git a/tinygrad_repo b/tinygrad_repo index 7296c74cbd..3501a71478 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 7296c74cbd2666da7dce95d7ca6dab5340653a5c +Subproject commit 3501a714785ff370cffb966a45d5f9cdf6c9ea7a diff --git a/tools/bodyteleop/web.py b/tools/bodyteleop/web.py index fd8f691d19..f91d6a1441 100644 --- a/tools/bodyteleop/web.py +++ b/tools/bodyteleop/web.py @@ -1,4 +1,3 @@ -import asyncio import dataclasses import json import logging @@ -6,8 +5,6 @@ import os import ssl import subprocess -import pyaudio -import wave from aiohttp import web from aiohttp import ClientSession @@ -22,35 +19,6 @@ TELEOPDIR = f"{BASEDIR}/tools/bodyteleop" WEBRTCD_HOST, WEBRTCD_PORT = "localhost", 5001 -## UTILS -async def play_sound(sound: str): - SOUNDS = { - "engage": "selfdrive/assets/sounds/engage.wav", - "disengage": "selfdrive/assets/sounds/disengage.wav", - "error": "selfdrive/assets/sounds/warning_immediate.wav", - } - assert sound in SOUNDS - - chunk = 5120 - with wave.open(os.path.join(BASEDIR, SOUNDS[sound]), "rb") as wf: - def callback(in_data, frame_count, time_info, status): - data = wf.readframes(frame_count) - return data, pyaudio.paContinue - - p = pyaudio.PyAudio() - stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), - channels=wf.getnchannels(), - rate=wf.getframerate(), - output=True, - frames_per_buffer=chunk, - stream_callback=callback) - stream.start_stream() - while stream.is_active(): - await asyncio.sleep(0) - stream.stop_stream() - stream.close() - p.terminate() - ## SSL def create_ssl_cert(cert_path: str, key_path: str): try: @@ -86,14 +54,6 @@ async def ping(request: 'web.Request'): return web.Response(text="pong") -async def sound(request: 'web.Request'): - params = await request.json() - sound_to_play = params["sound"] - - await play_sound(sound_to_play) - return web.json_response({"status": "ok"}) - - async def offer(request: 'web.Request'): params = await request.json() body = StreamRequestBody(params["sdp"], ["driver"], ["testJoystick"], ["carState"]) @@ -111,14 +71,13 @@ def main(): # Enable joystick debug mode Params().put_bool("JoystickDebugMode", True) - # App needs to be HTTPS for microphone and audio autoplay to work on the browser + # App needs to be HTTPS for WebRTC to work on the browser ssl_context = create_ssl_context() app = web.Application() app.router.add_get("/", index) app.router.add_get("/ping", ping, allow_head=True) app.router.add_post("/offer", offer) - app.router.add_post("/sound", sound) app.router.add_static('/static', os.path.join(TELEOPDIR, 'static')) web.run_app(app, access_log=None, host="0.0.0.0", port=5000, ssl_context=ssl_context) diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore index 362a51f5c9..3d64f83204 100644 --- a/tools/cabana/.gitignore +++ b/tools/cabana/.gitignore @@ -1,6 +1,6 @@ moc_* *.moc -cabana +_cabana dbc/car_fingerprint_to_dbc.json tests/test_cabana diff --git a/tools/cabana/README.md b/tools/cabana/README.md index 7933098e34..a721b1aa13 100644 --- a/tools/cabana/README.md +++ b/tools/cabana/README.md @@ -45,17 +45,17 @@ cabana --demo To load a specific route for replay, provide the route as an argument: ```shell -cabana "a2a0ccea32023010|2023-07-27--13-01-19" +cabana "5beb9b58bd12b691/0000010a--a51155e496" ``` -Replace "0ccea32023010|2023-07-27--13-01-19" with your desired route identifier. +Replace "5beb9b58bd12b691/0000010a--a51155e496" with your desired route identifier. ### Running Cabana with multiple cameras To run Cabana with multiple cameras, use the following command: ```shell -cabana "a2a0ccea32023010|2023-07-27--13-01-19" --dcam --ecam +cabana "5beb9b58bd12b691/0000010a--a51155e496" --dcam --ecam ``` ### Streaming CAN Messages from a comma Device diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d4cfc67280..cecb5ed8d9 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -1,14 +1,30 @@ import subprocess import os +import shutil -Import('env', 'arch', 'common', 'messaging', 'visionipc', 'replay_lib', 'cereal') +Import('env', 'arch', 'common', 'messaging', 'visionipc', 'cereal') + +# Detect Qt - skip build if not available +if arch == "Darwin": + try: + brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() + has_qt = os.path.isdir(os.path.join(brew_prefix, "opt/qt@5")) + except (FileNotFoundError, subprocess.CalledProcessError): + has_qt = False +else: + has_qt = shutil.which('qmake') is not None + +if not has_qt: + Return() + +SConscript(['#tools/replay/SConscript']) +Import('replay_lib') qt_env = env.Clone() qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "DBus", "Xml"] qt_libs = [] if arch == "Darwin": - brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() qt_env['QTDIR'] = f"{brew_prefix}/opt/qt@5" qt_dirs = [ os.path.join(qt_env['QTDIR'], "include"), @@ -31,12 +47,7 @@ else: qt_dirs += [f"{qt_install_headers}/QtGui/{qt_gui_dirs[0]}/QtGui", ] if qt_gui_dirs else [] qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules] - qt_libs = [f"Qt5{m}" for m in qt_modules] - if arch == "larch64": - qt_libs += ["GLESv2", "wayland-client"] - qt_env.PrependENVPath('PATH', Dir("#third_party/qt5/larch64/bin/").abspath) - elif arch != "Darwin": - qt_libs += ["GL"] + qt_libs = [f"Qt5{m}" for m in qt_modules] + ["GL"] qt_env['QT3DIR'] = qt_env['QTDIR'] qt_env.Tool('qt3') @@ -57,19 +68,20 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": - base_frameworks.append('OpenCL') base_frameworks.append('QtCharts') base_frameworks.append('QtSerialBus') else: - base_libs.append('OpenCL') base_libs.append('Qt5Charts') base_libs.append('Qt5SerialBus') qt_libs = base_libs cabana_env = qt_env.Clone() +if arch == "Darwin": + cabana_env['CPPPATH'] += [f"{brew_prefix}/include"] + cabana_env['LIBPATH'] += [f"{brew_prefix}/lib"] -cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs +cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'usb-1.0'] + qt_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] @@ -81,11 +93,11 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "asset 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', 'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', - 'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc', 'utils/api.cc', + 'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc', 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc', 'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) -cabana_env.Program('cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) +cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): cabana_env.Program('tests/test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs]) diff --git a/tools/cabana/cabana b/tools/cabana/cabana new file mode 100755 index 0000000000..00709734a5 --- /dev/null +++ b/tools/cabana/cabana @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +ROOT="$(cd "$DIR/../../" && pwd)" + +install_qt() { + if [[ "$(uname)" == "Darwin" ]]; then + brew install qt@5 + brew link qt@5 || true + else + SUDO="" + if [[ ! $(id -u) -eq 0 ]]; then + SUDO="sudo" + fi + $SUDO apt-get install -y --no-install-recommends \ + qtbase5-dev \ + qtbase5-dev-tools \ + qttools5-dev-tools \ + libqt5charts5-dev \ + libqt5svg5-dev \ + libqt5serialbus5-dev \ + libqt5x11extras5-dev \ + libqt5opengl5-dev + fi +} + +# Install Qt if not found +if ! command -v qmake &> /dev/null; then + echo "Qt not found, installing dependencies..." + install_qt +fi + +# Build _cabana +cd "$ROOT" +scons -j"$(nproc)" tools/cabana/_cabana + +exec "$DIR/_cabana" "$@" diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index bc2380e550..14491b1eff 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -571,7 +571,7 @@ void ChartView::showTip(double sec) { if (s.series->isVisible()) { QString value = "--"; // 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; }); + auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), sec, [](auto &p, double v) { return p.x() > v; }); if (it != s.vals.crend() && it->x() >= axis_x->min()) { value = s.sig->formatValue(it->y(), false); s.track_pt = *it; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 1ea3733ed0..a7040f891e 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -24,6 +24,8 @@ #include "tools/cabana/streamselector.h" #include "tools/cabana/tools/findsignal.h" #include "tools/cabana/utils/export.h" +#include "tools/replay/py_downloader.h" +#include "tools/replay/util.h" MainWindow::MainWindow(AbstractStream *stream, const QString &dbc_file) : QMainWindow() { loadFingerprints(); diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index 0a0c07a155..a9ceacd806 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -34,12 +34,12 @@ SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(p } void SignalModel::insertItem(SignalModel::Item *root_item, int pos, const cabana::Signal *sig) { - Item *parent_item = new Item{.sig = sig, .parent = root_item, .title = sig->name, .type = Item::Sig}; + Item *parent_item = new Item{.type = Item::Sig, .parent = root_item, .sig = sig, .title = sig->name}; root_item->children.insert(pos, parent_item); QString titles[]{"Name", "Size", "Receiver Nodes", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info", "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Table"}; for (int i = 0; i < std::size(titles); ++i) { - auto item = new Item{.sig = sig, .parent = parent_item, .title = titles[i], .type = (Item::Type)(i + Item::Name)}; + auto item = new Item{.type = (Item::Type)(i + Item::Name), .parent = parent_item, .sig = sig, .title = titles[i]}; parent_item->children.push_back(item); if (item->type == Item::ExtraInfo) { parent_item = item; diff --git a/tools/cabana/streams/devicestream.cc b/tools/cabana/streams/devicestream.cc index 462dd7a361..20eaa70cd6 100644 --- a/tools/cabana/streams/devicestream.cc +++ b/tools/cabana/streams/devicestream.cc @@ -6,7 +6,9 @@ #include "cereal/services.h" #include +#include #include +#include #include #include #include @@ -17,12 +19,37 @@ DeviceStream::DeviceStream(QObject *parent, QString address) : zmq_address(address), LiveStream(parent) { } +DeviceStream::~DeviceStream() { + if (!bridge_process) + return; + + bridge_process->terminate(); + if (!bridge_process->waitForFinished(3000)) { + bridge_process->kill(); + bridge_process->waitForFinished(); + } +} + +void DeviceStream::start() { + if (!zmq_address.isEmpty()) { + bridge_process = new QProcess(this); + QString bridge_path = QCoreApplication::applicationDirPath() + "/../../cereal/messaging/bridge"; + bridge_process->start(QFileInfo(bridge_path).absoluteFilePath(), QStringList { zmq_address, "/\"can/\"" }); + + if (!bridge_process->waitForStarted()) { + QMessageBox::warning(nullptr, tr("Error"), tr("Failed to start bridge: %1").arg(bridge_process->errorString())); + return; + } + } + + LiveStream::start(); +} + void DeviceStream::streamThread() { zmq_address.isEmpty() ? unsetenv("ZMQ") : setenv("ZMQ", "1", 1); std::unique_ptr context(Context::create()); - std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString(); - std::unique_ptr sock(SubSocket::create(context.get(), "can", address, false, true, services.at("can").queue_size)); + std::unique_ptr sock(SubSocket::create(context.get(), "can", "127.0.0.1", false, true, services.at("can").queue_size)); assert(sock != NULL); // run as fast as messages come in while (!QThread::currentThread()->isInterruptionRequested()) { diff --git a/tools/cabana/streams/devicestream.h b/tools/cabana/streams/devicestream.h index 3bdf224998..6beb300d7a 100644 --- a/tools/cabana/streams/devicestream.h +++ b/tools/cabana/streams/devicestream.h @@ -2,16 +2,21 @@ #include "tools/cabana/streams/livestream.h" +#include + class DeviceStream : public LiveStream { Q_OBJECT public: DeviceStream(QObject *parent, QString address = {}); + ~DeviceStream(); inline QString routeName() const override { return QString("Live Streaming From %1").arg(zmq_address.isEmpty() ? "127.0.0.1" : zmq_address); } protected: + void start() override; void streamThread() override; + QProcess *bridge_process = nullptr; const QString zmq_address; }; diff --git a/tools/cabana/streams/routes.cc b/tools/cabana/streams/routes.cc index 5915c98065..8539a00b5b 100644 --- a/tools/cabana/streams/routes.cc +++ b/tools/cabana/streams/routes.cc @@ -1,27 +1,33 @@ #include "tools/cabana/streams/routes.h" +#include #include #include #include #include #include +#include #include #include #include +#include +#include -class OneShotHttpRequest : public HttpRequest { -public: - OneShotHttpRequest(QObject *parent) : HttpRequest(parent, false) {} - void send(const QString &url) { - if (reply) { - reply->disconnect(); - reply->abort(); - reply->deleteLater(); - reply = nullptr; - } - sendRequest(url); +#include "tools/replay/py_downloader.h" + +namespace { + +// Parse a PyDownloader JSON response into (success, error_code). +std::pair checkApiResponse(const std::string &result) { + if (result.empty()) return {false, 500}; + auto doc = QJsonDocument::fromJson(QByteArray::fromStdString(result)); + if (doc.isObject() && doc.object().contains("error")) { + return {false, doc.object()["error"].toString() == "unauthorized" ? 401 : 500}; } -}; + return {true, 0}; +} + +} // namespace // The RouteListWidget class extends QListWidget to display a custom message when empty class RouteListWidget : public QListWidget { @@ -41,7 +47,7 @@ public: QString empty_text_ = tr("No items"); }; -RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent), route_requester_(new OneShotHttpRequest(this)) { +RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Remote routes")); QFormLayout *layout = new QFormLayout(this); @@ -52,41 +58,40 @@ RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent), route_requester_( 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); period_selector_->addItem(tr("Preserved"), -1); - // Connect signals and slots - QObject::connect(route_requester_, &HttpRequest::requestDone, this, &RoutesDialog::parseRouteList); 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); + connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); - // Send request to fetch devices - HttpRequest *http = new HttpRequest(this, false); - QObject::connect(http, &HttpRequest::requestDone, this, &RoutesDialog::parseDeviceList); - http->sendRequest(CommaApi::BASE_URL + "/v1/me/devices/"); + // Fetch devices + QPointer self = this; + QtConcurrent::run([self]() { + std::string result = PyDownloader::getDevices(); + auto [success, error_code] = checkApiResponse(result); + QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code]() { + if (self) self->parseDeviceList(r, success, error_code); + }, Qt::QueuedConnection); + }); } -void RoutesDialog::parseDeviceList(const QString &json, bool success, QNetworkReply::NetworkError err) { +void RoutesDialog::parseDeviceList(const QString &json, bool success, int error_code) { if (success) { device_list_->clear(); - auto devices = QJsonDocument::fromJson(json.toUtf8()).array(); - for (const QJsonValue &device : devices) { + for (const QJsonValue &device : QJsonDocument::fromJson(json.toUtf8()).array()) { 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")); + QMessageBox::warning(this, tr("Error"), error_code == 401 ? tr("Unauthorized. Authenticate with tools/lib/auth.py") : tr("Network error")); reject(); } - sender()->deleteLater(); } void RoutesDialog::fetchRoutes() { @@ -95,21 +100,31 @@ void RoutesDialog::fetchRoutes() { route_list_->clear(); route_list_->setEmptyText(tr("Loading...")); - // Construct URL with selected device and date range - QString url = QString("%1/v1/devices/%2").arg(CommaApi::BASE_URL, device_list_->currentText()); + + std::string did = device_list_->currentText().toStdString(); int period = period_selector_->currentData().toInt(); - if (period == -1) { - url += "/routes/preserved"; - } else { + + bool preserved = (period == -1); + int64_t start_ms = 0, end_ms = 0; + if (!preserved) { QDateTime now = QDateTime::currentDateTime(); - url += QString("/routes_segments?start=%1&end=%2") - .arg(now.addDays(-period).toMSecsSinceEpoch()) - .arg(now.toMSecsSinceEpoch()); + start_ms = now.addDays(-period).toMSecsSinceEpoch(); + end_ms = now.toMSecsSinceEpoch(); } - route_requester_->send(url); + + int request_id = ++fetch_id_; + QPointer self = this; + QtConcurrent::run([self, did, start_ms, end_ms, preserved, request_id]() { + std::string result = PyDownloader::getDeviceRoutes(did, start_ms, end_ms, preserved); + if (!self || self->fetch_id_ != request_id) return; + auto [success, error_code] = checkApiResponse(result); + QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code, request_id]() { + if (self && self->fetch_id_ == request_id) self->parseRouteList(r, success, error_code); + }, Qt::QueuedConnection); + }); } -void RoutesDialog::parseRouteList(const QString &json, bool success, QNetworkReply::NetworkError err) { +void RoutesDialog::parseRouteList(const QString &json, bool success, int error_code) { if (success) { for (const QJsonValue &route : QJsonDocument::fromJson(json.toUtf8()).array()) { QDateTime from, to; diff --git a/tools/cabana/streams/routes.h b/tools/cabana/streams/routes.h index 045dc67220..99fa67ef8c 100644 --- a/tools/cabana/streams/routes.h +++ b/tools/cabana/streams/routes.h @@ -1,11 +1,10 @@ #pragma once +#include #include #include -#include "tools/cabana/utils/api.h" class RouteListWidget; -class OneShotHttpRequest; class RoutesDialog : public QDialog { Q_OBJECT @@ -14,12 +13,12 @@ public: QString route(); protected: - void parseDeviceList(const QString &json, bool success, QNetworkReply::NetworkError err); - void parseRouteList(const QString &json, bool success, QNetworkReply::NetworkError err); + void parseDeviceList(const QString &json, bool success, int error_code); + void parseRouteList(const QString &json, bool success, int error_code); void fetchRoutes(); QComboBox *device_list_; QComboBox *period_selector_; RouteListWidget *route_list_; - OneShotHttpRequest *route_requester_; + std::atomic fetch_id_{0}; }; diff --git a/tools/cabana/utils/api.cc b/tools/cabana/utils/api.cc deleted file mode 100644 index 89379a8434..0000000000 --- a/tools/cabana/utils/api.cc +++ /dev/null @@ -1,171 +0,0 @@ -#include "tools/cabana/utils/api.h" - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "common/params.h" -#include "common/util.h" -#include "system/hardware/hw.h" -#include "tools/cabana/utils/util.h" - -QString getVersion() { - static QString version = QString::fromStdString(Params().get("Version")); - return version; -} - -QString getUserAgent() { - return "openpilot-" + getVersion(); -} - -std::optional getDongleId() { - std::string id = Params().get("DongleId"); - - if (!id.empty() && (id != "UnregisteredDevice")) { - return QString::fromStdString(id); - } else { - return {}; - } -} - -namespace CommaApi { - -EVP_PKEY *get_private_key() { - static std::unique_ptr pkey(nullptr, EVP_PKEY_free); - if (!pkey) { - FILE *fp = fopen(Path::rsa_file().c_str(), "rb"); - if (!fp) { - qDebug() << "No private key found, please run manager.py or registration.py"; - return nullptr; - } - pkey.reset(PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr)); - fclose(fp); - } - return pkey.get(); -} - -QByteArray rsa_sign(const QByteArray &data) { - EVP_PKEY *pkey = get_private_key(); - if (!pkey) return {}; - - EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); - if (!mdctx) return {}; - - QByteArray sig(EVP_PKEY_size(pkey), Qt::Uninitialized); - size_t sig_len = sig.size(); - - int ret = EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey); - ret &= EVP_DigestSignUpdate(mdctx, data.data(), data.size()); - ret &= EVP_DigestSignFinal(mdctx, (unsigned char*)sig.data(), &sig_len); - - EVP_MD_CTX_free(mdctx); - - if (ret != 1) return {}; - sig.resize(sig_len); - return sig; -} - -QString create_jwt(const QJsonObject &payloads, int expiry) { - QJsonObject header = {{"alg", "RS256"}}; - - auto t = QDateTime::currentSecsSinceEpoch(); - QJsonObject payload = {{"identity", getDongleId().value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}}; - for (auto it = payloads.begin(); it != payloads.end(); ++it) { - payload.insert(it.key(), it.value()); - } - - auto b64_opts = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals; - QString jwt = QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64(b64_opts) + '.' + - QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts); - - auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256); - return jwt + "." + rsa_sign(hash).toBase64(b64_opts); -} - -} // namespace CommaApi - -HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout) : create_jwt(create_jwt), QObject(parent) { - networkTimer = new QTimer(this); - networkTimer->setSingleShot(true); - networkTimer->setInterval(timeout); - connect(networkTimer, &QTimer::timeout, this, &HttpRequest::requestTimeout); -} - -bool HttpRequest::active() const { - return reply != nullptr; -} - -bool HttpRequest::timeout() const { - return reply && reply->error() == QNetworkReply::OperationCanceledError; -} - -void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method) { - if (active()) { - qDebug() << "HttpRequest is active"; - return; - } - QString token; - if (create_jwt) { - token = CommaApi::create_jwt(); - } else { - QString token_json = QString::fromStdString(util::read_file(util::getenv("HOME") + "/.comma/auth.json")); - QJsonDocument json_d = QJsonDocument::fromJson(token_json.toUtf8()); - token = json_d["access_token"].toString(); - } - - QNetworkRequest request; - request.setUrl(QUrl(requestURL)); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - - if (!token.isEmpty()) { - request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8()); - } - - if (method == HttpRequest::Method::GET) { - reply = nam()->get(request); - } else if (method == HttpRequest::Method::DELETE) { - reply = nam()->deleteResource(request); - } - - networkTimer->start(); - connect(reply, &QNetworkReply::finished, this, &HttpRequest::requestFinished); -} - -void HttpRequest::requestTimeout() { - reply->abort(); -} - -void HttpRequest::requestFinished() { - networkTimer->stop(); - - if (reply->error() == QNetworkReply::NoError) { - emit requestDone(reply->readAll(), true, reply->error()); - } else { - QString error; - if (reply->error() == QNetworkReply::OperationCanceledError) { - nam()->clearAccessCache(); - nam()->clearConnectionCache(); - error = "Request timed out"; - } else { - error = reply->errorString(); - } - emit requestDone(error, false, reply->error()); - } - - reply->deleteLater(); - reply = nullptr; -} - -QNetworkAccessManager *HttpRequest::nam() { - static QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(qApp); - return networkAccessManager; -} diff --git a/tools/cabana/utils/api.h b/tools/cabana/utils/api.h deleted file mode 100644 index ad64d7e722..0000000000 --- a/tools/cabana/utils/api.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "common/util.h" - -namespace CommaApi { - -const QString BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str(); -QByteArray rsa_sign(const QByteArray &data); -QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600); - -} // namespace CommaApi - -/** - * Makes a request to the request endpoint. - */ - -class HttpRequest : public QObject { - Q_OBJECT - -public: - enum class Method {GET, DELETE}; - - explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000); - void sendRequest(const QString &requestURL, const Method method = Method::GET); - bool active() const; - bool timeout() const; - -signals: - void requestDone(const QString &response, bool success, QNetworkReply::NetworkError error); - -protected: - QNetworkReply *reply = nullptr; - -private: - static QNetworkAccessManager *nam(); - QTimer *networkTimer = nullptr; - bool create_jwt; - -private slots: - void requestTimeout(); - void requestFinished(); -}; diff --git a/tools/cabana/utils/util.cc b/tools/cabana/utils/util.cc index ca069b358d..61e4ee0514 100644 --- a/tools/cabana/utils/util.cc +++ b/tools/cabana/utils/util.cc @@ -166,13 +166,13 @@ UnixSignalHandler::~UnixSignalHandler() { } void UnixSignalHandler::signalHandler(int s) { - ::write(sig_fd[0], &s, sizeof(s)); + (void)!::write(sig_fd[0], &s, sizeof(s)); } void UnixSignalHandler::handleSigTerm() { sn->setEnabled(false); int tmp; - ::read(sig_fd[1], &tmp, sizeof(tmp)); + (void)!::read(sig_fd[1], &tmp, sizeof(tmp)); printf("\nexiting...\n"); qApp->closeAllWindows(); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 55018f28f0..a05bf24b4a 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -16,13 +16,14 @@ const int MIN_VIDEO_HEIGHT = 100; const int THUMBNAIL_MARGIN = 3; +// Indexed by TimelineType: None, Engaged, AlertInfo, AlertWarning, AlertCritical, UserBookmark static const QColor timeline_colors[] = { - [(int)TimelineType::None] = QColor(111, 143, 175), - [(int)TimelineType::Engaged] = QColor(0, 163, 108), - [(int)TimelineType::UserBookmark] = Qt::magenta, - [(int)TimelineType::AlertInfo] = Qt::green, - [(int)TimelineType::AlertWarning] = QColor(255, 195, 0), - [(int)TimelineType::AlertCritical] = QColor(199, 0, 57), + QColor(111, 143, 175), + QColor(0, 163, 108), + Qt::green, + QColor(255, 195, 0), + QColor(199, 0, 57), + Qt::magenta, }; static Replay *getReplay() { diff --git a/tools/clip/run.py b/tools/clip/run.py index d338f097d6..a5587f239b 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -24,7 +24,7 @@ from openpilot.common.utils import Timer from msgq.visionipc import VisionIpcServer, VisionStreamType FRAMERATE = 20 -DEMO_ROUTE, DEMO_START, DEMO_END = 'a2a0ccea32023010/2023-07-27--13-01-19', 90, 105 +DEMO_ROUTE, DEMO_START, DEMO_END = '5beb9b58bd12b691/0000010a--a51155e496', 90, 105 logger = logging.getLogger('clip') @@ -85,7 +85,7 @@ def _parse_and_chunk_segment(args: tuple) -> list[dict]: if not messages: return [] - dt_ns, chunks, current, next_time = 1e9 / fps, [], {}, messages[0].logMonoTime + 1e9 / fps # type: ignore[var-annotated] + dt_ns, chunks, current, next_time = 1e9 / fps, [], {}, messages[0].logMonoTime + 1e9 / fps for msg in messages: if msg.logMonoTime >= next_time: chunks.append(current) @@ -159,7 +159,7 @@ def iter_segment_frames(camera_paths, start_time, end_time, fps=20, use_qcam=Fal seg_frames = FrameReader(path, pix_fmt="nv12") assert seg_frames is not None - frame = seg_frames[local_idx] if use_qcam else seg_frames.get(local_idx) # type: ignore[index, union-attr] + frame = seg_frames[local_idx] if use_qcam else seg_frames.get(local_idx) yield global_idx, frame @@ -286,7 +286,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, if big: from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView else: - from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView # type: ignore[assignment] + from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight timer.lap("import") diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh deleted file mode 100755 index 4454845fcd..0000000000 --- a/tools/install_python_dependencies.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Increase the pip timeout to handle TimeoutError -export PIP_DEFAULT_TIMEOUT=200 - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -ROOT="$DIR"/../ -cd "$ROOT" - -if ! command -v "uv" > /dev/null 2>&1; then - echo "installing uv..." - curl -LsSf --retry 5 --retry-delay 5 --retry-all-errors https://astral.sh/uv/install.sh | sh - UV_BIN="$HOME/.local/bin" - PATH="$UV_BIN:$PATH" -fi - -echo "updating uv..." -# ok to fail, can also fail due to installing with brew -uv self update || true - -echo "installing python packages..." -uv sync --frozen --all-extras -source .venv/bin/activate - -if [[ "$(uname)" == 'Darwin' ]]; then - touch "$ROOT"/.env - echo "export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES" >> "$ROOT"/.env -fi diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh deleted file mode 100755 index 5c2131d4bf..0000000000 --- a/tools/install_ubuntu_dependencies.sh +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env bash -set -e - -SUDO="" - -# Use sudo if not root -if [[ ! $(id -u) -eq 0 ]]; then - if [[ -z $(which sudo) ]]; then - echo "Please install sudo or run as root" - exit 1 - fi - SUDO="sudo" -fi - -# Check if stdin is open -if [ -t 0 ]; then - INTERACTIVE=1 -fi - -# Install common packages -function install_ubuntu_common_requirements() { - $SUDO apt-get update - - # normal stuff, mostly for the bare docker image - $SUDO apt-get install -y --no-install-recommends \ - ca-certificates \ - clang \ - build-essential \ - curl \ - libssl-dev \ - libcurl4-openssl-dev \ - locales \ - git \ - git-lfs \ - xvfb - - # TODO: vendor the rest of these in third_party/ - $SUDO apt-get install -y --no-install-recommends \ - gcc-arm-none-eabi \ - capnproto \ - libcapnp-dev \ - ffmpeg \ - libavformat-dev \ - libavcodec-dev \ - libavdevice-dev \ - libavutil-dev \ - libavfilter-dev \ - libbz2-dev \ - libeigen3-dev \ - libffi-dev \ - libgles2-mesa-dev \ - libglfw3-dev \ - libglib2.0-0 \ - libjpeg-dev \ - libqt5charts5-dev \ - libncurses5-dev \ - libusb-1.0-0-dev \ - libzmq3-dev \ - libzstd-dev \ - libsqlite3-dev \ - opencl-headers \ - ocl-icd-libopencl1 \ - ocl-icd-opencl-dev \ - portaudio19-dev \ - qttools5-dev-tools \ - libqt5svg5-dev \ - libqt5serialbus5-dev \ - libqt5x11extras5-dev \ - libqt5opengl5-dev \ - gettext -} - -# Install Ubuntu 24.04 LTS packages -function install_ubuntu_lts_latest_requirements() { - install_ubuntu_common_requirements - - $SUDO apt-get install -y --no-install-recommends \ - g++-12 \ - qtbase5-dev \ - qtbase5-dev-tools \ - python3-dev \ - python3-venv -} - -# Detect OS using /etc/os-release file -if [ -f "/etc/os-release" ]; then - source /etc/os-release - case "$VERSION_CODENAME" in - "jammy" | "kinetic" | "noble") - install_ubuntu_lts_latest_requirements - ;; - *) - echo "$ID $VERSION_ID is unsupported. This setup script is written for Ubuntu 24.04." - read -p "Would you like to attempt installation anyway? " -n 1 -r - echo "" - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 - fi - install_ubuntu_lts_latest_requirements - esac - - if [[ -d "/etc/udev/rules.d/" ]]; then - # Setup jungle udev rules - $SUDO tee /etc/udev/rules.d/12-panda_jungle.rules > /dev/null < /dev/null < /dev/null < - Get route file URLs as JSON + download - Download URL to local cache, print local path + devices - List user's devices as JSON + device-routes - List routes for a device as JSON +""" +import argparse +import hashlib +import json +import os +import sys +import tempfile +import shutil + +from openpilot.system.hardware.hw import Paths +from openpilot.tools.lib.api import CommaApi, UnauthorizedError, APIError +from openpilot.tools.lib.auth_config import get_token +from openpilot.tools.lib.url_file import URLFile + + +def api_call(func): + """Run an API call, outputting JSON result or error to stdout.""" + try: + result = func(CommaApi(get_token())) + json.dump(result, sys.stdout) + except UnauthorizedError: + json.dump({"error": "unauthorized"}, sys.stdout) + except APIError as e: + error = "not_found" if getattr(e, 'status_code', 0) == 404 else str(e) + json.dump({"error": error}, sys.stdout) + except Exception as e: + json.dump({"error": str(e)}, sys.stdout) + sys.stdout.write("\n") + sys.stdout.flush() + + +def cache_file_path(url): + url_without_query = url.split("?")[0] + return os.path.join(Paths.download_cache_root(), hashlib.sha256(url_without_query.encode()).hexdigest()) + + +def cmd_route_files(args): + api_call(lambda api: api.get(f"v1/route/{args.route}/files")) + + +def cmd_download(args): + url = args.url + use_cache = not args.no_cache + + if use_cache: + local_path = cache_file_path(url) + if os.path.exists(local_path): + sys.stdout.write(local_path + "\n") + sys.stdout.flush() + return + + try: + uf = URLFile(url, cache=False) + total = uf.get_length() + if total <= 0: + sys.stderr.write("ERROR:File not found or empty\n") + sys.stderr.flush() + sys.exit(1) + + os.makedirs(Paths.download_cache_root(), exist_ok=True) + tmp_fd, tmp_path = tempfile.mkstemp(dir=Paths.download_cache_root()) + try: + downloaded = 0 + chunk_size = 1024 * 1024 + with os.fdopen(tmp_fd, 'wb') as f: + while downloaded < total: + data = uf.read(min(chunk_size, total - downloaded)) + f.write(data) + downloaded += len(data) + sys.stderr.write(f"PROGRESS:{downloaded}:{total}\n") + sys.stderr.flush() + + if use_cache: + shutil.move(tmp_path, local_path) + sys.stdout.write(local_path + "\n") + else: + sys.stdout.write(tmp_path + "\n") + except Exception: + try: + os.unlink(tmp_path) + except OSError: + pass + raise + + except Exception as e: + sys.stderr.write(f"ERROR:{e}\n") + sys.stderr.flush() + sys.exit(1) + + sys.stdout.flush() + + +def cmd_devices(args): + api_call(lambda api: api.get("v1/me/devices/")) + + +def cmd_device_routes(args): + def fetch(api): + if args.preserved: + return api.get(f"v1/devices/{args.dongle_id}/routes/preserved") + params = {} + if args.start is not None: + params['start'] = args.start + if args.end is not None: + params['end'] = args.end + return api.get(f"v1/devices/{args.dongle_id}/routes_segments", params=params) + api_call(fetch) + + +def main(): + parser = argparse.ArgumentParser(description="File downloader CLI for openpilot tools") + subparsers = parser.add_subparsers(dest="command", required=True) + + p_rf = subparsers.add_parser("route-files") + p_rf.add_argument("route") + p_rf.set_defaults(func=cmd_route_files) + + p_dl = subparsers.add_parser("download") + p_dl.add_argument("url") + p_dl.add_argument("--no-cache", action="store_true") + p_dl.set_defaults(func=cmd_download) + + p_dev = subparsers.add_parser("devices") + p_dev.set_defaults(func=cmd_devices) + + p_dr = subparsers.add_parser("device-routes") + p_dr.add_argument("dongle_id") + p_dr.add_argument("--start", type=int, default=None) + p_dr.add_argument("--end", type=int, default=None) + p_dr.add_argument("--preserved", action="store_true") + p_dr.set_defaults(func=cmd_device_routes) + + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index c13f7fd313..c923651563 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -51,10 +51,10 @@ def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc', hwaccel "-vsync", "0", "-f", vid_fmt, "-flags2", "showall", - "-i", "-", + "-i", "pipe:0", "-f", "rawvideo", "-pix_fmt", pix_fmt, - "-"] + "pipe:1"] dat = subprocess.check_output(args, input=rawdat) ret: np.ndarray @@ -71,7 +71,7 @@ def ffprobe(fn, fmt=None): cmd = ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams"] if fmt: cmd += ["-f", fmt] - cmd += ["-i", "-"] + cmd += ["-i", "pipe:0"] try: with FileReader(fn) as f: diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index 0151940c44..123f142383 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -7,7 +7,7 @@ import os import pytest import requests -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from cereal import log as capnp_log from openpilot.tools.lib.logreader import LogsUnavailable, LogIterable, LogReader, parse_indirect, ReadMode diff --git a/tools/longitudinal_maneuvers/maneuver_helpers.py b/tools/longitudinal_maneuvers/maneuver_helpers.py new file mode 100644 index 0000000000..9fc65fb9e6 --- /dev/null +++ b/tools/longitudinal_maneuvers/maneuver_helpers.py @@ -0,0 +1,18 @@ +from enum import IntEnum + +class Axis(IntEnum): + TIME = 0 + EGO_POSITION = 1 + LEAD_DISTANCE= 2 + EGO_V = 3 + LEAD_V = 4 + EGO_A = 5 + D_REL = 6 + +axis_labels = {Axis.TIME: 'Time (s)', + Axis.EGO_POSITION: 'Ego position (m)', + Axis.LEAD_DISTANCE: 'Lead absolute position (m)', + Axis.EGO_V: 'Ego Velocity (m/s)', + Axis.LEAD_V: 'Lead Velocity (m/s)', + Axis.EGO_A: 'Ego acceleration (m/s^2)', + Axis.D_REL: 'Lead distance (m)'} diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py index 7623f669cd..ae3fee7355 100644 --- a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -5,29 +5,16 @@ import numpy as np import matplotlib.pyplot as plt from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.controls.tests.test_following_distance import desired_follow_distance +from openpilot.tools.longitudinal_maneuvers.maneuver_helpers import Axis, axis_labels from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver -TIME = 0 -LEAD_DISTANCE= 2 -EGO_V = 3 -EGO_A = 5 -D_REL = 6 - -axis_labels = ['Time (s)', - 'Ego position (m)', - 'Lead absolute position (m)', - 'Ego Velocity (m/s)', - 'Lead Velocity (m/s)', - 'Ego acceleration (m/s^2)', - 'Lead distance (m)' - ] def get_html_from_results(results, labels, AXIS): fig, ax = plt.subplots(figsize=(16, 8)) - for idx, speed in enumerate(list(results.keys())): - ax.plot(results[speed][:, TIME], results[speed][:, AXIS], label=labels[idx]) + for idx, key in enumerate(results.keys()): + ax.plot(results[key][:, Axis.TIME], results[key][:, AXIS], label=labels[idx]) - ax.set_xlabel('Time (s)') + ax.set_xlabel(axis_labels[Axis.TIME]) ax.set_ylabel(axis_labels[AXIS]) ax.legend(bbox_to_anchor=(1.02, 1), loc='upper left', borderaxespad=0) ax.grid(True, linestyle='--', alpha=0.7) @@ -60,8 +47,8 @@ def generate_mpc_tuning_report(): labels.append(f'{lead_accel} m/s^2 lead acceleration') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) results = {} @@ -78,12 +65,11 @@ def generate_mpc_tuning_report(): breakpoints=[0., 30.], ) valid, results[speed] = man.evaluate() - results[speed][:,2] = results[speed][:,2] - results[speed][:,1] labels.append(f'{speed} m/s approach speed') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_A)) - htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) results = {} @@ -104,9 +90,9 @@ def generate_mpc_tuning_report(): labels.append(f'{oscil} m/s oscillation size') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, D_REL)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) results = {} @@ -128,12 +114,12 @@ def generate_mpc_tuning_report(): breakpoints=bps, ) valid, results[oscil] = man.evaluate() - labels.append(f'{oscil} m/s oscilliation size') + labels.append(f'{oscil} m/s oscillation size') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, D_REL)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) results = {} @@ -150,12 +136,11 @@ def generate_mpc_tuning_report(): breakpoints=[0.], ) valid, results[distance] = man.evaluate() - results[distance][:,2] = results[distance][:,2] - results[distance][:,1] labels.append(f'{distance} m initial distance') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) results = {} @@ -172,12 +157,11 @@ def generate_mpc_tuning_report(): breakpoints=[0.], ) valid, results[distance] = man.evaluate() - results[distance][:,2] = results[distance][:,2] - results[distance][:,1] labels.append(f'{distance} m initial distance') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) results = {} @@ -195,12 +179,11 @@ def generate_mpc_tuning_report(): breakpoints=[0., 5., 5 + stop_time], ) valid, results[stop_time] = man.evaluate() - results[stop_time][:,2] = results[stop_time][:,2] - results[stop_time][:,1] labels.append(f'{stop_time} seconds stop time') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_A)) - htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) results = {} @@ -222,8 +205,8 @@ def generate_mpc_tuning_report(): labels.append(f'{speed} m/s speed') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_A)) - htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) results = {} @@ -244,8 +227,8 @@ def generate_mpc_tuning_report(): labels.append(f'{speed} m/s speed') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) results = {} @@ -267,8 +250,8 @@ def generate_mpc_tuning_report(): labels.append(f'{speed} m/s speed') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) results = {} @@ -290,8 +273,8 @@ def generate_mpc_tuning_report(): labels.append(f'{speed} m/s speed') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) return htmls diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh deleted file mode 100755 index ae8a1974ac..0000000000 --- a/tools/mac_setup.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash -set -e - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -ROOT="$(cd $DIR/../ && pwd)" -ARCH=$(uname -m) - -# homebrew update is slow -export HOMEBREW_NO_AUTO_UPDATE=1 - -if [[ $SHELL == "/bin/zsh" ]]; then - RC_FILE="$HOME/.zshrc" -elif [[ $SHELL == "/bin/bash" ]]; then - RC_FILE="$HOME/.bash_profile" -fi - -# Install brew if required -if [[ $(command -v brew) == "" ]]; then - echo "Installing Homebrew" - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - echo "[ ] installed brew t=$SECONDS" - - # make brew available now - if [[ $ARCH == "x86_64" ]]; then - echo 'eval "$(/usr/local/bin/brew shellenv)"' >> $RC_FILE - eval "$(/usr/local/bin/brew shellenv)" - else - echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> $RC_FILE - eval "$(/opt/homebrew/bin/brew shellenv)" - fi -else - brew up -fi - -brew bundle --file=$DIR/Brewfile - -echo "[ ] finished brew install t=$SECONDS" - -BREW_PREFIX=$(brew --prefix) - -# archive backend tools for pip dependencies -export LDFLAGS="$LDFLAGS -L${BREW_PREFIX}/opt/zlib/lib" -export LDFLAGS="$LDFLAGS -L${BREW_PREFIX}/opt/bzip2/lib" -export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/zlib/include" -export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/bzip2/include" - -# pycurl curl/openssl backend dependencies -export LDFLAGS="$LDFLAGS -L${BREW_PREFIX}/opt/openssl@3/lib" -export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/openssl@3/include" -export PYCURL_CURL_CONFIG=/usr/bin/curl-config -export PYCURL_SSL_LIBRARY=openssl - -# install python dependencies -$DIR/install_python_dependencies.sh -echo "[ ] installed python dependencies t=$SECONDS" - -# brew does not link qt5 by default -# check if qt5 can be linked, if not, prompt the user to link it -QT_BIN_LOCATION="$(command -v lupdate || :)" -if [ -n "$QT_BIN_LOCATION" ]; then - # if qt6 is linked, prompt the user to unlink it and link the right version - QT_BIN_VERSION="$(lupdate -version | awk '{print $NF}')" - if [[ ! "$QT_BIN_VERSION" =~ 5\.[0-9]+\.[0-9]+ ]]; then - echo - echo "lupdate/lrelease available at PATH is $QT_BIN_VERSION" - if [[ "$QT_BIN_LOCATION" == "$(brew --prefix)/"* ]]; then - echo "Run the following command to link qt5:" - echo "brew unlink qt@6 && brew link qt@5" - else - echo "Remove conflicting qt entries from PATH and run the following command to link qt5:" - echo "brew link qt@5" - fi - fi -else - brew link qt@5 -fi - -echo -echo "---- OPENPILOT SETUP DONE ----" -echo "Open a new shell or configure your active shell env by running:" -echo "source $RC_FILE" diff --git a/tools/op.sh b/tools/op.sh index 8c41926e0c..7c20403a27 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -35,6 +35,21 @@ function loge() { fi } +function retry() { + local attempts=$1 + shift + for i in $(seq 1 "$attempts"); do + if "$@"; then + return 0 + fi + if [ "$i" -lt "$attempts" ]; then + echo " Attempt $i/$attempts failed, retrying in 5s..." + sleep 5 + fi + done + return 1 +} + function op_run_command() { CMD="$@" @@ -62,7 +77,8 @@ function op_get_openpilot_dir() { done # Fallback to hardcoded directories if not found - for dir in "$HOME/openpilot" "/data/openpilot"; do + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + for dir in "${SCRIPT_DIR%/tools}" "$HOME/openpilot" "/data/openpilot"; do if [[ -f "$dir/launch_openpilot.sh" ]]; then OPENPILOT_ROOT="$dir" return 0 @@ -216,11 +232,7 @@ function op_setup() { echo "Installing dependencies..." st="$(date +%s)" - if [[ "$OSTYPE" == "linux-gnu"* ]]; then - SETUP_SCRIPT="tools/ubuntu_setup.sh" - elif [[ "$OSTYPE" == "darwin"* ]]; then - SETUP_SCRIPT="tools/mac_setup.sh" - fi + SETUP_SCRIPT="tools/setup_dependencies.sh" if ! $OPENPILOT_ROOT/$SETUP_SCRIPT; then echo -e " ↳ [${RED}✗${NC}] Dependencies installation failed!" loge "ERROR_DEPENDENCIES_INSTALLATION" @@ -229,9 +241,11 @@ function op_setup() { et="$(date +%s)" echo -e " ↳ [${GREEN}✔${NC}] Dependencies installed successfully in $((et - st)) seconds." + op_activate_venv + echo "Getting git submodules..." st="$(date +%s)" - if ! git submodule update --jobs 4 --init --recursive; then + if ! retry 3 git submodule update --jobs 4 --init --recursive; then echo -e " ↳ [${RED}✗${NC}] Getting git submodules failed!" loge "ERROR_GIT_SUBMODULES" return 1 @@ -241,7 +255,7 @@ function op_setup() { echo "Pulling git lfs files..." st="$(date +%s)" - if ! git lfs pull; then + if ! retry 3 git lfs pull; then echo -e " ↳ [${RED}✗${NC}] Pulling git lfs files failed!" loge "ERROR_GIT_LFS" return 1 @@ -262,6 +276,11 @@ function op_activate_venv() { set +e source $OPENPILOT_ROOT/.venv/bin/activate &> /dev/null || true set -e + + # persist venv on PATH across GitHub Actions steps + if [ -n "$GITHUB_PATH" ]; then + echo "$OPENPILOT_ROOT/.venv/bin" >> "$GITHUB_PATH" + fi } function op_venv() { @@ -292,6 +311,19 @@ function op_ssh() { op_run_command tools/scripts/ssh.py "$@" } +function op_script() { + op_before_cmd + + case $1 in + som-debug ) op_run_command panda/scripts/som_debug.sh "${@:2}" ;; + * ) + echo -e "Unknown script '$1'. Available scripts:" + echo -e " ${BOLD}som-debug${NC} SOM serial debug console via panda" + return 1 + ;; + esac +} + function op_check() { VERBOSE=1 op_before_cmd @@ -422,6 +454,9 @@ function op_default() { echo -e " ${BOLD}adb${NC} Run adb shell" echo -e " ${BOLD}ssh${NC} comma prime SSH helper" echo "" + echo -e "${BOLD}${UNDERLINE}Commands [Scripts]:${NC}" + echo -e " ${BOLD}script${NC} Run a script (e.g. op script som-debug)" + echo "" echo -e "${BOLD}${UNDERLINE}Commands [Testing]:${NC}" echo -e " ${BOLD}sim${NC} Run openpilot in a simulator" echo -e " ${BOLD}lint${NC} Run the linter" @@ -481,6 +516,7 @@ function _op() { post-commit ) shift 1; op_install_post_commit "$@" ;; adb ) shift 1; op_adb "$@" ;; ssh ) shift 1; op_ssh "$@" ;; + script ) shift 1; op_script "$@" ;; * ) op_default "$@" ;; esac } diff --git a/tools/plotjuggler/README.md b/tools/plotjuggler/README.md index 62905a252d..3249971bfc 100644 --- a/tools/plotjuggler/README.md +++ b/tools/plotjuggler/README.md @@ -35,17 +35,17 @@ optional arguments: Example using route name: -`./juggle.py "a2a0ccea32023010/2023-07-27--13-01-19"` +`./juggle.py "5beb9b58bd12b691/0000010a--a51155e496"` Examples using segment: -`./juggle.py "a2a0ccea32023010/2023-07-27--13-01-19/1"` +`./juggle.py "5beb9b58bd12b691/0000010a--a51155e496/1"` -`./juggle.py "a2a0ccea32023010/2023-07-27--13-01-19/1/q" # use qlogs` +`./juggle.py "5beb9b58bd12b691/0000010a--a51155e496/1/q" # use qlogs` Example using segment range: -`./juggle.py "a2a0ccea32023010/2023-07-27--13-01-19/0:1"` +`./juggle.py "5beb9b58bd12b691/0000010a--a51155e496/0:1"` ## Streaming diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 142e640504..c86945ef6b 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -21,7 +21,7 @@ juggle_dir = os.path.dirname(os.path.realpath(__file__)) os.environ['LD_LIBRARY_PATH'] = os.environ.get('LD_LIBRARY_PATH', '') + f":{juggle_dir}/bin/" -DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" +DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" 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") @@ -31,7 +31,7 @@ MAX_STREAMING_BUFFER_SIZE = 1000 def install(): m = f"{platform.system()}-{platform.machine()}" - supported = ("Linux-x86_64", "Linux-aarch64", "Darwin-arm64", "Darwin-x86_64") + supported = ("Linux-x86_64", "Linux-aarch64", "Darwin-arm64") if m not in supported: raise Exception(f"Unsupported platform: '{m}'. Supported platforms: {supported}") diff --git a/tools/plotjuggler/test_plotjuggler.py b/tools/plotjuggler/test_plotjuggler.py index a2c509f943..26bad25c3e 100644 --- a/tools/plotjuggler/test_plotjuggler.py +++ b/tools/plotjuggler/test_plotjuggler.py @@ -1,9 +1,12 @@ import os import glob +import shutil import signal import subprocess import time +import pytest + from openpilot.common.basedir import BASEDIR from openpilot.common.timeout import Timeout from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE, install @@ -12,6 +15,7 @@ PJ_DIR = os.path.join(BASEDIR, "tools/plotjuggler") class TestPlotJuggler: + @pytest.mark.skipif(not shutil.which('qmake'), reason="Qt not installed") def test_demo(self): install() diff --git a/tools/replay/README.md b/tools/replay/README.md index 794c08f6a3..d2beda9940 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -19,7 +19,7 @@ You can replay a route from your comma account by specifying the route name. tools/replay/replay # Example: -tools/replay/replay 'a2a0ccea32023010|2023-07-27--13-01-19' +tools/replay/replay '5beb9b58bd12b691/0000010a--a51155e496' # Replay the default demo route: tools/replay/replay --demo @@ -34,10 +34,10 @@ tools/replay/replay --data_dir="/path_to/route" # Example: # If you have a local route stored at /path_to_routes with segments like: -# a2a0ccea32023010|2023-07-27--13-01-19--0 -# a2a0ccea32023010|2023-07-27--13-01-19--1 +# 5beb9b58bd12b691/0000010a--a51155e496--0 +# 5beb9b58bd12b691/0000010a--a51155e496--1 # You can replay it like this: -tools/replay/replay "a2a0ccea32023010|2023-07-27--13-01-19" --data_dir="/path_to_routes" +tools/replay/replay "5beb9b58bd12b691/0000010a--a51155e496" --data_dir="/path_to_routes" ``` ## Send Messages via ZMQ diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 136c4119f6..3efa970b37 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -6,18 +6,13 @@ replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] -if arch == "Darwin": - base_frameworks.append('OpenCL') -else: - base_libs.append('OpenCL') - replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", - "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "api.cc"] + "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "py_downloader.cc"] if arch != "Darwin": replay_lib_src.append("qcom_decoder.cc") replay_lib = replay_env.Library("replay", replay_lib_src, LIBS=base_libs, FRAMEWORKS=base_frameworks) Export('replay_lib') -replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'zstd', 'curl', 'yuv', 'ncurses'] + base_libs +replay_libs = [replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'ncurses'] + base_libs replay_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/tools/replay/api.cc b/tools/replay/api.cc deleted file mode 100644 index 49923913de..0000000000 --- a/tools/replay/api.cc +++ /dev/null @@ -1,164 +0,0 @@ - -#include "tools/replay/api.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include "common/params.h" -#include "common/version.h" -#include "system/hardware/hw.h" - -#include "sunnypilot/common/version.h" - -namespace CommaApi2 { - -// Base64 URL-safe character set (uses '-' and '_' instead of '+' and '/') -static const std::string base64url_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789-_"; - -std::string base64url_encode(const std::string &in) { - std::string out; - int val = 0, valb = -6; - for (unsigned char c : in) { - val = (val << 8) + c; - valb += 8; - while (valb >= 0) { - out.push_back(base64url_chars[(val >> valb) & 0x3F]); - valb -= 6; - } - } - if (valb > -6) { - out.push_back(base64url_chars[((val << 8) >> (valb + 8)) & 0x3F]); - } - - return out; -} - -EVP_PKEY *get_rsa_private_key() { - static std::unique_ptr rsa_private(nullptr, EVP_PKEY_free); - if (!rsa_private) { - FILE *fp = fopen(Path::rsa_file().c_str(), "rb"); - if (!fp) { - std::cerr << "No RSA private key found, please run manager.py or registration.py" << std::endl; - return nullptr; - } - rsa_private.reset(PEM_read_PrivateKey(fp, NULL, NULL, NULL)); - fclose(fp); - } - return rsa_private.get(); -} - -std::string rsa_sign(const std::string &data) { - EVP_PKEY *private_key = get_rsa_private_key(); - if (!private_key) return {}; - - EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); - assert(mdctx != nullptr); - - std::vector sig(EVP_PKEY_size(private_key)); - uint32_t sig_len; - - EVP_SignInit(mdctx, EVP_sha256()); - EVP_SignUpdate(mdctx, data.data(), data.size()); - int ret = EVP_SignFinal(mdctx, sig.data(), &sig_len, private_key); - - EVP_MD_CTX_free(mdctx); - - assert(ret == 1); - assert(sig.size() == sig_len); - return std::string(sig.begin(), sig.begin() + sig_len); -} - -std::string create_jwt(const json11::Json &extra, int exp_time) { - int now = std::chrono::seconds(std::time(nullptr)).count(); - std::string dongle_id = Params().get("DongleId"); - - // Create header and payload - json11::Json header = json11::Json::object{{"alg", "RS256"}}; - auto payload = json11::Json::object{ - {"identity", dongle_id}, - {"iat", now}, - {"nbf", now}, - {"exp", now + exp_time}, - }; - // Merge extra payload - for (const auto &item : extra.object_items()) { - payload[item.first] = item.second; - } - - // JWT construction - std::string jwt = base64url_encode(header.dump()) + '.' + - base64url_encode(json11::Json(payload).dump()); - - // Hash and sign - std::string hash(SHA256_DIGEST_LENGTH, '\0'); - SHA256((uint8_t *)jwt.data(), jwt.size(), (uint8_t *)hash.data()); - std::string signature = rsa_sign(hash); - - return jwt + "." + base64url_encode(signature); -} - -std::string create_token(bool use_jwt, const json11::Json &payloads, int expiry) { - if (use_jwt) { - return create_jwt(payloads, expiry); - } - - std::string token_json = util::read_file(util::getenv("HOME") + "/.comma/auth.json"); - std::string err; - auto json = json11::Json::parse(token_json, err); - if (!err.empty()) { - std::cerr << "Error parsing auth.json " << err << std::endl; - return ""; - } - return json["access_token"].string_value(); -} - -std::string httpGet(const std::string &url, long *response_code) { - CURL *curl = curl_easy_init(); - assert(curl); - - std::string readBuffer; - const std::string token = CommaApi2::create_token(!Hardware::PC()); - - // Set up the lambda for the write callback - // The '+' makes the lambda non-capturing, allowing it to be used as a C function pointer - auto writeCallback = +[](char *contents, size_t size, size_t nmemb, std::string *userp) ->size_t{ - size_t totalSize = size * nmemb; - userp->append((char *)contents, totalSize); - return totalSize; - }; - - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - - // Handle headers - struct curl_slist *headers = nullptr; - headers = curl_slist_append(headers, "User-Agent: openpilot-" SUNNYPILOT_VERSION); - if (!token.empty()) { - headers = curl_slist_append(headers, ("Authorization: JWT " + token).c_str()); - } - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - - CURLcode res = curl_easy_perform(curl); - - if (response_code) { - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, response_code); - } - - curl_slist_free_all(headers); - curl_easy_cleanup(curl); - - return res == CURLE_OK ? readBuffer : std::string{}; -} - -} // namespace CommaApi diff --git a/tools/replay/api.h b/tools/replay/api.h deleted file mode 100644 index dff59c0659..0000000000 --- a/tools/replay/api.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include - -#include "common/util.h" -#include "third_party/json11/json11.hpp" - -namespace CommaApi2 { - -const std::string BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str(); -std::string create_token(bool use_jwt, const json11::Json& payloads = {}, int expiry = 3600); -std::string httpGet(const std::string &url, long *response_code = nullptr); - -} // namespace CommaApi2 diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index a57c8a125d..eeb385f8a4 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -9,6 +9,7 @@ #include "common/ratekeeper.h" #include "common/util.h" #include "common/version.h" +#include "tools/replay/py_downloader.h" #include "sunnypilot/common/version.h" diff --git a/tools/replay/filereader.cc b/tools/replay/filereader.cc index cb13775a34..93a6a1193f 100644 --- a/tools/replay/filereader.cc +++ b/tools/replay/filereader.cc @@ -1,49 +1,14 @@ #include "tools/replay/filereader.h" -#include - #include "common/util.h" -#include "system/hardware/hw.h" -#include "tools/replay/util.h" - -std::string cacheFilePath(const std::string &url) { - static std::string cache_path = [] { - const std::string comma_cache = Path::download_cache_root(); - util::create_directories(comma_cache, 0755); - return comma_cache.back() == '/' ? comma_cache : comma_cache + "/"; - }(); - - return cache_path + sha256(getUrlWithoutQuery(url)); -} +#include "tools/replay/py_downloader.h" std::string FileReader::read(const std::string &file, std::atomic *abort) { const bool is_remote = (file.find("https://") == 0) || (file.find("http://") == 0); - const std::string local_file = is_remote ? cacheFilePath(file) : file; - std::string result; - - if ((!is_remote || cache_to_local_) && util::file_exists(local_file)) { - result = util::read_file(local_file); - } else if (is_remote) { - result = download(file, abort); - if (cache_to_local_ && !result.empty()) { - std::ofstream fs(local_file, std::ios::binary | std::ios::out); - fs.write(result.data(), result.size()); - } + if (is_remote) { + std::string local_path = PyDownloader::download(file, cache_to_local_, abort); + if (local_path.empty()) return {}; + return util::read_file(local_path); } - return result; -} - -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); - util::sleep_for(3000); - } - - std::string result = httpGet(url, chunk_size_, abort); - if (!result.empty()) { - return result; - } - } - return {}; + return util::read_file(file); } diff --git a/tools/replay/filereader.h b/tools/replay/filereader.h index 34aa91e858..30740cd0ac 100644 --- a/tools/replay/filereader.h +++ b/tools/replay/filereader.h @@ -5,16 +5,10 @@ class FileReader { public: - FileReader(bool cache_to_local, size_t chunk_size = 0, int retries = 3) - : cache_to_local_(cache_to_local), chunk_size_(chunk_size), max_retries_(retries) {} + FileReader(bool cache_to_local) : cache_to_local_(cache_to_local) {} virtual ~FileReader() {} std::string read(const std::string &file, std::atomic *abort = nullptr); private: - std::string download(const std::string &url, std::atomic *abort); - size_t chunk_size_; - int max_retries_; bool cache_to_local_; }; - -std::string cacheFilePath(const std::string &url); diff --git a/tools/replay/framereader.cc b/tools/replay/framereader.cc index 96ec5915b4..a643f0d3a7 100644 --- a/tools/replay/framereader.cc +++ b/tools/replay/framereader.cc @@ -6,7 +6,8 @@ #include #include "common/util.h" -#include "third_party/libyuv/include/libyuv.h" +#include "libyuv.h" +#include "tools/replay/py_downloader.h" #include "tools/replay/util.h" #include "system/hardware/hw.h" @@ -71,13 +72,13 @@ FrameReader::~FrameReader() { if (input_ctx) avformat_close_input(&input_ctx); } -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 || url.find("http://") == 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; - } +bool FrameReader::load(CameraType type, const std::string &url, bool no_hw_decoder, std::atomic *abort, bool local_cache) { + std::string local_file_path; + if (url.find("https://") == 0 || url.find("http://") == 0) { + local_file_path = PyDownloader::download(url, local_cache, abort); + if (local_file_path.empty()) return false; + } else { + local_file_path = url; } return loadFromFile(type, local_file_path, no_hw_decoder, abort); } diff --git a/tools/replay/framereader.h b/tools/replay/framereader.h index d8e86fce0f..3609d64f8b 100644 --- a/tools/replay/framereader.h +++ b/tools/replay/framereader.h @@ -4,7 +4,6 @@ #include #include "msgq/visionipc/visionbuf.h" -#include "tools/replay/filereader.h" #include "tools/replay/util.h" #ifndef __APPLE__ @@ -22,8 +21,7 @@ class FrameReader { public: FrameReader(); ~FrameReader(); - 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(CameraType type, const std::string &url, bool no_hw_decoder = false, std::atomic *abort = nullptr, bool local_cache = false); bool loadFromFile(CameraType type, const std::string &file, bool no_hw_decoder = false, std::atomic *abort = nullptr); bool get(int idx, VisionBuf *buf); size_t getFrameCount() const { return packets_info.size(); } diff --git a/tools/replay/logreader.cc b/tools/replay/logreader.cc index 75abb8417b..997a4bc00f 100644 --- a/tools/replay/logreader.cc +++ b/tools/replay/logreader.cc @@ -6,8 +6,8 @@ #include "tools/replay/util.h" #include "common/util.h" -bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache, int chunk_size, int retries) { - std::string data = FileReader(local_cache, chunk_size, retries).read(url, abort); +bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache) { + std::string data = FileReader(local_cache).read(url, abort); if (!data.empty()) { if (url.find(".bz2") != std::string::npos || util::starts_with(data, "BZh9")) { data = decompressBZ2(data, abort); diff --git a/tools/replay/logreader.h b/tools/replay/logreader.h index f8d60ffadd..9219878ace 100644 --- a/tools/replay/logreader.h +++ b/tools/replay/logreader.h @@ -28,7 +28,7 @@ class LogReader { public: 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 local_cache = false); bool load(const char *data, size_t size, std::atomic *abort = nullptr); std::vector events; diff --git a/tools/replay/main.cc b/tools/replay/main.cc index 30f85b1700..bdb9cf4f35 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -132,6 +133,10 @@ int main(int argc, char *argv[]) { util::set_file_descriptor_limit(1024); #endif + // The vendored ncurses static library has a wrong compiled-in terminfo path. + // Point it at the system terminfo database if not already set. + setenv("TERMINFO_DIRS", "/usr/share/terminfo:/lib/terminfo:/usr/lib/terminfo", 0); + ReplayConfig config; if (!parseArgs(argc, argv, config)) { diff --git a/tools/replay/py_downloader.cc b/tools/replay/py_downloader.cc new file mode 100644 index 0000000000..efaf3c93a2 --- /dev/null +++ b/tools/replay/py_downloader.cc @@ -0,0 +1,218 @@ +#include "tools/replay/py_downloader.h" + +#include +#include +#include +#include +#include + +#include "tools/replay/util.h" + +namespace { + +static std::mutex handler_mutex; +static DownloadProgressHandler progress_handler = nullptr; + +// Run a Python command and capture stdout. Optionally parse stderr for PROGRESS lines. +// Returns stdout content. If abort is signaled, kills the child process. +std::string runPython(const std::vector &args, std::atomic *abort = nullptr, bool parse_progress = false) { + // Build argv for execvp + std::vector argv; + argv.push_back("python3"); + argv.push_back("-m"); + argv.push_back("openpilot.tools.lib.file_downloader"); + for (const auto &a : args) { + argv.push_back(a.c_str()); + } + argv.push_back(nullptr); + + int stdout_pipe[2]; + int stderr_pipe[2]; + if (pipe(stdout_pipe) != 0) { + rWarning("py_downloader: pipe() failed"); + return {}; + } + if (pipe(stderr_pipe) != 0) { + rWarning("py_downloader: pipe() failed"); + close(stdout_pipe[0]); close(stdout_pipe[1]); + return {}; + } + + pid_t pid = fork(); + if (pid < 0) { + rWarning("py_downloader: fork() failed"); + close(stdout_pipe[0]); close(stdout_pipe[1]); + close(stderr_pipe[0]); close(stderr_pipe[1]); + return {}; + } + + if (pid == 0) { + // Child process — detach from controlling terminal so Python + // cannot corrupt terminal settings needed by ncurses in the parent. + setsid(); + int devnull = open("/dev/null", O_RDONLY); + if (devnull >= 0) { + dup2(devnull, STDIN_FILENO); + if (devnull > STDERR_FILENO) close(devnull); + } + + // Clear OPENPILOT_PREFIX so the Python process uses default paths + // (e.g. ~/.comma/auth.json). The prefix is only for IPC in the parent. + unsetenv("OPENPILOT_PREFIX"); + + close(stdout_pipe[0]); + close(stderr_pipe[0]); + dup2(stdout_pipe[1], STDOUT_FILENO); + dup2(stderr_pipe[1], STDERR_FILENO); + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + execvp("python3", const_cast(argv.data())); + _exit(127); + } + + // Parent process + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + std::string stdout_data; + std::string stderr_buf; + char buf[4096]; + + // Use select() to read from both pipes + fd_set rfds; + int max_fd = std::max(stdout_pipe[0], stderr_pipe[0]); + bool stdout_open = true, stderr_open = true; + + while (stdout_open || stderr_open) { + if (abort && *abort) { + kill(pid, SIGTERM); + break; + } + + FD_ZERO(&rfds); + if (stdout_open) FD_SET(stdout_pipe[0], &rfds); + if (stderr_open) FD_SET(stderr_pipe[0], &rfds); + + struct timeval tv = {0, 100000}; // 100ms timeout + int ret = select(max_fd + 1, &rfds, nullptr, nullptr, &tv); + if (ret < 0) break; + + if (stdout_open && FD_ISSET(stdout_pipe[0], &rfds)) { + ssize_t n = read(stdout_pipe[0], buf, sizeof(buf)); + if (n <= 0) { + stdout_open = false; + } else { + stdout_data.append(buf, n); + } + } + + if (stderr_open && FD_ISSET(stderr_pipe[0], &rfds)) { + ssize_t n = read(stderr_pipe[0], buf, sizeof(buf)); + if (n <= 0) { + stderr_open = false; + } else { + stderr_buf.append(buf, n); + // Parse complete lines from stderr + size_t pos; + while ((pos = stderr_buf.find('\n')) != std::string::npos) { + std::string line = stderr_buf.substr(0, pos); + stderr_buf.erase(0, pos + 1); + + if (parse_progress && line.rfind("PROGRESS:", 0) == 0) { + // Parse "PROGRESS::" + auto colon1 = line.find(':', 9); + if (colon1 != std::string::npos) { + try { + uint64_t cur = std::stoull(line.c_str() + 9); + uint64_t total = std::stoull(line.c_str() + colon1 + 1); + std::lock_guard lk(handler_mutex); + if (progress_handler) { + progress_handler(cur, total, true); + } + } catch (...) {} + } + } else if (line.rfind("ERROR:", 0) == 0) { + rWarning("py_downloader: %s", line.c_str() + 6); + } + } + } + } + } + + // Drain remaining pipe data to prevent child from blocking on write + for (int fd : {stdout_pipe[0], stderr_pipe[0]}) { + while (read(fd, buf, sizeof(buf)) > 0) {} + close(fd); + } + + int status; + waitpid(pid, &status, 0); + + bool failed = (abort && *abort) || + (WIFEXITED(status) && WEXITSTATUS(status) != 0) || + WIFSIGNALED(status); + if (failed) { + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { + rWarning("py_downloader: process exited with code %d", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + rWarning("py_downloader: process killed by signal %d", WTERMSIG(status)); + } + std::lock_guard lk(handler_mutex); + if (progress_handler) { + progress_handler(0, 0, false); + } + return {}; + } + + // Trim trailing newline + while (!stdout_data.empty() && (stdout_data.back() == '\n' || stdout_data.back() == '\r')) { + stdout_data.pop_back(); + } + + return stdout_data; +} + +} // namespace + +void installDownloadProgressHandler(DownloadProgressHandler handler) { + std::lock_guard lk(handler_mutex); + progress_handler = handler; +} + +namespace PyDownloader { + +std::string download(const std::string &url, bool use_cache, std::atomic *abort) { + std::vector args = {"download", url}; + if (!use_cache) { + args.push_back("--no-cache"); + } + return runPython(args, abort, true); +} + +std::string getRouteFiles(const std::string &route) { + return runPython({"route-files", route}); +} + +std::string getDevices() { + return runPython({"devices"}); +} + +std::string getDeviceRoutes(const std::string &dongle_id, int64_t start_ms, int64_t end_ms, bool preserved) { + std::vector args = {"device-routes", dongle_id}; + if (preserved) { + args.push_back("--preserved"); + } else { + if (start_ms > 0) { + args.push_back("--start"); + args.push_back(std::to_string(start_ms)); + } + if (end_ms > 0) { + args.push_back("--end"); + args.push_back(std::to_string(end_ms)); + } + } + return runPython(args); +} + +} // namespace PyDownloader diff --git a/tools/replay/py_downloader.h b/tools/replay/py_downloader.h new file mode 100644 index 0000000000..535189784c --- /dev/null +++ b/tools/replay/py_downloader.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +typedef std::function DownloadProgressHandler; +void installDownloadProgressHandler(DownloadProgressHandler handler); + +namespace PyDownloader { + +// Downloads url to local cache, returns local file path. Reports progress via installDownloadProgressHandler. +std::string download(const std::string &url, bool use_cache = true, std::atomic *abort = nullptr); + +// Returns JSON string of route files (same format as /v1/route/.../files API) +std::string getRouteFiles(const std::string &route); + +// Returns JSON string of user's devices +std::string getDevices(); + +// Returns JSON string of device routes +std::string getDeviceRoutes(const std::string &dongle_id, int64_t start_ms = 0, int64_t end_ms = 0, bool preserved = false); + +} // namespace PyDownloader diff --git a/tools/replay/qcom_decoder.cc b/tools/replay/qcom_decoder.cc index eb5409daa3..44ff16ce4f 100644 --- a/tools/replay/qcom_decoder.cc +++ b/tools/replay/qcom_decoder.cc @@ -175,8 +175,8 @@ bool MsmVidc::setPlaneFormat(enum v4l2_buf_type type, uint32_t fourcc) { bool MsmVidc::setFPS(uint32_t fps) { struct v4l2_streamparm streamparam = { .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .parm.output.timeperframe = {1, fps} }; + streamparam.parm.output.timeperframe = {1, fps}; util::safe_ioctl(fd, VIDIOC_S_PARM, &streamparam, "VIDIOC_S_PARM failed"); return true; } diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 58c1b71b8a..3e2bc7c00e 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -12,7 +12,7 @@ #include "tools/replay/seg_mgr.h" #include "tools/replay/timeline.h" -#define DEMO_ROUTE "a2a0ccea32023010|2023-07-27--13-01-19" +#define DEMO_ROUTE "5beb9b58bd12b691/0000010a--a51155e496" enum REPLAY_FLAGS { REPLAY_FLAG_NONE = 0x0000, diff --git a/tools/replay/route.cc b/tools/replay/route.cc index ba00828267..e7b8ed6bba 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -6,7 +6,7 @@ #include "third_party/json11/json11.hpp" #include "system/hardware/hw.h" -#include "tools/replay/api.h" +#include "tools/replay/py_downloader.h" #include "tools/replay/replay.h" #include "tools/replay/util.h" @@ -103,43 +103,44 @@ bool Route::loadFromAutoSource() { return !segments_.empty(); } -bool Route::loadFromServer(int retries) { - const std::string url = CommaApi2::BASE_URL + "/v1/route/" + route_.str + "/files"; - for (int i = 1; i <= retries; ++i) { - long response_code = 0; - std::string result = CommaApi2::httpGet(url, &response_code); - if (response_code == 200) { - return loadFromJson(result); - } - - if (response_code == 401 || response_code == 403) { - rWarning(">> Unauthorized. Authenticate with tools/lib/auth.py <<"); - err_ = RouteLoadError::Unauthorized; - break; - } - if (response_code == 404) { - rWarning("The specified route could not be found on the server."); - err_ = RouteLoadError::FileNotFound; - break; - } - +bool Route::loadFromServer() { + std::string result = PyDownloader::getRouteFiles(route_.str); + if (result.empty()) { err_ = RouteLoadError::NetworkError; - rWarning("Retrying %d/%d", i, retries); - util::sleep_for(3000); - } - - return false; -} - -bool Route::loadFromJson(const std::string &json) { - const static std::regex rx(R"(\/(\d+)\/)"); - std::string err; - auto jsonData = json11::Json::parse(json, err); - if (!err.empty()) { - rWarning("JSON parsing error: %s", err.c_str()); + rWarning("Failed to fetch route files from server"); return false; } - for (const auto &value : jsonData.object_items()) { + + // Check for error field in JSON response + std::string parse_err; + auto json = json11::Json::parse(result, parse_err); + if (!parse_err.empty()) { + err_ = RouteLoadError::NetworkError; + rWarning("Failed to parse route files response"); + return false; + } + + if (json.is_object() && json["error"].is_string()) { + const std::string &error = json["error"].string_value(); + if (error == "unauthorized") { + rWarning(">> Unauthorized. Authenticate with tools/lib/auth.py <<"); + err_ = RouteLoadError::Unauthorized; + } else if (error == "not_found") { + rWarning("The specified route could not be found on the server."); + err_ = RouteLoadError::FileNotFound; + } else { + rWarning("API error: %s", error.c_str()); + err_ = RouteLoadError::NetworkError; + } + return false; + } + + return loadFromJson(json); +} + +bool Route::loadFromJson(const json11::Json &json) { + const static std::regex rx(R"(\/(\d+)\/)"); + for (const auto &value : json.object_items()) { const auto &urlArray = value.second.array_items(); for (const auto &url : urlArray) { std::string url_str = url.string_value(); @@ -225,10 +226,10 @@ 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((CameraType)id, 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); } else { log = std::make_unique(filters_); - success = log->load(file, &abort_, local_cache, 0, 3); + success = log->load(file, &abort_, local_cache); } if (!success) { diff --git a/tools/replay/route.h b/tools/replay/route.h index 0375252a19..119a81152e 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -8,6 +8,7 @@ #include #include +#include "third_party/json11/json11.hpp" #include "tools/replay/framereader.h" #include "tools/replay/logreader.h" #include "tools/replay/util.h" @@ -55,8 +56,8 @@ protected: bool loadSegments(); bool loadFromAutoSource(); bool loadFromLocal(); - bool loadFromServer(int retries = 3); - bool loadFromJson(const std::string &json); + bool loadFromServer(); + bool loadFromJson(const json11::Json &json); void addFileToSegment(int seg_num, const std::string &file); RouteIdentifier route_ = {}; std::string data_dir_; diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc index aed3de59a8..45fcc98191 100644 --- a/tools/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -1,5 +1,6 @@ #define CATCH_CONFIG_MAIN #include "catch2/catch.hpp" +#include "tools/replay/filereader.h" #include "tools/replay/replay.h" const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; diff --git a/tools/replay/timeline.cc b/tools/replay/timeline.cc index 39ea8b1bed..08dca5c89e 100644 --- a/tools/replay/timeline.cc +++ b/tools/replay/timeline.cc @@ -55,7 +55,7 @@ void Timeline::buildTimeline(const Route &route, uint64_t route_start_ts, bool l if (should_exit_) break; auto log = std::make_shared(); - if (!log->load(segment.second.qlog, &should_exit_, local_cache, 0, 3) || log->events.empty()) { + if (!log->load(segment.second.qlog, &should_exit_, local_cache) || log->events.empty()) { continue; // Skip if log loading fails or no events } diff --git a/tools/replay/util.cc b/tools/replay/util.cc index 481564322e..7294de8282 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -1,20 +1,13 @@ #include "tools/replay/util.h" #include -#include #include #include -#include -#include #include #include -#include #include -#include #include -#include -#include #include #include "common/timing.h" @@ -51,91 +44,6 @@ void logMessage(ReplyMsgType type, const char *fmt, ...) { free(msg_buf); } -namespace { - -struct CURLGlobalInitializer { - CURLGlobalInitializer() { curl_global_init(CURL_GLOBAL_DEFAULT); } - ~CURLGlobalInitializer() { curl_global_cleanup(); } -}; - -static CURLGlobalInitializer curl_initializer; - -template -struct MultiPartWriter { - T *buf; - size_t *total_written; - size_t offset; - size_t end; - - size_t write(char *data, size_t size, size_t count) { - size_t bytes = size * count; - if ((offset + bytes) > end) return 0; - - if constexpr (std::is_same::value) { - memcpy(buf->data() + offset, data, bytes); - } else if constexpr (std::is_same::value) { - buf->seekp(offset); - buf->write(data, bytes); - } - - offset += bytes; - *total_written += bytes; - return bytes; - } -}; - -template -size_t write_cb(char *data, size_t size, size_t count, void *userp) { - auto w = (MultiPartWriter *)userp; - return w->write(data, size, count); -} - -size_t dumy_write_cb(char *data, size_t size, size_t count, void *userp) { return size * count; } - -struct DownloadStats { - void installDownloadProgressHandler(DownloadProgressHandler handler) { - std::lock_guard lk(lock); - download_progress_handler = handler; - } - - void add(const std::string &url, uint64_t total_bytes) { - std::lock_guard lk(lock); - items[url] = {0, total_bytes}; - } - - void remove(const std::string &url) { - std::lock_guard lk(lock); - items.erase(url); - } - - void update(const std::string &url, uint64_t downloaded, bool success = true) { - std::lock_guard lk(lock); - items[url].first = downloaded; - - auto stat = std::accumulate(items.begin(), items.end(), std::pair{}, [=](auto &a, auto &b){ - return std::pair{a.first + b.second.first, a.second + b.second.second}; - }); - double tm = millis_since_boot(); - if (download_progress_handler && ((tm - prev_tm) > 500 || !success || stat.first >= stat.second)) { - download_progress_handler(stat.first, stat.second, success); - prev_tm = tm; - } - } - - std::mutex lock; - std::map> items; - double prev_tm = 0; - DownloadProgressHandler download_progress_handler = nullptr; -}; - -static DownloadStats download_stats; - -} // namespace - -void installDownloadProgressHandler(DownloadProgressHandler handler) { - download_stats.installDownloadProgressHandler(handler); -} - std::string formattedDataSize(size_t size) { if (size < 1024) { return std::to_string(size) + " B"; @@ -146,140 +54,11 @@ std::string formattedDataSize(size_t size) { } } -size_t getRemoteFileSize(const std::string &url, std::atomic *abort) { - CURL *curl = curl_easy_init(); - if (!curl) return -1; - - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dumy_write_cb); - curl_easy_setopt(curl, CURLOPT_HEADER, 1); - curl_easy_setopt(curl, CURLOPT_NOBODY, 1); - curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - - CURLM *cm = curl_multi_init(); - curl_multi_add_handle(cm, curl); - int still_running = 1; - while (still_running > 0 && !(abort && *abort)) { - CURLMcode mc = curl_multi_perform(cm, &still_running); - if (mc != CURLM_OK) break; - if (still_running > 0) { - curl_multi_wait(cm, nullptr, 0, 1000, nullptr); - } - } - - double content_length = -1; - curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length); - curl_multi_remove_handle(cm, curl); - curl_easy_cleanup(curl); - curl_multi_cleanup(cm); - return content_length > 0 ? (size_t)content_length : 0; -} - std::string getUrlWithoutQuery(const std::string &url) { size_t idx = url.find("?"); return (idx == std::string::npos ? url : url.substr(0, idx)); } -template -bool httpDownload(const std::string &url, T &buf, size_t chunk_size, size_t content_length, std::atomic *abort) { - download_stats.add(url, content_length); - - int parts = 1; - if (chunk_size > 0 && content_length > 10 * 1024 * 1024) { - parts = std::nearbyint(content_length / (float)chunk_size); - parts = std::clamp(parts, 1, 5); - } - - CURLM *cm = curl_multi_init(); - size_t written = 0; - std::map> writers; - const int part_size = content_length / parts; - for (int i = 0; i < parts; ++i) { - CURL *eh = curl_easy_init(); - writers[eh] = { - .buf = &buf, - .total_written = &written, - .offset = (size_t)(i * part_size), - .end = i == parts - 1 ? content_length : (i + 1) * part_size, - }; - curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb); - curl_easy_setopt(eh, CURLOPT_WRITEDATA, (void *)(&writers[eh])); - curl_easy_setopt(eh, CURLOPT_URL, url.c_str()); - curl_easy_setopt(eh, CURLOPT_RANGE, util::string_format("%d-%d", writers[eh].offset, writers[eh].end - 1).c_str()); - curl_easy_setopt(eh, CURLOPT_HTTPGET, 1); - curl_easy_setopt(eh, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1); - - curl_multi_add_handle(cm, eh); - } - - int still_running = 1; - size_t prev_written = 0; - while (still_running > 0 && !(abort && *abort)) { - 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; - int msgs_left = -1; - int complete = 0; - while ((msg = curl_multi_info_read(cm, &msgs_left)) && !(abort && *abort)) { - if (msg->msg == CURLMSG_DONE) { - if (msg->data.result == CURLE_OK) { - long res_status = 0; - curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &res_status); - if (res_status == 206) { - complete++; - } else { - rWarning("Download failed: http error code: %d", res_status); - } - } else { - rWarning("Download failed: connection failure: %d", msg->data.result); - } - } - } - - bool success = complete == parts; - download_stats.update(url, written, success); - download_stats.remove(url); - - for (const auto &[e, w] : writers) { - curl_multi_remove_handle(cm, e); - curl_easy_cleanup(e); - } - curl_multi_cleanup(cm); - - return success; -} - -std::string httpGet(const std::string &url, size_t chunk_size, std::atomic *abort) { - size_t size = getRemoteFileSize(url, abort); - if (size == 0) return {}; - - std::string result(size, '\0'); - return httpDownload(url, result, chunk_size, size, abort) ? result : ""; -} - -bool httpDownload(const std::string &url, const std::string &file, size_t chunk_size, std::atomic *abort) { - size_t size = getRemoteFileSize(url, abort); - if (size == 0) return false; - - std::ofstream of(file, std::ios::binary | std::ios::out); - of.seekp(size - 1).write("\0", 1); - return httpDownload(url, of, chunk_size, size, abort); -} - std::string decompressBZ2(const std::string &in, std::atomic *abort) { return decompressBZ2((std::byte *)in.data(), in.size(), abort); } diff --git a/tools/replay/util.h b/tools/replay/util.h index 1f61951d21..ee92190337 100644 --- a/tools/replay/util.h +++ b/tools/replay/util.h @@ -53,12 +53,6 @@ std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic std::string decompressZST(const std::string &in, std::atomic *abort = nullptr); std::string decompressZST(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); std::string getUrlWithoutQuery(const std::string &url); -size_t getRemoteFileSize(const std::string &url, std::atomic *abort = nullptr); -std::string httpGet(const std::string &url, size_t chunk_size = 0, std::atomic *abort = nullptr); - -typedef std::function DownloadProgressHandler; -void installDownloadProgressHandler(DownloadProgressHandler); -bool httpDownload(const std::string &url, const std::string &file, size_t chunk_size = 0, std::atomic *abort = nullptr); std::string formattedDataSize(size_t size); std::string extractFileName(const std::string& file); std::vector split(std::string_view source, char delimiter); diff --git a/tools/scripts/adb_ssh.sh b/tools/scripts/adb_ssh.sh index ad65693722..4527a0296d 100755 --- a/tools/scripts/adb_ssh.sh +++ b/tools/scripts/adb_ssh.sh @@ -2,7 +2,9 @@ set -euo pipefail # Forward all openpilot service ports -mapfile -t SERVICE_PORTS < <(python3 - <<'PY' +while IFS=' ' read -r name port; do + adb forward "tcp:${port}" "tcp:${port}" > /dev/null +done < <(python3 - <<'PY' from cereal.services import SERVICE_LIST FNV_PRIME = 0x100000001b3 @@ -29,12 +31,6 @@ for name, port in sorted(ports): PY ) -for entry in "${SERVICE_PORTS[@]}"; do - name="${entry% *}" - port="${entry##* }" - adb forward "tcp:${port}" "tcp:${port}" > /dev/null -done - # Forward SSH port first for interactive shell access. adb forward tcp:2222 tcp:22 diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh new file mode 100755 index 0000000000..0b785bf4a2 --- /dev/null +++ b/tools/setup_dependencies.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +ROOT="$(cd "$DIR/../" && pwd)" + +function retry() { + local attempts=$1 + shift + for i in $(seq 1 "$attempts"); do + if "$@"; then + return 0 + fi + if [ "$i" -lt "$attempts" ]; then + echo " Attempt $i/$attempts failed, retrying in 5s..." + sleep 5 + fi + done + return 1 +} + +function install_ubuntu_deps() { + SUDO="" + + if [[ ! $(id -u) -eq 0 ]]; then + if [[ -z $(which sudo) ]]; then + echo "Please install sudo or run as root" + exit 1 + fi + SUDO="sudo" + fi + + # Detect OS using /etc/os-release file + if [ -f "/etc/os-release" ]; then + source /etc/os-release + case "$VERSION_CODENAME" in + "jammy" | "kinetic" | "noble") + ;; + *) + echo "$ID $VERSION_ID is unsupported. This setup script is written for Ubuntu 24.04." + read -p "Would you like to attempt installation anyway? " -n 1 -r + echo "" + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi + ;; + esac + else + echo "No /etc/os-release in the system. Make sure you're running on Ubuntu, or similar." + exit 1 + fi + + $SUDO apt-get update + + # normal stuff, mostly for the bare docker image + $SUDO apt-get install -y --no-install-recommends \ + ca-certificates \ + build-essential \ + curl \ + libcurl4-openssl-dev \ + locales \ + git \ + xvfb + + if [[ -d "/etc/udev/rules.d/" ]]; then + # Setup jungle udev rules + $SUDO tee /etc/udev/rules.d/12-panda_jungle.rules > /dev/null < /dev/null < /dev/null < /dev/null 2>&1; then + echo "installing uv..." + # TODO: outer retry can be removed once https://github.com/axodotdev/cargo-dist/pull/2311 is merged + retry 3 sh -c 'curl --retry 5 --retry-delay 5 --retry-all-errors -LsSf https://astral.sh/uv/install.sh | UV_GITHUB_TOKEN="${GITHUB_TOKEN:-}" sh' + UV_BIN="$HOME/.local/bin" + PATH="$UV_BIN:$PATH" + fi + + echo "updating uv..." + # ok to fail, can also fail due to installing with brew + uv self update || true + + echo "installing python packages..." + uv sync --frozen --all-extras + source .venv/bin/activate +} + +function install_macos_deps() { + if ! command -v brew > /dev/null 2>&1; then + echo "homebrew not found, skipping macOS system dependency install" + return 0 + fi + + if ! command -v cmake > /dev/null 2>&1; then + brew install cmake + fi +} + +# --- Main --- + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + install_ubuntu_deps + echo "[ ] installed system dependencies t=$SECONDS" +elif [[ "$OSTYPE" == "darwin"* ]]; then + install_macos_deps + if [[ $SHELL == "/bin/zsh" ]]; then + RC_FILE="$HOME/.zshrc" + elif [[ $SHELL == "/bin/bash" ]]; then + RC_FILE="$HOME/.bash_profile" + fi +fi + +if [ -f "$ROOT/pyproject.toml" ]; then + install_python_deps + echo "[ ] installed python dependencies t=$SECONDS" +fi + +if [[ "$OSTYPE" == "darwin"* ]] && [[ -n "${RC_FILE:-}" ]]; then + echo + echo "---- OPENPILOT SETUP DONE ----" + echo "Open a new shell or configure your active shell env by running:" + echo "source $RC_FILE" +fi diff --git a/tools/sim/launch_openpilot.sh b/tools/sim/launch_openpilot.sh index fa5ac731bd..ea3c4cb8f1 100755 --- a/tools/sim/launch_openpilot.sh +++ b/tools/sim/launch_openpilot.sh @@ -6,7 +6,7 @@ export SIMULATION="1" export SKIP_FW_QUERY="1" export FINGERPRINT="HONDA_CIVIC_2022" -export BLOCK="${BLOCK},camerad,loggerd,encoderd,micd,logmessaged" +export BLOCK="${BLOCK},camerad,loggerd,encoderd,micd,logmessaged,manage_athenad,manage_sunnylinkd" if [[ "$CI" ]]; then # TODO: offscreen UI should work export BLOCK="${BLOCK},ui" diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh deleted file mode 100755 index be4cfb68fa..0000000000 --- a/tools/ubuntu_setup.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -set -e - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" - -# NOTE: this is used in a docker build, so do not run any scripts here. - -"$DIR"/install_ubuntu_dependencies.sh -"$DIR"/install_python_dependencies.sh diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 67ad2cc8cb..6abbc47935 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -10,10 +10,6 @@ What's needed: ## Setup openpilot - Follow [this readme](../README.md) to install and build the requirements -- Install OpenCL Driver (Ubuntu) -``` -sudo apt install pocl-opencl-icd -``` ## Connect the hardware - Connect the camera first diff --git a/uv.lock b/uv.lock index 8909934e9a..f5c128fcb7 100644 --- a/uv.lock +++ b/uv.lock @@ -60,27 +60,20 @@ wheels = [ [[package]] name = "aiortc" -version = "1.10.1" +version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aioice" }, { name = "av" }, - { name = "cffi" }, { name = "cryptography" }, { name = "google-crc32c" }, { name = "pyee" }, { name = "pylibsrtp" }, { name = "pyopenssl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/f8/408e092748521889c9d33dddcef920afd9891cf6db4615ba6b6bfe114ff8/aiortc-1.10.1.tar.gz", hash = "sha256:64926ad86bde20c1a4dacb7c3a164e57b522606b70febe261fada4acf79641b5", size = 1179406, upload-time = "2025-02-02T17:36:38.684Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/9c/4e027bfe0195de0442da301e2389329496745d40ae44d2d7c4571c4290ce/aiortc-1.14.0.tar.gz", hash = "sha256:adc8a67ace10a085721e588e06a00358ed8eaf5f6b62f0a95358ff45628dd762", size = 1180864, upload-time = "2025-10-13T21:40:37.905Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/6b/74547a30d1ddcc81f905ef4ff7fcc2c89b7482cb2045688f2aaa4fa918aa/aiortc-1.10.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3bef536f38394b518aefae9dbf9cdd08f39e4c425f316f9692f0d8dc724810bd", size = 1218457, upload-time = "2025-02-02T17:36:23.172Z" }, - { url = "https://files.pythonhosted.org/packages/46/92/b4ccf39cd18e366ace2a11dc7d98ed55967b4b325707386b5788149db15e/aiortc-1.10.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8842c02e38513d9432ef22982572833487bb015f23348fa10a690616dbf55143", size = 898855, upload-time = "2025-02-02T17:36:25.9Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e9/2676de48b493787d8b03129713e6bb2dfbacca2a565090f2a89cbad71f96/aiortc-1.10.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:954a420de01c0bf6b07a0c58b662029b1c4204ddbd8f5c4162bbdebd43f882b1", size = 1750403, upload-time = "2025-02-02T17:36:28.446Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9d/ab6d09183cdaf5df060923d9bd5c9ed5fb1802661d9401dba35f3c85a57b/aiortc-1.10.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7c0d46fb30307a9d7deb4b7d66f0b0e73b77a7221b063fb6dc78821a5d2aa1e", size = 1867886, upload-time = "2025-02-02T17:36:30.209Z" }, - { url = "https://files.pythonhosted.org/packages/c2/71/0b5666e6b965dbd9a7f331aa827a6c3ab3eb4d582fefb686a7f4227b7954/aiortc-1.10.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89582f6923046f79f15d9045f432bc78191eacc95f6bed18714e86ec935188d9", size = 1893709, upload-time = "2025-02-02T17:36:32.342Z" }, - { url = "https://files.pythonhosted.org/packages/9d/0a/8c0c78fad79ef595a0ed6e2ab413900e6bd0eac65fc5c31c9d8736bff909/aiortc-1.10.1-cp39-abi3-win32.whl", hash = "sha256:d1cbe87f740b33ffaa8e905f21092773e74916be338b64b81c8b79af4c3847eb", size = 923265, upload-time = "2025-02-02T17:36:34.685Z" }, - { url = "https://files.pythonhosted.org/packages/73/12/a27dd588a4988021da88cb4d338d8ee65ac097afc14e9193ab0be4a48790/aiortc-1.10.1-cp39-abi3-win_amd64.whl", hash = "sha256:c9a5a0b23f8a77540068faec8837fa0a65b0396c20f09116bdb874b75e0b6abe", size = 1009488, upload-time = "2025-02-02T17:36:36.317Z" }, + { url = "https://files.pythonhosted.org/packages/57/ab/31646a49209568cde3b97eeade0d28bb78b400e6645c56422c101df68932/aiortc-1.14.0-py3-none-any.whl", hash = "sha256:4b244d7e482f4e1f67e685b3468269628eca1ec91fa5b329ab517738cfca086e", size = 93183, upload-time = "2025-10-13T21:40:36.59Z" }, ] [[package]] @@ -107,18 +100,29 @@ wheels = [ [[package]] name = "av" -version = "13.1.0" +version = "16.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0c/9d/486d31e76784cc0ad943f420c5e05867263b32b37e2f4b0f7f22fdc1ca3a/av-13.1.0.tar.gz", hash = "sha256:d3da736c55847d8596eb8c26c60e036f193001db3bc5c10da8665622d906c17e", size = 3957908, upload-time = "2024-10-06T04:54:57.507Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/cd/3a83ffbc3cc25b39721d174487fb0d51a76582f4a1703f98e46170ce83d4/av-16.1.0.tar.gz", hash = "sha256:a094b4fd87a3721dacf02794d3d2c82b8d712c85b9534437e82a8a978c175ffd", size = 4285203, upload-time = "2026-01-11T07:31:33.772Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/aa/4bdd8ce59173574fc6e0c282c71ee6f96fca82643d97bf172bc4cb5a5674/av-13.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:261dbc3f4b55f4f8f3375b10b2258fca7f2ab7a6365c01bc65e77a0d5327a195", size = 24268674, upload-time = "2024-10-06T04:53:11.251Z" }, - { url = "https://files.pythonhosted.org/packages/17/b4/b267dd5bad99eed49ec6731827c6bcb5ab03864bf732a7ebb81e3df79911/av-13.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83d259ef86b9054eb914bc7c6a7f6092a6d75cb939295e70ee979cfd92a67b99", size = 19475617, upload-time = "2024-10-06T04:53:13.832Z" }, - { url = "https://files.pythonhosted.org/packages/68/32/4209e51f54d7b54a1feb576d309c671ed1ff437b54fcc4ec68c239199e0a/av-13.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3b4d3ca159eceab97e3c0fb08fe756520fb95508417f76e48198fda2a5b0806", size = 32468873, upload-time = "2024-10-06T04:53:17.639Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d8/c174da5f06b24f3c9e36f91fd02a7411c39da9ce792c17964260d4be675e/av-13.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40e8f757e373b73a2dc4640852a00cce4a4a92ef19b2e642a96d6994cd1fffbf", size = 31818484, upload-time = "2024-10-06T04:53:21.509Z" }, - { url = "https://files.pythonhosted.org/packages/7f/22/0dd8d1d5cad415772bb707d16aea8b81cf75d340d11d3668eea43468c730/av-13.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8aaec2c0bfd024359db3821d679009d4e637e1bee0321d20f61c54ed6b20f41", size = 34398652, upload-time = "2024-10-06T04:53:25.798Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ff/48fa68888b8d5bae36d915556ff18f9e5fdc6b5ff5ae23dc4904c9713168/av-13.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ea0deab0e6a739cb742fba2a3983d8102f7516a3cdf3c46669f3cac0ed1f351", size = 25781343, upload-time = "2024-10-06T04:53:29.577Z" }, + { url = "https://files.pythonhosted.org/packages/9c/84/2535f55edcd426cebec02eb37b811b1b0c163f26b8d3f53b059e2ec32665/av-16.1.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:640f57b93f927fba8689f6966c956737ee95388a91bd0b8c8b5e0481f73513d6", size = 26945785, upload-time = "2026-01-09T20:18:34.486Z" }, + { url = "https://files.pythonhosted.org/packages/b6/17/ffb940c9e490bf42e86db4db1ff426ee1559cd355a69609ec1efe4d3a9eb/av-16.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ae3fb658eec00852ebd7412fdc141f17f3ddce8afee2d2e1cf366263ad2a3b35", size = 21481147, upload-time = "2026-01-09T20:18:36.716Z" }, + { url = "https://files.pythonhosted.org/packages/15/c1/e0d58003d2d83c3921887d5c8c9b8f5f7de9b58dc2194356a2656a45cfdc/av-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ee558d9c02a142eebcbe55578a6d817fedfde42ff5676275504e16d07a7f86", size = 39517197, upload-time = "2026-01-11T09:57:31.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/77/787797b43475d1b90626af76f80bfb0c12cfec5e11eafcfc4151b8c80218/av-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7ae547f6d5fa31763f73900d43901e8c5fa6367bb9a9840978d57b5a7ae14ed2", size = 41174337, upload-time = "2026-01-11T09:57:35.792Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/d90df7f1e3b97fc5554cf45076df5045f1e0a6adf13899e10121229b826c/av-16.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8cf065f9d438e1921dc31fc7aa045790b58aee71736897866420d80b5450f62a", size = 40817720, upload-time = "2026-01-11T09:57:39.039Z" }, + { url = "https://files.pythonhosted.org/packages/80/6f/13c3a35f9dbcebafd03fe0c4cbd075d71ac8968ec849a3cfce406c35a9d2/av-16.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a345877a9d3cc0f08e2bc4ec163ee83176864b92587afb9d08dff50f37a9a829", size = 42267396, upload-time = "2026-01-11T09:57:42.115Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/275df9607f7fb44317ccb1d4be74827185c0d410f52b6e2cd770fe209118/av-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f49243b1d27c91cd8c66fdba90a674e344eb8eb917264f36117bf2b6879118fd", size = 31752045, upload-time = "2026-01-11T09:57:45.106Z" }, ] +[[package]] +name = "bzip2" +version = "1.0.8" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } + +[[package]] +name = "capnproto" +version = "1.0.1" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } + [[package]] name = "casadi" version = "3.7.2" @@ -138,11 +142,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] @@ -205,15 +209,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] -[[package]] -name = "cloudpickle" -version = "3.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, -] - [[package]] name = "codespell" version = "2.4.1" @@ -296,31 +291,41 @@ wheels = [ [[package]] name = "cryptography" -version = "43.0.3" +version = "46.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", size = 686989, upload-time = "2024-10-18T15:58:32.918Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/f3/01fdf26701a26f4b4dbc337a26883ad5bccaa6f1bbbdd29cd89e22f18a1c/cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", size = 6225303, upload-time = "2024-10-18T15:57:36.753Z" }, - { url = "https://files.pythonhosted.org/packages/a3/01/4896f3d1b392025d4fcbecf40fdea92d3df8662123f6835d0af828d148fd/cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", size = 3760905, upload-time = "2024-10-18T15:57:39.166Z" }, - { url = "https://files.pythonhosted.org/packages/0a/be/f9a1f673f0ed4b7f6c643164e513dbad28dd4f2dcdf5715004f172ef24b6/cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", size = 3977271, upload-time = "2024-10-18T15:57:41.227Z" }, - { url = "https://files.pythonhosted.org/packages/4e/49/80c3a7b5514d1b416d7350830e8c422a4d667b6d9b16a9392ebfd4a5388a/cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", size = 3746606, upload-time = "2024-10-18T15:57:42.903Z" }, - { url = "https://files.pythonhosted.org/packages/0e/16/a28ddf78ac6e7e3f25ebcef69ab15c2c6be5ff9743dd0709a69a4f968472/cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", size = 3986484, upload-time = "2024-10-18T15:57:45.434Z" }, - { url = "https://files.pythonhosted.org/packages/01/f5/69ae8da70c19864a32b0315049866c4d411cce423ec169993d0434218762/cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", size = 3852131, upload-time = "2024-10-18T15:57:47.267Z" }, - { url = "https://files.pythonhosted.org/packages/fd/db/e74911d95c040f9afd3612b1f732e52b3e517cb80de8bf183be0b7d413c6/cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", size = 4075647, upload-time = "2024-10-18T15:57:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/56/48/7b6b190f1462818b324e674fa20d1d5ef3e24f2328675b9b16189cbf0b3c/cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", size = 2623873, upload-time = "2024-10-18T15:57:51.822Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b1/0ebff61a004f7f89e7b65ca95f2f2375679d43d0290672f7713ee3162aff/cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", size = 3068039, upload-time = "2024-10-18T15:57:54.426Z" }, - { url = "https://files.pythonhosted.org/packages/30/d5/c8b32c047e2e81dd172138f772e81d852c51f0f2ad2ae8a24f1122e9e9a7/cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", size = 6222984, upload-time = "2024-10-18T15:57:56.174Z" }, - { url = "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", size = 3762968, upload-time = "2024-10-18T15:57:58.206Z" }, - { url = "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", size = 3977754, upload-time = "2024-10-18T15:58:00.683Z" }, - { url = "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", size = 3749458, upload-time = "2024-10-18T15:58:02.225Z" }, - { url = "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", size = 3988220, upload-time = "2024-10-18T15:58:04.331Z" }, - { url = "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", size = 3853898, upload-time = "2024-10-18T15:58:06.113Z" }, - { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592, upload-time = "2024-10-18T15:58:08.673Z" }, - { url = "https://files.pythonhosted.org/packages/81/1e/ffcc41b3cebd64ca90b28fd58141c5f68c83d48563c88333ab660e002cd3/cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", size = 2623145, upload-time = "2024-10-18T15:58:10.264Z" }, - { url = "https://files.pythonhosted.org/packages/87/5c/3dab83cc4aba1f4b0e733e3f0c3e7d4386440d660ba5b1e3ff995feb734d/cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", size = 3068026, upload-time = "2024-10-18T15:58:11.916Z" }, + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, ] [[package]] @@ -356,22 +361,12 @@ wheels = [ [[package]] name = "dearpygui" -version = "2.1.1" +version = "2.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/41/2146e8d03d28b5a66d5282beb26ffd9ab68a729a29d31e2fe91809271bf5/dearpygui-2.1.1-cp312-cp312-macosx_10_6_x86_64.whl", hash = "sha256:238aea7b4be7376f564dae8edd563b280ec1483a03786022969938507691e017", size = 2101529, upload-time = "2025-11-14T14:47:39.646Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c5/fcc37ef834fe225241aa4f18d77aaa2903134f283077978d65a901c624c6/dearpygui-2.1.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:c27ca6ecd4913555b717f3bb341c0b6a27d6c9fdc9932f0b3c31ae2ef893ae35", size = 1895555, upload-time = "2025-11-14T14:47:48.149Z" }, - { url = "https://files.pythonhosted.org/packages/74/66/19f454ba02d5f03a847cc1dfee4a849cd2307d97add5ba26fecdca318adb/dearpygui-2.1.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:8c071e9c165d89217bdcdaf769c6069252fcaee50bf369489add524107932273", size = 2641509, upload-time = "2025-11-14T14:47:54.581Z" }, - { url = "https://files.pythonhosted.org/packages/5e/58/d01538556103d544a5a5b4cbcb00646ff92d8a97f0a6283a56bede4307c8/dearpygui-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f2291313d2035f8a4108e13f60d8c1a0e7c19af7554a7739a3fd15b3d5af8f7", size = 1808971, upload-time = "2025-11-14T14:47:28.15Z" }, -] - -[[package]] -name = "dictdiffer" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/7b/35cbccb7effc5d7e40f4c55e2b79399e1853041997fcda15c9ff160abba0/dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578", size = 31513, upload-time = "2021-07-22T13:24:29.276Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/ef/4cb333825d10317a36a1154341ba37e6e9c087bac99c1990ef07ffdb376f/dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595", size = 16754, upload-time = "2021-07-22T13:24:26.783Z" }, + { url = "https://files.pythonhosted.org/packages/17/c8/b4afdac89c7bf458513366af3143f7383d7b09721637989c95788d93e24c/dearpygui-2.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:34ceae1ca1b65444e49012d6851312e44f08713da1b8cc0150cf41f1c207af9c", size = 1931443, upload-time = "2026-02-17T14:21:54.394Z" }, + { url = "https://files.pythonhosted.org/packages/43/93/a2d083b2e0edb095be815662cc41e40cf9ea7b65d6323e47bb30df7eb284/dearpygui-2.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:e1fae9ae59fec0e41773df64c80311a6ba67696219dde5506a2a4c013e8bcdfa", size = 2592645, upload-time = "2026-02-17T14:22:02.869Z" }, + { url = "https://files.pythonhosted.org/packages/80/ba/eae13acaad479f522db853e8b1ccd695a7bc8da2b9685c1d70a3b318df89/dearpygui-2.2-cp312-cp312-win_amd64.whl", hash = "sha256:7d399543b5a26ab6426ef3bbd776e55520b491b3e169647bde5e6b2de3701b35", size = 1830531, upload-time = "2026-02-17T14:21:43.386Z" }, ] [[package]] @@ -384,16 +379,9 @@ wheels = [ ] [[package]] -name = "ewmhlib" -version = "0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "typing-extensions" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/3a/46ca34abf0725a754bc44ef474ad34aedcc3ea23b052d97b18b76715a6a9/EWMHlib-0.2-py3-none-any.whl", hash = "sha256:f5b07d8cfd4c7734462ee744c32d490f2f3233fa7ab354240069344208d2f6f5", size = 46657, upload-time = "2024-04-17T08:15:56.338Z" }, -] +name = "eigen" +version = "3.4.0" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "execnet" @@ -405,22 +393,9 @@ wheels = [ ] [[package]] -name = "farama-notifications" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/2c/8384832b7a6b1fd6ba95bbdcae26e7137bb3eedc955c42fd5cdcc086cfbf/Farama-Notifications-0.0.4.tar.gz", hash = "sha256:13fceff2d14314cf80703c8266462ebf3733c7d165336eee998fc58e545efd18", size = 2131, upload-time = "2023-02-27T18:28:41.047Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/2c/ffc08c54c05cdce6fbed2aeebc46348dbe180c6d2c541c7af7ba0aa5f5f8/Farama_Notifications-0.0.4-py3-none-any.whl", hash = "sha256:14de931035a41961f7c056361dc7f980762a143d05791ef5794a751a2caf05ae", size = 2511, upload-time = "2023-02-27T18:28:39.447Z" }, -] - -[[package]] -name = "filelock" -version = "3.20.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, -] +name = "ffmpeg" +version = "7.1.0" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "fonttools" @@ -464,6 +439,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] +[[package]] +name = "gcc-arm-none-eabi" +version = "13.2.1" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } + [[package]] name = "ghp-import" version = "2.1.0" @@ -476,6 +456,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] +[[package]] +name = "git-lfs" +version = "3.6.1" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } + [[package]] name = "google-crc32c" version = "1.8.0" @@ -489,21 +474,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, ] -[[package]] -name = "gymnasium" -version = "1.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cloudpickle" }, - { name = "farama-notifications" }, - { name = "numpy" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/59/653a9417d98ed3e29ef9734ba52c3495f6c6823b8d5c0c75369f25111708/gymnasium-1.2.3.tar.gz", hash = "sha256:2b2cb5b5fbbbdf3afb9f38ca952cc48aa6aa3e26561400d940747fda3ad42509", size = 829230, upload-time = "2025-12-18T16:51:10.234Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/d3/ea5f088e3638dbab12e5c20d6559d5b3bdaeaa1f2af74e526e6815836285/gymnasium-1.2.3-py3-none-any.whl", hash = "sha256:e6314bba8f549c7fdcc8677f7cd786b64908af6e79b57ddaa5ce1825bffb5373", size = 952113, upload-time = "2025-12-18T16:51:08.445Z" }, -] - [[package]] name = "hypothesis" version = "6.47.5" @@ -604,6 +574,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, ] +[[package]] +name = "libjpeg" +version = "3.1.0" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } + [[package]] name = "libusb1" version = "3.3.1" @@ -616,50 +591,9 @@ wheels = [ ] [[package]] -name = "lxml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, - { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, - { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, - { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, - { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, - { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, - { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, -] - -[[package]] -name = "mapbox-earcut" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/7b/bbf6b00488662be5d2eb7a188222c264b6f713bac10dc4a77bf37a4cb4b6/mapbox_earcut-2.0.0.tar.gz", hash = "sha256:81eab6b86cf99551deb698b98e3f7502c57900e5c479df15e1bdaf1a57f0f9d6", size = 39934, upload-time = "2025-11-16T18:41:27.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/93/846804029d955c3c841d8efff77c2b0e8d9aab057d3a077dc8e3f88b5ea4/mapbox_earcut-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db55ce18e698bc9d90914ee7d4f8c3e4d23827456ece7c5d7a1ec91e90c7122b", size = 55623, upload-time = "2025-11-16T18:40:32.113Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f6/cc9ece104bc3876b350dba6fef7f34fb7b20ecc028d2cdbdbecb436b1ed1/mapbox_earcut-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01dd6099d16123baf582a11b2bd1d59ce848498cf0cdca3812fd1f8b20ff33b7", size = 52028, upload-time = "2025-11-16T18:40:33.516Z" }, - { url = "https://files.pythonhosted.org/packages/88/6e/230da4aabcc56c99e9bddb4c43ce7d4ba3609c0caf2d316fb26535d7c60c/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5a098aae26a52282bc981a38e7bf6b889d2ea7442f2cd1903d2ba842f4ff07", size = 56351, upload-time = "2025-11-16T18:40:35.217Z" }, - { url = "https://files.pythonhosted.org/packages/1a/f7/5cdd3752526e91d91336c7263af7767b291d21e63c89d7190a60051f0f87/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de35f241d0b9110ad9260f295acedd9d7cc0d7acfe30d36b1b3ee8419c2caba1", size = 59209, upload-time = "2025-11-16T18:40:36.634Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a2/b7781416cb93b37b95d0444e03f87184de8815e57ff202ce4105fa921325/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cb63ab85e2e430c350f93e75c13f8b91cb8c8a045f3cd714c390b69a720368a", size = 152316, upload-time = "2025-11-16T18:40:38.147Z" }, - { url = "https://files.pythonhosted.org/packages/c1/74/396338e3d345e4e36fb23a0380921098b6a95ce7fb19c4777f4185a5974e/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb3c9f069fc3795306db87f8139f70c4f047532f897a3de05f54dc1faebc97f6", size = 157268, upload-time = "2025-11-16T18:40:39.753Z" }, - { url = "https://files.pythonhosted.org/packages/56/2c/66fd137ea86c508f6cd7247f7f6e2d1dabffc9f0e9ccf14c71406b197af1/mapbox_earcut-2.0.0-cp312-cp312-win32.whl", hash = "sha256:eb290e6676217707ed238dd55e07b0a8ca3ab928f6a27c4afefb2ff3af08d7cb", size = 51226, upload-time = "2025-11-16T18:40:41.018Z" }, - { url = "https://files.pythonhosted.org/packages/b8/84/7b78e37b0c2109243c0dad7d9ba9774b02fcee228bf61cf727a5aa1702e2/mapbox_earcut-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ef5b3319a43375272ad2cad9333ed16e569b5102e32a4241451358897e6f6ee", size = 56417, upload-time = "2025-11-16T18:40:42.173Z" }, - { url = "https://files.pythonhosted.org/packages/75/7f/cd7195aa27c1c8f2b9d38025a5a8663f32cd01c07b648a54b1308ab26c15/mapbox_earcut-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4a3706feb5cc8c782d8f68bb0110c8d551304043f680a87a54b0651a2c208c3", size = 50111, upload-time = "2025-11-16T18:40:43.334Z" }, -] +name = "libyuv" +version = "1922.0" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "markdown" @@ -726,57 +660,13 @@ wheels = [ [[package]] name = "metadrive-simulator" -version = "0.4.2.4" -source = { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" } +version = "0.4.2.3" +source = { git = "https://github.com/commaai/metadrive.git?rev=minimal#2716f55a9c7b928ce957a497a15c2c19840c08bc" } dependencies = [ - { name = "filelock" }, - { name = "gymnasium" }, - { name = "lxml" }, - { name = "matplotlib" }, { name = "numpy" }, - { name = "opencv-python-headless" }, { name = "panda3d" }, { name = "panda3d-gltf" }, - { name = "pillow" }, - { name = "progressbar" }, - { name = "psutil" }, - { name = "pygments" }, - { name = "requests" }, - { name = "shapely" }, - { name = "tqdm" }, - { name = "yapf" }, ] -wheels = [ - { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" }, -] - -[package.metadata] -requires-dist = [ - { name = "cuda-python", marker = "extra == 'cuda'", specifier = "==12.0.0" }, - { name = "filelock" }, - { name = "glfw", marker = "extra == 'cuda'" }, - { name = "gym", marker = "extra == 'gym'", specifier = ">=0.19.0,<=0.26.0" }, - { name = "gymnasium", specifier = ">=0.28" }, - { name = "lxml" }, - { name = "matplotlib" }, - { name = "numpy", specifier = ">=1.21.6" }, - { name = "opencv-python-headless" }, - { name = "panda3d", specifier = "==1.10.14" }, - { name = "panda3d-gltf", specifier = "==0.13" }, - { name = "pillow" }, - { name = "progressbar" }, - { name = "psutil" }, - { name = "pygments" }, - { name = "pyopengl", marker = "extra == 'cuda'", specifier = "==3.1.6" }, - { name = "pyopengl-accelerate", marker = "extra == 'cuda'", specifier = "==3.1.6" }, - { name = "pyrr", marker = "extra == 'cuda'", specifier = "==0.10.3" }, - { name = "requests" }, - { name = "shapely" }, - { name = "tqdm" }, - { name = "yapf" }, - { name = "zmq", marker = "extra == 'ros'" }, -] -provides-extras = ["cuda", "gym", "ros"] [[package]] name = "mkdocs" @@ -816,33 +706,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, ] -[[package]] -name = "ml-dtypes" -version = "0.5.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, - { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, -] - -[[package]] -name = "mouseinfo" -version = "0.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyperclip" }, - { name = "python3-xlib", marker = "sys_platform == 'linux'" }, - { name = "rubicon-objc", marker = "sys_platform == 'darwin'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/fa/b2ba8229b9381e8f6381c1dcae6f4159a7f72349e414ed19cfbbd1817173/MouseInfo-0.1.3.tar.gz", hash = "sha256:2c62fb8885062b8e520a3cce0a297c657adcc08c60952eb05bc8256ef6f7f6e7", size = 10850, upload-time = "2020-03-27T21:20:10.136Z" } - [[package]] name = "mpmath" version = "1.3.0" @@ -879,6 +742,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] +[[package]] +name = "ncurses" +version = "6.5" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } + [[package]] name = "numpy" version = "2.4.2" @@ -898,26 +766,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, ] -[[package]] -name = "onnx" -version = "1.20.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ml-dtypes" }, - { name = "numpy" }, - { name = "protobuf" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980, upload-time = "2026-01-10T01:40:03.043Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547, upload-time = "2026-01-10T01:39:31.015Z" }, - { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021, upload-time = "2026-01-10T01:39:33.885Z" }, - { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450, upload-time = "2026-01-10T01:39:36.589Z" }, - { url = "https://files.pythonhosted.org/packages/74/a7/edce1403e05a46e59b502fae8e3350ceeac5841f8e8f1561e98562ed9b09/onnx-1.20.1-cp312-abi3-win32.whl", hash = "sha256:564c35a94811979808ab5800d9eb4f3f32c12daedba7e33ed0845f7c61ef2431", size = 16238216, upload-time = "2026-01-10T01:39:39.46Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c7/8690c81200ae652ac550c1df52f89d7795e6cc941f3cb38c9ef821419e80/onnx-1.20.1-cp312-abi3-win_amd64.whl", hash = "sha256:9fe7f9a633979d50984b94bda8ceb7807403f59a341d09d19342dc544d0ca1d5", size = 16389207, upload-time = "2026-01-10T01:39:41.955Z" }, - { url = "https://files.pythonhosted.org/packages/01/a0/4fb0e6d36eaf079af366b2c1f68bafe92df6db963e2295da84388af64abc/onnx-1.20.1-cp312-abi3-win_arm64.whl", hash = "sha256:21d747348b1c8207406fa2f3e12b82f53e0d5bb3958bcd0288bd27d3cb6ebb00", size = 16344155, upload-time = "2026-01-10T01:39:45.536Z" }, -] - [[package]] name = "opencv-python-headless" version = "4.13.0.92" @@ -943,24 +791,31 @@ source = { editable = "." } dependencies = [ { name = "aiohttp" }, { name = "aiortc" }, + { name = "av" }, + { name = "bzip2" }, + { name = "capnproto" }, { name = "casadi" }, { name = "cffi" }, { name = "crcmod-plus" }, { name = "cython" }, + { name = "eigen" }, + { name = "ffmpeg" }, + { name = "git-lfs" }, { name = "inputs" }, { name = "jeepney" }, { name = "json-rpc" }, + { name = "libjpeg" }, { name = "libusb1" }, - { name = "mapbox-earcut" }, + { name = "libyuv" }, + { name = "ncurses" }, { name = "numpy" }, - { name = "onnx" }, + { name = "openssl3" }, { name = "psutil" }, - { name = "pyaudio" }, { name = "pycapnp" }, { name = "pycryptodome" }, { name = "pyjwt" }, - { name = "pyopenssl" }, { name = "pyserial" }, + { name = "python3-dev" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "raylib" }, @@ -975,18 +830,16 @@ dependencies = [ { name = "tqdm" }, { name = "websocket-client" }, { name = "xattr" }, + { name = "zeromq" }, { name = "zstandard" }, + { name = "zstd" }, ] [package.optional-dependencies] dev = [ - { name = "av" }, - { name = "dictdiffer" }, + { name = "gcc-arm-none-eabi" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, - { name = "parameterized" }, - { name = "pyautogui" }, - { name = "pywinctl" }, ] docs = [ { name = "jinja2" }, @@ -1002,7 +855,6 @@ testing = [ { name = "pytest-cpp" }, { name = "pytest-mock" }, { name = "pytest-subtests" }, - { name = "pytest-timeout" }, { name = "pytest-xdist" }, { name = "ruff" }, { name = "ty" }, @@ -1016,7 +868,9 @@ tools = [ requires-dist = [ { name = "aiohttp" }, { name = "aiortc" }, - { name = "av", marker = "extra == 'dev'" }, + { name = "av" }, + { name = "bzip2", git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases" }, + { name = "capnproto", git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases" }, { name = "casadi", specifier = ">=3.6.6" }, { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, @@ -1024,38 +878,38 @@ requires-dist = [ { name = "crcmod-plus" }, { name = "cython" }, { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, - { name = "dictdiffer", marker = "extra == 'dev'" }, + { name = "eigen", git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases" }, + { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases" }, + { name = "gcc-arm-none-eabi", marker = "extra == 'dev'", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, + { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney" }, { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, + { name = "libjpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases" }, { name = "libusb1" }, - { name = "mapbox-earcut" }, + { name = "libyuv", git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases" }, { name = "matplotlib", marker = "extra == 'dev'" }, - { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" }, + { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", git = "https://github.com/commaai/metadrive.git?rev=minimal" }, { name = "mkdocs", marker = "extra == 'docs'" }, + { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases" }, { name = "numpy", specifier = ">=2.0" }, - { name = "onnx", specifier = ">=1.14.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, - { name = "parameterized", marker = "extra == 'dev'", specifier = ">=0.8,<0.9" }, + { name = "openssl3", git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, - { name = "pyaudio" }, - { name = "pyautogui", marker = "extra == 'dev'" }, - { name = "pycapnp", specifier = "==2.1.0" }, + { name = "pycapnp" }, { name = "pycryptodome" }, { name = "pyjwt" }, - { name = "pyopenssl", specifier = "<24.3.0" }, { name = "pyserial" }, { name = "pytest", marker = "extra == 'testing'" }, { name = "pytest-asyncio", marker = "extra == 'testing'" }, { name = "pytest-cpp", marker = "extra == 'testing'" }, { name = "pytest-mock", marker = "extra == 'testing'" }, { name = "pytest-subtests", marker = "extra == 'testing'" }, - { name = "pytest-timeout", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, - { name = "pywinctl", marker = "extra == 'dev'" }, + { name = "python3-dev", git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "raylib", specifier = ">5.5.0.3" }, @@ -1072,10 +926,17 @@ requires-dist = [ { name = "ty", marker = "extra == 'testing'" }, { name = "websocket-client" }, { name = "xattr" }, + { name = "zeromq", git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases" }, { name = "zstandard" }, + { name = "zstd", git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases" }, ] provides-extras = ["docs", "testing", "dev", "tools"] +[[package]] +name = "openssl3" +version = "3.4.1" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } + [[package]] name = "packaging" version = "26.0" @@ -1124,15 +985,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/5d/3744c6550dddf933785a37cdd4a9921fe13284e6d115b5a2637fe390f158/panda3d_simplepbr-0.13.1-py3-none-any.whl", hash = "sha256:cda41cb57cff035b851646956cfbdcc408bee42511dabd4f2d7bd4fbf48c57a9", size = 2457097, upload-time = "2025-03-30T16:57:39.729Z" }, ] -[[package]] -name = "parameterized" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c6/23/2288f308d238b4f261c039cafcd650435d624de97c6ffc903f06ea8af50f/parameterized-0.8.1.tar.gz", hash = "sha256:41bbff37d6186430f77f900d777e5bb6a24928a1c46fb1de692f8b52b8833b5c", size = 23936, upload-time = "2021-01-09T20:35:18.235Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/13/fe468c8c7400a8eca204e6e160a29bf7dcd45a76e20f1c030f3eaa690d93/parameterized-0.8.1-py2.py3-none-any.whl", hash = "sha256:9cbb0b69a03e8695d68b3399a8a5825200976536fe1cb79db60ed6a4c8c9efe9", size = 26354, upload-time = "2021-01-09T20:35:16.307Z" }, -] - [[package]] name = "pathspec" version = "1.0.4" @@ -1144,30 +996,30 @@ wheels = [ [[package]] name = "pillow" -version = "12.1.0" +version = "12.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, - { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, - { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, - { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, - { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, - { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, - { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, - { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, ] [[package]] name = "platformdirs" -version = "4.5.1" +version = "4.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] [[package]] @@ -1191,12 +1043,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/46/eba9be9daa403fa94854ce16a458c29df9a01c6c047931c3d8be6016cd9a/pre_commit_hooks-6.0.0-py2.py3-none-any.whl", hash = "sha256:76161b76d321d2f8ee2a8e0b84c30ee8443e01376121fd1c90851e33e3bd7ee2", size = 41338, upload-time = "2025-08-09T19:25:03.513Z" }, ] -[[package]] -name = "progressbar" -version = "2.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/a6/b8e451f6cff1c99b4747a2f7235aa904d2d49e8e1464e0b798272aa84358/progressbar-2.5.tar.gz", hash = "sha256:5d81cb529da2e223b53962afd6c8ca0f05c6670e40309a7219eacc36af9b6c63", size = 10046, upload-time = "2018-06-29T02:32:00.222Z" } - [[package]] name = "propcache" version = "0.4.1" @@ -1221,21 +1067,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] -[[package]] -name = "protobuf" -version = "6.33.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, - { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, -] - [[package]] name = "psutil" version = "7.2.2" @@ -1252,50 +1083,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] -[[package]] -name = "pyaudio" -version = "0.2.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/1d/8878c7752febb0f6716a7e1a52cb92ac98871c5aa522cba181878091607c/PyAudio-0.2.14.tar.gz", hash = "sha256:78dfff3879b4994d1f4fc6485646a57755c6ee3c19647a491f790a0895bd2f87", size = 47066, upload-time = "2023-11-07T07:11:48.806Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/45/8d2b76e8f6db783f9326c1305f3f816d4a12c8eda5edc6a2e1d03c097c3b/PyAudio-0.2.14-cp312-cp312-win32.whl", hash = "sha256:5fce4bcdd2e0e8c063d835dbe2860dac46437506af509353c7f8114d4bacbd5b", size = 144750, upload-time = "2023-11-07T07:11:40.142Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/d25812e5f79f06285767ec607b39149d02aa3b31d50c2269768f48768930/PyAudio-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:12f2f1ba04e06ff95d80700a78967897a489c05e093e3bffa05a84ed9c0a7fa3", size = 164126, upload-time = "2023-11-07T07:11:41.539Z" }, -] - -[[package]] -name = "pyautogui" -version = "0.9.54" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mouseinfo" }, - { name = "pygetwindow" }, - { name = "pymsgbox" }, - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, - { name = "pyscreeze" }, - { name = "python3-xlib", marker = "sys_platform == 'linux'" }, - { name = "pytweening" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/65/ff/cdae0a8c2118a0de74b6cf4cbcdcaf8fd25857e6c3f205ce4b1794b27814/PyAutoGUI-0.9.54.tar.gz", hash = "sha256:dd1d29e8fd118941cb193f74df57e5c6ff8e9253b99c7b04f39cfc69f3ae04b2", size = 61236, upload-time = "2023-05-24T20:11:32.972Z" } - [[package]] name = "pycapnp" -version = "2.1.0" +version = "2.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/86/a57e3c92acd3e1d2fc3dcad683ada191f722e4ac927e1a384b228ec2780a/pycapnp-2.1.0.tar.gz", hash = "sha256:69cc3d861fee1c9b26c73ad2e8a5d51e76ad87e4ff9be33a4fd2fc72f5846aec", size = 689734, upload-time = "2025-09-05T03:50:40.851Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/7b/b2f356bc24220068beffc03e94062e8059a1383addb837303794398aec36/pycapnp-2.2.2.tar.gz", hash = "sha256:7f6c23c2283173a3cb6f1a5086dd0114779d508a7cd1b138d25a6357857d02b6", size = 730142, upload-time = "2026-01-21T01:22:13.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/0e/66b41ba600e5f2523e900b7cc0d2e8496b397a1f2d6a5b7b323ab83418b7/pycapnp-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d2ec561bc948d11f64f43bf9601bede5d6a603d105ae311bd5583c7130624a4", size = 1619223, upload-time = "2025-09-05T03:48:54.64Z" }, - { url = "https://files.pythonhosted.org/packages/40/6e/9bcb30180bd40cb0534124ff7f8ba8746a735018d593f608bf40c97821c0/pycapnp-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132cd97f57f6b6636323ca9b68d389dd90b96e87af38cde31e2b5c5a064f277e", size = 1484321, upload-time = "2025-09-05T03:48:55.85Z" }, - { url = "https://files.pythonhosted.org/packages/14/0a/9ee1c9ecaff499e4fd1df2f0335bc20f666ec6ce5cd80f8ab055007f3c9b/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:568e79268ba7c02a71fe558a8aec1ae3c0f0e6aff809ff618a46afe4964957d2", size = 5143502, upload-time = "2025-09-05T03:48:57.733Z" }, - { url = "https://files.pythonhosted.org/packages/4d/50/65837e1416f7a8861ca1e8fe4582a5aef37192d7ef5e2ecfe46880bfdf9c/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:bcbf6f882d78d368c8e4bb792295392f5c4d71ddffa13a48da27e7bd47b99e37", size = 5508134, upload-time = "2025-09-05T03:48:59.383Z" }, - { url = "https://files.pythonhosted.org/packages/a1/59/46df6db800e77dbc3cc940723fb3fd7bc837327c858edf464a0f904bf547/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:dc25b96e393410dde25c61c1df3ce644700ef94826c829426d58c2c6b3e2d2f5", size = 5631794, upload-time = "2025-09-05T03:49:03.511Z" }, - { url = "https://files.pythonhosted.org/packages/63/9d/18e978500d5f6bd8d152f4d6919e3cfb83ead8a71c14613bbb54322df8b9/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:48938e0436ab1be615fc0a41434119a2065490a6212b9a5e56949e89b0588b76", size = 5369378, upload-time = "2025-09-05T03:49:05.539Z" }, - { url = "https://files.pythonhosted.org/packages/96/dc/726f1917e9996dc29f9fd1cf30674a14546cdbdfa0777e1982b6bd1ad628/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c20de0f6e0b3fa9fa1df3864cf46051db3511b63bc29514d1092af65f2b82a0", size = 5999140, upload-time = "2025-09-05T03:49:07.341Z" }, - { url = "https://files.pythonhosted.org/packages/fd/3a/3bbc4c5776fc32fbf8a59df5c7c5810efd292b933cd6545eb4b16d896268/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:18caca6527862475167c10ea0809531130585aa8a86cc76cd1629eb87ee30637", size = 6454308, upload-time = "2025-09-05T03:49:08.998Z" }, - { url = "https://files.pythonhosted.org/packages/bf/dd/17e2d7808424f10ffddc47329b980488ed83ec716c504791787e593a7a93/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9dcc11237697007b66e3bfc500d2ad892bd79672c9b50d61fbf728c6aaf936de", size = 6544212, upload-time = "2025-09-05T03:49:10.675Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5b/68090013128d7853f34c43828dd4dc80a7c8516fd1b56057b134e1e4c2c0/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c151edf78155b6416e7cb31e2e333d302d742ba52bb37d4dbdf71e75cc999d46", size = 6295279, upload-time = "2025-09-05T03:49:12.712Z" }, - { url = "https://files.pythonhosted.org/packages/5b/52/7d85212b4fcf127588888f71d3dbf5558ee7dc302eba760b12b1b325f9a3/pycapnp-2.1.0-cp312-cp312-win32.whl", hash = "sha256:c09b28419321dafafc644d60c57ff8ccaf3c3e686801b6060c612a7a3c580944", size = 1038995, upload-time = "2025-09-05T03:49:14.165Z" }, - { url = "https://files.pythonhosted.org/packages/f2/12/25d283ebf5c28717364647672e7494dc46196ca7a662f5420e4866f45687/pycapnp-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:560cb69cc02b0347e85b0629e4c2f0a316240900aa905392f9df6bab0a359989", size = 1176620, upload-time = "2025-09-05T03:49:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/8a/76/f8f81d32ddf950e934ec144facbc112e5acbef31a63ba5be0c5f34a00fd5/pycapnp-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b86cb8ea5b8011b562c4e022325a826a62f91196ceb5aa33a766c0bea0b8fd3", size = 1605194, upload-time = "2026-01-21T01:20:29.604Z" }, + { url = "https://files.pythonhosted.org/packages/50/dd/a31be782d56a8648fef899f39aeeab867cf544a6b170871e3f4cbfc58af6/pycapnp-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2353531cfa669e3eeb99be9f993573341650276abec46676d687cc12b3e6b6d9", size = 1486613, upload-time = "2026-01-21T01:20:31.415Z" }, + { url = "https://files.pythonhosted.org/packages/aa/bf/8da830dda94eb7327c6508d6c26fbd964897d742f8c1c0ec48623f0c515b/pycapnp-2.2.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ee27bdc78c7ccd8eaa0fe31e09f0ec4ef31deda3f475fc9373bb4b0de8083053", size = 5186701, upload-time = "2026-01-21T01:20:32.836Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a1/13d0baa2f337f4f6fe8c2142646ba437a26b9c433f5d7ce016a912bad052/pycapnp-2.2.2-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:a8ded808911d1d7a9a2197626c09eea6e269e74dc1276760789538b1efcf6cd5", size = 5239464, upload-time = "2026-01-21T01:20:34.793Z" }, + { url = "https://files.pythonhosted.org/packages/82/76/0451c64b5f0132e4b75a0afe8cec957c8bf8fa981264a7c0b264cb94663e/pycapnp-2.2.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:59e92e1db40041d82a95eab0bd8de2676ce50c6b97c1457e2edde4d134b6d046", size = 5542887, upload-time = "2026-01-21T01:20:36.463Z" }, + { url = "https://files.pythonhosted.org/packages/04/00/d025d68d9a5330d55cbe2d018091cacfef0835c3ad422eb6778c4525041f/pycapnp-2.2.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:ee1e9ac2f0b80fa892b922b60e36efc925d072ecf1204ba3e59d8d9ac7c3dc83", size = 5659696, upload-time = "2026-01-21T01:20:38.069Z" }, + { url = "https://files.pythonhosted.org/packages/58/b7/28f7c539a5f4cbaa12e55ec27d081d11473464230f2e801e4714606d3453/pycapnp-2.2.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:53273b385be78ed8ac997ff8697f2a4c760e93c190b509822a937de5531f4861", size = 5413827, upload-time = "2026-01-21T01:20:39.781Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a7/83bc13d90675f0cee8a38d4ad8401bb2f8662c543b3a6622aeffb7b56b1e/pycapnp-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:812cbdd002bc542b63f969b85c6b9041dfdaf4185613635a6d4feea84c9092fa", size = 6046815, upload-time = "2026-01-21T01:20:42.172Z" }, + { url = "https://files.pythonhosted.org/packages/0d/8a/80f46baa1684bbcc4754ce22c5a44693a1209a64de6df2b256b85b8b8a97/pycapnp-2.2.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9c330218a44bd649b96f565dbf5326d183fdd20f9887bdedfeabd73f0366c2e1", size = 6367625, upload-time = "2026-01-21T01:20:44.004Z" }, + { url = "https://files.pythonhosted.org/packages/02/00/60e82eaf6b4e78d887157bf9f18234c852771cc575355e63d1114c4a5d79/pycapnp-2.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:796aa0ba18bcd4e6b2815471bbed059ad7ee8a815a30e81ac8a9aa030ec7818d", size = 6487265, upload-time = "2026-01-21T01:20:46.137Z" }, + { url = "https://files.pythonhosted.org/packages/57/6e/2dedd8f95dc22357c50a775ee2b8711b3d711f30344d244141e0e1962c3e/pycapnp-2.2.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:251a6abdd64b9b11d2a8e16fc365b922ef6ba6c968959b72a3a3d9d8ec8cc8d7", size = 6576699, upload-time = "2026-01-21T01:20:47.987Z" }, + { url = "https://files.pythonhosted.org/packages/2f/53/f7f69ed1d11ea30ea4f0f6d8319fbc18bc8781c480c118005e0a394492a7/pycapnp-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6aab811e0fcc27ae8bf5f04dedaa7e0af47e0d4db51d9c85ab0d2dad26a46bd7", size = 6344114, upload-time = "2026-01-21T01:20:50.367Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/ab78ee42797ff44c7e1fc0d1aa9396c6742cb05ff01a7cdf9c8f19e0defe/pycapnp-2.2.2-cp312-cp312-win32.whl", hash = "sha256:5061c85dd8f843b2656720ca6976d2a9b418845580c6f6d9602f7119fc2208d5", size = 1047207, upload-time = "2026-01-21T01:20:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fb/6edf56d5144c476270fa8b2e6a660ef5a188fb0097193e342618fbcb0210/pycapnp-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:700eb8c77405222903af3fb5a371c0d766f86139c3d51f4bff41ccd6403b51f9", size = 1185178, upload-time = "2026-01-21T01:20:53.429Z" }, ] [[package]] @@ -1328,25 +1135,16 @@ wheels = [ [[package]] name = "pyee" -version = "13.0.0" +version = "13.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/04/e7c1fe4dc78a6fdbfd6c337b1c3732ff543b8a397683ab38378447baa331/pyee-13.0.1.tar.gz", hash = "sha256:0b931f7c14535667ed4c7e0d531716368715e860b988770fc7eb8578d1f67fc8", size = 31655, upload-time = "2026-02-14T21:12:28.044Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl", hash = "sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228", size = 15659, upload-time = "2026-02-14T21:12:26.263Z" }, ] -[[package]] -name = "pygetwindow" -version = "0.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyrect" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e1/70/c7a4f46dbf06048c6d57d9489b8e0f9c4c3d36b7479f03c5ca97eaa2541d/PyGetWindow-0.0.9.tar.gz", hash = "sha256:17894355e7d2b305cd832d717708384017c1698a90ce24f6f7fbf0242dd0a688", size = 9699, upload-time = "2020-10-04T02:12:50.806Z" } - [[package]] name = "pygments" version = "2.19.2" @@ -1387,2352 +1185,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/ec/6e02b2561d056ea5b33046e3cad21238e6a9097b97d6ccc0fbe52b50c858/pylibsrtp-1.0.0-cp310-abi3-win_arm64.whl", hash = "sha256:2696bdb2180d53ac55d0eb7b58048a2aa30cd4836dd2ca683669889137a94d2a", size = 1159246, upload-time = "2025-10-13T16:12:30.285Z" }, ] -[[package]] -name = "pymonctl" -version = "0.92" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ewmhlib", marker = "sys_platform == 'linux'" }, - { name = "pyobjc", marker = "sys_platform == 'darwin'" }, - { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "typing-extensions" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/13/076a20da28b82be281f7e43e16d9da0f545090f5d14b2125699232b9feba/PyMonCtl-0.92-py3-none-any.whl", hash = "sha256:2495d8dab78f9a7dbce37e74543e60b8bd404a35c3108935697dda7768611b5a", size = 45945, upload-time = "2024-04-22T10:07:09.566Z" }, -] - -[[package]] -name = "pymsgbox" -version = "2.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/6a/e80da7594ee598a776972d09e2813df2b06b3bc29218f440631dfa7c78a8/pymsgbox-2.0.1.tar.gz", hash = "sha256:98d055c49a511dcc10fa08c3043e7102d468f5e4b3a83c6d3c61df722c7d798d", size = 20768, upload-time = "2025-09-09T00:38:56.863Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/3e/08c8cac81b2b2f7502746e6b9c8e5b0ec6432cd882c605560fc409aaf087/pymsgbox-2.0.1-py3-none-any.whl", hash = "sha256:5de8ec19bca2ca7e6c09d39c817c83f17c75cee80275235f43a9931db699f73b", size = 9994, upload-time = "2025-09-09T00:38:55.672Z" }, -] - -[[package]] -name = "pyobjc" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-addressbook" }, - { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-applescriptkit" }, - { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-applicationservices" }, - { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-arkit", marker = "platform_release >= '25.0'" }, - { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-automator" }, - { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4'" }, - { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-carbon" }, - { name = "pyobjc-framework-cfnetwork" }, - { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-compositorservices", marker = "platform_release >= '25.0'" }, - { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coreaudiokit" }, - { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-coredata" }, - { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-coremidi" }, - { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-coreservices" }, - { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-coretext" }, - { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0'" }, - { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-discrecording" }, - { name = "pyobjc-framework-discrecordingui" }, - { name = "pyobjc-framework-diskarbitration" }, - { name = "pyobjc-framework-dvdplayback" }, - { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-exceptionhandling" }, - { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-fskit", marker = "platform_release >= '24.4'" }, - { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-gamesave", marker = "platform_release >= '25.0'" }, - { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-installerplugins" }, - { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-iobluetooth" }, - { name = "pyobjc-framework-iobluetoothui" }, - { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-latentsemanticmapping" }, - { name = "pyobjc-framework-launchservices" }, - { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0'" }, - { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-network", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-osakit" }, - { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-preferencepanes" }, - { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-quartz" }, - { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4'" }, - { name = "pyobjc-framework-screensaver" }, - { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-searchkit" }, - { name = "pyobjc-framework-security" }, - { name = "pyobjc-framework-securityfoundation" }, - { name = "pyobjc-framework-securityinterface" }, - { name = "pyobjc-framework-securityui", marker = "platform_release >= '24.4'" }, - { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-social", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-syncservices" }, - { name = "pyobjc-framework-systemconfiguration" }, - { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-webkit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/17/06/d77639ba166cc09aed2d32ae204811b47bc5d40e035cdc9bff7fff72ec5f/pyobjc-12.1.tar.gz", hash = "sha256:686d6db3eb3182fac9846b8ce3eedf4c7d2680b21b8b8d6e6df054a17e92a12d", size = 11345, upload-time = "2025-11-14T10:07:28.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/00/1085de7b73abf37ec27ad59f7a1d7a406e6e6da45720bced2e198fdf1ddf/pyobjc-12.1-py3-none-any.whl", hash = "sha256:6f8c36cf87b1159d2ca1aa387ffc3efcd51cc3da13ef47c65f45e6d9fbccc729", size = 4226, upload-time = "2025-11-14T09:30:25.185Z" }, -] - -[[package]] -name = "pyobjc-core" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" }, -] - -[[package]] -name = "pyobjc-framework-accessibility" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2d/87/8ca40428d05a668fecc638f2f47dba86054dbdc35351d247f039749de955/pyobjc_framework_accessibility-12.1.tar.gz", hash = "sha256:5ff362c3425edc242d49deec11f5f3e26e565cefb6a2872eda59ab7362149772", size = 29800, upload-time = "2025-11-14T10:08:31.949Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/95/9ea0d1c16316b4b5babf4b0515e9a133ac64269d3ec031f15ee9c7c2a8c1/pyobjc_framework_accessibility-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:537691a0b28fedb8385cd093df069a6e5d7e027629671fc47b50210404eca20b", size = 11335, upload-time = "2025-11-14T09:35:30.81Z" }, -] - -[[package]] -name = "pyobjc-framework-accounts" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/65/10/f6fe336c7624d6753c1f6edac102310ce4434d49b548c479e8e6420d4024/pyobjc_framework_accounts-12.1.tar.gz", hash = "sha256:76d62c5e7b831eb8f4c9ca6abaf79d9ed961dfffe24d89a041fb1de97fe56a3e", size = 15202, upload-time = "2025-11-14T10:08:33.995Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/70/5f9214250f92fbe2e07f35778875d2771d612f313af2a0e4bacba80af28e/pyobjc_framework_accounts-12.1-py2.py3-none-any.whl", hash = "sha256:e1544ad11a2f889a7aaed649188d0e76d58595a27eec07ca663847a7adb21ae5", size = 5104, upload-time = "2025-11-14T09:35:40.246Z" }, -] - -[[package]] -name = "pyobjc-framework-addressbook" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/18/28/0404af2a1c6fa8fd266df26fb6196a8f3fb500d6fe3dab94701949247bea/pyobjc_framework_addressbook-12.1.tar.gz", hash = "sha256:c48b740cf981103cef1743d0804a226d86481fcb839bd84b80e9a586187e8000", size = 44359, upload-time = "2025-11-14T10:08:37.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/33/da709c69cbb60df9522cd614d5c23c15b649b72e5d62fed1048e75c70e7b/pyobjc_framework_addressbook-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7893dd784322f4674299fb3ca40cb03385e5eddb78defd38f08c0b730813b56c", size = 12894, upload-time = "2025-11-14T09:35:47.498Z" }, -] - -[[package]] -name = "pyobjc-framework-adservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/04/1c3d3e0a1ac981664f30b33407dcdf8956046ecde6abc88832cf2aa535f4/pyobjc_framework_adservices-12.1.tar.gz", hash = "sha256:7a31fc8d5c6fd58f012db87c89ba581361fc905114bfb912e0a3a87475c02183", size = 11793, upload-time = "2025-11-14T10:08:39.56Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/13/f7796469b25f50750299c4b0e95dc2f75c7c7fc4c93ef2c644f947f10529/pyobjc_framework_adservices-12.1-py2.py3-none-any.whl", hash = "sha256:9ca3c55e35b2abb3149a0bce5de9a1f7e8ee4f8642036910ca8586ab2e161538", size = 3492, upload-time = "2025-11-14T09:35:57.344Z" }, -] - -[[package]] -name = "pyobjc-framework-adsupport" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/77/f26a2e9994d4df32e9b3680c8014e350b0f1c78d7673b3eba9de2e04816f/pyobjc_framework_adsupport-12.1.tar.gz", hash = "sha256:9a68480e76de567c339dca29a8c739d6d7b5cad30e1cd585ff6e49ec2fc283dd", size = 11645, upload-time = "2025-11-14T10:08:41.439Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/1a/3e90d5a09953bde7b60946cd09cca1411aed05dea855cb88cb9e944c7006/pyobjc_framework_adsupport-12.1-py2.py3-none-any.whl", hash = "sha256:97dcd8799dd61f047bb2eb788bbde81f86e95241b5e5173a3a61cfc05b5598b1", size = 3401, upload-time = "2025-11-14T09:35:59.039Z" }, -] - -[[package]] -name = "pyobjc-framework-applescriptkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/f1/e0c07b2a9eb98f1a2050f153d287a52a92f873eeddb41b74c52c144d8767/pyobjc_framework_applescriptkit-12.1.tar.gz", hash = "sha256:cb09f88cf0ad9753dedc02720065818f854b50e33eb4194f0ea34de6d7a3eb33", size = 11451, upload-time = "2025-11-14T10:08:43.328Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/70/6c399c6ebc37a4e48acf63967e0a916878aedfe420531f6d739215184c0c/pyobjc_framework_applescriptkit-12.1-py2.py3-none-any.whl", hash = "sha256:b955fc017b524027f635d92a8a45a5fd9fbae898f3e03de16ecd94aa4c4db987", size = 4352, upload-time = "2025-11-14T09:36:00.705Z" }, -] - -[[package]] -name = "pyobjc-framework-applescriptobjc" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/4b/e4d1592207cbe17355e01828bdd11dd58f31356108f6a49f5e0484a5df50/pyobjc_framework_applescriptobjc-12.1.tar.gz", hash = "sha256:dce080ed07409b0dda2fee75d559bd312ea1ef0243a4338606440f282a6a0f5f", size = 11588, upload-time = "2025-11-14T10:08:45.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/5f/9ce6706399706930eb29c5308037109c30cfb36f943a6df66fdf38cc842a/pyobjc_framework_applescriptobjc-12.1-py2.py3-none-any.whl", hash = "sha256:79068f982cc22471712ce808c0a8fd5deea11258fc8d8c61968a84b1962a3d10", size = 4454, upload-time = "2025-11-14T09:36:02.276Z" }, -] - -[[package]] -name = "pyobjc-framework-applicationservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coretext" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/be/6a/d4e613c8e926a5744fc47a9e9fea08384a510dc4f27d844f7ad7a2d793bd/pyobjc_framework_applicationservices-12.1.tar.gz", hash = "sha256:c06abb74f119bc27aeb41bf1aef8102c0ae1288aec1ac8665ea186a067a8945b", size = 103247, upload-time = "2025-11-14T10:08:52.18Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/a7/55fa88def5c02732c4b747606ff1cbce6e1f890734bbd00f5596b21eaa02/pyobjc_framework_applicationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c8f6e2fb3b3e9214ab4864ef04eee18f592b46a986c86ea0113448b310520532", size = 32835, upload-time = "2025-11-14T09:36:11.855Z" }, -] - -[[package]] -name = "pyobjc-framework-apptrackingtransparency" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0d/de/f24348982ecab0cb13067c348fc5fbc882c60d704ca290bada9a2b3e594b/pyobjc_framework_apptrackingtransparency-12.1.tar.gz", hash = "sha256:e25bf4e4dfa2d929993ee8e852b28fdf332fa6cde0a33328fdc3b2f502fa50ec", size = 12407, upload-time = "2025-11-14T10:08:54.118Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/b2/90120b93ecfb099b6af21696c26356ad0f2182bdef72b6cba28aa6472ca6/pyobjc_framework_apptrackingtransparency-12.1-py2.py3-none-any.whl", hash = "sha256:23a98ade55495f2f992ecf62c3cbd8f648cbd68ba5539c3f795bf66de82e37ca", size = 3879, upload-time = "2025-11-14T09:36:26.425Z" }, -] - -[[package]] -name = "pyobjc-framework-arkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/8b/843fe08e696bca8e7fc129344965ab6280f8336f64f01ba0a8862d219c3f/pyobjc_framework_arkit-12.1.tar.gz", hash = "sha256:0c5c6b702926179700b68ba29b8247464c3b609fd002a07a3308e72cfa953adf", size = 35814, upload-time = "2025-11-14T10:08:57.55Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/1e/64c55b409243b3eb9abc7a99e7b27ad4e16b9e74bc4b507fb7e7b81fd41a/pyobjc_framework_arkit-12.1-py2.py3-none-any.whl", hash = "sha256:f6d39e28d858ee03f052d6780a552247e682204382dbc090f1d3192fa1b21493", size = 8302, upload-time = "2025-11-14T09:36:28.127Z" }, -] - -[[package]] -name = "pyobjc-framework-audiovideobridging" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/51/f81581e7a3c5cb6c9254c6f1e1ee1d614930493761dec491b5b0d49544b9/pyobjc_framework_audiovideobridging-12.1.tar.gz", hash = "sha256:6230ace6bec1f38e8a727c35d054a7be54e039b3053f98e6dd8d08d6baee2625", size = 38457, upload-time = "2025-11-14T10:09:01.122Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/8e/a28badfcc6c731696e3d3a8a83927bd844d992f9152f903c2fee355702ca/pyobjc_framework_audiovideobridging-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:010021502649e2cca4e999a7c09358d48c6b0ed83530bbc0b85bba6834340e4b", size = 11052, upload-time = "2025-11-14T09:36:34.475Z" }, -] - -[[package]] -name = "pyobjc-framework-authenticationservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6c/18/86218de3bf67fc1d810065f353d9df70c740de567ebee8550d476cb23862/pyobjc_framework_authenticationservices-12.1.tar.gz", hash = "sha256:cef71faeae2559f5c0ff9a81c9ceea1c81108e2f4ec7de52a98c269feff7a4b6", size = 58683, upload-time = "2025-11-14T10:09:06.003Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/1d/e9f296fe1ee9a074ff6c45ce9eb109fc3b45696de000f373265c8e42fd47/pyobjc_framework_authenticationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6fd5ce10fe5359cbbfe03eb12cab3e01992b32ab65653c579b00ac93cf674985", size = 20738, upload-time = "2025-11-14T09:36:51.094Z" }, -] - -[[package]] -name = "pyobjc-framework-automaticassessmentconfiguration" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/24/080afe8189c47c4bb3daa191ccfd962400ca31a67c14b0f7c2d002c2e249/pyobjc_framework_automaticassessmentconfiguration-12.1.tar.gz", hash = "sha256:2b732c02d9097682ca16e48f5d3b10056b740bc091e217ee4d5715194c8970b1", size = 21895, upload-time = "2025-11-14T10:09:08.779Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/b2/fbec3d649bf275d7a9604e5f56015be02ef8dcf002f4ae4d760436b8e222/pyobjc_framework_automaticassessmentconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c2e22ea67d7e6d6a84d968169f83d92b59857a49ab12132de07345adbfea8a62", size = 9332, upload-time = "2025-11-14T09:37:07.083Z" }, -] - -[[package]] -name = "pyobjc-framework-automator" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7e/08/362bf6ac2bba393c46cf56078d4578b692b56857c385e47690637a72f0dd/pyobjc_framework_automator-12.1.tar.gz", hash = "sha256:7491a99347bb30da3a3f744052a03434ee29bee3e2ae520576f7e796740e4ba7", size = 186068, upload-time = "2025-11-14T10:09:20.82Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/36/2e8c36ddf20d501f9d344ed694e39021190faffc44b596f3a430bf437174/pyobjc_framework_automator-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4df9aec77f0fbca66cd3534d1b8398fe6f3e3c2748c0fc12fec2546c7f2e3ffd", size = 10034, upload-time = "2025-11-14T09:37:20.293Z" }, -] - -[[package]] -name = "pyobjc-framework-avfoundation" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/42/c026ab308edc2ed5582d8b4b93da6b15d1b6557c0086914a4aabedd1f032/pyobjc_framework_avfoundation-12.1.tar.gz", hash = "sha256:eda0bb60be380f9ba2344600c4231dd58a3efafa99fdc65d3673ecfbb83f6fcb", size = 310047, upload-time = "2025-11-14T10:09:40.069Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/00/ca471e5dd33f040f69320832e45415d00440260bf7f8221a9df4c4662659/pyobjc_framework_avfoundation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bf634f89265b4d93126153200d885b6de4859ed6b3bc65e69ff75540bc398406", size = 83375, upload-time = "2025-11-14T09:37:47.262Z" }, -] - -[[package]] -name = "pyobjc-framework-avkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/a9/e44db1a1f26e2882c140f1d502d508b1f240af9048909dcf1e1a687375b4/pyobjc_framework_avkit-12.1.tar.gz", hash = "sha256:a5c0ddb0cb700f9b09c8afeca2c58952d554139e9bb078236d2355b1fddfb588", size = 28473, upload-time = "2025-11-14T10:09:43.105Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/34/e77b18f7ed0bd707afd388702e910bdf2d0acee39d1139e8619c916d3eb4/pyobjc_framework_avkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eef2c0a51465de025a4509db05ef18ca2b678bb00ee0a8fbad7fd470edfd58f9", size = 11613, upload-time = "2025-11-14T09:38:19.78Z" }, -] - -[[package]] -name = "pyobjc-framework-avrouting" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6e/83/15bf6c28ec100dae7f92d37c9e117b3b4ee6b4873db062833e16f1cfd6c4/pyobjc_framework_avrouting-12.1.tar.gz", hash = "sha256:6a6c5e583d14f6501df530a9d0559a32269a821fc8140e3646015f097155cd1c", size = 20031, upload-time = "2025-11-14T10:09:45.701Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/54/fa24f666525c1332a11b2de959c9877b0fe08f00f29ecf96964b24246c13/pyobjc_framework_avrouting-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c0fb0d3d260527320377a70c87688ca5e4a208b09fddcae2b4257d7fe9b1e18", size = 8450, upload-time = "2025-11-14T09:38:34.941Z" }, -] - -[[package]] -name = "pyobjc-framework-backgroundassets" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/d1/e917fba82790495152fd3508c5053827658881cf7e9887ba60def5e3f221/pyobjc_framework_backgroundassets-12.1.tar.gz", hash = "sha256:8da34df9ae4519c360c429415477fdaf3fbba5addbc647b3340b8783454eb419", size = 26210, upload-time = "2025-11-14T10:09:48.792Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/34/bbba61f0e8ecb0fe0da7aa2c9ea15f7cb0dca2fb2914fcdcd77b782b5c11/pyobjc_framework_backgroundassets-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2c11cb98650c1a4bc68eeb4b040541ba96613434c5957e98e9bb363413b23c91", size = 10786, upload-time = "2025-11-14T09:38:48.341Z" }, -] - -[[package]] -name = "pyobjc-framework-browserenginekit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5d/b9/39f9de1730e6f8e73be0e4f0c6087cd9439cbe11645b8052d22e1fb8e69b/pyobjc_framework_browserenginekit-12.1.tar.gz", hash = "sha256:6a1a34a155778ab55ab5f463e885f2a3b4680231264e1fe078e62ddeccce49ed", size = 29120, upload-time = "2025-11-14T10:09:51.582Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/e0/8d2cebbfcfd6aacb805ae0ae7ba931f6a39140540b2e1e96719e7be28359/pyobjc_framework_browserenginekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d15766bb841b081447015c9626e2a766febfe651f487893d29c5d72bef976b94", size = 11545, upload-time = "2025-11-14T09:39:00.988Z" }, -] - -[[package]] -name = "pyobjc-framework-businesschat" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4d/da/bc09b6ed19e9ea38ecca9387c291ca11fa680a8132d82b27030f82551c23/pyobjc_framework_businesschat-12.1.tar.gz", hash = "sha256:f6fa3a8369a1a51363e1757530128741d9d09ed90692a1d6777a4c0fbad25868", size = 12055, upload-time = "2025-11-14T10:09:53.436Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/53/88/4c727424b05efa33ed7f6c45e40333e5a8a8dc5bb238e34695addd68463b/pyobjc_framework_businesschat-12.1-py2.py3-none-any.whl", hash = "sha256:f66ce741507b324de3c301d72ba0cfa6aaf7093d7235972332807645c118cc29", size = 3474, upload-time = "2025-11-14T09:39:10.771Z" }, -] - -[[package]] -name = "pyobjc-framework-calendarstore" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/88/41/ae955d1c44dcc18b5b9df45c679e9a08311a0f853b9d981bca760cf1eef2/pyobjc_framework_calendarstore-12.1.tar.gz", hash = "sha256:f9a798d560a3c99ad4c0d2af68767bc5695d8b1aabef04d8377861cd1d6d1670", size = 52272, upload-time = "2025-11-14T10:09:58.48Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/70/f68aebdb7d3fa2dec2e9da9e9cdaa76d370de326a495917dbcde7bb7711e/pyobjc_framework_calendarstore-12.1-py2.py3-none-any.whl", hash = "sha256:18533e0fcbcdd29ee5884dfbd30606710f65df9b688bf47daee1438ee22e50cc", size = 5285, upload-time = "2025-11-14T09:39:12.473Z" }, -] - -[[package]] -name = "pyobjc-framework-callkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1a/c0/1859d4532d39254df085309aff55b85323576f00a883626325af40da4653/pyobjc_framework_callkit-12.1.tar.gz", hash = "sha256:fd6dc9688b785aab360139d683be56f0844bf68bf5e45d0eb770cb68221083cc", size = 29171, upload-time = "2025-11-14T10:10:01.336Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/b7/b3a498b14751b4be6af5272c9be9ded718aa850ebf769b052c7d610a142a/pyobjc_framework_callkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:12adc0ace464a057f8908187698e1d417c6c53619797a69d096f4329bffb1089", size = 11334, upload-time = "2025-11-14T09:39:18.622Z" }, -] - -[[package]] -name = "pyobjc-framework-carbon" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0c/0f/9ab8e518a4e5ac4a1e2fdde38a054c32aef82787ff7f30927345c18b7765/pyobjc_framework_carbon-12.1.tar.gz", hash = "sha256:57a72807db252d5746caccc46da4bd20ff8ea9e82109af9f72735579645ff4f0", size = 37293, upload-time = "2025-11-14T10:10:04.464Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/9e/91853c8f98b9d5bccf464113908620c94cc12c2a3e4625f3ce172e3ea4bc/pyobjc_framework_carbon-12.1-py2.py3-none-any.whl", hash = "sha256:f8b719b3c7c5cf1d61ac7c45a8a70b5e5e5a83fa02f5194c2a48a7e81a3d1b7f", size = 4625, upload-time = "2025-11-14T09:39:27.937Z" }, -] - -[[package]] -name = "pyobjc-framework-cfnetwork" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d2/6a/f5f0f191956e187db85312cbffcc41bf863670d121b9190b4a35f0d36403/pyobjc_framework_cfnetwork-12.1.tar.gz", hash = "sha256:2d16e820f2d43522c793f55833fda89888139d7a84ca5758548ba1f3a325a88d", size = 44383, upload-time = "2025-11-14T10:10:08.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/0b/28034e63f3a25b30ede814469c3f57d44268cbced19664c84a8664200f9d/pyobjc_framework_cfnetwork-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:92760da248c757085fc39bce4388a0f6f0b67540e51edf60a92ad60ca907d071", size = 19135, upload-time = "2025-11-14T09:39:36.382Z" }, -] - -[[package]] -name = "pyobjc-framework-cinematic" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-metal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/67/4e/f4cc7f9f7f66df0290c90fe445f1ff5aa514c6634f5203fe049161053716/pyobjc_framework_cinematic-12.1.tar.gz", hash = "sha256:795068c30447548c0e8614e9c432d4b288b13d5614622ef2f9e3246132329b06", size = 21215, upload-time = "2025-11-14T10:10:10.795Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/a0/cd85c827ce5535c08d936e5723c16ee49f7ff633f2e9881f4f58bf83e4ce/pyobjc_framework_cinematic-12.1-py2.py3-none-any.whl", hash = "sha256:c003543bb6908379680a93dfd77a44228686b86c118cf3bc930f60241d0cd141", size = 5031, upload-time = "2025-11-14T09:39:49.003Z" }, -] - -[[package]] -name = "pyobjc-framework-classkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/ef/67815278023b344a79c7e95f748f647245d6f5305136fc80615254ad447c/pyobjc_framework_classkit-12.1.tar.gz", hash = "sha256:8d1e9dd75c3d14938ff533d88b72bca2d34918e4461f418ea323bfb2498473b4", size = 26298, upload-time = "2025-11-14T10:10:13.406Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/5e/cf43c647af872499fc8e80cc6ac6e9ad77d9c77861dc2e62bdd9b01473ce/pyobjc_framework_classkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c027a3cd9be5fee3f605589118b8b278297c384a271f224c1a98b224e0c087e6", size = 8877, upload-time = "2025-11-14T09:39:54.979Z" }, -] - -[[package]] -name = "pyobjc-framework-cloudkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-accounts" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coredata" }, - { name = "pyobjc-framework-corelocation" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2d/09/762ee4f3ae8568b8e0e5392c705bc4aa1929aa454646c124ca470f1bf9fc/pyobjc_framework_cloudkit-12.1.tar.gz", hash = "sha256:1dddd38e60863f88adb3d1d37d3b4ccb9cbff48c4ef02ab50e36fa40c2379d2f", size = 53730, upload-time = "2025-11-14T10:10:17.831Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/71/cbef7179bf1a594558ea27f1e5ad18f5c17ef71a8a24192aae16127bc849/pyobjc_framework_cloudkit-12.1-py2.py3-none-any.whl", hash = "sha256:875e37bf1a2ce3d05c2492692650104f2d908b56b71a0aedf6620bc517c6c9ca", size = 11090, upload-time = "2025-11-14T09:40:04.207Z" }, -] - -[[package]] -name = "pyobjc-framework-cocoa" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" }, -] - -[[package]] -name = "pyobjc-framework-collaboration" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/64/21/77fe64b39eae98412de1a0d33e9c735aa9949d53fff6b2d81403572b410b/pyobjc_framework_collaboration-12.1.tar.gz", hash = "sha256:2afa264d3233fc0a03a56789c6fefe655ffd81a2da4ba1dc79ea0c45931ad47b", size = 14299, upload-time = "2025-11-14T10:13:04.631Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/66/1507de01f1e2b309f8e11553a52769e4e2e9939ed770b5b560ef5bc27bc1/pyobjc_framework_collaboration-12.1-py2.py3-none-any.whl", hash = "sha256:182d6e6080833b97f9bef61738ae7bacb509714538f0d7281e5f0814c804b315", size = 4907, upload-time = "2025-11-14T09:42:55.781Z" }, -] - -[[package]] -name = "pyobjc-framework-colorsync" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/b4/706e4cc9db25b400201fc90f3edfaa1ab2d51b400b19437b043a68532078/pyobjc_framework_colorsync-12.1.tar.gz", hash = "sha256:d69dab7df01245a8c1bd536b9231c97993a5d1a2765d77692ce40ebbe6c1b8e9", size = 25269, upload-time = "2025-11-14T10:13:07.522Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/e1/82e45c712f43905ee1e6d585180764e8fa6b6f1377feb872f9f03c8c1fb8/pyobjc_framework_colorsync-12.1-py2.py3-none-any.whl", hash = "sha256:41e08d5b9a7af4b380c9adab24c7ff59dfd607b3073ae466693a3e791d8ffdc9", size = 6020, upload-time = "2025-11-14T09:42:57.504Z" }, -] - -[[package]] -name = "pyobjc-framework-compositorservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-metal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/c5/0ba31d7af7e464b7f7ece8c2bd09112bdb0b7260848402e79ba6aacc622c/pyobjc_framework_compositorservices-12.1.tar.gz", hash = "sha256:028e357bbee7fbd3723339a321bbe14e6da5a772708a661a13eea5f17c89e4ab", size = 23292, upload-time = "2025-11-14T10:13:10.392Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/34/5a2de8d531dbb88023898e0b5d2ce8edee14751af6c70e6103f6aa31a669/pyobjc_framework_compositorservices-12.1-py2.py3-none-any.whl", hash = "sha256:9ef22d4eacd492e13099b9b8936db892cdbbef1e3d23c3484e0ed749f83c4984", size = 5910, upload-time = "2025-11-14T09:42:59.154Z" }, -] - -[[package]] -name = "pyobjc-framework-contacts" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4b/a0/ce0542d211d4ea02f5cbcf72ee0a16b66b0d477a4ba5c32e00117703f2f0/pyobjc_framework_contacts-12.1.tar.gz", hash = "sha256:89bca3c5cf31404b714abaa1673577e1aaad6f2ef49d4141c6dbcc0643a789ad", size = 42378, upload-time = "2025-11-14T10:13:14.203Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/c8/2c4638c0d06447886a34070eebb9ba57407d4dd5f0fcb7ab642568272b88/pyobjc_framework_contacts-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2e5ce33b686eb9c0a39351938a756442ea8dea88f6ae2f16bff5494a8569c687", size = 12165, upload-time = "2025-11-14T09:43:05.119Z" }, -] - -[[package]] -name = "pyobjc-framework-contactsui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-contacts" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/0c/7bb7f898456a81d88d06a1084a42e374519d2e40a668a872b69b11f8c1f9/pyobjc_framework_contactsui-12.1.tar.gz", hash = "sha256:aaeca7c9e0c9c4e224d73636f9a558f9368c2c7422155a41fd4d7a13613a77c1", size = 18769, upload-time = "2025-11-14T10:13:16.301Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/ab/319aa52dfe6f836f4dc542282c2c13996222d4f5c9ea7ff8f391b12dac83/pyobjc_framework_contactsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:057f40d2f6eb1b169a300675ec75cc7a747cddcbcee8ece133e652a7086c5ab5", size = 7888, upload-time = "2025-11-14T09:43:18.502Z" }, -] - -[[package]] -name = "pyobjc-framework-coreaudio" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/84/d1/0b884c5564ab952ff5daa949128c64815300556019c1bba0cf2ca752a1a0/pyobjc_framework_coreaudio-12.1.tar.gz", hash = "sha256:a9e72925fcc1795430496ce0bffd4ddaa92c22460a10308a7283ade830089fe1", size = 75077, upload-time = "2025-11-14T10:13:22.345Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/48/05b5192122e23140cf583eac99ccc5bf615591d6ff76483ba986c38ee750/pyobjc_framework_coreaudio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a5ad6309779663f846ab36fe6c49647e470b7e08473c3e48b4f004017bdb68a4", size = 36908, upload-time = "2025-11-14T09:43:36.108Z" }, -] - -[[package]] -name = "pyobjc-framework-coreaudiokit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/41/1c/5c7e39b9361d4eec99b9115b593edd9825388acd594cb3b4519f8f1ac12c/pyobjc_framework_coreaudiokit-12.1.tar.gz", hash = "sha256:b83624f8de3068ab2ca279f786be0804da5cf904ff9979d96007b69ef4869e1e", size = 20137, upload-time = "2025-11-14T10:13:24.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/d7/f171c04c6496afeaad2ab658b0c810682c8407127edc94d4b3f3b90c2bb1/pyobjc_framework_coreaudiokit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:97d5dd857e73d5b597cfc980972b021314b760e2f5bdde7bbba0334fbf404722", size = 7273, upload-time = "2025-11-14T09:43:55.411Z" }, -] - -[[package]] -name = "pyobjc-framework-corebluetooth" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4b/25/d21d6cb3fd249c2c2aa96ee54279f40876a0c93e7161b3304bf21cbd0bfe/pyobjc_framework_corebluetooth-12.1.tar.gz", hash = "sha256:8060c1466d90bbb9100741a1091bb79975d9ba43911c9841599879fc45c2bbe0", size = 33157, upload-time = "2025-11-14T10:13:28.064Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/56/01fef62a479cdd6ff9ee40b6e062a205408ff386ce5ba56d7e14a71fcf73/pyobjc_framework_corebluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe72c9732ee6c5c793b9543f08c1f5bdd98cd95dfc9d96efd5708ec9d6eeb213", size = 13209, upload-time = "2025-11-14T09:44:08.203Z" }, -] - -[[package]] -name = "pyobjc-framework-coredata" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3e/c5/8cd46cd4f1b7cf88bdeed3848f830ea9cdcc4e55cd0287a968a2838033fb/pyobjc_framework_coredata-12.1.tar.gz", hash = "sha256:1e47d3c5e51fdc87a90da62b97cae1bc49931a2bb064db1305827028e1fc0ffa", size = 124348, upload-time = "2025-11-14T10:13:36.435Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/29/fe24dc81e0f154805534923a56fe572c3b296092f086cf5a239fccc2d46a/pyobjc_framework_coredata-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3ee3581ca23ead0b152257e98622fe0bf7e7948f30a62a25a17cafe28fe015e", size = 16409, upload-time = "2025-11-14T09:44:23.582Z" }, -] - -[[package]] -name = "pyobjc-framework-corehaptics" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/2f/74a3da79d9188b05dd4be4428a819ea6992d4dfaedf7d629027cf1f57bfc/pyobjc_framework_corehaptics-12.1.tar.gz", hash = "sha256:521dd2986c8a4266d583dd9ed9ae42053b11ae7d3aa89bf53fbee88307d8db10", size = 22164, upload-time = "2025-11-14T10:13:38.941Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/f4/f469d6a9cac7c195f3d08fa65f94c32dd1dcf97a54b481be648fb3a7a5f3/pyobjc_framework_corehaptics-12.1-py2.py3-none-any.whl", hash = "sha256:a3b07d36ddf5c86a9cdaa411ab53d09553d26ea04fc7d4f82d21a84f0fc05fc0", size = 5382, upload-time = "2025-11-14T09:44:34.725Z" }, -] - -[[package]] -name = "pyobjc-framework-corelocation" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/79/b75885e0d75397dc2fe1ed9ca80be2b64c18b817f5fb924277cb1bf7b163/pyobjc_framework_corelocation-12.1.tar.gz", hash = "sha256:3674e9353f949d91dde6230ad68f6d5748a7f0424751e08a2c09d06050d66231", size = 53511, upload-time = "2025-11-14T10:13:43.384Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/57/1b670890fbf650f1a00afe5ee897ea3856a4a1417c2304c633ee2e978ed0/pyobjc_framework_corelocation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c35ad29a062fea7d417fd8997a9309660ba7963f2847c004e670efbe6bb5b00", size = 12721, upload-time = "2025-11-14T09:44:41.185Z" }, -] - -[[package]] -name = "pyobjc-framework-coremedia" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/7d/5ad600ff7aedfef8ba8f51b11d9aaacdf247b870bd14045d6e6f232e3df9/pyobjc_framework_coremedia-12.1.tar.gz", hash = "sha256:166c66a9c01e7a70103f3ca44c571431d124b9070612ef63a1511a4e6d9d84a7", size = 89566, upload-time = "2025-11-14T10:13:49.788Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/ae/f773cdc33c34a3f9ce6db829dbf72661b65c28ea9efaec8940364185b977/pyobjc_framework_coremedia-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:161a627f5c8cd30a5ebb935189f740e21e6cd94871a9afd463efdb5d51e255fa", size = 29396, upload-time = "2025-11-14T09:44:57.563Z" }, -] - -[[package]] -name = "pyobjc-framework-coremediaio" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/8e/23baee53ccd6c011c965cff62eb55638b4088c3df27d2bf05004105d6190/pyobjc_framework_coremediaio-12.1.tar.gz", hash = "sha256:880b313b28f00b27775d630174d09e0b53d1cdbadb74216618c9dd5b3eb6806a", size = 51100, upload-time = "2025-11-14T10:13:54.277Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/0c/9425c53c9a8c26e468e065ba12ef076bab20197ff7c82052a6dddd46d42b/pyobjc_framework_coremediaio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1108f8a278928fbca465f95123ea4a56456bd6571c1dc8b91793e6c61d624517", size = 17277, upload-time = "2025-11-14T09:45:17.457Z" }, -] - -[[package]] -name = "pyobjc-framework-coremidi" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/75/96/2d583060a71a73c8a7e6d92f2a02675621b63c1f489f2639e020fae34792/pyobjc_framework_coremidi-12.1.tar.gz", hash = "sha256:3c6f1fd03997c3b0f20ab8545126b1ce5f0cddcc1587dffacad876c161da8c54", size = 55587, upload-time = "2025-11-14T10:13:58.903Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/2d/99520f6f1685e4cad816e55cbf6d85f8ce6ea908107950e2d37dc17219d8/pyobjc_framework_coremidi-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e84ffc1de59691c04201b0872e184fe55b5589f3a14876bd14460f3b5f3cd109", size = 24317, upload-time = "2025-11-14T09:45:34.92Z" }, -] - -[[package]] -name = "pyobjc-framework-coreml" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465, upload-time = "2025-11-14T10:14:02.265Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/39/4defef0deb25c5d7e3b7826d301e71ac5b54ef901b7dac4db1adc00f172d/pyobjc_framework_coreml-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10dc8e8db53d7631ebc712cad146e3a9a9a443f4e1a037e844149a24c3c42669", size = 11356, upload-time = "2025-11-14T09:45:52.271Z" }, -] - -[[package]] -name = "pyobjc-framework-coremotion" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2c/eb/abef7d405670cf9c844befc2330a46ee59f6ff7bac6f199bf249561a2ca6/pyobjc_framework_coremotion-12.1.tar.gz", hash = "sha256:8e1b094d34084cc8cf07bedc0630b4ee7f32b0215011f79c9e3cd09d205a27c7", size = 33851, upload-time = "2025-11-14T10:14:05.619Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/75/89fa4aab818aeca21ac0a60b7ceb89a9e685df0ddd3828d36a6f84a0cff0/pyobjc_framework_coremotion-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a77908ab83c422030f913a2a761d196359ab47f6d1e7c76f21de2c6c05ea2f5f", size = 10406, upload-time = "2025-11-14T09:46:05.076Z" }, -] - -[[package]] -name = "pyobjc-framework-coreservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-fsevents" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/b3/52338a3ff41713f7d7bccaf63bef4ba4a8f2ce0c7eaff39a3629d022a79a/pyobjc_framework_coreservices-12.1.tar.gz", hash = "sha256:fc6a9f18fc6da64c166fe95f2defeb7ac8a9836b3b03bb6a891d36035260dbaa", size = 366150, upload-time = "2025-11-14T10:14:28.133Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/6c/33984caaf497fc5a6f86350d7ca4fac8abeb2bc33203edc96955a21e8c05/pyobjc_framework_coreservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8751dc2edcb7cfa248bf8a274c4d6493e8d53ef28a843827a4fc9a0a8b04b8be", size = 30206, upload-time = "2025-11-14T09:46:22.732Z" }, -] - -[[package]] -name = "pyobjc-framework-corespotlight" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/99/d0/88ca73b0cf23847af463334989dd8f98e44f801b811e7e1d8a5627ec20b4/pyobjc_framework_corespotlight-12.1.tar.gz", hash = "sha256:57add47380cd0bbb9793f50a4a4b435a90d4ebd2a33698e058cb353ddfb0d068", size = 38002, upload-time = "2025-11-14T10:14:31.948Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/3b/d3031eddff8029859de6d92b1f741625b1c233748889141a6a5a89b96f0e/pyobjc_framework_corespotlight-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bfcea64ab3250e2886d202b8731be3817b5ac0c8c9f43e77d0d5a0b6602e71a7", size = 9996, upload-time = "2025-11-14T09:46:47.157Z" }, -] - -[[package]] -name = "pyobjc-framework-coretext" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/29/da/682c9c92a39f713bd3c56e7375fa8f1b10ad558ecb075258ab6f1cdd4a6d/pyobjc_framework_coretext-12.1.tar.gz", hash = "sha256:e0adb717738fae395dc645c9e8a10bb5f6a4277e73cba8fa2a57f3b518e71da5", size = 90124, upload-time = "2025-11-14T10:14:38.596Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/0f/ddf45bf0e3ba4fbdc7772de4728fd97ffc34a0b5a15e1ab1115b202fe4ae/pyobjc_framework_coretext-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d246fa654bdbf43bae3969887d58f0b336c29b795ad55a54eb76397d0e62b93c", size = 30108, upload-time = "2025-11-14T09:47:04.228Z" }, -] - -[[package]] -name = "pyobjc-framework-corewlan" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/88/71/739a5d023566b506b3fd3d2412983faa95a8c16226c0dcd0f67a9294a342/pyobjc_framework_corewlan-12.1.tar.gz", hash = "sha256:a9d82ec71ef61f37e1d611caf51a4203f3dbd8caf827e98128a1afaa0fd2feb5", size = 32417, upload-time = "2025-11-14T10:14:41.921Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/31/3e9cf2c0ac3c979062958eae7a275b602515c9c76fd30680e1ee0fea82ae/pyobjc_framework_corewlan-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cba04c0550fc777767cd3a5471e4ed837406ab182d7d5c273bc5ce6ea237bfe", size = 9958, upload-time = "2025-11-14T09:47:22.474Z" }, -] - -[[package]] -name = "pyobjc-framework-cryptotokenkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6b/7c/d03ff4f74054578577296f33bc669fce16c7827eb1a553bb372b5aab30ca/pyobjc_framework_cryptotokenkit-12.1.tar.gz", hash = "sha256:c95116b4b7a41bf5b54aff823a4ef6f4d9da4d0441996d6d2c115026a42d82f5", size = 32716, upload-time = "2025-11-14T10:14:45.024Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/c7/aecba253cf21303b2c9f3ce03fc0e987523609d7839ea8e0a688ae816c96/pyobjc_framework_cryptotokenkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ef51a86c1d0125fabdfad0b3efa51098fb03660d8dad2787d82e8b71c9f189de", size = 12633, upload-time = "2025-11-14T09:47:35.707Z" }, -] - -[[package]] -name = "pyobjc-framework-datadetection" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/db/97/9b03832695ec4d3008e6150ddfdc581b0fda559d9709a98b62815581259a/pyobjc_framework_datadetection-12.1.tar.gz", hash = "sha256:95539e46d3bc970ce890aa4a97515db10b2690597c5dd362996794572e5d5de0", size = 12323, upload-time = "2025-11-14T10:14:46.769Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/1c/5d2f941501e84da8fef8ef3fd378b5c083f063f083f97dd3e8a07f0404b3/pyobjc_framework_datadetection-12.1-py2.py3-none-any.whl", hash = "sha256:4dc8e1d386d655b44b2681a4a2341fb2fc9addbf3dda14cb1553cd22be6a5387", size = 3497, upload-time = "2025-11-14T09:47:45.826Z" }, -] - -[[package]] -name = "pyobjc-framework-devicecheck" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/af/c676107c40d51f55d0a42043865d7246db821d01241b518ea1d3b3ef1394/pyobjc_framework_devicecheck-12.1.tar.gz", hash = "sha256:567e85fc1f567b3fe64ac1cdc323d989509331f64ee54fbcbde2001aec5adbdb", size = 12885, upload-time = "2025-11-14T10:14:48.804Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/d8/1f1b13fa4775b6474c9ad0f4b823953eaeb6c11bd6f03fa8479429b36577/pyobjc_framework_devicecheck-12.1-py2.py3-none-any.whl", hash = "sha256:ffd58148bdef4a1ee8548b243861b7d97a686e73808ca0efac5bef3c430e4a15", size = 3684, upload-time = "2025-11-14T09:47:47.25Z" }, -] - -[[package]] -name = "pyobjc-framework-devicediscoveryextension" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/91/b0/e6e2ed6a7f4b689746818000a003ff7ab9c10945df66398ae8d323ae9579/pyobjc_framework_devicediscoveryextension-12.1.tar.gz", hash = "sha256:60e12445fad97ff1f83472255c943685a8f3a9d95b3126d887cfe769b7261044", size = 14718, upload-time = "2025-11-14T10:14:50.723Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/0c/005fe8db1e19135f493a3de8c8d38031e1ad2d626de4ef89f282acf4aff7/pyobjc_framework_devicediscoveryextension-12.1-py2.py3-none-any.whl", hash = "sha256:d6d6b606d27d4d88efc0bed4727c375e749149b360290c3ad2afc52337739a1b", size = 4321, upload-time = "2025-11-14T09:47:48.78Z" }, -] - -[[package]] -name = "pyobjc-framework-dictionaryservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/c0/daf03cdaf6d4e04e0cf164db358378c07facd21e4e3f8622505d72573e2c/pyobjc_framework_dictionaryservices-12.1.tar.gz", hash = "sha256:354158f3c55d66681fa903c7b3cb05a435b717fa78d0cef44d258d61156454a7", size = 10573, upload-time = "2025-11-14T10:14:53.961Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/13/ab308e934146cfd54691ddad87e572cd1edb6659d795903c4c75904e2d7d/pyobjc_framework_dictionaryservices-12.1-py2.py3-none-any.whl", hash = "sha256:578854eec17fa473ac17ab30050a7bbb2ab69f17c5c49b673695254c3e88ad4b", size = 3930, upload-time = "2025-11-14T09:47:50.782Z" }, -] - -[[package]] -name = "pyobjc-framework-discrecording" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/87/8bd4544793bfcdf507174abddd02b1f077b48fab0004b3db9a63142ce7e9/pyobjc_framework_discrecording-12.1.tar.gz", hash = "sha256:6defc8ea97fb33b4d43870c673710c04c3dc48be30cdf78ba28191a922094990", size = 55607, upload-time = "2025-11-14T10:14:58.276Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/70/14a5aa348a5eba16e8773bb56698575cf114aa55aa303037b7000fc53959/pyobjc_framework_discrecording-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:865f1551e58459da6073360afc8f2cc452472c676ba83dcaa9b0c44e7775e4b5", size = 14566, upload-time = "2025-11-14T09:47:57.503Z" }, -] - -[[package]] -name = "pyobjc-framework-discrecordingui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-discrecording" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/30/63/8667f5bb1ecb556add04e86b278cb358dc1f2f03862705cae6f09097464c/pyobjc_framework_discrecordingui-12.1.tar.gz", hash = "sha256:6793d4a1a7f3219d063f39d87f1d4ebbbb3347e35d09194a193cfe16cba718a8", size = 16450, upload-time = "2025-11-14T10:15:00.254Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/4e/76016130c27b98943c5758a05beab3ba1bc9349ee881e1dfc509ea954233/pyobjc_framework_discrecordingui-12.1-py2.py3-none-any.whl", hash = "sha256:6544ef99cad3dee95716c83cb207088768b6ecd3de178f7e1b17df5997689dfd", size = 4702, upload-time = "2025-11-14T09:48:08.01Z" }, -] - -[[package]] -name = "pyobjc-framework-diskarbitration" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/42/f75fcabec1a0033e4c5235cc8225773f610321d565b63bf982c10c6bbee4/pyobjc_framework_diskarbitration-12.1.tar.gz", hash = "sha256:6703bc5a09b38a720c9ffca356b58f7e99fa76fc988c9ec4d87112344e63dfc2", size = 17121, upload-time = "2025-11-14T10:15:02.223Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/65/c1f54c47af17cb6b923eab85e95f22396c52f90ee8f5b387acffad9a99ea/pyobjc_framework_diskarbitration-12.1-py2.py3-none-any.whl", hash = "sha256:54caf3079fe4ae5ac14466a9b68923ee260a1a88a8290686b4a2015ba14c2db6", size = 4877, upload-time = "2025-11-14T09:48:09.945Z" }, -] - -[[package]] -name = "pyobjc-framework-dvdplayback" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/dd/7859a58e8dd336c77f83feb76d502e9623c394ea09322e29a03f5bc04d32/pyobjc_framework_dvdplayback-12.1.tar.gz", hash = "sha256:279345d4b5fb2c47dd8e5c2fd289e644b6648b74f5c25079805eeb61bfc4a9cd", size = 32332, upload-time = "2025-11-14T10:15:05.257Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/7d/22c07c28fab1f15f0d364806e39a6ca63c737c645fe7e98e157878b5998c/pyobjc_framework_dvdplayback-12.1-py2.py3-none-any.whl", hash = "sha256:af911cc222272a55b46a1a02a46a355279aecfd8132231d8d1b279e252b8ad4c", size = 8243, upload-time = "2025-11-14T09:48:11.824Z" }, -] - -[[package]] -name = "pyobjc-framework-eventkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b6/42/4ec97e641fdcf30896fe76476181622954cb017117b1429f634d24816711/pyobjc_framework_eventkit-12.1.tar.gz", hash = "sha256:7c1882be2f444b1d0f71e9a0cd1e9c04ad98e0261292ab741fc9de0b8bbbbae9", size = 28538, upload-time = "2025-11-14T10:15:07.878Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/35/142f43227627d6324993869d354b9e57eb1e88c4e229e2271592254daf25/pyobjc_framework_eventkit-12.1-py2.py3-none-any.whl", hash = "sha256:3d2d36d5bd9e0a13887a6ac7cdd36675985ebe2a9cb3cdf8cec0725670c92c60", size = 6820, upload-time = "2025-11-14T09:48:14.035Z" }, -] - -[[package]] -name = "pyobjc-framework-exceptionhandling" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/17/5c9d4164f7ccf6b9100be0ad597a7857395dd58ea492cba4f0e9c0b77049/pyobjc_framework_exceptionhandling-12.1.tar.gz", hash = "sha256:7f0719eeea6695197fce0e7042342daa462683dc466eb6a442aad897032ab00d", size = 16694, upload-time = "2025-11-14T10:15:10.173Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/ad/8e05acf3635f20ea7d878be30d58a484c8b901a8552c501feb7893472f86/pyobjc_framework_exceptionhandling-12.1-py2.py3-none-any.whl", hash = "sha256:2f1eae14cf0162e53a0888d9ffe63f047501fe583a23cdc9c966e89f48cf4713", size = 7113, upload-time = "2025-11-14T09:48:15.685Z" }, -] - -[[package]] -name = "pyobjc-framework-executionpolicy" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/11/db765e76e7b00e1521d7bb3a61ae49b59e7573ac108da174720e5d96b61b/pyobjc_framework_executionpolicy-12.1.tar.gz", hash = "sha256:682866589365cd01d3a724d8a2781794b5cba1e152411a58825ea52d7b972941", size = 12594, upload-time = "2025-11-14T10:15:12.077Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/2c/f10352398f10f244401ab8f53cabd127dc3f5dbbfc8de83464661d716671/pyobjc_framework_executionpolicy-12.1-py2.py3-none-any.whl", hash = "sha256:c3a9eca3bd143cf202787dd5e3f40d954c198f18a5e0b8b3e2fcdd317bf33a52", size = 3739, upload-time = "2025-11-14T09:48:17.35Z" }, -] - -[[package]] -name = "pyobjc-framework-extensionkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9a/d4/e9b1f74d29ad9dea3d60468d59b80e14ed3a19f9f7a25afcbc10d29c8a1e/pyobjc_framework_extensionkit-12.1.tar.gz", hash = "sha256:773987353e8aba04223dbba3149253db944abfb090c35318b3a770195b75da6d", size = 18694, upload-time = "2025-11-14T10:15:14.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/d9/8064dad6114a489e5439cc20d9fb0dd64cfc406d875b4a3c87015b3f6266/pyobjc_framework_extensionkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e01d705c7ac6d080ae34a81db6d9b81875eabefa63fd6eafbfa30f676dd780b", size = 7932, upload-time = "2025-11-14T09:48:23.653Z" }, -] - -[[package]] -name = "pyobjc-framework-externalaccessory" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8e/35/86c097ae2fdf912c61c1276e80f3e090a3fc898c75effdf51d86afec456b/pyobjc_framework_externalaccessory-12.1.tar.gz", hash = "sha256:079f770a115d517a6ab87db1b8a62ca6cdf6c35ae65f45eecc21b491e78776c0", size = 20958, upload-time = "2025-11-14T10:15:16.419Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/52/984034396089766b6e5ff3be0f93470e721c420fa9d1076398557532234f/pyobjc_framework_externalaccessory-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dedbf7a09375ac19668156c1417bd7829565b164a246b714e225b9cbb6a351ad", size = 8932, upload-time = "2025-11-14T09:48:37.393Z" }, -] - -[[package]] -name = "pyobjc-framework-fileprovider" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/9a/724b1fae5709f8860f06a6a2a46de568f9bb8bdb2e2aae45b4e010368f51/pyobjc_framework_fileprovider-12.1.tar.gz", hash = "sha256:45034e0d00ae153c991aa01cb1fd41874650a30093e77ba73401dcce5534c8ad", size = 43071, upload-time = "2025-11-14T10:15:19.989Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/f5/56f0751a2988b2caca89d6800c8f29246828d1a7498bb676ef1ab28000b7/pyobjc_framework_fileprovider-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:89b140ea8369512ddf4164b007cbe35b4d97d1dcb8affa12a7264c0ab8d56e45", size = 21003, upload-time = "2025-11-14T09:48:53.128Z" }, -] - -[[package]] -name = "pyobjc-framework-fileproviderui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-fileprovider" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/00/234f9b93f75255845df81d9d5ea20cb83ecb5c0a4e59147168b622dd0b9d/pyobjc_framework_fileproviderui-12.1.tar.gz", hash = "sha256:15296429d9db0955abc3242b2920b7a810509a85118dbc185f3ac8234e5a6165", size = 12437, upload-time = "2025-11-14T10:15:22.044Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/65/cc4397511bd0af91993d6302a2aed205296a9ad626146eefdfc8a9624219/pyobjc_framework_fileproviderui-12.1-py2.py3-none-any.whl", hash = "sha256:521a914055089e28631018bd78df4c4f7416e98b4150f861d4a5bc97d5b1ffe4", size = 3715, upload-time = "2025-11-14T09:49:04.213Z" }, -] - -[[package]] -name = "pyobjc-framework-findersync" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/63/c8da472e0910238a905bc48620e005a1b8ae7921701408ca13e5fb0bfb4b/pyobjc_framework_findersync-12.1.tar.gz", hash = "sha256:c513104cef0013c233bf8655b527df665ce6f840c8bc0b3781e996933d4dcfa6", size = 13507, upload-time = "2025-11-14T10:15:24.161Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/9f/ec7f393e3e2fd11cbdf930d884a0ba81078bdb61920b3cba4f264de8b446/pyobjc_framework_findersync-12.1-py2.py3-none-any.whl", hash = "sha256:e07abeca52c486cf14927f617afc27afa7a3828b99fab3ad02355105fb29203e", size = 4889, upload-time = "2025-11-14T09:49:05.763Z" }, -] - -[[package]] -name = "pyobjc-framework-fsevents" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/17/21f45d2bca2efc72b975f2dfeae7a163dbeabb1236c1f188578403fd4f09/pyobjc_framework_fsevents-12.1.tar.gz", hash = "sha256:a22350e2aa789dec59b62da869c1b494a429f8c618854b1383d6473f4c065a02", size = 26487, upload-time = "2025-11-14T10:15:26.796Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/e3/2c5eeea390c0b053e2d73b223af3ec87a3e99a8106e8d3ee79942edb0822/pyobjc_framework_fsevents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2949358513fd7bc622fb362b5c4af4fc24fc6307320070ca410885e5e13d975", size = 13141, upload-time = "2025-11-14T09:49:11.947Z" }, -] - -[[package]] -name = "pyobjc-framework-fskit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/55/d00246d6e6d9756e129e1d94bc131c99eece2daa84b2696f6442b8a22177/pyobjc_framework_fskit-12.1.tar.gz", hash = "sha256:ec54e941cdb0b7d800616c06ca76a93685bd7119b8aa6eb4e7a3ee27658fc7ba", size = 42372, upload-time = "2025-11-14T10:15:30.411Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/a9/0c47469fe80fa14bc698bb0a5b772b44283cc3aca0f67e7f70ab45e09b24/pyobjc_framework_fskit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:50972897adea86508cfee33ec4c23aa91dede97e9da1640ea2fe74702b065be1", size = 20250, upload-time = "2025-11-14T09:49:28.065Z" }, -] - -[[package]] -name = "pyobjc-framework-gamecenter" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d2/f8/b5fd86f6b722d4259228922e125b50e0a6975120a1c4d957e990fb84e42c/pyobjc_framework_gamecenter-12.1.tar.gz", hash = "sha256:de4118f14c9cf93eb0316d49da410faded3609ce9cd63425e9ef878cebb7ea72", size = 31473, upload-time = "2025-11-14T10:15:33.38Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/ee/b496cc4248c5b901e159d6d9a437da9b86a3105fc3999a66744ba2b2c884/pyobjc_framework_gamecenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e8d6d10b868be7c00c2d5a0944cc79315945735dcf17eaa3fec1a7986d26be9b", size = 18868, upload-time = "2025-11-14T09:49:44.767Z" }, -] - -[[package]] -name = "pyobjc-framework-gamecontroller" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/21/14/353bb1fe448cd833839fd199ab26426c0248088753e63c22fe19dc07530f/pyobjc_framework_gamecontroller-12.1.tar.gz", hash = "sha256:64ed3cc4844b67f1faeb540c7cc8d512c84f70b3a4bafdb33d4663a2b2a2b1d8", size = 54554, upload-time = "2025-11-14T10:15:37.591Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/28/9f03d0ef7c78340441f78b19fb2d2c952af04a240da5ed30c7cf2d0d0f4e/pyobjc_framework_gamecontroller-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:878aa6590c1510e91bfc8710d6c880e7a8f3656a7b7b6f4f3af487a6f677ccd5", size = 20949, upload-time = "2025-11-14T09:50:01.608Z" }, -] - -[[package]] -name = "pyobjc-framework-gamekit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/52/7b/d625c0937557f7e2e64200fdbeb867d2f6f86b2f148b8d6bfe085e32d872/pyobjc_framework_gamekit-12.1.tar.gz", hash = "sha256:014d032c3484093f1409f8f631ba8a0fd2ff7a3ae23fd9d14235340889854c16", size = 63833, upload-time = "2025-11-14T10:15:42.842Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/05/1c49e1030dc9f2812fa8049442158be76c32f271075f4571f94e4389ea86/pyobjc_framework_gamekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eee796d5781157f2c5684f7ef4c2a7ace9d9b408a26a9e7e92e8adf5a3f63d7", size = 22493, upload-time = "2025-11-14T09:50:19.129Z" }, -] - -[[package]] -name = "pyobjc-framework-gameplaykit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-spritekit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e2/11/c310bbc2526f95cce662cc1f1359bb11e2458eab0689737b4850d0f6acb7/pyobjc_framework_gameplaykit-12.1.tar.gz", hash = "sha256:935ebd806d802888969357946245d35a304c530c86f1ffe584e2cf21f0a608a8", size = 41511, upload-time = "2025-11-14T10:15:46.529Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/1f/e5fe404f92ec0f9c8c37b00d6cb3ba96ee396c7f91b0a41a39b64bfc2743/pyobjc_framework_gameplaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:309b0d7479f702830c9be92dbe5855ac2557a9d23f05f063caf9d9fdb85ff5f0", size = 13150, upload-time = "2025-11-14T09:50:36.884Z" }, -] - -[[package]] -name = "pyobjc-framework-gamesave" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1b/1f/8d05585c844535e75dbc242dd6bdfecfc613d074dcb700362d1c908fb403/pyobjc_framework_gamesave-12.1.tar.gz", hash = "sha256:eb731c97aa644e78a87838ed56d0e5bdbaae125bdc8854a7772394877312cc2e", size = 12654, upload-time = "2025-11-14T10:15:48.344Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/ec/93d48cb048a1b35cea559cc9261b07f0d410078b3af029121302faa410d0/pyobjc_framework_gamesave-12.1-py2.py3-none-any.whl", hash = "sha256:432e69f8404be9290d42c89caba241a3156ed52013947978ac54f0f032a14ffd", size = 3689, upload-time = "2025-11-14T09:50:47.263Z" }, -] - -[[package]] -name = "pyobjc-framework-healthkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/af/67/436630d00ba1028ea33cc9df2fc28e081481433e5075600f2ea1ff00f45e/pyobjc_framework_healthkit-12.1.tar.gz", hash = "sha256:29c5e5de54b41080b7a4b0207698ac6f600dcb9149becc9c6b3a69957e200e5c", size = 91802, upload-time = "2025-11-14T10:15:54.661Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/87/bb1c438de51c4fa733a99ce4d3301e585f14d7efd94031a97707c0be2b46/pyobjc_framework_healthkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:15b6fc958ff5de42888b18dffdec839cb36d2dd8b82076ed2f21a51db5271109", size = 20799, upload-time = "2025-11-14T09:50:54.531Z" }, -] - -[[package]] -name = "pyobjc-framework-imagecapturecore" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/a1/39347381fc7d3cd5ab942d86af347b25c73f0ddf6f5227d8b4d8f5328016/pyobjc_framework_imagecapturecore-12.1.tar.gz", hash = "sha256:c4776c59f4db57727389d17e1ffd9c567b854b8db52198b3ccc11281711074e5", size = 46397, upload-time = "2025-11-14T10:15:58.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/13/632957b284dec3743d73fb30dbdf03793b3cf1b4c62e61e6484d870f3879/pyobjc_framework_imagecapturecore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2777e17ff71fb5a327a897e48c5c7b5a561723a80f990d26e6ed5a1b8748816", size = 16012, upload-time = "2025-11-14T09:51:12.058Z" }, -] - -[[package]] -name = "pyobjc-framework-inputmethodkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5d/b8/d33dd8b7306029bbbd80525bf833fc547e6a223c494bf69a534487283a28/pyobjc_framework_inputmethodkit-12.1.tar.gz", hash = "sha256:f63b6fe2fa7f1412eae63baea1e120e7865e3b68ccfb7d8b0a4aadb309f2b278", size = 23054, upload-time = "2025-11-14T10:16:01.464Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/c2/59bea66405784b25f5d4e821467ba534a0b92dfc98e07257c971e2a8ed73/pyobjc_framework_inputmethodkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0b7d813d46a060572fc0c14ef832e4fe538ebf64e5cab80ee955191792ce0110", size = 9506, upload-time = "2025-11-14T09:51:26.924Z" }, -] - -[[package]] -name = "pyobjc-framework-installerplugins" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/60/ca4ab04eafa388a97a521db7d60a812e2f81a3c21c2372587872e6b074f9/pyobjc_framework_installerplugins-12.1.tar.gz", hash = "sha256:1329a193bd2e92a2320a981a9a421a9b99749bade3e5914358923e94fe995795", size = 25277, upload-time = "2025-11-14T10:16:04.379Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/1f/31dca45db3342882a628aa1b27707a283d4dc7ef558fddd2533175a0661a/pyobjc_framework_installerplugins-12.1-py2.py3-none-any.whl", hash = "sha256:d2201c81b05bdbe0abf0af25db58dc230802573463bea322f8b2863e37b511d5", size = 4813, upload-time = "2025-11-14T09:51:37.836Z" }, -] - -[[package]] -name = "pyobjc-framework-instantmessage" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d4/67/66754e0d26320ba24a33608ca94d3f38e60ee6b2d2e094cb6269b346fdd4/pyobjc_framework_instantmessage-12.1.tar.gz", hash = "sha256:f453118d5693dc3c94554791bd2aaafe32a8b03b0e3d8ec3934b44b7fdd1f7e7", size = 31217, upload-time = "2025-11-14T10:16:07.693Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/38/6ae95b5c87d887c075bd5f4f7cca3d21dafd0a77cfdde870e87ca17579eb/pyobjc_framework_instantmessage-12.1-py2.py3-none-any.whl", hash = "sha256:cd91d38e8f356afd726b6ea8c235699316ea90edfd3472965c251efbf4150bc9", size = 5436, upload-time = "2025-11-14T09:51:39.557Z" }, -] - -[[package]] -name = "pyobjc-framework-intents" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/a1/3bab6139e94b97eca098e1562f5d6840e3ff10ea1f7fd704a17111a97d5b/pyobjc_framework_intents-12.1.tar.gz", hash = "sha256:bd688c3ab34a18412f56e459e9dae29e1f4152d3c2048fcacdef5fc49dfb9765", size = 132262, upload-time = "2025-11-14T10:16:16.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/90/e9489492ae90b4c1ffd02c1221c0432b8768d475787e7887f79032c2487a/pyobjc_framework_intents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ea9f3e79bf4baf6c7b0fd2d2797184ed51a372bf7f32974b4424f9bd067ef50", size = 32156, upload-time = "2025-11-14T09:51:49.438Z" }, -] - -[[package]] -name = "pyobjc-framework-intentsui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-intents" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/cf/f0e385b9cfbf153d68efe8d19e5ae672b59acbbfc1f9b58faaefc5ec8c9e/pyobjc_framework_intentsui-12.1.tar.gz", hash = "sha256:16bdf4b7b91c0d1ec9d5513a1182861f1b5b7af95d4f4218ff7cf03032d57f99", size = 19784, upload-time = "2025-11-14T10:16:18.716Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/17/06812542a9028f5b2dcce56f52f25633c08b638faacd43bad862aad1b41d/pyobjc_framework_intentsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cb894fcc4c9ea613a424dcf6fb48142d51174559b82cfdafac8cb47555c842cf", size = 8983, upload-time = "2025-11-14T09:52:07.667Z" }, -] - -[[package]] -name = "pyobjc-framework-iobluetooth" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/aa/ca3944bbdfead4201b4ae6b51510942c5a7d8e5e2dc3139a071c74061fdf/pyobjc_framework_iobluetooth-12.1.tar.gz", hash = "sha256:8a434118812f4c01dfc64339d41fe8229516864a59d2803e9094ee4cbe2b7edd", size = 155241, upload-time = "2025-11-14T10:16:28.896Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/b6/933b56afb5e84c3c35c074c9e30d7b701c6038989d4867867bdaa7ab618b/pyobjc_framework_iobluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:111a6e54be9e9dcf77fa2bf84fdac09fae339aa33087d8647ea7ffbd34765d4c", size = 40439, upload-time = "2025-11-14T09:52:26.071Z" }, -] - -[[package]] -name = "pyobjc-framework-iobluetoothui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-iobluetooth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8f/39/31d9a4e8565a4b1ec0a9ad81480dc0879f3df28799eae3bc22d1dd53705d/pyobjc_framework_iobluetoothui-12.1.tar.gz", hash = "sha256:81f8158bdfb2966a574b6988eb346114d6a4c277300c8c0a978c272018184e6f", size = 16495, upload-time = "2025-11-14T10:16:31.212Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/c9/69aeda0cdb5d25d30dc4596a1c5b464fc81b5c0c4e28efc54b7e11bde51c/pyobjc_framework_iobluetoothui-12.1-py2.py3-none-any.whl", hash = "sha256:a6d8ab98efa3029130577a57ee96b183c35c39b0f1c53a7534f8838260fab993", size = 4045, upload-time = "2025-11-14T09:52:42.201Z" }, -] - -[[package]] -name = "pyobjc-framework-iosurface" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/07/61/0f12ad67a72d434e1c84b229ec760b5be71f53671ee9018593961c8bfeb7/pyobjc_framework_iosurface-12.1.tar.gz", hash = "sha256:4b9d0c66431aa296f3ca7c4f84c00dc5fc961194830ad7682fdbbc358fa0db55", size = 17690, upload-time = "2025-11-14T10:16:33.282Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ad/793d98a7ed9b775dc8cce54144cdab0df1808a1960ee017e46189291a8f3/pyobjc_framework_iosurface-12.1-py2.py3-none-any.whl", hash = "sha256:e784e248397cfebef4655d2c0025766d3eaa4a70474e363d084fc5ce2a4f2a3f", size = 4902, upload-time = "2025-11-14T09:52:43.899Z" }, -] - -[[package]] -name = "pyobjc-framework-ituneslibrary" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/46/d9bcec88675bf4ee887b9707bd245e2a793e7cb916cf310f286741d54b1f/pyobjc_framework_ituneslibrary-12.1.tar.gz", hash = "sha256:7f3aa76c4d05f6fa6015056b88986cacbda107c3f29520dd35ef0936c7367a6e", size = 23730, upload-time = "2025-11-14T10:16:36.127Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/92/b598694a1713ee46f45c4bfb1a0425082253cbd2b1caf9f8fd50f292b017/pyobjc_framework_ituneslibrary-12.1-py2.py3-none-any.whl", hash = "sha256:fb678d7c3ff14c81672e09c015e25880dac278aa819971f4d5f75d46465932ef", size = 5205, upload-time = "2025-11-14T09:52:45.733Z" }, -] - -[[package]] -name = "pyobjc-framework-kernelmanagement" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0a/7e/ecbac119866e8ac2cce700d7a48a4297946412ac7cbc243a7084a6582fb1/pyobjc_framework_kernelmanagement-12.1.tar.gz", hash = "sha256:488062893ac2074e0c8178667bf864a21f7909c11111de2f6a10d9bc579df59d", size = 11773, upload-time = "2025-11-14T10:16:38.216Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/32/04325a20f39d88d6d712437e536961a9e6a4ec19f204f241de6ed54d1d84/pyobjc_framework_kernelmanagement-12.1-py2.py3-none-any.whl", hash = "sha256:926381bfbfbc985c3e6dfcb7004af21bb16ff66ecbc08912b925989a705944ff", size = 3704, upload-time = "2025-11-14T09:52:47.268Z" }, -] - -[[package]] -name = "pyobjc-framework-latentsemanticmapping" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/88/3c/b621dac54ae8e77ac25ee75dd93e310e2d6e0faaf15b8da13513258d6657/pyobjc_framework_latentsemanticmapping-12.1.tar.gz", hash = "sha256:f0b1fa823313eefecbf1539b4ed4b32461534b7a35826c2cd9f6024411dc9284", size = 15526, upload-time = "2025-11-14T10:16:40.149Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/8e/74a7eb29b545f294485cd3cf70557b4a35616555fe63021edbb3e0ea4c20/pyobjc_framework_latentsemanticmapping-12.1-py2.py3-none-any.whl", hash = "sha256:7d760213b42bc8b1bc1472e1873c0f78ee80f987225978837b1fecdceddbdbf4", size = 5471, upload-time = "2025-11-14T09:52:48.939Z" }, -] - -[[package]] -name = "pyobjc-framework-launchservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/37/d0/24673625922b0ad21546be5cf49e5ec1afaa4553ae92f222adacdc915907/pyobjc_framework_launchservices-12.1.tar.gz", hash = "sha256:4d2d34c9bd6fb7f77566155b539a2c70283d1f0326e1695da234a93ef48352dc", size = 20470, upload-time = "2025-11-14T10:16:42.499Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/af/9a0aebaab4c15632dc8fcb3669c68fa541a3278d99541d9c5f966fbc0909/pyobjc_framework_launchservices-12.1-py2.py3-none-any.whl", hash = "sha256:e63e78fceeed4d4dc807f9dabd5cf90407e4f552fab6a0d75a8d0af63094ad3c", size = 3905, upload-time = "2025-11-14T09:52:50.71Z" }, -] - -[[package]] -name = "pyobjc-framework-libdispatch" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/26/e8/75b6b9b3c88b37723c237e5a7600384ea2d84874548671139db02e76652b/pyobjc_framework_libdispatch-12.1.tar.gz", hash = "sha256:4035535b4fae1b5e976f3e0e38b6e3442ffea1b8aa178d0ca89faa9b8ecdea41", size = 38277, upload-time = "2025-11-14T10:16:46.235Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/6f/96e15c7b2f7b51fc53252216cd0bed0c3541bc0f0aeb32756fefd31bed7d/pyobjc_framework_libdispatch-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e9570d7a9a3136f54b0b834683bf3f206acd5df0e421c30f8fd4f8b9b556789", size = 15650, upload-time = "2025-11-14T09:52:59.284Z" }, -] - -[[package]] -name = "pyobjc-framework-libxpc" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/16/e4/364db7dc26f235e3d7eaab2f92057f460b39800bffdec3128f113388ac9f/pyobjc_framework_libxpc-12.1.tar.gz", hash = "sha256:e46363a735f3ecc9a2f91637750623f90ee74f9938a4e7c833e01233174af44d", size = 35186, upload-time = "2025-11-14T10:16:49.503Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/7f/fdec72430f90921b154517a6f9bbeefa7bacfb16b91320742eb16a5955c5/pyobjc_framework_libxpc-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ba93e91e9ca79603dd265382e9f80e9bd32309cd09c8ac3e6489fc5b233676c8", size = 19730, upload-time = "2025-11-14T09:53:17.113Z" }, -] - -[[package]] -name = "pyobjc-framework-linkpresentation" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e3/58/c0c5919d883485ccdb6dccd8ecfe50271d2f6e6ab7c9b624789235ccec5a/pyobjc_framework_linkpresentation-12.1.tar.gz", hash = "sha256:84df6779591bb93217aa8bd82c10e16643441678547d2d73ba895475a02ade94", size = 13330, upload-time = "2025-11-14T10:16:52.169Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/51/226eb45f196f3bf93374713571aae6c8a4760389e1d9435c4a4cc3f38ea4/pyobjc_framework_linkpresentation-12.1-py2.py3-none-any.whl", hash = "sha256:853a84c7b525b77b114a7a8d798aef83f528ed3a6803bda12184fe5af4e79a47", size = 3865, upload-time = "2025-11-14T09:53:28.386Z" }, -] - -[[package]] -name = "pyobjc-framework-localauthentication" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8d/0e/7e5d9a58bb3d5b79a75d925557ef68084171526191b1c0929a887a553d4f/pyobjc_framework_localauthentication-12.1.tar.gz", hash = "sha256:2284f587d8e1206166e4495b33f420c1de486c36c28c4921d09eec858a699d05", size = 29947, upload-time = "2025-11-14T10:16:54.923Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/93/91761ad4e5fa1c3ec25819865d1ccfbee033987147087bff4fcce67a4dc4/pyobjc_framework_localauthentication-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3af1acd287d830cc7f912f46cde0dab054952bde0adaf66c8e8524311a68d279", size = 10773, upload-time = "2025-11-14T09:53:34.074Z" }, -] - -[[package]] -name = "pyobjc-framework-localauthenticationembeddedui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-localauthentication" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/20/83ab4180e29b9a4a44d735c7f88909296c6adbe6250e8e00a156aff753e1/pyobjc_framework_localauthenticationembeddedui-12.1.tar.gz", hash = "sha256:a15ec44bf2769c872e86c6b550b6dd4f58d4eda40ad9ff00272a67d279d1d4e9", size = 13611, upload-time = "2025-11-14T10:16:57.145Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/7d/0d46639c7a26b6af928ab4c822cd28b733791e02ac28cc84c3014bcf7dc7/pyobjc_framework_localauthenticationembeddedui-12.1-py2.py3-none-any.whl", hash = "sha256:a7ce7b56346597b9f4768be61938cbc8fc5b1292137225b6c7f631b9cde97cd7", size = 3991, upload-time = "2025-11-14T09:53:42.958Z" }, -] - -[[package]] -name = "pyobjc-framework-mailkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/98/3d9028620c1cd32ff4fb031155aba3b5511e980cdd114dd51383be9cb51b/pyobjc_framework_mailkit-12.1.tar.gz", hash = "sha256:d5574b7259baec17096410efcaacf5d45c7bb5f893d4c25cbb7072369799b652", size = 20996, upload-time = "2025-11-14T10:16:59.449Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/8d/3c968b736a3a8bd9d8e870b39b1c772a013eea1b81b89fc4efad9021a6cb/pyobjc_framework_mailkit-12.1-py2.py3-none-any.whl", hash = "sha256:536ac0c4ea3560364cd159a6512c3c18a744a12e4e0883c07df0f8a2ff21e3fe", size = 4871, upload-time = "2025-11-14T09:53:44.697Z" }, -] - -[[package]] -name = "pyobjc-framework-mapkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-corelocation" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/bb/2a668203c20e509a648c35e803d79d0c7f7816dacba74eb5ad8acb186790/pyobjc_framework_mapkit-12.1.tar.gz", hash = "sha256:dbc32dc48e821aaa9b4294402c240adbc1c6834e658a07677b7c19b7990533c5", size = 63520, upload-time = "2025-11-14T10:17:04.221Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/00/a3de41cdf3e6cd7a144e38999fe1ea9777ad19e19d863f2da862e7affe7b/pyobjc_framework_mapkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84ad7766271c114bdc423e4e2ff5433e5fc6771a3338b5f8e4b54d0340775800", size = 22518, upload-time = "2025-11-14T09:53:52.727Z" }, -] - -[[package]] -name = "pyobjc-framework-mediaaccessibility" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e0/10/dc1007e56944ed2e981e69e7b2fed2b2202c79b0d5b742b29b1081d1cbdd/pyobjc_framework_mediaaccessibility-12.1.tar.gz", hash = "sha256:cc4e3b1d45e84133d240318d53424eff55968f5c6873c2c53267598853445a3f", size = 16325, upload-time = "2025-11-14T10:17:07.454Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/0c/7fb5462561f59d739192c6d02ba0fd36ad7841efac5a8398a85a030ef7fc/pyobjc_framework_mediaaccessibility-12.1-py2.py3-none-any.whl", hash = "sha256:2ff8845c97dd52b0e5cf53990291e6d77c8a73a7aac0e9235d62d9a4256916d1", size = 4800, upload-time = "2025-11-14T09:54:05.04Z" }, -] - -[[package]] -name = "pyobjc-framework-mediaextension" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d6/aa/1e8015711df1cdb5e4a0aa0ed4721409d39971ae6e1e71915e3ab72423a3/pyobjc_framework_mediaextension-12.1.tar.gz", hash = "sha256:44409d63cc7d74e5724a68e3f9252cb62fd0fd3ccf0ca94c6a33e5c990149953", size = 39425, upload-time = "2025-11-14T10:17:11.486Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/99038bcf72ec68e452709af10a087c1377c2d595ba4e66d7a2b0775145d2/pyobjc_framework_mediaextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:442bc3a759efb5c154cb75d643a5e182297093533fcdd1c24be6f64f68b93371", size = 38973, upload-time = "2025-11-14T09:54:16.701Z" }, -] - -[[package]] -name = "pyobjc-framework-medialibrary" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/e9/848ebd02456f8fdb41b42298ec585bfed5899dbd30306ea5b0a7e4c4b341/pyobjc_framework_medialibrary-12.1.tar.gz", hash = "sha256:690dcca09b62511df18f58e8566cb33d9652aae09fe63a83f594bd018b5edfcd", size = 15995, upload-time = "2025-11-14T10:17:15.45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/cd/eeaf8585a343fda5b8cf3b8f144c872d1057c845202098b9441a39b76cb0/pyobjc_framework_medialibrary-12.1-py2.py3-none-any.whl", hash = "sha256:1f03ad6802a5c6e19ee3208b065689d3ec79defe1052cb80e00f54e1eff5f2a0", size = 4361, upload-time = "2025-11-14T09:54:32.259Z" }, -] - -[[package]] -name = "pyobjc-framework-mediaplayer" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/f0/851f6f47e11acbd62d5f5dcb8274afc969135e30018591f75bf3cbf6417f/pyobjc_framework_mediaplayer-12.1.tar.gz", hash = "sha256:5ef3f669bdf837d87cdb5a486ec34831542360d14bcba099c7c2e0383380794c", size = 35402, upload-time = "2025-11-14T10:17:18.97Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/c0/038ee3efd286c0fbc89c1e0cb688f4670ed0e5803aa36e739e79ffc91331/pyobjc_framework_mediaplayer-12.1-py2.py3-none-any.whl", hash = "sha256:85d9baec131807bfdf0f4c24d4b943e83cce806ab31c95c7e19c78e3fb7eefc8", size = 7120, upload-time = "2025-11-14T09:54:33.901Z" }, -] - -[[package]] -name = "pyobjc-framework-mediatoolbox" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/71/be5879380a161f98212a336b432256f307d1dcbaaaeb8ec988aea2ada2cd/pyobjc_framework_mediatoolbox-12.1.tar.gz", hash = "sha256:385b48746a5f08756ee87afc14037e552954c427ed5745d7ece31a21a7bad5ab", size = 22305, upload-time = "2025-11-14T10:17:22.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/94/d5ee221f2afbc64b2a7074efe25387cd8700e8116518904b28091ea6ad74/pyobjc_framework_mediatoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d7bcfeeff3fbf7e9e556ecafd8eaed2411df15c52baf134efa7480494e6faf6d", size = 12818, upload-time = "2025-11-14T09:54:41.251Z" }, -] - -[[package]] -name = "pyobjc-framework-metal" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/06/a84f7eb8561d5631954b9458cfca04b690b80b5b85ce70642bc89335f52a/pyobjc_framework_metal-12.1.tar.gz", hash = "sha256:bb554877d5ee2bf3f340ad88e8fe1b85baab7b5ec4bd6ae0f4f7604147e3eae7", size = 181847, upload-time = "2025-11-14T10:17:34.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/48/9286d06e1b14c11b65d3fea1555edc0061d9ebe11898dff8a14089e3a4c9/pyobjc_framework_metal-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38ab566b5a2979a43e13593d3eb12000a45e574576fe76996a5e1eb75ad7ac78", size = 75841, upload-time = "2025-11-14T09:55:06.801Z" }, -] - -[[package]] -name = "pyobjc-framework-metalfx" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/09/ce5c74565677fde66de3b9d35389066b19e5d1bfef9d9a4ad80f0c858c0c/pyobjc_framework_metalfx-12.1.tar.gz", hash = "sha256:1551b686fb80083a97879ce0331bdb1d4c9b94557570b7ecc35ebf40ff65c90b", size = 29470, upload-time = "2025-11-14T10:17:37.16Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/0b/508e3af499694f4eec74cc3ab0530e38db76e43a27db9ecb98c50c68f5f9/pyobjc_framework_metalfx-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a4418ae5c2eb77ec00695fa720a547638dc252dfd77ecb6feb88f713f5a948fd", size = 15062, upload-time = "2025-11-14T09:55:37.352Z" }, -] - -[[package]] -name = "pyobjc-framework-metalkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-metal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/14/15/5091147aae12d4011a788b93971c3376aaaf9bf32aa935a2c9a06a71e18b/pyobjc_framework_metalkit-12.1.tar.gz", hash = "sha256:14cc5c256f0e3471b412a5b3582cb2a0d36d3d57401a8aa09e433252d1c34824", size = 25473, upload-time = "2025-11-14T10:17:39.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/c0/c8b5b060895cd51493afe3f09909b7e34893b1161cf4d93bc8e3cd306129/pyobjc_framework_metalkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c4869076571d94788fe539fabfdd568a5c8e340936c7726d2551196640bd152", size = 8755, upload-time = "2025-11-14T09:55:51.683Z" }, -] - -[[package]] -name = "pyobjc-framework-metalperformanceshaders" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/68/58da38e54aa0d8c19f0d3084d8c84e92d54cc8c9254041f07119d86aa073/pyobjc_framework_metalperformanceshaders-12.1.tar.gz", hash = "sha256:b198e755b95a1de1525e63c3b14327ae93ef1d88359e6be1ce554a3493755b50", size = 137301, upload-time = "2025-11-14T10:17:49.554Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/84/d505496fca9341e0cb11258ace7640cd986fe3e831f8b4749035e9f82109/pyobjc_framework_metalperformanceshaders-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c00e786c352b3ff5d86cf0cf3a830dc9f6fc32a03ae1a7539d20d11324adb2e8", size = 33242, upload-time = "2025-11-14T09:56:09.354Z" }, -] - -[[package]] -name = "pyobjc-framework-metalperformanceshadersgraph" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metalperformanceshaders" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a7/56/7ad0cd085532f7bdea9a8d4e9a2dfde376d26dd21e5eabdf1a366040eff8/pyobjc_framework_metalperformanceshadersgraph-12.1.tar.gz", hash = "sha256:b8fd017b47698037d7b172d41bed7a4835f4c4f2a288235819d200005f89ee35", size = 42992, upload-time = "2025-11-14T10:17:53.502Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/c9/5e7fd0d4bc9bdf7b442f36e020677c721ba9b4c1dc1fa3180085f22a4ef9/pyobjc_framework_metalperformanceshadersgraph-12.1-py2.py3-none-any.whl", hash = "sha256:85a1c7a6114ada05c7924b3235a1a98c45359410d148097488f15aee5ebb6ab9", size = 6481, upload-time = "2025-11-14T09:56:23.66Z" }, -] - -[[package]] -name = "pyobjc-framework-metrickit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ba/13/5576ddfbc0b174810a49171e2dbe610bdafd3b701011c6ecd9b3a461de8a/pyobjc_framework_metrickit-12.1.tar.gz", hash = "sha256:77841daf6b36ba0c19df88545fd910c0516acf279e6b7b4fa0a712a046eaa9f1", size = 27627, upload-time = "2025-11-14T10:17:56.353Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/04/8da5126e47306438c99750f1dfed430d7cc388f6b7f420ae748f3060ab96/pyobjc_framework_metrickit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3ec96e9ec7dc37fbce57dae277f0d89c66ffe1c3fa2feaca1b7125f8b2b29d87", size = 8120, upload-time = "2025-11-14T09:56:28.73Z" }, -] - -[[package]] -name = "pyobjc-framework-mlcompute" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/69/15f8ce96c14383aa783c8e4bc1e6d936a489343bb197b8e71abb3ddc1cb8/pyobjc_framework_mlcompute-12.1.tar.gz", hash = "sha256:3281db120273dcc56e97becffd5cedf9c62042788289f7be6ea067a863164f1e", size = 40698, upload-time = "2025-11-14T10:17:59.792Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/f7/4614b9ccd0151795e328b9ed881fbcbb13e577a8ec4ae3507edb1a462731/pyobjc_framework_mlcompute-12.1-py2.py3-none-any.whl", hash = "sha256:4f0fc19551d710a03dfc4c7129299897544ff8ea76db6c7539ecc2f9b2571bde", size = 6744, upload-time = "2025-11-14T09:56:36.973Z" }, -] - -[[package]] -name = "pyobjc-framework-modelio" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b4/11/32c358111b623b4a0af9e90470b198fffc068b45acac74e1ba711aee7199/pyobjc_framework_modelio-12.1.tar.gz", hash = "sha256:d041d7bca7c2a4526344d3e593347225b7a2e51a499b3aa548895ba516d1bdbb", size = 66482, upload-time = "2025-11-14T10:18:04.92Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/0e/b8331100f0d658ecb3e87e75c108e2ae8ac7c78b521fd5ad0205b60a2584/pyobjc_framework_modelio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:68d971917c289fdddf69094c74915d2ccb746b42b150e0bdc16d8161e6164022", size = 20193, upload-time = "2025-11-14T09:56:44.296Z" }, -] - -[[package]] -name = "pyobjc-framework-multipeerconnectivity" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/87/35/0d0bb6881004cb238cfd7bf74f4b2e42601a1accdf27b2189ec61cf3a2dc/pyobjc_framework_multipeerconnectivity-12.1.tar.gz", hash = "sha256:7123f734b7174cacbe92a51a62b4645cc9033f6b462ff945b504b62e1b9e6c1c", size = 22816, upload-time = "2025-11-14T10:18:07.363Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/8d/0646ff7db36942829f0e84be18ba44bc5cd96d6a81651f8e7dc0974821c1/pyobjc_framework_multipeerconnectivity-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c3bd254a16debed321debf4858f9c9b7d41572ddf1058a4bacf6a5bcfedeeff", size = 12001, upload-time = "2025-11-14T09:57:01.027Z" }, -] - -[[package]] -name = "pyobjc-framework-naturallanguage" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/d1/c81c0cdbb198d498edc9bc5fbb17e79b796450c17bb7541adbf502f9ad65/pyobjc_framework_naturallanguage-12.1.tar.gz", hash = "sha256:cb27a1e1e5b2913d308c49fcd2fd04ab5ea87cb60cac4a576a91ebf6a50e52f6", size = 23524, upload-time = "2025-11-14T10:18:09.883Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/d8/715a11111f76c80769cb267a19ecf2a4ac76152a6410debb5a4790422256/pyobjc_framework_naturallanguage-12.1-py2.py3-none-any.whl", hash = "sha256:a02ef383ec88948ca28f03ab8995523726b3bc75c49f593b5c89c218bcbce7ce", size = 5320, upload-time = "2025-11-14T09:57:10.294Z" }, -] - -[[package]] -name = "pyobjc-framework-netfs" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/46/68/4bf0e5b8cc0780cf7acf0aec54def58c8bcf8d733db0bd38f5a264d1af06/pyobjc_framework_netfs-12.1.tar.gz", hash = "sha256:e8d0c25f41d7d9ced1aa2483238d0a80536df21f4b588640a72e1bdb87e75c1e", size = 14799, upload-time = "2025-11-14T10:18:11.85Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/6b/8c2f223879edd3e3f030d0a9c9ba812775519c6d0c257e3e7255785ca6e7/pyobjc_framework_netfs-12.1-py2.py3-none-any.whl", hash = "sha256:0021f8b141e693d3821524c170e9c645090eb320e80c2935ddb978a6e8b8da81", size = 4163, upload-time = "2025-11-14T09:57:11.845Z" }, -] - -[[package]] -name = "pyobjc-framework-network" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/13/a71270a1b0a9ec979e68b8ec84b0f960e908b17b51cb3cac246a74d52b6b/pyobjc_framework_network-12.1.tar.gz", hash = "sha256:dbf736ff84d1caa41224e86ff84d34b4e9eb6918ae4e373a44d3cb597648a16a", size = 56990, upload-time = "2025-11-14T10:18:16.714Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/ef/a53f04f43e93932817f2ea71689dcc8afe3b908d631c21d11ec30c7b2e87/pyobjc_framework_network-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5e53aad64eae2933fe12d49185d66aca62fb817abf8a46f86b01e436ce1b79e4", size = 19613, upload-time = "2025-11-14T09:57:19.571Z" }, -] - -[[package]] -name = "pyobjc-framework-networkextension" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bf/3e/ac51dbb2efa16903e6af01f3c1f5a854c558661a7a5375c3e8767ac668e8/pyobjc_framework_networkextension-12.1.tar.gz", hash = "sha256:36abc339a7f214ab6a05cb2384a9df912f247163710741e118662bd049acfa2e", size = 62796, upload-time = "2025-11-14T10:18:21.769Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/14/4934b10ade5ad0518001bfc25260d926816b9c7d08d85ef45e8a61fdef1b/pyobjc_framework_networkextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:adc9baacfc532944d67018e381c7645f66a9fa0064939a5a841476d81422cdcc", size = 14376, upload-time = "2025-11-14T09:57:36.132Z" }, -] - -[[package]] -name = "pyobjc-framework-notificationcenter" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c6/12/ae0fe82fb1e02365c9fe9531c9de46322f7af09e3659882212c6bf24d75e/pyobjc_framework_notificationcenter-12.1.tar.gz", hash = "sha256:2d09f5ab9dc39770bae4fa0c7cfe961e6c440c8fc465191d403633dccc941094", size = 21282, upload-time = "2025-11-14T10:18:24.51Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/05/3168637dd425257df5693c2ceafecf92d2e6833c0aaa6594d894a528d797/pyobjc_framework_notificationcenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82a735bd63f315f0a56abd206373917b7d09a0ae35fd99f1639a0fac4c525c0a", size = 9895, upload-time = "2025-11-14T09:57:51.151Z" }, -] - -[[package]] -name = "pyobjc-framework-opendirectory" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/11/bc2f71d3077b3bd078dccad5c0c5c57ec807fefe3d90c97b97dd0ed3d04b/pyobjc_framework_opendirectory-12.1.tar.gz", hash = "sha256:2c63ce5dd179828ef2d8f9e3961da3bfa971a57db07a6c34eedc296548a928bb", size = 61049, upload-time = "2025-11-14T10:18:29.336Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/e7/3c2dece9c5b28af28a44d72a27b35ea5ffac31fed7cbd8d696ea75dc4a81/pyobjc_framework_opendirectory-12.1-py2.py3-none-any.whl", hash = "sha256:b5b5a5cf3cc2fb25147b16b79f046b90e3982bf3ded1b210a993d8cfdba737c4", size = 11845, upload-time = "2025-11-14T09:58:00.175Z" }, -] - -[[package]] -name = "pyobjc-framework-osakit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cb/b9/bf52c555c75a83aa45782122432fa06066bb76469047f13d06fb31e585c4/pyobjc_framework_osakit-12.1.tar.gz", hash = "sha256:36ea6acf03483dc1e4344a0cce7250a9656f44277d12bc265fa86d4cbde01f23", size = 17102, upload-time = "2025-11-14T10:18:31.354Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/10/30a15d7b23e6fcfa63d41ca4c7356c39ff81300249de89c3ff28216a9790/pyobjc_framework_osakit-12.1-py2.py3-none-any.whl", hash = "sha256:c49165336856fd75113d2e264a98c6deb235f1bd033eae48f661d4d832d85e6b", size = 4162, upload-time = "2025-11-14T09:58:01.953Z" }, -] - -[[package]] -name = "pyobjc-framework-oslog" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/12/42/805c9b4ac6ad25deb4215989d8fc41533d01e07ffd23f31b65620bade546/pyobjc_framework_oslog-12.1.tar.gz", hash = "sha256:d0ec6f4e3d1689d5e4341bc1130c6f24cb4ad619939f6c14d11a7e80c0ac4553", size = 21193, upload-time = "2025-11-14T10:18:33.645Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/60/0b742347d484068e9d6867cd95dedd1810c790b6aca45f6ef1d0f089f1f5/pyobjc_framework_oslog-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:072a41d36fcf780a070f13ac2569f8bafbb5ae4792fab4136b1a4d602dd9f5b4", size = 7813, upload-time = "2025-11-14T09:58:07.768Z" }, -] - -[[package]] -name = "pyobjc-framework-passkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6c/d4/2afb59fb0f99eb2f03888850887e536f1ef64b303fd756283679471a5189/pyobjc_framework_passkit-12.1.tar.gz", hash = "sha256:d8c27c352e86a3549bf696504e6b25af5f2134b173d9dd60d66c6d3da53bb078", size = 53835, upload-time = "2025-11-14T10:18:37.906Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/dc/9cb27e8b7b00649af5e802815ffa8928bd8a619f2984a1bea7dabd28f741/pyobjc_framework_passkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e95a484ec529dbf1d44f5f7f1406502a77bda733511e117856e3dca9fa29c5c", size = 14102, upload-time = "2025-11-14T09:58:20.903Z" }, -] - -[[package]] -name = "pyobjc-framework-pencilkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7e/43/859068016bcbe7d80597d5c579de0b84b0da62c5c55cdf9cc940e9f9c0f8/pyobjc_framework_pencilkit-12.1.tar.gz", hash = "sha256:d404982d1f7a474369f3e7fea3fbd6290326143fa4138d64b6753005a6263dc4", size = 17664, upload-time = "2025-11-14T10:18:40.045Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/26/daf47dcfced8f7326218dced5c68ed2f3b522ec113329218ce1305809535/pyobjc_framework_pencilkit-12.1-py2.py3-none-any.whl", hash = "sha256:33b88e5ed15724a12fd8bf27a68614b654ff739d227e81161298bc0d03acca4f", size = 4206, upload-time = "2025-11-14T09:58:30.814Z" }, -] - -[[package]] -name = "pyobjc-framework-phase" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/51/3b25eaf7ca85f38ceef892fdf066b7faa0fec716f35ea928c6ffec6ae311/pyobjc_framework_phase-12.1.tar.gz", hash = "sha256:3a69005c572f6fd777276a835115eb8359a33673d4a87e754209f99583534475", size = 32730, upload-time = "2025-11-14T10:18:43.102Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/9f/1ae45db731e8d6dd3e0b408c3accd0cf3236849e671f95c7c8cf95687240/pyobjc_framework_phase-12.1-py2.py3-none-any.whl", hash = "sha256:99a1c1efc6644f5312cce3693117d4e4482538f65ad08fe59e41e2579b67ab17", size = 6902, upload-time = "2025-11-14T09:58:32.436Z" }, -] - -[[package]] -name = "pyobjc-framework-photos" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b8/53/f8a3dc7f711034d2283e289cd966fb7486028ea132a24260290ff32d3525/pyobjc_framework_photos-12.1.tar.gz", hash = "sha256:adb68aaa29e186832d3c36a0b60b0592a834e24c5263e9d78c956b2b77dce563", size = 47034, upload-time = "2025-11-14T10:18:47.27Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/38/e6f25aec46a1a9d0a310795606cc43f9823d41c3e152114b814b597835a8/pyobjc_framework_photos-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eda8a584a851506a1ebbb2ee8de2cb1ed9e3431e6a642ef6a9543e32117d17b9", size = 12358, upload-time = "2025-11-14T09:58:38.131Z" }, -] - -[[package]] -name = "pyobjc-framework-photosui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/40/a5/14c538828ed1a420e047388aedc4a2d7d9292030d81bf6b1ced2ec27b6e9/pyobjc_framework_photosui-12.1.tar.gz", hash = "sha256:9141234bb9d17687f1e8b66303158eccdd45132341fbe5e892174910035f029a", size = 29886, upload-time = "2025-11-14T10:18:50.238Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/a2/b5afca8039b1a659a2a979bb1bdbdddfdf9b1d2724a2cc4633dca2573d5f/pyobjc_framework_photosui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:713e3bb25feb5ea891e67260c2c0769cab44a7f11b252023bfcf9f8c29dd1206", size = 11714, upload-time = "2025-11-14T09:58:53.674Z" }, -] - -[[package]] -name = "pyobjc-framework-preferencepanes" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/bc/e87df041d4f7f6b7721bf7996fa02aa0255939fb0fac0ecb294229765f92/pyobjc_framework_preferencepanes-12.1.tar.gz", hash = "sha256:b2a02f9049f136bdeca7642b3307637b190850e5853b74b5c372bc7d88ef9744", size = 24543, upload-time = "2025-11-14T10:18:53.259Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/7b/8ceec1ab0446224d685e243e2770c5a5c92285bcab0b9324dbe7a893ae5a/pyobjc_framework_preferencepanes-12.1-py2.py3-none-any.whl", hash = "sha256:1b3af9db9e0cfed8db28c260b2cf9a22c15fda5f0ff4c26157b17f99a0e29bbf", size = 4797, upload-time = "2025-11-14T09:59:03.998Z" }, -] - -[[package]] -name = "pyobjc-framework-pushkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a9/45/de756b62709add6d0615f86e48291ee2bee40223e7dde7bbe68a952593f0/pyobjc_framework_pushkit-12.1.tar.gz", hash = "sha256:829a2fc8f4780e75fc2a41217290ee0ff92d4ade43c42def4d7e5af436d8ae82", size = 19465, upload-time = "2025-11-14T10:18:57.727Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/01/74cf1dd0764c590de05dc1e87d168031e424f834721940b7bb02c67fe821/pyobjc_framework_pushkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7bdf472a55ac65154e03f54ae0bcad64c4cf45e9b1acba62f15107f2bc994d69", size = 8177, upload-time = "2025-11-14T09:59:11.155Z" }, -] - -[[package]] -name = "pyobjc-framework-quartz" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" }, -] - -[[package]] -name = "pyobjc-framework-quicklookthumbnailing" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/97/1a/b90539500e9a27c2049c388d85a824fc0704009b11e33b05009f52a6dc67/pyobjc_framework_quicklookthumbnailing-12.1.tar.gz", hash = "sha256:4f7e09e873e9bda236dce6e2f238cab571baeb75eca2e0bc0961d5fcd85f3c8f", size = 14790, upload-time = "2025-11-14T10:21:26.442Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/22/7bd07b5b44bf8540514a9f24bc46da68812c1fd6c63bb2d3496e5ea44bf0/pyobjc_framework_quicklookthumbnailing-12.1-py2.py3-none-any.whl", hash = "sha256:5efe50b0318188b3a4147681788b47fce64709f6fe0e1b5d020e408ef40ab08e", size = 4234, upload-time = "2025-11-14T10:01:02.209Z" }, -] - -[[package]] -name = "pyobjc-framework-replaykit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/35/f8/b92af879734d91c1726227e7a03b9e68ab8d9d2bb1716d1a5c29254087f2/pyobjc_framework_replaykit-12.1.tar.gz", hash = "sha256:95801fd35c329d7302b2541f2754e6574bf36547ab869fbbf41e408dfa07268a", size = 23312, upload-time = "2025-11-14T10:21:29.18Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/fc/c68d2111b2655148d88574959d3d8b21d3a003573013301d4d2a7254c1af/pyobjc_framework_replaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b0528c2a6188440fdc2017f0924c0a0f15d0a2f6aa295f1d1c2d6b3894c22f1d", size = 10120, upload-time = "2025-11-14T10:01:08.397Z" }, -] - -[[package]] -name = "pyobjc-framework-safariservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3e/4b/8f896bafbdbfa180a5ba1e21a6f5dc63150c09cba69d85f68708e02866ae/pyobjc_framework_safariservices-12.1.tar.gz", hash = "sha256:6a56f71c1e692bca1f48fe7c40e4c5a41e148b4e3c6cfb185fd80a4d4a951897", size = 25165, upload-time = "2025-11-14T10:21:32.041Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/67/3a/8c525562fd782c88bc44e8c07fc2c073919f98dead08fffd50f280ef1afa/pyobjc_framework_safariservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b475abc82504fc1c0801096a639562d6a6d37370193e8e4a406de9199a7cea13", size = 7281, upload-time = "2025-11-14T10:01:21.238Z" }, -] - -[[package]] -name = "pyobjc-framework-safetykit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/bf/ad6bf60ceb61614c9c9f5758190971e9b90c45b1c7a244e45db64138b6c2/pyobjc_framework_safetykit-12.1.tar.gz", hash = "sha256:0cd4850659fb9b5632fd8ad21f2de6863e8303ff0d51c5cc9c0034aac5db08d8", size = 20086, upload-time = "2025-11-14T10:21:34.212Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/0c/08a20fb7516405186c0fe7299530edd4aa22c24f73290198312447f26c8c/pyobjc_framework_safetykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e4977f7069a23252053d1a42b1a053aefc19b85c960a5214b05daf3c037a6f16", size = 8550, upload-time = "2025-11-14T10:01:32.885Z" }, -] - -[[package]] -name = "pyobjc-framework-scenekit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/8c/1f4005cf0cb68f84dd98b93bbc0974ee7851bb33d976791c85e042dc2278/pyobjc_framework_scenekit-12.1.tar.gz", hash = "sha256:1bd5b866f31fd829f26feac52e807ed942254fd248115c7c742cfad41d949426", size = 101212, upload-time = "2025-11-14T10:21:41.265Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/f1/4986bd96e0ba0f60bff482a6b135b9d6db65d56578d535751f18f88190f0/pyobjc_framework_scenekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:40aea10098893f0b06191f1e79d7b25e12e36a9265549d324238bdb25c7e6df0", size = 33597, upload-time = "2025-11-14T10:01:51.297Z" }, -] - -[[package]] -name = "pyobjc-framework-screencapturekit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2d/7f/73458db1361d2cb408f43821a1e3819318a0f81885f833d78d93bdc698e0/pyobjc_framework_screencapturekit-12.1.tar.gz", hash = "sha256:50992c6128b35ab45d9e336f0993ddd112f58b8c8c8f0892a9cb42d61bd1f4c9", size = 32573, upload-time = "2025-11-14T10:21:44.497Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/a8/533acdbf26e0a908ff640d3a445481f3c948682ca887be6711b5fcf82682/pyobjc_framework_screencapturekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:27df138ce2dfa9d4aae5106d4877e9ed694b5a174643c058f1c48678ffc7001a", size = 11504, upload-time = "2025-11-14T10:02:11.36Z" }, -] - -[[package]] -name = "pyobjc-framework-screensaver" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/99/7cfbce880cea61253a44eed594dce66c2b2fbf29e37eaedcd40cffa949e9/pyobjc_framework_screensaver-12.1.tar.gz", hash = "sha256:c4ca111317c5a3883b7eace0a9e7dd72bc6ffaa2ca954bdec918c3ab7c65c96f", size = 22229, upload-time = "2025-11-14T10:21:47.299Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/a4/2481711f2e9557b90bac74fa8bf821162cf7b65835732ae560fd52e9037e/pyobjc_framework_screensaver-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3c90c2299eac6d01add81427ae2f90d7724f15d676261e838d7a7750f812322", size = 8422, upload-time = "2025-11-14T10:02:24.49Z" }, -] - -[[package]] -name = "pyobjc-framework-screentime" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/10/11/ba18f905321895715dac3cae2071c2789745ae13605b283b8114b41e0459/pyobjc_framework_screentime-12.1.tar.gz", hash = "sha256:583de46b365543bbbcf27cd70eedd375d397441d64a2cf43c65286fd9c91af55", size = 13413, upload-time = "2025-11-14T10:21:49.17Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/06/904174de6170e11b53673cc5844e5f13394eeeed486e0bcdf5288c1b0853/pyobjc_framework_screentime-12.1-py2.py3-none-any.whl", hash = "sha256:d34a068ec8ba2704987fcd05c37c9a9392de61d92933e6e71c8e4eaa4dfce029", size = 3963, upload-time = "2025-11-14T10:02:32.577Z" }, -] - -[[package]] -name = "pyobjc-framework-scriptingbridge" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0c/cb/adc0a09e8c4755c2281bd12803a87f36e0832a8fc853a2d663433dbb72ce/pyobjc_framework_scriptingbridge-12.1.tar.gz", hash = "sha256:0e90f866a7e6a8aeaf723d04c826657dd528c8c1b91e7a605f8bb947c74ad082", size = 20339, upload-time = "2025-11-14T10:21:51.769Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/46/e0b07d2b3ff9effb8b1179a6cc681a953d3dfbf0eb8b1d6a0e54cef2e922/pyobjc_framework_scriptingbridge-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8083cd68c559c55a3787b2e74fc983c8665e5078571475aaeabf4f34add36b62", size = 8356, upload-time = "2025-11-14T10:02:38.559Z" }, -] - -[[package]] -name = "pyobjc-framework-searchkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6e/60/a38523198430e14fdef21ebe62a93c43aedd08f1f3a07ea3d96d9997db5d/pyobjc_framework_searchkit-12.1.tar.gz", hash = "sha256:ddd94131dabbbc2d7c3f17db3da87c1a712c431310eef16f07187771e7e85226", size = 30942, upload-time = "2025-11-14T10:21:55.483Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/46/4f9cd3011f47b43b21b2924ab3770303c3f0a4d16f05550d38c5fcb42e78/pyobjc_framework_searchkit-12.1-py2.py3-none-any.whl", hash = "sha256:844ce62b7296b19da8db7dedd539d07f7b3fb3bb8b029c261f7bcf0e01a97758", size = 3733, upload-time = "2025-11-14T10:02:47.026Z" }, -] - -[[package]] -name = "pyobjc-framework-security" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280, upload-time = "2025-11-14T10:02:58.097Z" }, -] - -[[package]] -name = "pyobjc-framework-securityfoundation" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/57/d5/c2b77e83c1585ba43e5f00c917273ba4bf7ed548c1b691f6766eb0418d52/pyobjc_framework_securityfoundation-12.1.tar.gz", hash = "sha256:1f39f4b3db6e3bd3a420aaf4923228b88e48c90692cf3612b0f6f1573302a75d", size = 12669, upload-time = "2025-11-14T10:22:09.256Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/1e/349fb71a413b37b1b41e712c7ca180df82144478f8a9a59497d66d0f2ea2/pyobjc_framework_securityfoundation-12.1-py2.py3-none-any.whl", hash = "sha256:579cf23e63434226f78ffe0afb8426e971009588e4ad812c478d47dfd558201c", size = 3792, upload-time = "2025-11-14T10:03:14.459Z" }, -] - -[[package]] -name = "pyobjc-framework-securityinterface" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f9/64/bf5b5d82655112a2314422ee649f1e1e73d4381afa87e1651ce7e8444694/pyobjc_framework_securityinterface-12.1.tar.gz", hash = "sha256:deef11ad03be8d9ff77db6e7ac40f6b641ee2d72eaafcf91040537942472e88b", size = 25552, upload-time = "2025-11-14T10:22:12.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/3e/17889a6de03dc813606bb97887dc2c4c2d4e7c8f266bc439548bae756e90/pyobjc_framework_securityinterface-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cb5e79a73ea17663ebd29e350401162d93e42343da7d96c77efb38ae64ff01f", size = 10783, upload-time = "2025-11-14T10:03:20.202Z" }, -] - -[[package]] -name = "pyobjc-framework-securityui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/83/3f/d870305f5dec58cd02966ca06ac29b69fb045d8b46dfb64e2da31f295345/pyobjc_framework_securityui-12.1.tar.gz", hash = "sha256:f1435fed85edc57533c334a4efc8032170424b759da184cb7a7a950ceea0e0b6", size = 12184, upload-time = "2025-11-14T10:22:14.323Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/7f/eff9ffdd34511cc95a60e5bd62f1cfbcbcec1a5012ef1168161506628c87/pyobjc_framework_securityui-12.1-py2.py3-none-any.whl", hash = "sha256:3e988b83c9a2bb0393207eaa030fc023a8708a975ac5b8ea0508cdafc2b60705", size = 3594, upload-time = "2025-11-14T10:03:29.628Z" }, -] - -[[package]] -name = "pyobjc-framework-sensitivecontentanalysis" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/ce/17bf31753e14cb4d64fffaaba2377453c4977c2c5d3cf2ff0a3db30026c7/pyobjc_framework_sensitivecontentanalysis-12.1.tar.gz", hash = "sha256:2c615ac10e93eb547b32b214cd45092056bee0e79696426fd09978dc3e670f25", size = 13745, upload-time = "2025-11-14T10:22:16.447Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/23/c99568a0d4e38bd8337d52e4ae25a0b0bd540577f2e06f3430c951d73209/pyobjc_framework_sensitivecontentanalysis-12.1-py2.py3-none-any.whl", hash = "sha256:faf19d32d4599ac2b18fb1ccdc3e33b2b242bdf34c02e69978bd62d3643ad068", size = 4230, upload-time = "2025-11-14T10:03:31.26Z" }, -] - -[[package]] -name = "pyobjc-framework-servicemanagement" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/d0/b26c83ae96ab55013df5fedf89337d4d62311b56ce3f520fc7597d223d82/pyobjc_framework_servicemanagement-12.1.tar.gz", hash = "sha256:08120981749a698033a1d7a6ab99dbbe412c5c0d40f2b4154014b52113511c1d", size = 14585, upload-time = "2025-11-14T10:22:18.735Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/5d/1009c32189f9cb26da0124b4a60640ed26dd8ad453810594f0cbfab0ff70/pyobjc_framework_servicemanagement-12.1-py2.py3-none-any.whl", hash = "sha256:9a2941f16eeb71e55e1cd94f50197f91520778c7f48ad896761f5e78725cc08f", size = 5357, upload-time = "2025-11-14T10:03:32.928Z" }, -] - -[[package]] -name = "pyobjc-framework-sharedwithyou" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-sharedwithyoucore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/8b/8ab209a143c11575a857e2111acc5427fb4986b84708b21324cbcbf5591b/pyobjc_framework_sharedwithyou-12.1.tar.gz", hash = "sha256:167d84794a48f408ee51f885210c616fda1ec4bff3dd8617a4b5547f61b05caf", size = 24791, upload-time = "2025-11-14T10:22:21.248Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/ee/e5113ce985a480d13a0fa3d41a242c8068dc09b3c13210557cf5cc6a544a/pyobjc_framework_sharedwithyou-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a99a6ebc6b6de7bc8663b1f07332fab9560b984a57ce344dc5703f25258f258d", size = 8763, upload-time = "2025-11-14T10:03:38.467Z" }, -] - -[[package]] -name = "pyobjc-framework-sharedwithyoucore" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/ef/84059c5774fd5435551ab7ab40b51271cfb9997b0d21f491c6b429fe57a8/pyobjc_framework_sharedwithyoucore-12.1.tar.gz", hash = "sha256:0813149eeb755d718b146ec9365eb4ca3262b6af9ff9ba7db2f7b6f4fd104518", size = 22350, upload-time = "2025-11-14T10:22:23.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/0e/0c2b0591ebc72d437dccca7a1e7164c5f11dde2189d4f4c707a132bab740/pyobjc_framework_sharedwithyoucore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed928266ae9d577ff73de72a03bebc66a751918eb59ca660a9eca157392f17be", size = 8530, upload-time = "2025-11-14T10:03:50.839Z" }, -] - -[[package]] -name = "pyobjc-framework-shazamkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/2c/8d82c5066cc376de68ad8c1454b7c722c7a62215e5c2f9dac5b33a6c3d42/pyobjc_framework_shazamkit-12.1.tar.gz", hash = "sha256:71db2addd016874639a224ed32b2000b858802b0370c595a283cce27f76883fe", size = 22518, upload-time = "2025-11-14T10:22:25.996Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/5e/7d60d8e7b036b20d0e94cd7c4563e7414653344482e85fbc7facffabc95f/pyobjc_framework_shazamkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e184dd0f61a604b1cfcf44418eb95b943e7b8f536058a29e4b81acadd27a9420", size = 8577, upload-time = "2025-11-14T10:04:04.182Z" }, -] - -[[package]] -name = "pyobjc-framework-social" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/21/afc6f37dfdd2cafcba0227e15240b5b0f1f4ad57621aeefda2985ac9560e/pyobjc_framework_social-12.1.tar.gz", hash = "sha256:1963db6939e92ae40dd9d68852e8f88111cbfd37a83a9fdbc9a0c08993ca7e60", size = 13184, upload-time = "2025-11-14T10:22:28.048Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/fb/090867e332d49a1e492e4b8972ac6034d1c7d17cf39f546077f35be58c46/pyobjc_framework_social-12.1-py2.py3-none-any.whl", hash = "sha256:2f3b36ba5769503b1bc945f85fd7b255d42d7f6e417d78567507816502ff2b44", size = 4462, upload-time = "2025-11-14T10:04:14.578Z" }, -] - -[[package]] -name = "pyobjc-framework-soundanalysis" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6b/d6/5039b61edc310083425f87ce2363304d3a87617e941c1d07968c63b5638d/pyobjc_framework_soundanalysis-12.1.tar.gz", hash = "sha256:e2deead8b9a1c4513dbdcf703b21650dcb234b60a32d08afcec4895582b040b1", size = 14804, upload-time = "2025-11-14T10:22:29.998Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/53/d3/8df5183d52d20d459225d3f5d24f55e01b8cd9fe587ed972e3f20dd18709/pyobjc_framework_soundanalysis-12.1-py2.py3-none-any.whl", hash = "sha256:8b2029ab48c1a9772f247f0aea995e8c3ff4706909002a9c1551722769343a52", size = 4188, upload-time = "2025-11-14T10:04:16.12Z" }, -] - -[[package]] -name = "pyobjc-framework-speech" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8d/3d/194cf19fe7a56c2be5dfc28f42b3b597a62ebb1e1f52a7dd9c55b917ac6c/pyobjc_framework_speech-12.1.tar.gz", hash = "sha256:2a2a546ba6c52d5dd35ddcfee3fd9226a428043d1719597e8701851a6566afdd", size = 25218, upload-time = "2025-11-14T10:22:32.505Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/1b/224cb98c9c32a6d5e68072f89d26444095be54c6f461efe4fefe9d1330a5/pyobjc_framework_speech-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cae4b88ef9563157a6c9e66b37778fc4022ee44dd1a2a53081c2adbb69698945", size = 9254, upload-time = "2025-11-14T10:04:21.361Z" }, -] - -[[package]] -name = "pyobjc-framework-spritekit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b6/78/d683ebe0afb49f46d2d21d38c870646e7cb3c2e83251f264e79d357b1b74/pyobjc_framework_spritekit-12.1.tar.gz", hash = "sha256:a851f4ef5aa65cc9e08008644a528e83cb31021a1c0f17ebfce4de343764d403", size = 64470, upload-time = "2025-11-14T10:22:37.569Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/38/97c3b6c3437e3e9267fb4e1cd86e0da4eff07e0abe7cd6923644d2dfc878/pyobjc_framework_spritekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1649e57c25145795d04bb6a1ec44c20ef7cf0af7c60a9f6f5bc7998dd269db1e", size = 17802, upload-time = "2025-11-14T10:04:35.346Z" }, -] - -[[package]] -name = "pyobjc-framework-storekit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/87/8a66a145feb026819775d44975c71c1c64df4e5e9ea20338f01456a61208/pyobjc_framework_storekit-12.1.tar.gz", hash = "sha256:818452e67e937a10b5c8451758274faa44ad5d4329df0fa85735115fb0608da9", size = 34574, upload-time = "2025-11-14T10:22:40.73Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/9f/938985e506de0cc3a543e44e1f9990e9e2fb8980b8f3bcfc8f7921d09061/pyobjc_framework_storekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9fe2d65a2b644bb6b4fdd3002292cba153560917de3dd6cf969431fa32d21dd0", size = 12819, upload-time = "2025-11-14T10:04:50.945Z" }, -] - -[[package]] -name = "pyobjc-framework-symbols" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9a/ce/a48819eb8524fa2dc11fb3dd40bb9c4dcad0596fe538f5004923396c2c6c/pyobjc_framework_symbols-12.1.tar.gz", hash = "sha256:7d8e999b8a59c97d38d1d343b6253b1b7d04bf50b665700957d89c8ac43b9110", size = 12782, upload-time = "2025-11-14T10:22:42.609Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/ea/6e9af9c750d68109ac54fbffb5463e33a7b54ffe8b9901a5b6b603b7884b/pyobjc_framework_symbols-12.1-py2.py3-none-any.whl", hash = "sha256:c72eecbc25f6bfcd39c733067276270057c5aca684be20fdc56def645f2b6446", size = 3331, upload-time = "2025-11-14T10:05:01.333Z" }, -] - -[[package]] -name = "pyobjc-framework-syncservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coredata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/21/91/6d03a988831ddb0fb001b13573560e9a5bcccde575b99350f98fe56a2dd4/pyobjc_framework_syncservices-12.1.tar.gz", hash = "sha256:6a213e93d9ce15128810987e4c5de8c73cfab1564ac8d273e6b437a49965e976", size = 31032, upload-time = "2025-11-14T10:22:45.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/ac/a83cdd120e279ee905e9085afda90992159ed30c6a728b2c56fa2d36b6ea/pyobjc_framework_syncservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cd629bea95692aad2d26196657cde2fbadedae252c7846964228661a600b900", size = 13411, upload-time = "2025-11-14T10:05:07.741Z" }, -] - -[[package]] -name = "pyobjc-framework-systemconfiguration" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/7d/50848df8e1c6b5e13967dee9fb91d3391fe1f2399d2d0797d2fc5edb32ba/pyobjc_framework_systemconfiguration-12.1.tar.gz", hash = "sha256:90fe04aa059876a21626931c71eaff742a27c79798a46347fd053d7008ec496e", size = 59158, upload-time = "2025-11-14T10:22:53.056Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/d3/bb935c3d4bae9e6ce4a52638e30eea7039c480dd96bc4f0777c9fabda21b/pyobjc_framework_systemconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e5bb9103d39483964431db7125195c59001b7bff2961869cfe157b4c861e52d", size = 21578, upload-time = "2025-11-14T10:05:25.572Z" }, -] - -[[package]] -name = "pyobjc-framework-systemextensions" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/12/01/8a706cd3f7dfcb9a5017831f2e6f9e5538298e90052db3bb8163230cbc4f/pyobjc_framework_systemextensions-12.1.tar.gz", hash = "sha256:243e043e2daee4b5c46cd90af5fff46b34596aac25011bab8ba8a37099685eeb", size = 20701, upload-time = "2025-11-14T10:22:58.257Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/cc/a42883d6ad0ae257a7fa62660b4dd13be15f8fa657922f9a5b6697f26e28/pyobjc_framework_systemextensions-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:01fac4f8d88c0956d9fc714d24811cd070e67200ba811904317d91e849e38233", size = 9166, upload-time = "2025-11-14T10:05:41.479Z" }, -] - -[[package]] -name = "pyobjc-framework-threadnetwork" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/62/7e/f1816c3461e4121186f2f7750c58af083d1826bbd73f72728da3edcf4915/pyobjc_framework_threadnetwork-12.1.tar.gz", hash = "sha256:e071eedb41bfc1b205111deb54783ec5a035ccd6929e6e0076336107fdd046ee", size = 12788, upload-time = "2025-11-14T10:23:00.329Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/b8/94b37dd353302c051a76f1a698cf55b5ad50ca061db7f0f332aa9e195766/pyobjc_framework_threadnetwork-12.1-py2.py3-none-any.whl", hash = "sha256:07d937748fc54199f5ec04d5a408e8691a870481c11b641785c2adc279dd8e4b", size = 3771, upload-time = "2025-11-14T10:05:49.899Z" }, -] - -[[package]] -name = "pyobjc-framework-uniformtypeidentifiers" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/65/b8/dd9d2a94509a6c16d965a7b0155e78edf520056313a80f0cd352413f0d0b/pyobjc_framework_uniformtypeidentifiers-12.1.tar.gz", hash = "sha256:64510a6df78336579e9c39b873cfcd03371c4b4be2cec8af75a8a3d07dff607d", size = 17030, upload-time = "2025-11-14T10:23:02.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/5f/1f10f5275b06d213c9897850f1fca9c881c741c1f9190cea6db982b71824/pyobjc_framework_uniformtypeidentifiers-12.1-py2.py3-none-any.whl", hash = "sha256:ec5411e39152304d2a7e0e426c3058fa37a00860af64e164794e0bcffee813f2", size = 4901, upload-time = "2025-11-14T10:05:51.532Z" }, -] - -[[package]] -name = "pyobjc-framework-usernotifications" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/cd/e0253072f221fa89a42fe53f1a2650cc9bf415eb94ae455235bd010ee12e/pyobjc_framework_usernotifications-12.1.tar.gz", hash = "sha256:019ccdf2d400f9a428769df7dba4ea97c02453372bc5f8b75ce7ae54dfe130f9", size = 29749, upload-time = "2025-11-14T10:23:05.364Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/c95053a475246464cba686e16269b0973821601910d1947d088b855a8dac/pyobjc_framework_usernotifications-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:412afb2bf5fe0049f9c4e732e81a8a35d5ebf97c30a5a6abd276259d020c82ac", size = 9644, upload-time = "2025-11-14T10:05:56.801Z" }, -] - -[[package]] -name = "pyobjc-framework-usernotificationsui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-usernotifications" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0e/03/73e29fd5e5973cb3800c9d56107c1062547ef7524cbcc757c3cbbd5465c6/pyobjc_framework_usernotificationsui-12.1.tar.gz", hash = "sha256:51381c97c7344099377870e49ed0871fea85ba50efe50ab05ccffc06b43ec02e", size = 13125, upload-time = "2025-11-14T10:23:07.259Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/c8/52ac8a879079c1fbf25de8335ff506f7db87ff61e64838b20426f817f5d5/pyobjc_framework_usernotificationsui-12.1-py2.py3-none-any.whl", hash = "sha256:11af59dc5abfcb72c08769ab4d7ca32a628527a8ba341786431a0d2dacf31605", size = 3933, upload-time = "2025-11-14T10:06:05.478Z" }, -] - -[[package]] -name = "pyobjc-framework-videosubscriberaccount" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/10/f8/27927a9c125c622656ee5aada4596ccb8e5679da0260742360f193df6dcf/pyobjc_framework_videosubscriberaccount-12.1.tar.gz", hash = "sha256:750459fa88220ab83416f769f2d5d210a1f77b8938fa4d119aad0002fc32846b", size = 18793, upload-time = "2025-11-14T10:23:09.33Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/ca/e2f982916267508c1594f1e50d27bf223a24f55a5e175ab7d7822a00997c/pyobjc_framework_videosubscriberaccount-12.1-py2.py3-none-any.whl", hash = "sha256:381a5e8a3016676e52b88e38b706559fa09391d33474d8a8a52f20a883104a7b", size = 4825, upload-time = "2025-11-14T10:06:07.027Z" }, -] - -[[package]] -name = "pyobjc-framework-videotoolbox" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/5f/6995ee40dc0d1a3460ee183f696e5254c0ad14a25b5bc5fd9bd7266c077b/pyobjc_framework_videotoolbox-12.1.tar.gz", hash = "sha256:7adc8670f3b94b086aed6e86c3199b388892edab4f02933c2e2d9b1657561bef", size = 57825, upload-time = "2025-11-14T10:23:13.825Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/a5/91c6c95416f41c412c2079950527cb746c0712ec319c51a6c728c8d6b231/pyobjc_framework_videotoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eb6ce6837344ee319122066c16ada4beb913e7bfd62188a8d14b1ecbb5a89234", size = 18908, upload-time = "2025-11-14T10:06:14.087Z" }, -] - -[[package]] -name = "pyobjc-framework-virtualization" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/6a/9d110b5521d9b898fad10928818c9f55d66a4af9ac097426c65a9878b095/pyobjc_framework_virtualization-12.1.tar.gz", hash = "sha256:e96afd8e801e92c6863da0921e40a3b68f724804f888bce43791330658abdb0f", size = 40682, upload-time = "2025-11-14T10:23:17.456Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/f2/0da47e91f3f8eeda9a8b4bb0d3a0c54a18925009e99b66a8226b9e06ce1e/pyobjc_framework_virtualization-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d5724b38e64b39ab5ec3b45993afa29fc88b307d99ee2c7a1c0fd770e9b4b21", size = 13131, upload-time = "2025-11-14T10:06:29.337Z" }, -] - -[[package]] -name = "pyobjc-framework-vision" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreml" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538, upload-time = "2025-11-14T10:23:21.979Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/5a/23502935b3fc877d7573e743fc3e6c28748f33a45c43851d503bde52cde7/pyobjc_framework_vision-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6b3211d84f3a12aad0cde752cfd43a80d0218960ac9e6b46b141c730e7d655bd", size = 16625, upload-time = "2025-11-14T10:06:44.422Z" }, -] - -[[package]] -name = "pyobjc-framework-webkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" }, -] - [[package]] name = "pyopenssl" -version = "24.2.1" +version = "25.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/70/ff56a63248562e77c0c8ee4aefc3224258f1856977e0c1472672b62dadb8/pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95", size = 184323, upload-time = "2024-07-20T17:26:31.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329", size = 184073, upload-time = "2025-09-17T00:32:21.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/dd/e0aa7ebef5168c75b772eda64978c597a9129b46be17779054652a7999e4/pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d", size = 58390, upload-time = "2024-07-20T17:26:29.057Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", size = 57268, upload-time = "2025-09-17T00:32:19.474Z" }, ] [[package]] @@ -3744,27 +1207,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] -[[package]] -name = "pyperclip" -version = "1.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, -] - -[[package]] -name = "pyrect" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/04/2ba023d5f771b645f7be0c281cdacdcd939fe13d1deb331fc5ed1a6b3a98/PyRect-0.2.0.tar.gz", hash = "sha256:f65155f6df9b929b67caffbd57c0947c5ae5449d3b580d178074bffb47a09b78", size = 17219, upload-time = "2022-03-16T04:45:52.36Z" } - -[[package]] -name = "pyscreeze" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/f0/cb456ac4f1a73723d5b866933b7986f02bacea27516629c00f8e7da94c2d/pyscreeze-1.0.1.tar.gz", hash = "sha256:cf1662710f1b46aa5ff229ee23f367da9e20af4a78e6e365bee973cad0ead4be", size = 27826, upload-time = "2024-08-20T23:03:07.291Z" } - [[package]] name = "pyserial" version = "3.5" @@ -3840,18 +1282,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/64/bba465299b37448b4c1b84c7a04178399ac22d47b3dc5db1874fe55a2bd3/pytest_subtests-0.15.0-py3-none-any.whl", hash = "sha256:da2d0ce348e1f8d831d5a40d81e3aeac439fec50bd5251cbb7791402696a9493", size = 9185, upload-time = "2025-10-20T16:26:17.239Z" }, ] -[[package]] -name = "pytest-timeout" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, -] - [[package]] name = "pytest-xdist" version = "3.7.1.dev24+g2b4372bd6" @@ -3874,70 +1304,9 @@ wheels = [ ] [[package]] -name = "python-xlib" -version = "0.33" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" }, -] - -[[package]] -name = "python3-xlib" -version = "0.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ef/c6/2c5999de3bb1533521f1101e8fe56fd9c266732f4d48011c7c69b29d12ae/python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8", size = 132828, upload-time = "2014-05-31T12:28:59.603Z" } - -[[package]] -name = "pytweening" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/0c/c16bc93ac2755bac0066a8ecbd2a2931a1735a6fffd99a2b9681c7e83e90/pytweening-1.2.0.tar.gz", hash = "sha256:243318b7736698066c5f362ec5c2b6434ecf4297c3c8e7caa8abfe6af4cac71b", size = 171241, upload-time = "2024-02-20T03:37:56.809Z" } - -[[package]] -name = "pywin32" -version = "311" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, -] - -[[package]] -name = "pywinbox" -version = "0.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ewmhlib", marker = "sys_platform == 'linux'" }, - { name = "pyobjc", marker = "sys_platform == 'darwin'" }, - { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "typing-extensions" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/37/d59397221e15d2a7f38afaa4e8e6b8c244d818044f7daa0bdc5988df0a69/PyWinBox-0.7-py3-none-any.whl", hash = "sha256:8b2506a8dd7afa0a910b368762adfac885274132ef9151b0c81b0d2c6ffd6f83", size = 12274, upload-time = "2024-04-17T10:10:31.899Z" }, -] - -[[package]] -name = "pywinctl" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ewmhlib", marker = "sys_platform == 'linux'" }, - { name = "pymonctl" }, - { name = "pyobjc", marker = "sys_platform == 'darwin'" }, - { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "pywinbox" }, - { name = "typing-extensions" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/33/8e4f632210b28fc9e998a9ab990e7ed97ecd2800cc50038e3800e1d85dbe/PyWinCtl-0.4.1-py3-none-any.whl", hash = "sha256:4d875e22969e1c6239d8c73156193630aaab876366167b8d97716f956384b089", size = 63158, upload-time = "2024-09-23T08:33:39.881Z" }, -] +name = "python3-dev" +version = "3.12.8" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "pyyaml" @@ -4044,38 +1413,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" }, ] -[[package]] -name = "rubicon-objc" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/d2/d39ecd205661a5c14c90dbd92a722a203848a3621785c9783716341de427/rubicon_objc-0.5.3.tar.gz", hash = "sha256:74c25920c5951a05db9d3a1aac31d23816ec7dacc841a5b124d911b99ea71b9a", size = 171512, upload-time = "2025-12-03T03:51:10.264Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/ab/e834c01138c272fb2e37d2f3c7cba708bc694dbc7b3f03b743f29ceb92d5/rubicon_objc-0.5.3-py3-none-any.whl", hash = "sha256:31dedcda9be38435f5ec067906e1eea5d0ddb790330e98a22e94ff424758b415", size = 64414, upload-time = "2025-12-03T03:51:09.082Z" }, -] - [[package]] name = "ruff" -version = "0.15.0" +version = "0.15.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, - { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, - { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, - { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, - { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, - { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, - { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, - { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, - { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, - { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, - { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, - { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, + { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, + { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, + { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, ] [[package]] @@ -4089,15 +1449,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.52.0" +version = "2.53.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/59/eb/1b497650eb564701f9a7b8a95c51b2abe9347ed2c0b290ba78f027ebe4ea/sentry_sdk-2.52.0.tar.gz", hash = "sha256:fa0bec872cfec0302970b2996825723d67390cdd5f0229fb9efed93bd5384899", size = 410273, upload-time = "2026-02-04T15:03:54.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/06/66c8b705179bc54087845f28fd1b72f83751b6e9a195628e2e9af9926505/sentry_sdk-2.53.0.tar.gz", hash = "sha256:6520ef2c4acd823f28efc55e43eb6ce2e6d9f954a95a3aa96b6fd14871e92b77", size = 412369, upload-time = "2026-02-16T11:11:14.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/63/2c6daf59d86b1c30600bff679d039f57fd1932af82c43c0bde1cbc55e8d4/sentry_sdk-2.52.0-py2.py3-none-any.whl", hash = "sha256:931c8f86169fc6f2752cb5c4e6480f0d516112e78750c312e081ababecbaf2ed", size = 435547, upload-time = "2026-02-04T15:03:51.567Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/2fdf854bc3b9c7f55219678f812600a20a138af2dd847d99004994eada8f/sentry_sdk-2.53.0-py2.py3-none-any.whl", hash = "sha256:46e1ed8d84355ae54406c924f6b290c3d61f4048625989a723fd622aab838899", size = 437908, upload-time = "2026-02-16T11:11:13.227Z" }, ] [[package]] @@ -4127,25 +1487,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, ] -[[package]] -name = "shapely" -version = "2.1.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" }, - { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" }, - { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" }, - { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844, upload-time = "2025-09-24T13:50:35.459Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842, upload-time = "2025-09-24T13:50:37.478Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714, upload-time = "2025-09-24T13:50:39.9Z" }, - { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745, upload-time = "2025-09-24T13:50:41.414Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -4212,26 +1553,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.15" +version = "0.0.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/25/257602d316b9333089b688a7a11b33ebc660b74e8dacf400dc3dfdea1594/ty-0.0.15.tar.gz", hash = "sha256:4f9a5b8df208c62dba56e91b93bed8b5bb714839691b8cff16d12c983bfa1174", size = 5101936, upload-time = "2026-02-05T01:06:34.922Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/5e/da108b9eeb392e02ff0478a34e9651490b36af295881cb56575b83f0cc3a/ty-0.0.19.tar.gz", hash = "sha256:ee3d9ed4cb586e77f6efe3d0fe5a855673ca438a3d533a27598e1d3502a2948a", size = 5220026, upload-time = "2026-02-26T12:13:15.215Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/c5/35626e732b79bf0e6213de9f79aff59b5f247c0a1e3ce0d93e675ab9b728/ty-0.0.15-py3-none-linux_armv6l.whl", hash = "sha256:68e092458516c61512dac541cde0a5e4e5842df00b4e81881ead8f745ddec794", size = 10138374, upload-time = "2026-02-05T01:07:03.804Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8a/48fd81664604848f79d03879b3ca3633762d457a069b07e09fb1b87edd6e/ty-0.0.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79f2e75289eae3cece94c51118b730211af4ba5762906f52a878041b67e54959", size = 9947858, upload-time = "2026-02-05T01:06:47.453Z" }, - { url = "https://files.pythonhosted.org/packages/b6/85/c1ac8e97bcd930946f4c94db85b675561d590b4e72703bf3733419fc3973/ty-0.0.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:112a7b26e63e48cc72c8c5b03227d1db280cfa57a45f2df0e264c3a016aa8c3c", size = 9443220, upload-time = "2026-02-05T01:06:44.98Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d9/244bc02599d950f7a4298fbc0c1b25cc808646b9577bdf7a83470b2d1cec/ty-0.0.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71f62a2644972975a657d9dc867bf901235cde51e8d24c20311067e7afd44a56", size = 9949976, upload-time = "2026-02-05T01:07:01.515Z" }, - { url = "https://files.pythonhosted.org/packages/7e/ab/3a0daad66798c91a33867a3ececf17d314ac65d4ae2bbbd28cbfde94da63/ty-0.0.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e48b42be2d257317c85b78559233273b655dd636fc61e7e1d69abd90fd3cba4", size = 9965918, upload-time = "2026-02-05T01:06:54.283Z" }, - { url = "https://files.pythonhosted.org/packages/39/4e/e62b01338f653059a7c0cd09d1a326e9a9eedc351a0f0de9db0601658c3d/ty-0.0.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27dd5b52a421e6871c5bfe9841160331b60866ed2040250cb161886478ab3e4f", size = 10424943, upload-time = "2026-02-05T01:07:08.777Z" }, - { url = "https://files.pythonhosted.org/packages/65/b5/7aa06655ce69c0d4f3e845d2d85e79c12994b6d84c71699cfb437e0bc8cf/ty-0.0.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76b85c9ec2219e11c358a7db8e21b7e5c6674a1fb9b6f633836949de98d12286", size = 10964692, upload-time = "2026-02-05T01:06:37.103Z" }, - { url = "https://files.pythonhosted.org/packages/13/04/36fdfe1f3c908b471e246e37ce3d011175584c26d3853e6c5d9a0364564c/ty-0.0.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e8204c61d8ede4f21f2975dce74efdb80fafb2fae1915c666cceb33ea3c90b", size = 10692225, upload-time = "2026-02-05T01:06:49.714Z" }, - { url = "https://files.pythonhosted.org/packages/13/41/5bf882649bd8b64ded5fbce7fb8d77fb3b868de1a3b1a6c4796402b47308/ty-0.0.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af87c3be7c944bb4d6609d6c63e4594944b0028c7bd490a525a82b88fe010d6d", size = 10516776, upload-time = "2026-02-05T01:06:52.047Z" }, - { url = "https://files.pythonhosted.org/packages/56/75/66852d7e004f859839c17ffe1d16513c1e7cc04bcc810edb80ca022a9124/ty-0.0.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50dccf7398505e5966847d366c9e4c650b8c225411c2a68c32040a63b9521eea", size = 9928828, upload-time = "2026-02-05T01:06:56.647Z" }, - { url = "https://files.pythonhosted.org/packages/65/72/96bc16c7b337a3ef358fd227b3c8ef0c77405f3bfbbfb59ee5915f0d9d71/ty-0.0.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:bd797b8f231a4f4715110259ad1ad5340a87b802307f3e06d92bfb37b858a8f3", size = 9978960, upload-time = "2026-02-05T01:06:29.567Z" }, - { url = "https://files.pythonhosted.org/packages/a0/18/d2e316a35b626de2227f832cd36d21205e4f5d96fd036a8af84c72ecec1b/ty-0.0.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9deb7f20e18b25440a9aa4884f934ba5628ef456dbde91819d5af1a73da48af3", size = 10135903, upload-time = "2026-02-05T01:06:59.256Z" }, - { url = "https://files.pythonhosted.org/packages/02/d3/b617a79c9dad10c888d7c15cd78859e0160b8772273637b9c4241a049491/ty-0.0.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7b31b3de031255b90a5f4d9cb3d050feae246067c87130e5a6861a8061c71754", size = 10615879, upload-time = "2026-02-05T01:07:06.661Z" }, - { url = "https://files.pythonhosted.org/packages/fb/b0/2652a73c71c77296a6343217063f05745da60c67b7e8a8e25f2064167fce/ty-0.0.15-py3-none-win32.whl", hash = "sha256:9362c528ceb62c89d65c216336d28d500bc9f4c10418413f63ebc16886e16cc1", size = 9578058, upload-time = "2026-02-05T01:06:42.928Z" }, - { url = "https://files.pythonhosted.org/packages/84/6e/08a4aedebd2a6ce2784b5bc3760e43d1861f1a184734a78215c2d397c1df/ty-0.0.15-py3-none-win_amd64.whl", hash = "sha256:4db040695ae67c5524f59cb8179a8fa277112e69042d7dfdac862caa7e3b0d9c", size = 10457112, upload-time = "2026-02-05T01:06:39.885Z" }, - { url = "https://files.pythonhosted.org/packages/b3/be/1991f2bc12847ae2d4f1e3ac5dcff8bb7bc1261390645c0755bb55616355/ty-0.0.15-py3-none-win_arm64.whl", hash = "sha256:e5a98d4119e77d6136461e16ae505f8f8069002874ab073de03fbcb1a5e8bf25", size = 9937490, upload-time = "2026-02-05T01:06:32.388Z" }, + { url = "https://files.pythonhosted.org/packages/5a/31/fd8c6067abb275bea11523d21ecf64e1d870b1ce80cac529cf6636df1471/ty-0.0.19-py3-none-linux_armv6l.whl", hash = "sha256:29bed05d34c8a7597567b8e327c53c1aed4a07dcfbe6c81e6d60c7444936ad77", size = 10268470, upload-time = "2026-02-26T12:13:42.881Z" }, + { url = "https://files.pythonhosted.org/packages/15/de/16a11bbf7d98c75849fc41f5d008b89bb5d080a4b10dc8ea851ee2bd371b/ty-0.0.19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79140870c688c97ec68e723c28935ddef9d91a76d48c68e665fe7c851e628b8a", size = 10098562, upload-time = "2026-02-26T12:13:31.618Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4f/086d6ff6686eadf903913c45b53ab96694b62bbfee1d8cf3e55a9b5aa4b2/ty-0.0.19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6e9c1f9cfa6a26f7881d14d75cf963af743f6c4189e6aa3e3b4056a65f22e730", size = 9604073, upload-time = "2026-02-26T12:13:24.645Z" }, + { url = "https://files.pythonhosted.org/packages/95/13/888a6b6c7ed4a880fee91bec997f775153ce86215ee4c56b868516314734/ty-0.0.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbca43b050edf1db2e64ae7b79add233c2aea2855b8a876081bbd032edcd0610", size = 10106295, upload-time = "2026-02-26T12:13:40.584Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e8/05a372cae8da482de73b8246fb43236bf11e24ac28c879804568108759db/ty-0.0.19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8acaa88ab1955ca6b15a0ccc274011c4961377fe65c3948e5d2b212f2517b87c", size = 10098234, upload-time = "2026-02-26T12:13:33.725Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f1/5b0958e9e9576e7662192fe689bbb3dc88e631a4e073db3047793a547d58/ty-0.0.19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a901b6a6dd9d17d5b3b2e7bafc3057294e88da3f5de507347316687d7f191a1", size = 10607218, upload-time = "2026-02-26T12:13:17.576Z" }, + { url = "https://files.pythonhosted.org/packages/fb/ab/358c78b77844f58ff5aca368550ab16c719f1ab0ec892ceb1114d7500f4e/ty-0.0.19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8deafdaaaee65fd121c66064da74a922d8501be4a2d50049c71eab521a23eff7", size = 11160593, upload-time = "2026-02-26T12:13:36.008Z" }, + { url = "https://files.pythonhosted.org/packages/95/59/827fc346d66a59fe48e9689a5ceb67dbbd5b4de2e8d4625371af39a2e8b7/ty-0.0.19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e56071af280897441018f74f921b97d53aec0856f8af85f4f949df8eda07d", size = 10822392, upload-time = "2026-02-26T12:13:29.415Z" }, + { url = "https://files.pythonhosted.org/packages/81/f9/3bbfbbe35478de9bcd63848f4bc9bffda72278dd9732dbad3efc3978432e/ty-0.0.19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdf5885130393ce74501dba792f48ce0a515756ec81c33a4b324bdf3509df6e", size = 10707139, upload-time = "2026-02-26T12:13:20.148Z" }, + { url = "https://files.pythonhosted.org/packages/12/9e/597023b183ec4ade83a36a0cea5c103f3bffa34f70813d46386c61447fb8/ty-0.0.19-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:877e89005c8f9d1dbff5ad14cbac9f35c528406fde38926f9b44f24830de8d6a", size = 10096933, upload-time = "2026-02-26T12:13:45.266Z" }, + { url = "https://files.pythonhosted.org/packages/1e/76/d0d2f6e674db2a17c8efa5e26682b9dfa8d34774705f35902a7b45ebd3bd/ty-0.0.19-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:39bd1da051c1e4d316efaf79dbed313255633f7c6ad6e24d29f4d9c6ffaf4de6", size = 10109547, upload-time = "2026-02-26T12:13:22.17Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b0/76026c06b852a3aa4fdb5bd329fdc2175aaf3c64a3fafece9cc4df167cee/ty-0.0.19-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87df8415a6c9cb27b8f1382fcdc6052e59f5b9f50f78bc14663197eb5c8d3699", size = 10289110, upload-time = "2026-02-26T12:13:38.29Z" }, + { url = "https://files.pythonhosted.org/packages/14/6c/f3b3a189816b4f079b20fe5d0d7ee38e38a472f53cc6770bb6571147e3de/ty-0.0.19-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:89b6bb23c332ed5c38dd859eb5793f887abcc936f681a40d4ea68e35eac1af33", size = 10796479, upload-time = "2026-02-26T12:13:10.992Z" }, + { url = "https://files.pythonhosted.org/packages/3d/18/caee33d1ce9dd50bd94c26cde7cda4f6971e22e474e7d72a5c86d745ad58/ty-0.0.19-py3-none-win32.whl", hash = "sha256:19b33df3aa7af7b1a9eaa4e1175c3b4dec0f5f2e140243e3492c8355c37418f3", size = 9677215, upload-time = "2026-02-26T12:13:08.519Z" }, + { url = "https://files.pythonhosted.org/packages/81/41/18fc0771d0b1da7d7cc2fc9af278d3122b754fe8b521a748734f4e16ecfd/ty-0.0.19-py3-none-win_amd64.whl", hash = "sha256:b9052c61464cdd76bc8e6796f2588c08700f25d0dcbc225bb165e390ea9d96a4", size = 10651252, upload-time = "2026-02-26T12:13:13.035Z" }, + { url = "https://files.pythonhosted.org/packages/8b/8c/26f7ce8863eb54510082747b3dfb1046ba24f16fc11de18c0e5feb36ff18/ty-0.0.19-py3-none-win_arm64.whl", hash = "sha256:9329804b66dcbae8e7af916ef4963221ed53b8ec7d09b0793591c5ae8a0f3270", size = 10093195, upload-time = "2026-02-26T12:13:26.816Z" }, ] [[package]] @@ -4300,18 +1641,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/63/188f7cb41ab35d795558325d5cc8ab552171d5498cfb178fd14409651e18/xattr-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2aaa5d66af6523332189108f34e966ca120ff816dfa077ca34b31e6263f8a236", size = 37754, upload-time = "2025-10-13T22:16:15.306Z" }, ] -[[package]] -name = "yapf" -version = "0.43.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/81/6acd6601f61e31cfb8729d3da6d5df966f80f374b78eff83760714487338/yapf-0.43.0-py3-none-any.whl", hash = "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca", size = 256158, upload-time = "2024-11-14T00:11:39.37Z" }, -] - [[package]] name = "yarl" version = "1.22.0" @@ -4342,6 +1671,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, ] +[[package]] +name = "zeromq" +version = "4.3.5" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } + [[package]] name = "zstandard" version = "0.25.0" @@ -4366,3 +1700,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, ] + +[[package]] +name = "zstd" +version = "1.5.6" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" }