diff --git a/.devcontainer/.gitignore b/.devcontainer/.gitignore new file mode 100644 index 0000000000..5682fe1190 --- /dev/null +++ b/.devcontainer/.gitignore @@ -0,0 +1,3 @@ +.Xauthority +.env +.host/ \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..08d3abaca9 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,11 @@ +FROM ghcr.io/commaai/openpilot-base:latest + +# remove gitconfig if exists, since its gonna be replaced by host one +RUN rm -f /root/.gitconfig + +RUN apt update && apt install -y vim net-tools usbutils htop ripgrep tmux +RUN pip install ipython jupyter jupyterlab + +RUN cd $HOME && \ + curl -O https://raw.githubusercontent.com/commaai/agnos-builder/master/userspace/home/.tmux.conf && \ + curl -O https://github.com/commaai/agnos-builder/blob/master/userspace/home/.vimrc diff --git a/.devcontainer/container_post_create.sh b/.devcontainer/container_post_create.sh new file mode 100755 index 0000000000..52a4b5f858 --- /dev/null +++ b/.devcontainer/container_post_create.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +source .devcontainer/.host/.env + +# override display flag for mac +if [[ $HOST_OS == darwin ]]; then + echo "Setting up DISPLAY override for macOS..." + cat <> /root/.bashrc +if [ -n "\$DISPLAY" ]; then + DISPLAY_NUM=\$(echo "\$DISPLAY" | awk -F: '{print \$NF}') + export DISPLAY=host.docker.internal:\$DISPLAY_NUM +fi +EOF +fi diff --git a/.devcontainer/container_post_start.sh b/.devcontainer/container_post_start.sh new file mode 100755 index 0000000000..4404f6a9a9 --- /dev/null +++ b/.devcontainer/container_post_start.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# setup safe directories for submodules +SUBMODULE_DIRS=$(git config --file .gitmodules --get-regexp path | awk '{ print $2 }') +for DIR in $SUBMODULE_DIRS; do + git config --global --add safe.directory "$PWD/$DIR" +done diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..7224f251a7 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,34 @@ +{ + "name": "openpilot devcontainer", + "build": { + "dockerfile": "Dockerfile" + }, + "postCreateCommand": ".devcontainer/container_post_create.sh", + "postStartCommand": ".devcontainer/container_post_start.sh", + "initializeCommand": ".devcontainer/host_setup.sh", + "privileged": true, + "containerEnv": { + "DISPLAY": "${localEnv:DISPLAY}", + "PYTHONPATH": "${containerWorkspaceFolder}", + "force_color_prompt": "1" + }, + "runArgs": [ + "--volume=/tmp/.X11-unix:/tmp/.X11-unix", + "--volume=${localWorkspaceFolder}/.devcontainer/.host/.Xauthority:/root/.Xauthority", + "--volume=${localEnv:HOME}/.comma:/root/.comma", + "--volume=/tmp/comma_download_cache:/tmp/comma_download_cache", + "--volume=/tmp/devcontainer_scons_cache:/tmp/scons_cache", + "--shm-size=1G" + ], + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-vscode.cpptools", + "ms-toolsai.jupyter", + "guyskk.language-cython", + "lharri73.dbc" + ] + } + } +} \ No newline at end of file diff --git a/.devcontainer/host_setup.sh b/.devcontainer/host_setup.sh new file mode 100755 index 0000000000..e8a0781a2c --- /dev/null +++ b/.devcontainer/host_setup.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# pull base image +docker pull ghcr.io/commaai/openpilot-base:latest + +# setup .host dir +mkdir -p .devcontainer/.host + +# setup links to Xauthority +XAUTHORITY_LINK=".devcontainer/.host/.Xauthority" +rm -f $XAUTHORITY_LINK +if [[ -z $XAUTHORITY ]]; then + echo "XAUTHORITY not set. Fallback to ~/.Xauthority ..." + if ! [[ -f $HOME/.Xauthority ]]; then + echo "~/.XAuthority file does not exist. GUI tools may not work properly." + touch $XAUTHORITY_LINK # dummy file to satisfy container volume mount + else + ln -sf $HOME/.Xauthority $XAUTHORITY_LINK + fi +else + ln -sf $XAUTHORITY $XAUTHORITY_LINK +fi + +# setup host env file +HOST_INFO_FILE=".devcontainer/.host/.env" +SYSTEM=$(uname -s | tr '[:upper:]' '[:lower:]') +echo "HOST_OS=\"$SYSTEM\"" > $HOST_INFO_FILE diff --git a/.github/labeler.yaml b/.github/labeler.yaml new file mode 100644 index 0000000000..4c4d371d43 --- /dev/null +++ b/.github/labeler.yaml @@ -0,0 +1,61 @@ +CI / testing: + - all: + - changed-files: ['.github/**'] + - all: + - changed-files: ['**/test_*'] + +car: + - all: + - changed-files: ['selfdrive/car/**'] + +body: + - all: + - changed-files: ['selfdrive/car/body/*'] +chrysler: + - all: + - changed-files: ['selfdrive/car/chrysler/*'] +ford: + - all: + - changed-files: ['selfdrive/car/ford/*'] +gm: + - all: + - changed-files: ['selfdrive/car/gm/*'] +honda: + - all: + - changed-files: ['selfdrive/car/honda/*'] +hyundai: + - all: + - changed-files: ['selfdrive/car/hyundai/*'] +mazda: + - all: + - changed-files: ['selfdrive/car/mazda/*'] +nissan: + - all: + - changed-files: ['selfdrive/car/nissan/*'] +subaru: + - all: + - changed-files: ['selfdrive/car/subaru/*'] +tesla: + - all: + - changed-files: ['selfdrive/car/tesla/*'] +toyota: + - all: + - changed-files: ['selfdrive/car/toyota/*'] +volkswagen: + - all: + - changed-files: ['selfdrive/car/volkswagen/*'] + +simulation: + - all: + - changed-files: ['tools/sim/**'] +ui: + - all: + - changed-files: ['selfdrive/ui/**'] +tools: + - all: + - changed-files: ['tools/**'] + +multilanguage: + - all: + - changed-files: ['selfdrive/ui/translations/**'] + diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml index 581c88be15..a2c2daab1a 100644 --- a/.github/workflows/badges.yaml +++ b/.github/workflows/badges.yaml @@ -7,7 +7,7 @@ on: env: BASE_IMAGE: openpilot-base DOCKER_REGISTRY: ghcr.io/commaai - RUN: docker run --shm-size 1G -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 ~/scons_cache:/tmp/scons_cache -v ~/comma_download_cache:/tmp/comma_download_cache -v ~/openpilot_cache:/tmp/openpilot_cache $DOCKER_REGISTRY/$BASE_IMAGE:latest /bin/sh -c + RUN: docker run --shm-size 1G -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/sh -c jobs: badges: @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v3 with: submodules: true - - uses: ./.github/workflows/setup + - uses: ./.github/workflows/setup-with-retry - name: Push badges run: | ${{ env.RUN }} "scons -j$(nproc) && python selfdrive/ui/translations/create_badges.py" diff --git a/.github/workflows/compile-openpilot/action.yaml b/.github/workflows/compile-openpilot/action.yaml new file mode 100644 index 0000000000..8775c96262 --- /dev/null +++ b/.github/workflows/compile-openpilot/action.yaml @@ -0,0 +1,27 @@ +name: 'compile openpilot' + +inputs: + cache_key_prefix: + description: 'Prefix for caching key' + required: false + default: 'scons' + +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@v3 + if: github.ref == 'refs/heads/master' + with: + path: .ci_cache/scons_cache + key: ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} diff --git a/.github/workflows/labeler.yaml b/.github/workflows/labeler.yaml new file mode 100644 index 0000000000..20f7260ef7 --- /dev/null +++ b/.github/workflows/labeler.yaml @@ -0,0 +1,18 @@ +name: "Pull Request Labeler" +on: + pull_request_target: + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: false + - uses: actions/labeler@v5.0.0-alpha.1 + with: + dot: true + configuration-path: .github/labeler.yaml \ No newline at end of file diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index 6acc7a2e9c..8b16ea90b9 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -5,12 +5,8 @@ on: workflow_dispatch: env: - BASE_IMAGE: openpilot-base - DOCKER_REGISTRY: ghcr.io/commaai - DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} - BUILD: | - DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . + BUILD: selfdrive/test/docker_build.sh prebuilt jobs: build_prebuilt: @@ -18,7 +14,7 @@ jobs: runs-on: ubuntu-20.04 if: github.repository == 'commaai/openpilot' env: - IMAGE_NAME: openpilot-prebuilt + PUSH_IMAGE: true steps: - name: Wait for green check mark if: ${{ github.event_name != 'workflow_dispatch' }} @@ -31,13 +27,7 @@ jobs: - uses: actions/checkout@v3 with: submodules: true - - name: Build Docker image - run: | - eval "$BUILD" - DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f Dockerfile.openpilot . - - name: Push to container registry + - name: Build and Push docker image run: | $DOCKER_LOGIN - docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest - docker tag $DOCKER_REGISTRY/$IMAGE_NAME:latest $DOCKER_REGISTRY/$IMAGE_NAME:$GITHUB_SHA - docker push $DOCKER_REGISTRY/$IMAGE_NAME:$GITHUB_SHA + eval "$BUILD" diff --git a/.github/workflows/repo.yml b/.github/workflows/repo-maintenance.yaml similarity index 65% rename from .github/workflows/repo.yml rename to .github/workflows/repo-maintenance.yaml index 1445aa635d..060b7f3e6c 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo-maintenance.yaml @@ -1,4 +1,4 @@ -name: repo +name: repo maintenance on: schedule: @@ -6,13 +6,17 @@ on: workflow_dispatch: jobs: - pre-commit-autoupdate: - name: pre-commit autoupdate + updates: + name: updates runs-on: ubuntu-20.04 container: image: ghcr.io/commaai/openpilot-base:latest steps: - uses: actions/checkout@v3 + - name: poetry lock + run: | + pip install poetry + poetry lock - name: pre-commit autoupdate run: | git config --global --add safe.directory '*' @@ -21,8 +25,8 @@ jobs: uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 with: token: ${{ secrets.ACTIONS_CREATE_PR_PAT }} - commit-message: Update pre-commit hook versions - title: 'pre-commit: autoupdate hooks' - branch: pre-commit-updates + commit-message: Update Python packages and pre-commit hooks + title: 'Update Python packages and pre-commit hooks' + branch: auto-package-updates base: master delete-branch: true diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 65e10e61ed..854faf4545 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -12,23 +12,21 @@ concurrency: env: PYTHONWARNINGS: error - BASE_IMAGE: openpilot-base CL_BASE_IMAGE: openpilot-base-cl - DOCKER_REGISTRY: ghcr.io/commaai AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }} DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} - BUILD: | - DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . + BUILD: selfdrive/test/docker_build.sh base + + RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -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/sh -c - RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -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 ~/scons_cache:/tmp/scons_cache -v ~/comma_download_cache:/tmp/comma_download_cache -v ~/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c + BUILD_CL: selfdrive/test/docker_build.sh cl - BUILD_CL: | - DOCKER_BUILDKIT=1 docker build --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl . - RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -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 ~/scons_cache:/tmp/scons_cache -v ~/comma_download_cache:/tmp/comma_download_cache -v ~/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c + RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c UNIT_TEST: coverage run --append -m unittest discover + PYTEST: pytest --continue-on-collection-errors --cov --cov-report=xml --cov-append --durations=0 --durations-min=5 jobs: build_release: @@ -43,7 +41,7 @@ jobs: - name: Build devel timeout-minutes: 1 run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh - - uses: ./.github/workflows/setup + - uses: ./.github/workflows/setup-with-retry - name: Check submodules if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' timeout-minutes: 1 @@ -64,34 +62,28 @@ jobs: run: | cd $GITHUB_WORKSPACE cp .pre-commit-config.yaml $STRIPPED_DIR - cp mypy.ini $STRIPPED_DIR cp pyproject.toml $STRIPPED_DIR cp poetry.lock $STRIPPED_DIR cd $STRIPPED_DIR ${{ env.RUN }} "unset PYTHONWARNINGS && pre-commit run --all" - build_all: - name: build all - runs-on: ubuntu-20.04 + build: + strategy: + matrix: + arch: ${{ fromJson( (github.repository == 'commaai/openpilot') && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} + runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v3 with: submodules: true - - uses: ./.github/workflows/setup - - name: Build openpilot with all flags - timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 12 || 30) }} # allow more time when we missed the scons cache - run: ${{ env.RUN }} "scons -j$(nproc) --extras && release/check-dirty.sh" - - name: Cleanup scons cache and rebuild - timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 2 || 30) }} # allow more time when we missed the scons cache - run: | - ${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \ - scons -j$(nproc) --cache-populate" - - name: Save scons cache - uses: actions/cache/save@v3 - if: github.ref == 'refs/heads/master' + - uses: ./.github/workflows/setup-with-retry + with: + docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} + cache_key_prefix: scons_${{ matrix.arch }} + - uses: ./.github/workflows/compile-openpilot + timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache with: - path: ~/scons_cache - key: scons-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + cache_key_prefix: scons_${{ matrix.arch }} build_mac: name: build macos @@ -118,6 +110,8 @@ jobs: uses: actions/cache@v3 with: path: | + .env + .venv ~/github_brew_cache_entries.txt ~/.pyenv ~/Library/Caches/pypoetry @@ -143,24 +137,23 @@ jobs: fi - name: Install dependencies if: steps.dependency-cache.outputs.cache-hit != 'true' - run: ./tools/mac_setup.sh + run: SKIP_PROMPT=1 ./tools/mac_setup.sh env: # package install has DeprecationWarnings PYTHONWARNINGS: default - name: Build openpilot run: | - source tools/openpilot_env.sh + eval "$(pyenv init --path)" poetry run scons -j$(nproc) - name: Run tests run: | - source tools/openpilot_env.sh - export PYTHONPATH=$PWD + eval "$(pyenv init --path)" poetry run tools/plotjuggler/test_plotjuggler.py - name: Pre Cache - Cleanup scons cache if: github.ref == 'refs/heads/master' run: | - source tools/openpilot_env.sh rm -rf /tmp/scons_cache/* + eval "$(pyenv init --path)" poetry run scons -j$(nproc) --cache-populate - name: Save scons cache id: scons-save-cache @@ -186,28 +179,46 @@ jobs: docker_push: name: docker push - runs-on: ubuntu-20.04 + strategy: + matrix: + arch: ${{ fromJson( (github.repository == 'commaai/openpilot') && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} + runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' steps: - uses: actions/checkout@v3 with: submodules: true - - name: Build Docker image - run: eval "$BUILD" - - name: Push to container registry + - name: Setup to push to repo run: | + echo "PUSH_IMAGE=true" >> "$GITHUB_ENV" + echo "TARGET_ARCHITECTURE=${{ matrix.arch }}" >> "$GITHUB_ENV" $DOCKER_LOGIN - docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest - docker tag $DOCKER_REGISTRY/$BASE_IMAGE:latest $DOCKER_REGISTRY/$BASE_IMAGE:$GITHUB_SHA - docker push $DOCKER_REGISTRY/$BASE_IMAGE:$GITHUB_SHA - - name: Build CL Docker image - run: eval "$BUILD_CL" - - name: Push to container registry + - uses: ./.github/workflows/setup-with-retry + with: + git-lfs: false + docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} + - name: Build and push CL Docker image + if: matrix.arch == 'x86_64' + run: | + unset TARGET_ARCHITECTURE + eval "$BUILD_CL" + + docker_push_multiarch: + name: docker push multiarch tag + runs-on: ubuntu-20.04 + if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' + needs: [docker_push] + steps: + - uses: actions/checkout@v3 + with: + submodules: false + - name: Setup docker run: | $DOCKER_LOGIN - docker push $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest - docker tag $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest $DOCKER_REGISTRY/$CL_BASE_IMAGE:$GITHUB_SHA - docker push $DOCKER_REGISTRY/$CL_BASE_IMAGE:$GITHUB_SHA + - name: Merge x64 and arm64 tags + run: | + export PUSH_IMAGE=true + selfdrive/test/docker_tag_multiarch.sh base x86_64 aarch64 static_analysis: name: static analysis @@ -229,7 +240,7 @@ jobs: - uses: actions/checkout@v3 with: submodules: true - - uses: ./.github/workflows/setup + - uses: ./.github/workflows/setup-with-retry - name: Build openpilot run: ${{ env.RUN }} "scons -j$(nproc)" - name: Run valgrind @@ -247,68 +258,47 @@ jobs: - uses: actions/checkout@v3 with: submodules: true - - uses: ./.github/workflows/setup + - uses: ./.github/workflows/setup-with-retry - name: Build openpilot timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache run: ${{ env.RUN }} "scons -j$(nproc)" - name: Run unit tests - timeout-minutes: 40 + timeout-minutes: 15 run: | ${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \ - $UNIT_TEST common && \ - $UNIT_TEST opendbc/can && \ - $UNIT_TEST selfdrive/boardd && \ - $UNIT_TEST selfdrive/controls && \ - $UNIT_TEST selfdrive/monitoring && \ - $UNIT_TEST system/loggerd && \ - $UNIT_TEST selfdrive/car && \ - $UNIT_TEST selfdrive/locationd && \ - $UNIT_TEST selfdrive/test/longitudinal_maneuvers && \ - $UNIT_TEST system/tests && \ - $UNIT_TEST system/ubloxd && \ - selfdrive/locationd/test/_test_locationd_lib.py && \ - ./system/ubloxd/tests/test_glonass_runner && \ - $UNIT_TEST selfdrive/athena && \ - $UNIT_TEST selfdrive/thermald && \ - $UNIT_TEST system/hardware/tici && \ - $UNIT_TEST tools/lib/tests && \ + $PYTEST -n auto --dist=loadscope --timeout 30 -o cpp_files=test_* && \ ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ ./selfdrive/ui/tests/test_translations.py && \ - ./common/tests/test_util && \ - ./common/tests/test_swaglog && \ - ./selfdrive/boardd/tests/test_boardd_usbprotocol && \ - ./system/loggerd/tests/test_logger &&\ - ./system/proclogd/tests/test_proclog && \ - ./tools/replay/tests/test_replay && \ - ./tools/cabana/tests/test_cabana && \ ./system/camerad/test/ae_gray_test && \ - ./selfdrive/test/process_replay/test_fuzzy.py && \ - coverage xml" + ./selfdrive/test/process_replay/test_fuzzy.py" - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v3 process_replay: name: process replay - runs-on: ubuntu-20.04 + runs-on: ${{ ((github.event.pull_request.head.repo.full_name == 'commaai/openpilot') || (github.repository == 'commaai/openpilot')) && 'buildjet-8vcpu-ubuntu-2004' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v3 with: submodules: true - - uses: ./.github/workflows/setup + - uses: ./.github/workflows/setup-with-retry + with: + docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} - name: Cache test routes id: dependency-cache uses: actions/cache@v3 with: - path: ~/comma_download_cache + path: .ci_cache/comma_download_cache key: proc-replay-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/ref_commit') }} - name: Build openpilot run: | ${{ env.RUN }} "scons -j$(nproc)" - name: Run replay - timeout-minutes: 20 + timeout-minutes: 30 run: | ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ + chmod -R 777 /tmp/comma_download_cache && \ coverage xml" - name: Print diff id: print-diff @@ -323,7 +313,7 @@ jobs: - name: Upload reference logs if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} run: | - ${{ env.RUN }} "CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" + ${{ env.RUN }} "unset PYTHONWARNINGS && CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v3 @@ -334,7 +324,7 @@ jobs: - uses: actions/checkout@v3 with: submodules: true - - uses: ./.github/workflows/setup + - uses: ./.github/workflows/setup-with-retry - name: Build base Docker image run: eval "$BUILD" - name: Build Docker image @@ -345,7 +335,7 @@ jobs: ${{ env.RUN }} "scons -j$(nproc)" # PYTHONWARNINGS triggers a SyntaxError in onnxruntime - name: Run model replay with ONNX - timeout-minutes: 2 + timeout-minutes: 3 run: | ${{ env.RUN_CL }} "unset PYTHONWARNINGS && \ ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ @@ -370,20 +360,19 @@ jobs: - uses: actions/checkout@v3 with: submodules: true - - uses: ./.github/workflows/setup + - uses: ./.github/workflows/setup-with-retry - name: Cache test routes id: dependency-cache uses: actions/cache@v3 with: - path: ~/comma_download_cache + path: .ci_cache/comma_download_cache key: car_models-${{ hashFiles('selfdrive/car/tests/test_models.py', 'selfdrive/car/tests/routes.py') }}-${{ matrix.job }} - name: Build openpilot run: ${{ env.RUN }} "scons -j$(nproc)" - name: Test car models timeout-minutes: 25 run: | - ${{ env.RUN }} "coverage run -m pytest selfdrive/car/tests/test_models.py && \ - coverage xml && \ + ${{ env.RUN }} "$PYTEST -n auto --dist=loadscope selfdrive/car/tests/test_models.py && \ chmod -R 777 /tmp/comma_download_cache" env: NUM_JOBS: 5 @@ -400,7 +389,7 @@ jobs: with: submodules: true ref: ${{ github.event.pull_request.base.ref }} - - uses: ./.github/workflows/setup + - uses: ./.github/workflows/setup-with-retry - name: Get base car info run: | ${{ env.RUN }} "scons -j$(nproc) && python selfdrive/debug/dump_car_info.py --path /tmp/openpilot_cache/base_car_info" @@ -408,9 +397,11 @@ jobs: - uses: actions/checkout@v3 with: submodules: true + path: current - name: Save car docs diff id: save_diff run: | + cd current ${{ env.RUN }} "scons -j$(nproc)" output=$(${{ env.RUN }} "python selfdrive/debug/print_docs_diff.py --path /tmp/openpilot_cache/base_car_info") output="${output//$'\n'/'%0A'}" diff --git a/.github/workflows/setup-with-retry/action.yaml b/.github/workflows/setup-with-retry/action.yaml new file mode 100644 index 0000000000..159778211d --- /dev/null +++ b/.github/workflows/setup-with-retry/action.yaml @@ -0,0 +1,54 @@ +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: '' + git_lfs: + description: 'Whether or not to pull the git lfs' + required: false + default: 'true' + cache_key_prefix: + description: 'Prefix for caching key' + required: false + default: 'scons_x86_64' + sleep_time: + description: 'Time to sleep between retries' + required: false + default: 30 + +runs: + using: "composite" + steps: + - id: setup1 + uses: ./.github/workflows/setup + continue-on-error: true + with: + docker_hub_pat: ${{ inputs.docker_hub_pat }} + git_lfs: ${{ inputs.git_lfs }} + cache_key_prefix: ${{ inputs.cache_key_prefix }} + 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: + docker_hub_pat: ${{ inputs.docker_hub_pat }} + git_lfs: ${{ inputs.git_lfs }} + cache_key_prefix: ${{ inputs.cache_key_prefix }} + 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: + docker_hub_pat: ${{ inputs.docker_hub_pat }} + git_lfs: ${{ inputs.git_lfs }} + cache_key_prefix: ${{ inputs.cache_key_prefix }} + is_retried: true diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml index a6c58aae08..d00f968b5c 100644 --- a/.github/workflows/setup/action.yaml +++ b/.github/workflows/setup/action.yaml @@ -1,19 +1,51 @@ name: 'openpilot env setup' inputs: + docker_hub_pat: + description: 'Auth token for Docker Hub, required for BuildJet jobs' + required: true + default: '' git_lfs: description: 'Whether or not to pull the git lfs' - required: false + required: true default: 'true' + cache_key_prefix: + description: 'Prefix for caching key' + required: true + default: 'scons_x86_64' + 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 + # do this after checkout to ensure our custom LFS config is used to pull from GitLab - shell: bash if: ${{ inputs.git_lfs == 'true' }} run: git lfs pull + # on BuildJet runners, must be logged into DockerHub to avoid rate limiting + # https://buildjet.com/for-github-actions/docs/guides/docker + - shell: bash + if: ${{ contains(runner.name, 'buildjet') && inputs.docker_hub_pat == '' }} + run: | + echo "Need to set the Docker Hub PAT secret as an input to this action" + exit 1 + - name: Login to Docker Hub + if: contains(runner.name, 'buildjet') + shell: bash + run: | + docker login -u adeebshihadeh -p ${{ inputs.docker_hub_pat }} + # build cache - id: date shell: bash @@ -23,12 +55,29 @@ runs: - id: restore-scons-cache uses: actions/cache/restore@v3 with: - path: ~/scons_cache - key: scons-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + path: .ci_cache/scons_cache + key: ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} restore-keys: | - scons-${{ env.CACHE_COMMIT_DATE }}- - scons- - + ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}- + ${{ inputs.cache_key_prefix }}- + # if we didn't get a cache hit, make the directory manually so it doesn't fail on future steps + - id: scons-cache-setup + shell: bash + if: steps.restore-scons-cache.outputs.cache-hit != 'true' + run: mkdir -p $GITHUB_WORKSPACE/.ci_cache/scons_cache + # 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 {} \; + - id: setup-buildx-action + if: contains(runner.name, 'buildjet') + name: Set up Docker Buildx on buildjet to ensure a consistent cache + uses: docker/setup-buildx-action@v2 + with: + driver: docker-container # build our docker image - shell: bash - run: eval ${{ env.BUILD }} + run: eval ${{ env.BUILD }} \ No newline at end of file diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index e0d471882f..c7a5f80c3b 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -13,17 +13,15 @@ concurrency: env: BASE_IMAGE: openpilot-base CL_BASE_IMAGE: openpilot-base-cl - DOCKER_REGISTRY: ghcr.io/commaai DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} - BUILD: | - DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . + BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v ~/scons_cache:/tmp/scons_cache -v ~/comma_download_cache:/tmp/comma_download_cache -v ~/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c + RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c - BUILD_CL: | - DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl . - RUN_CL: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v ~/scons_cache:/tmp/scons_cache -v ~/comma_download_cache:/tmp/comma_download_cache -v ~/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c + BUILD_CL: selfdrive/test/docker_build.sh cl + + RUN_CL: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c jobs: @@ -35,57 +33,68 @@ jobs: - uses: actions/checkout@v3 with: submodules: true - - name: Build Docker image - run: eval "$BUILD" - - name: Unit test + - uses: ./.github/workflows/setup-with-retry + - name: Build openpilot + timeout-minutes: 5 + run: ${{ env.RUN }} "scons -j$(nproc) cereal/ common/ --minimal" + - name: Test PlotJuggler timeout-minutes: 2 run: | - ${{ env.RUN }} "scons -j$(nproc) --directory=/tmp/openpilot/cereal && \ - apt-get update && \ - apt-get install -y libdw-dev libqt5svg5-dev libqt5x11extras5-dev && \ - cd /tmp/openpilot/tools/plotjuggler && \ - ./test_plotjuggler.py" + ${{ env.RUN }} "pytest tools/plotjuggler/" simulator: name: simulator runs-on: ubuntu-20.04 - env: - IMAGE_NAME: openpilot-sim if: github.repository == 'commaai/openpilot' timeout-minutes: 45 steps: - uses: actions/checkout@v3 with: submodules: true - - uses: ./.github/workflows/setup + - uses: ./.github/workflows/setup-with-retry - name: Build base cl image run: eval "$BUILD_CL" - - name: Build simulator image - run: DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f tools/sim/Dockerfile.sim . - - name: Push to container registry + - name: Setup to push to repo if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' run: | + echo "PUSH_IMAGE=true" >> "$GITHUB_ENV" $DOCKER_LOGIN - docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest + - name: Build and push sim image + run: | + selfdrive/test/docker_build.sh sim docs: name: build docs runs-on: ubuntu-20.04 timeout-minutes: 45 - env: - BUILD: | - DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/openpilot-docs-base:latest -t $DOCKER_REGISTRY/openpilot-docs-base:latest -f docs/docker/Dockerfile --target openpilot-docs-base . - DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/openpilot-docs-base:latest --cache-from $DOCKER_REGISTRY/openpilot-docs:latest -t $DOCKER_REGISTRY/openpilot-docs:latest -f docs/docker/Dockerfile . steps: - uses: actions/checkout@v3 with: submodules: true - - uses: ./.github/workflows/setup + - uses: ./.github/workflows/setup-with-retry with: git_lfs: false - - name: Push docker container + - name: Setup to push to repo if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' run: | + echo "PUSH_IMAGE=true" >> "$GITHUB_ENV" $DOCKER_LOGIN - docker push $DOCKER_REGISTRY/openpilot-docs-base:latest - docker push $DOCKER_REGISTRY/openpilot-docs:latest \ No newline at end of file + - name: Build and push docs image + run: | + selfdrive/test/docker_build.sh docs + + devcontainer: + name: devcontainer + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + - name: Setup Dev Container CLI + run: npm install -g @devcontainers/cli + - name: Build dev container image + run: devcontainer build --workspace-folder . + - name: Run dev container + run: devcontainer up --workspace-folder . + - name: Test environment + run: devcontainer exec --workspace-folder . scons --dry-run diff --git a/.gitignore b/.gitignore index 004c0a6441..afb3d5b1c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,12 @@ venv/ .venv/ +.ci_cache .env .clang-format .DS_Store .tags .ipynb_checkpoints .idea -.moc_files .overlay_init .overlay_consistent .sconsign.dblite @@ -48,6 +48,7 @@ selfdrive/mapd/default_speeds_by_region.json system/proclogd/proclogd selfdrive/ui/_ui selfdrive/ui/translations/alerts_generated.h +selfdrive/ui/translations/tmp selfdrive/test/longitudinal_maneuvers/out selfdrive/car/tests/cars_dump system/camerad/camerad diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b777bc4368..a5da03b266 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +exclude: '^(tinygrad_repo)' repos: - repo: meta hooks: @@ -14,6 +15,8 @@ repos: - id: check-yaml - id: check-merge-conflict - id: check-symlinks + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable - id: check-added-large-files args: ['--maxkb=100'] - repo: https://github.com/codespell-project/codespell @@ -35,7 +38,7 @@ repos: args: ['--explicit-package-bases'] exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.284 + rev: v0.0.288 hooks: - id: ruff exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' @@ -53,6 +56,18 @@ repos: - --quiet - --force - -j8 +- repo: https://github.com/cpplint/cpplint + rev: 1.6.1 + hooks: + - id: cpplint + exclude: '^(third_party/)|(cereal/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(generated/)' + args: + - --quiet + - --counting=total + - --linelength=240 + # https://google.github.io/styleguide/cppguide.html + # relevant rules are whitelisted, see all options with: cpplint --filter= + - --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces - repo: local hooks: - id: test_translations @@ -61,10 +76,14 @@ repos: language: script pass_filenames: false - repo: https://github.com/python-poetry/poetry - rev: '1.5.0' + rev: '1.6.0' hooks: - id: poetry-check - id: poetry-lock name: validate poetry lock - args: + args: - --check +- repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.26.3 + hooks: + - id: check-github-workflows diff --git a/Dockerfile.openpilot b/Dockerfile.openpilot index 51907b7a44..3541be92b0 100644 --- a/Dockerfile.openpilot +++ b/Dockerfile.openpilot @@ -10,6 +10,7 @@ WORKDIR ${OPENPILOT_PATH} COPY SConstruct ${OPENPILOT_PATH} +COPY ./openpilot ${OPENPILOT_PATH}/openpilot COPY ./third_party ${OPENPILOT_PATH}/third_party COPY ./site_scons ${OPENPILOT_PATH}/site_scons COPY ./laika ${OPENPILOT_PATH}/laika diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index f8b20be351..e0581bd46e 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -12,22 +12,28 @@ 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 cd /tmp && \ + tools/install_ubuntu_dependencies.sh && \ + rm -rf /var/lib/apt/lists/* && \ + rm -rf /tmp/* && \ + # remove unused architectures from gcc for panda + cd /usr/lib/gcc/arm-none-eabi/9.2.1 && \ + rm -rf arm/ && \ + rm -rf thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp + ENV POETRY_VIRTUALENVS_CREATE=false ENV PYENV_VERSION=3.11.4 ENV PYENV_ROOT="/root/.pyenv" ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH" COPY pyproject.toml poetry.lock .python-version /tmp/ -COPY tools/ubuntu_setup.sh tools/install_python_dependencies.sh /tmp/tools/ +COPY tools/install_python_dependencies.sh /tmp/tools/ + RUN cd /tmp && \ - tools/ubuntu_setup.sh && \ - rm -rf /var/lib/apt/lists/* && \ + tools/install_python_dependencies.sh && \ rm -rf /tmp/* && \ rm -rf /root/.cache && \ - pip uninstall -y poetry && \ - # remove unused architectures from gcc for panda - cd /usr/lib/gcc/arm-none-eabi/9.2.1 && \ - rm -rf arm/ && \ - rm -rf thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp + pip uninstall -y poetry RUN sudo git config --global --add safe.directory /tmp/openpilot diff --git a/Jenkinsfile b/Jenkinsfile index a698cc68cc..176c82bcc9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,4 +1,4 @@ -def phone(String ip, String step_label, String cmd) { +def device(String ip, String step_label, String cmd) { withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) { def ssh_cmd = """ ssh -tt -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END' @@ -51,225 +51,186 @@ END""" } } -def phone_steps(String device_type, steps) { - lock(resource: "", label: device_type, inversePrecedence: true, variable: 'device_ip', quantity: 1) { - timeout(time: 20, unit: 'MINUTES') { - phone(device_ip, "git checkout", readFile("selfdrive/test/setup_device_ci.sh"),) - steps.each { item -> - phone(device_ip, item[0], item[1]) - } +def deviceStage(String stageName, String deviceType, List env, def steps) { + stage(stageName) { + if (currentBuild.result != null) { + return } - } -} -pipeline { - agent none - environment { - CI = "1" - PYTHONWARNINGS = "error" - TEST_DIR = "/data/openpilot" - SOURCE_DIR = "/data/openpilot_source/" - AZURE_TOKEN = credentials('azure_token') - MAPBOX_TOKEN = credentials('mapbox_token') - } - options { - timeout(time: 3, unit: 'HOURS') - disableConcurrentBuilds(abortPrevious: env.BRANCH_NAME != 'master') - } + def extra = env.collect { "export ${it}" }.join('\n'); - stages { - stage('build release3-staging') { - agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } - when { - branch 'devel-staging' - } - steps { - phone_steps("tici-needs-can", [ - ["build release3-staging & dashcam3-staging", "RELEASE_BRANCH=release3-staging DASHCAM_BRANCH=dashcam3-staging $SOURCE_DIR/release/build_release.sh"], - ]) + docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') { + lock(resource: "", label: deviceType, inversePrecedence: true, variable: 'device_ip', quantity: 1) { + timeout(time: 20, unit: 'MINUTES') { + device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh")) + steps.each { item -> + device(device_ip, item[0], item[1]) + } + } } } + } +} - stage('build nightly') { - agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } - when { - branch 'master-ci' - } - steps { - phone_steps("tici-needs-can", [ - ["build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"], - ]) - } +def pcStage(String stageName, Closure body) { + node { + stage(stageName) { + if (currentBuild.result != null) { + return } - stage('openpilot tests') { - when { - not { - anyOf { - branch 'master-ci'; branch 'devel'; branch 'devel-staging'; - branch 'release3'; branch 'release3-staging'; branch 'dashcam3'; branch 'dashcam3-staging'; - branch 'testing-closet*'; branch 'hotfix-*' - } + checkout scm + + def dockerArgs = '--user=root -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/scons_cache:/tmp/scons_cache'; + docker.build("openpilot-base:build-${env.GIT_COMMIT}", "-f Dockerfile.openpilot_base .").inside(dockerArgs) { + timeout(time: 20, unit: 'MINUTES') { + try { + sh "git config --global --add safe.directory '*'" + sh "git submodule update --init --recursive" + sh "git lfs pull" + body() + } finally { + sh "rm -rf ${env.WORKSPACE}/* || true" + sh "rm -rf .* || true" } } + } + } + } +} - parallel { - - /* - stage('simulator') { - agent { - dockerfile { - filename 'Dockerfile.sim_nvidia' - dir 'tools/sim' - args '--user=root' - } - } - steps { - sh "git config --global --add safe.directory '*'" - sh "git submodule update --init --recursive" - sh "git lfs pull" - lock(resource: "", label: "simulator", inversePrecedence: true, quantity: 1) { - sh "${WORKSPACE}/tools/sim/build_container.sh" - sh "DETACH=1 ${WORKSPACE}/tools/sim/start_carla.sh" - sh "${WORKSPACE}/tools/sim/start_openpilot_docker.sh" - } - } +def setupCredentials() { + withCredentials([ + string(credentialsId: 'azure_token', variable: 'AZURE_TOKEN'), + string(credentialsId: 'mapbox_token', variable: 'MAPBOX_TOKEN') + ]) { + env.AZURE_TOKEN = "${AZURE_TOKEN}" + env.MAPBOX_TOKEN = "${MAPBOX_TOKEN}" + } +} - post { - always { - sh "docker kill carla_sim || true" - sh "rm -rf ${WORKSPACE}/* || true" - sh "rm -rf .* || true" - } - } - } - */ +node { + env.CI = "1" + env.PYTHONWARNINGS = "error" + env.TEST_DIR = "/data/openpilot" + env.SOURCE_DIR = "/data/openpilot_source/" + setupCredentials() - stage('PC tests') { - agent { - dockerfile { - filename 'Dockerfile.openpilot_base' - args '--user=root -v /tmp/comma_download_cache:/tmp/comma_download_cache' - } - } - steps { - sh "git config --global --add safe.directory '*'" - sh "git submodule update --init --depth=1 --recursive" - sh "git lfs pull" - // tests that our build system's dependencies are configured properly, needs a machine with lots of cores - sh "scons --clean && scons --no-cache --random -j42" - sh "INTERNAL_SEG_CNT=500 INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt FILEREADER_CACHE=1 \ - pytest -n42 --dist=loadscope selfdrive/car/tests/test_models.py" - } + env.GIT_BRANCH = checkout(scm).GIT_BRANCH + env.GIT_COMMIT = checkout(scm).GIT_COMMIT - post { - always { - sh "rm -rf ${WORKSPACE}/* || true" - sh "rm -rf .* || true" - } - } - } + def excludeBranches = ['master-ci', 'devel', 'devel-staging', 'release3', 'release3-staging', + 'dashcam3', 'dashcam3-staging', 'testing-closet*', 'hotfix-*'] + def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*') - stage('tizi-tests') { - agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } - steps { - phone_steps("tizi", [ - ["build openpilot", "cd selfdrive/manager && ./build.py"], - ["test boardd loopback", "SINGLE_PANDA=1 pytest selfdrive/boardd/tests/test_boardd_loopback.py"], - ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"], - ["test sensord", "cd system/sensord/tests && pytest test_sensord.py"], - ["test camerad", "pytest system/camerad/test/test_camerad.py"], - ["test exposure", "pytest system/camerad/test/test_exposure.py"], - ["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"], - ["test hw", "pytest system/hardware/tici/tests/test_hardware.py"], - ["test rawgpsd", "pytest system/sensord/rawgps/test_rawgps.py"], - ]) - } - } - - stage('build') { - agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } - environment { - R3_PUSH = "${env.BRANCH_NAME == 'master' ? '1' : ' '}" - } - steps { - phone_steps("tici-needs-can", [ - ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR ./build_devel.sh"], - ["build openpilot", "cd selfdrive/manager && ./build.py"], - ["check dirty", "release/check-dirty.sh"], - ["onroad tests", "cd selfdrive/test/ && ./test_onroad.py"], - ["time to onroad", "cd selfdrive/test/ && pytest test_time_to_onroad.py"], - ]) - } - } - - stage('loopback-tests') { - agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } - steps { - phone_steps("tici-loopback", [ - ["build openpilot", "cd selfdrive/manager && ./build.py"], - ["test boardd loopback", "pytest selfdrive/boardd/tests/test_boardd_loopback.py"], - ]) - } - } + if (env.BRANCH_NAME != 'master') { + properties([ + disableConcurrentBuilds(abortPrevious: true) + ]) + } - stage('HW + Unit Tests') { - agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } - steps { - phone_steps("tici-common", [ - ["build", "cd selfdrive/manager && ./build.py"], - ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"], - ["test power draw", "pytest system/hardware/tici/tests/test_power_draw.py"], - ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"], - ["test pigeond", "pytest system/sensord/tests/test_pigeond.py"], - ["test manager", "pytest selfdrive/manager/test/test_manager.py"], - ["test nav", "pytest selfdrive/navd/tests/"], - ]) - } - } + try { + if (env.BRANCH_NAME == 'devel-staging') { + deviceStage("build release3-staging", "tici-needs-can", [], [ + ["build release3-staging & dashcam3-staging", "RELEASE_BRANCH=release3-staging DASHCAM_BRANCH=dashcam3-staging $SOURCE_DIR/release/build_release.sh"], + ]) + } - stage('camerad') { - agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } - steps { - phone_steps("tici-ar0231", [ - ["build", "cd selfdrive/manager && ./build.py"], - ["test camerad", "pytest system/camerad/test/test_camerad.py"], - ["test exposure", "pytest system/camerad/test/test_exposure.py"], - ]) - phone_steps("tici-ox03c10", [ - ["build", "cd selfdrive/manager && ./build.py"], - ["test camerad", "pytest system/camerad/test/test_camerad.py"], - ["test exposure", "pytest system/camerad/test/test_exposure.py"], - ]) - } - } + if (env.BRANCH_NAME == 'master-ci') { + deviceStage("build nightly", "tici-needs-can", [], [ + ["build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"], + ]) + } - stage('sensord') { - agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } - steps { - phone_steps("tici-lsmc", [ - ["build", "cd selfdrive/manager && ./build.py"], - ["test sensord", "cd system/sensord/tests && pytest test_sensord.py"], - ]) - phone_steps("tici-bmx-lsm", [ - ["build", "cd selfdrive/manager && ./build.py"], - ["test sensord", "cd system/sensord/tests && pytest test_sensord.py"], - ]) - } + if (!env.BRANCH_NAME.matches(excludeRegex)) { + parallel ( + // tici tests + 'onroad tests': { + deviceStage("onroad", "tici-needs-can", ["SKIP_COPY=1"], [ + ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR ./build_devel.sh"], + ["build openpilot", "cd selfdrive/manager && ./build.py"], + ["check dirty", "release/check-dirty.sh"], + ["onroad tests", "cd selfdrive/test/ && ./test_onroad.py"], + ["time to onroad", "cd selfdrive/test/ && pytest test_time_to_onroad.py"], + ]) + }, + 'HW + Unit Tests': { + deviceStage("tici", "tici-common", ["UNSAFE=1"], [ + ["build", "cd selfdrive/manager && ./build.py"], + ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"], + ["test power draw", "./system/hardware/tici/tests/test_power_draw.py"], + ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"], + ["test pigeond", "pytest system/sensord/tests/test_pigeond.py"], + ["test manager", "pytest selfdrive/manager/test/test_manager.py"], + ["test nav", "pytest selfdrive/navd/tests/"], + ]) + }, + 'loopback': { + deviceStage("tici", "tici-loopback", ["UNSAFE=1"], [ + ["build openpilot", "cd selfdrive/manager && ./build.py"], + ["test boardd loopback", "pytest selfdrive/boardd/tests/test_boardd_loopback.py"], + ]) + }, + 'camerad': { + deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [ + ["build", "cd selfdrive/manager && ./build.py"], + ["test camerad", "pytest system/camerad/test/test_camerad.py"], + ["test exposure", "pytest system/camerad/test/test_exposure.py"], + ]) + deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [ + ["build", "cd selfdrive/manager && ./build.py"], + ["test camerad", "pytest system/camerad/test/test_camerad.py"], + ["test exposure", "pytest system/camerad/test/test_exposure.py"], + ]) + }, + 'sensord': { + deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [ + ["build", "cd selfdrive/manager && ./build.py"], + ["test sensord", "cd system/sensord/tests && pytest test_sensord.py"], + ]) + deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [ + ["build", "cd selfdrive/manager && ./build.py"], + ["test sensord", "cd system/sensord/tests && pytest test_sensord.py"], + ]) + }, + 'replay': { + deviceStage("tici", "tici-replay", ["UNSAFE=1"], [ + ["build", "cd selfdrive/manager && ./build.py"], + ["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"], + ]) + }, + 'tizi': { + deviceStage("tizi", "tizi", ["UNSAFE=1"], [ + ["build openpilot", "cd selfdrive/manager && ./build.py"], + ["test boardd loopback", "SINGLE_PANDA=1 pytest selfdrive/boardd/tests/test_boardd_loopback.py"], + ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"], + ["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"], + ["test hw", "pytest system/hardware/tici/tests/test_hardware.py"], + ["test rawgpsd", "pytest system/sensord/rawgps/test_rawgps.py"], + ]) + }, + + // *** PC tests *** + 'PC tests': { + pcStage("PC tests") { + // tests that our build system's dependencies are configured properly, + // needs a machine with lots of cores + sh label: "test multi-threaded build", script: "scons --no-cache --random -j42" } - - stage('replay') { - agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } - steps { - phone_steps("tici-replay", [ - ["build", "cd selfdrive/manager && ./build.py"], - ["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"], - ]) - } + }, + 'car tests': { + pcStage("car tests") { + sh "scons -j30" + sh label: "test_models.py", script: "INTERNAL_SEG_CNT=250 INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt FILEREADER_CACHE=1 \ + pytest -n42 --dist=loadscope selfdrive/car/tests/test_models.py" + sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest -n42 selfdrive/car/tests/test_car_interfaces.py" } + }, - } + ) } - + } catch (Exception e) { + currentBuild.result = 'FAILED' + throw e } -} +} \ No newline at end of file diff --git a/README.md b/README.md old mode 100755 new mode 100644 index 5978a0e5dd..523f459f3b --- a/README.md +++ b/README.md @@ -39,13 +39,17 @@ Running on a dedicated device in a car ------ To use openpilot in a car, you need four things -* A supported device to run this software: a [comma 3X](https://comma.ai/shop/comma-3x) or comma three. -* This software. The setup procedure of the comma 3/3X allows the user to enter a URL for custom software. -The URL, openpilot.comma.ai will install the release version of openpilot. To install openpilot master, you can use installer.comma.ai/commaai/master, and replacing commaai with another GitHub username can install a fork. -* One of [the 250+ supported cars](docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run openpilot. -* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car. - -We have detailed instructions for [how to mount the device in a car](https://comma.ai/setup). +1. **Supported Device:** A comma 3/3X. You can purchase these devices from (https://comma.ai/shop/comma-3x) + +2. **Software:** The setup procedure for the comma 3/3X allows users to enter a URL for custom software. + To install the release version of openpilot, use the URL `openpilot.comma.ai`. + To install openpilot master (for more advanced users), use the URL `installer.comma.ai/commaai/master`. You can replace "commaai" with another GitHub username to install a fork. + +3. **Supported Car:** Ensure that you have one of [the 250+ supported cars](docs/CARS.md). openpilot supports a wide range of car makes including Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and many more. + If your car is not officially listed as supported but has adaptive cruise control and lane-keeping assist, it's likely capable of running openpilot. + +4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3/3X to your car. + We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Running on PC ------ diff --git a/RELEASES.md b/RELEASES.md index e9cfd69f7c..89f864dd71 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,12 @@ -Version 0.9.5 (202X-XX-XX) +Version 0.9.5 (2023-09-27) ======================== +* New driving model + * Improved navigate on openpilot performance using navigation instructions as an additional model input +* Hyundai Azera 2022 support thanks to sunnyhaibin! +* Hyundai Ioniq 6 2023 support thanks to sunnyhaibin, alamo3, and sshane! +* Hyundai Kona Electric 2023 (Korean version) support thanks to sunnyhaibin and haram-KONA! +* Kia K8 Hybrid (with HDA II) 2023 support thanks to sunnyhaibin! +* Kia Sorento Hybrid 2023 support thanks to sunnyhaibin! * Lexus IS 2023 support thanks to L3R5! Version 0.9.4 (2023-07-27) diff --git a/SConstruct b/SConstruct index e384e31d94..a086f8448a 100644 --- a/SConstruct +++ b/SConstruct @@ -1,5 +1,4 @@ import os -import shutil import subprocess import sys import sysconfig @@ -15,10 +14,6 @@ AGNOS = TICI Decider('MD5-timestamp') -AddOption('--extras', - action='store_true', - help='build misc extras, like setup and installer files') - AddOption('--kaitai', action='store_true', help='Regenerate kaitai struct parsers') @@ -49,21 +44,16 @@ AddOption('--external-sconscript', dest='external_sconscript', help='add an external SConscript to the build') -AddOption('--no-thneed', - action='store_true', - dest='no_thneed', - help='avoid using thneed') - AddOption('--pc-thneed', action='store_true', dest='pc_thneed', help='use thneed on pc') -AddOption('--no-test', +AddOption('--minimal', action='store_false', - dest='test', + dest='extras', default=os.path.islink(Dir('#laika/').abspath), - help='skip building test files') + help='the minimum build to run openpilot. no tests, tools, etc.') ## Architecture name breakdown (arch) ## - larch64: linux tici aarch64 @@ -254,18 +244,6 @@ def progress_function(node): if os.environ.get('SCONS_PROGRESS'): Progress(progress_function, interval=node_interval) -SHARED = False - -# TODO: this can probably be removed -def abspath(x): - if arch == 'aarch64': - pth = os.path.join("/data/pythonpath", x[0].path) - env.Depends(pth, x) - return File(pth) - else: - # rpath works elsewhere - return x[0].path.rsplit("/", 1)[1][:-3] - # Cython build environment py_include = sysconfig.get_paths()['include'] envCython = env.Clone() @@ -336,18 +314,6 @@ qt_env['CXXFLAGS'] += qt_flags qt_env['LIBPATH'] += ['#selfdrive/ui'] qt_env['LIBS'] = qt_libs -# Have to respect cache-readonly -if GetOption('cache_readonly'): - local_moc_files_dir = Dir("#.moc_files").abspath - cache_moc_files_dir = cache_dir + "/moc_files" - if os.path.exists(local_moc_files_dir): - shutil.rmtree(local_moc_files_dir) - if os.path.exists(cache_moc_files_dir): - shutil.copytree(cache_moc_files_dir, local_moc_files_dir) - qt_env['QT3_MOCHPREFIX'] = local_moc_files_dir + "/moc_" -else: - qt_env['QT3_MOCHPREFIX'] = cache_dir + '/moc_files/moc_' - if GetOption("clazy"): checks = [ "level0", @@ -359,33 +325,35 @@ if GetOption("clazy"): qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0] qt_env['ENV']['CLAZY_CHECKS'] = ','.join(checks) -Export('env', 'qt_env', 'arch', 'real_arch', 'SHARED') +Export('env', 'qt_env', 'arch', 'real_arch') +# Build common module SConscript(['common/SConscript']) Import('_common', '_gpucommon') -if SHARED: - common, gpucommon = abspath(common), abspath(gpucommon) -else: - common = [_common, 'json11'] - gpucommon = [_gpucommon] +common = [_common, 'json11'] +gpucommon = [_gpucommon] Export('common', 'gpucommon') -# cereal and messaging are shared with the system +# Build cereal and messaging SConscript(['cereal/SConscript']) -if SHARED: - cereal = abspath([File('cereal/libcereal_shared.so')]) - messaging = abspath([File('cereal/libmessaging_shared.so')]) -else: - cereal = [File('#cereal/libcereal.a')] - messaging = [File('#cereal/libmessaging.a')] - visionipc = [File('#cereal/libvisionipc.a')] -Export('cereal', 'messaging', 'visionipc') +cereal = [File('#cereal/libcereal.a')] +messaging = [File('#cereal/libmessaging.a')] +visionipc = [File('#cereal/libvisionipc.a')] +messaging_python = [File('#cereal/messaging/messaging_pyx.so')] -# Build rednose library and ekf models +Export('cereal', 'messaging', 'messaging_python', 'visionipc') +# Build other submodules +SConscript([ + 'body/board/SConscript', + 'opendbc/can/SConscript', + 'panda/SConscript', +]) + +# Build rednose library and ekf models rednose_deps = [ "#selfdrive/locationd/models/constants.py", "#selfdrive/locationd/models/gnss_helpers.py", @@ -427,20 +395,8 @@ if arch != "Darwin": ]) # Build openpilot - -# build submodules -SConscript([ - 'body/board/SConscript', - 'cereal/SConscript', - 'opendbc/can/SConscript', - 'panda/SConscript', -]) - SConscript(['third_party/SConscript']) -SConscript(['common/kalman/SConscript']) -SConscript(['common/transformations/SConscript']) - SConscript(['selfdrive/boardd/SConscript']) SConscript(['selfdrive/controls/lib/lateral_mpc_lib/SConscript']) SConscript(['selfdrive/controls/lib/longitudinal_mpc_lib/SConscript']) @@ -449,7 +405,7 @@ SConscript(['selfdrive/navd/SConscript']) SConscript(['selfdrive/modeld/SConscript']) SConscript(['selfdrive/ui/SConscript']) -if (arch in ['x86_64', 'aarch64', 'Darwin'] and Dir('#tools/cabana/').exists()) or GetOption('extras'): +if arch in ['x86_64', 'aarch64', 'Darwin'] and Dir('#tools/cabana/').exists() and GetOption('extras'): SConscript(['tools/replay/SConscript']) SConscript(['tools/cabana/SConscript']) diff --git a/body b/body index 396fa7b923..6ff44357a3 160000 --- a/body +++ b/body @@ -1 +1 @@ -Subproject commit 396fa7b923099640d6843f338950ed41300793ec +Subproject commit 6ff44357a3e416d29044b1d085a3e9223db9691a diff --git a/cereal b/cereal index 1ee48e0110..4b334f6f10 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 1ee48e0110a46fbdd9db50ed89a38bb5a748cfcb +Subproject commit 4b334f6f10877e4a666b23983de2d27934ebf3b1 diff --git a/common/SConscript b/common/SConscript index 5d6170611f..97322b248d 100644 --- a/common/SConscript +++ b/common/SConscript @@ -1,9 +1,4 @@ -Import('env', 'envCython', 'arch', 'SHARED') - -if SHARED: - fxn = env.SharedLibrary -else: - fxn = env.Library +Import('env', 'envCython', 'arch') common_libs = [ 'params.cc', @@ -12,24 +7,35 @@ common_libs = [ 'util.cc', 'i2c.cc', 'watchdog.cc', + 'ratekeeper.cc' ] if arch != "Darwin": common_libs.append('gpio.cc') -_common = fxn('common', common_libs, LIBS="json11") +_common = env.Library('common', common_libs, LIBS="json11") files = [ 'clutil.cc', ] -_gpucommon = fxn('gpucommon', files) +_gpucommon = env.Library('gpucommon', files) Export('_common', '_gpucommon') -if GetOption('test'): - env.Program('tests/test_util', ['tests/test_util.cc'], LIBS=[_common]) - env.Program('tests/test_swaglog', ['tests/test_swaglog.cc'], LIBS=[_common, 'json11', 'zmq', 'pthread']) +if GetOption('extras'): + env.Program('tests/test_common', + ['tests/test_runner.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc', 'tests/test_ratekeeper.cc'], + LIBS=[_common, 'json11', 'zmq', 'pthread']) + +# Cython bindings +params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11']) + +SConscript([ + 'kalman/SConscript', + 'transformations/SConscript' +]) + +Import('simple_kalman_python', 'transformations_python') +common_python = [params_python, simple_kalman_python, transformations_python] -# Cython -envCython.Program('clock.so', 'clock.pyx') -envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11']) +Export('common_python') diff --git a/common/api/__init__.py b/common/api/__init__.py index c1fa635bd6..0eb8aa7627 100644 --- a/common/api/__init__.py +++ b/common/api/__init__.py @@ -2,8 +2,8 @@ import jwt import os import requests from datetime import datetime, timedelta -from common.basedir import PERSIST -from system.version import get_version +from openpilot.common.basedir import PERSIST +from openpilot.system.version import get_version API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') diff --git a/common/basedir.py b/common/basedir.py index 371b54d3ef..b4486f9f08 100644 --- a/common/basedir.py +++ b/common/basedir.py @@ -1,7 +1,7 @@ import os from pathlib import Path -from system.hardware import PC +from openpilot.system.hardware import PC BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) diff --git a/common/clock.pyx b/common/clock.pyx deleted file mode 100644 index 81333565c5..0000000000 --- a/common/clock.pyx +++ /dev/null @@ -1,24 +0,0 @@ -# distutils: language = c++ -# cython: language_level = 3 -from posix.time cimport clock_gettime, timespec, CLOCK_MONOTONIC_RAW, clockid_t - -IF UNAME_SYSNAME == "Darwin": - # Darwin doesn't have a CLOCK_BOOTTIME - CLOCK_BOOTTIME = CLOCK_MONOTONIC_RAW -ELSE: - from posix.time cimport CLOCK_BOOTTIME - -cdef double readclock(clockid_t clock_id): - cdef timespec ts - cdef double current - - clock_gettime(clock_id, &ts) - current = ts.tv_sec + (ts.tv_nsec / 1000000000.) - return current - -def monotonic_time(): - return readclock(CLOCK_MONOTONIC_RAW) - -def sec_since_boot(): - return readclock(CLOCK_BOOTTIME) - diff --git a/common/clutil.cc b/common/clutil.cc index fab1649ee1..4f2a783d3e 100644 --- a/common/clutil.cc +++ b/common/clutil.cc @@ -75,6 +75,10 @@ cl_device_id cl_get_device_id(cl_device_type device_type) { return nullptr; } +cl_context cl_create_context(cl_device_id device_id) { + return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); +} + 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); } diff --git a/common/clutil.h b/common/clutil.h index be1a07c332..af986d6434 100644 --- a/common/clutil.h +++ b/common/clutil.h @@ -22,6 +22,7 @@ }) cl_device_id cl_get_device_id(cl_device_type device_type); +cl_context cl_create_context(cl_device_id device_id); 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_binary(cl_context ctx, cl_device_id device_id, const uint8_t* binary, size_t length, 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/gpio.cc b/common/gpio.cc index 8a16cd3703..dd7ba34b6d 100644 --- a/common/gpio.cc +++ b/common/gpio.cc @@ -1,5 +1,7 @@ #include "common/gpio.h" +#include + #ifdef __APPLE__ int gpio_init(int pin_nr, bool output) { return 0; @@ -29,7 +31,7 @@ int gpio_init(int pin_nr, bool output) { char pin_dir_path[50]; int pin_dir_path_len = snprintf(pin_dir_path, sizeof(pin_dir_path), "/sys/class/gpio/gpio%d/direction", pin_nr); - if(pin_dir_path_len <= 0) { + if (pin_dir_path_len <= 0) { return -1; } const char *value = output ? "out" : "in"; @@ -40,7 +42,7 @@ int gpio_set(int pin_nr, bool high) { char pin_val_path[50]; int pin_val_path_len = snprintf(pin_val_path, sizeof(pin_val_path), "/sys/class/gpio/gpio%d/value", pin_nr); - if(pin_val_path_len <= 0) { + if (pin_val_path_len <= 0) { return -1; } return util::write_file(pin_val_path, (void*)(high ? "1" : "0"), 1); @@ -68,7 +70,7 @@ int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int p rq.eventflags = GPIOEVENT_REQUEST_BOTH_EDGES; strncpy(rq.consumer_label, consumer_label, std::size(rq.consumer_label) - 1); - int ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &rq); + int ret = util::safe_ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &rq); if (ret == -1) { LOGE("Unable to get line event from ioctl : %s", strerror(errno)); close(fd); diff --git a/common/gpio.h b/common/gpio.h index b2f67f8ba3..89cdedd66c 100644 --- a/common/gpio.h +++ b/common/gpio.h @@ -5,7 +5,7 @@ #define GPIO_HUB_RST_N 30 #define GPIO_UBLOX_RST_N 32 #define GPIO_UBLOX_SAFEBOOT_N 33 - #define GPIO_UBLOX_PWR_EN 34 + #define GPIO_GNSS_PWR_EN 34 /* SCHEMATIC LABEL: GPIO_UBLOX_PWR_EN */ #define GPIO_STM_RST_N 124 #define GPIO_STM_BOOT0 134 #define GPIO_BMX_ACCEL_INT 21 @@ -17,7 +17,7 @@ #define GPIO_HUB_RST_N 0 #define GPIO_UBLOX_RST_N 0 #define GPIO_UBLOX_SAFEBOOT_N 0 - #define GPIO_UBLOX_PWR_EN 0 + #define GPIO_GNSS_PWR_EN 0 /* SCHEMATIC LABEL: GPIO_UBLOX_PWR_EN */ #define GPIO_STM_RST_N 0 #define GPIO_STM_BOOT0 0 #define GPIO_BMX_ACCEL_INT 0 diff --git a/common/i2c.cc b/common/i2c.cc index ef788ac9ea..3d6c79efc5 100644 --- a/common/i2c.cc +++ b/common/i2c.cc @@ -8,7 +8,6 @@ #include #include -#include "common/util.h" #include "common/swaglog.h" #include "common/util.h" @@ -26,36 +25,42 @@ I2CBus::I2CBus(uint8_t bus_id) { snprintf(bus_name, 20, "/dev/i2c-%d", bus_id); i2c_fd = HANDLE_EINTR(open(bus_name, O_RDWR)); - if(i2c_fd < 0) { + if (i2c_fd < 0) { throw std::runtime_error("Failed to open I2C bus"); } } I2CBus::~I2CBus() { - if(i2c_fd >= 0) { close(i2c_fd); } + if (i2c_fd >= 0) { + close(i2c_fd); + } } int I2CBus::read_register(uint8_t device_address, uint register_address, uint8_t *buffer, uint8_t len) { + std::lock_guard lk(m); + int ret = 0; ret = HANDLE_EINTR(ioctl(i2c_fd, I2C_SLAVE, device_address)); - if(ret < 0) { goto fail; } + if (ret < 0) { goto fail; } ret = i2c_smbus_read_i2c_block_data(i2c_fd, register_address, len, buffer); - if((ret < 0) || (ret != len)) { goto fail; } + if ((ret < 0) || (ret != len)) { goto fail; } fail: return ret; } int I2CBus::set_register(uint8_t device_address, uint register_address, uint8_t data) { + std::lock_guard lk(m); + int ret = 0; ret = HANDLE_EINTR(ioctl(i2c_fd, I2C_SLAVE, device_address)); - if(ret < 0) { goto fail; } + if (ret < 0) { goto fail; } ret = i2c_smbus_write_byte_data(i2c_fd, register_address, data); - if(ret < 0) { goto fail; } + if (ret < 0) { goto fail; } fail: return ret; diff --git a/common/i2c.h b/common/i2c.h index 0669116bb8..ca0d4635b8 100644 --- a/common/i2c.h +++ b/common/i2c.h @@ -1,12 +1,14 @@ #pragma once #include +#include #include class I2CBus { private: int i2c_fd; + std::mutex m; public: I2CBus(uint8_t bus_id); diff --git a/common/kalman/SConscript b/common/kalman/SConscript index d60354c987..7cba4a9a61 100644 --- a/common/kalman/SConscript +++ b/common/kalman/SConscript @@ -1,3 +1,5 @@ Import('envCython') -envCython.Program('simple_kalman_impl.so', 'simple_kalman_impl.pyx') +simple_kalman_python = envCython.Program('simple_kalman_impl.so', 'simple_kalman_impl.pyx') + +Export('simple_kalman_python') diff --git a/common/kalman/simple_kalman.py b/common/kalman/simple_kalman.py index 33289e4f50..5e1b6ce1fb 100644 --- a/common/kalman/simple_kalman.py +++ b/common/kalman/simple_kalman.py @@ -1,3 +1,12 @@ -# pylint: skip-file -from common.kalman.simple_kalman_impl import KF1D as KF1D +from openpilot.common.kalman.simple_kalman_impl import KF1D as KF1D assert KF1D +import numpy as np + +def get_kalman_gain(dt, A, C, Q, R, iterations=100): + P = np.zeros_like(Q) + for _ in range(iterations): + P = A.dot(P).dot(A.T) + dt * Q + S = C.dot(P).dot(C.T) + R + K = P.dot(C.T).dot(np.linalg.inv(S)) + P = (np.eye(len(P)) - K.dot(C)).dot(P) + return K \ No newline at end of file diff --git a/common/kalman/tests/test_simple_kalman.py b/common/kalman/tests/test_simple_kalman.py index 96b2527655..32cc79fc3d 100644 --- a/common/kalman/tests/test_simple_kalman.py +++ b/common/kalman/tests/test_simple_kalman.py @@ -3,8 +3,8 @@ import random import timeit import numpy as np -from common.kalman.simple_kalman import KF1D -from common.kalman.simple_kalman_old import KF1D as KF1D_old +from openpilot.common.kalman.simple_kalman import KF1D +from openpilot.common.kalman.simple_kalman_old import KF1D as KF1D_old class TestSimpleKalman(unittest.TestCase): @@ -54,8 +54,8 @@ class TestSimpleKalman(unittest.TestCase): setup = """ import numpy as np -from common.kalman.simple_kalman import KF1D -from common.kalman.simple_kalman_old import KF1D as KF1D_old +from openpilot.common.kalman.simple_kalman import KF1D +from openpilot.common.kalman.simple_kalman_old import KF1D as KF1D_old dt = 0.01 x0_0 = 0.0 diff --git a/common/logging_extra.py b/common/logging_extra.py index e2637d2e05..5e0584c7bc 100644 --- a/common/logging_extra.py +++ b/common/logging_extra.py @@ -197,7 +197,7 @@ class SwagLogger(logging.Logger): filename = os.path.normcase(co.co_filename) # TODO: is this pylint exception correct? - if filename == _srcfile: # pylint: disable=comparison-with-callable + if filename == _srcfile: f = f.f_back continue sinfo = None diff --git a/common/mat.h b/common/mat.h index 626f3404fe..8e10d61971 100644 --- a/common/mat.h +++ b/common/mat.h @@ -1,7 +1,7 @@ #pragma once typedef struct vec3 { - float v[3]; + float v[3]; } vec3; typedef struct vec4 { @@ -9,7 +9,7 @@ typedef struct vec4 { } vec4; typedef struct mat3 { - float v[3*3]; + float v[3*3]; } mat3; typedef struct mat4 { diff --git a/common/params.cc b/common/params.cc index e8ab42c0b0..e510b60160 100644 --- a/common/params.cc +++ b/common/params.cc @@ -64,7 +64,7 @@ bool create_params_path(const std::string ¶m_path, const std::string &key_pa std::string ensure_params_path(const std::string &prefix, const std::string &path = {}) { std::string params_path = path.empty() ? Path::params() : path; if (!create_params_path(params_path, params_path + prefix)) { - throw std::runtime_error(util::string_format("Failed to ensure params path, errno=%d", errno)); + throw std::runtime_error(util::string_format("Failed to ensure params path, errno=%d, path=%s", errno, params_path.c_str())); } return params_path; } @@ -158,6 +158,7 @@ std::unordered_map keys = { {"LongitudinalPersonality", PERSISTENT}, {"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"NavDestinationWaypoints", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, + {"NavPastDestinations", PERSISTENT}, {"NavSettingLeftSide", PERSISTENT}, {"NavSettingTime24h", PERSISTENT}, {"NavdRender", PERSISTENT}, diff --git a/common/params.h b/common/params.h index 24b1bffeb1..fbe0bba6b0 100644 --- a/common/params.h +++ b/common/params.h @@ -15,7 +15,11 @@ enum ParamKeyType { class Params { public: - Params(const std::string &path = {}); + explicit Params(const std::string &path = {}); + // Not copyable. + Params(const Params&) = delete; + Params& operator=(const Params&) = delete; + std::vector allKeys() const; bool checkKey(const std::string &key); ParamKeyType getKeyType(const std::string &key); diff --git a/common/params.py b/common/params.py index b6be424d41..ea8ac7514a 100644 --- a/common/params.py +++ b/common/params.py @@ -1,4 +1,5 @@ -from common.params_pyx import Params, ParamKeyType, UnknownKeyName, put_nonblocking, put_bool_nonblocking # pylint: disable=no-name-in-module, import-error +from openpilot.common.params_pyx import Params, ParamKeyType, UnknownKeyName, put_nonblocking, \ + put_bool_nonblocking assert Params assert ParamKeyType assert UnknownKeyName diff --git a/common/params_pyx.pyx b/common/params_pyx.pyx old mode 100755 new mode 100644 index abb3199d05..cc6bd287a6 --- a/common/params_pyx.pyx +++ b/common/params_pyx.pyx @@ -14,7 +14,7 @@ cdef extern from "common/params.h": ALL cdef cppclass c_Params "Params": - c_Params(string) nogil + c_Params(string) nogil except + string get(string, bool) nogil bool getBool(string, bool) nogil int remove(string) nogil diff --git a/selfdrive/test/process_replay/helpers.py b/common/prefix.py similarity index 60% rename from selfdrive/test/process_replay/helpers.py rename to common/prefix.py index 5cf1acfa59..6b7e7e2fd7 100644 --- a/selfdrive/test/process_replay/helpers.py +++ b/common/prefix.py @@ -2,13 +2,14 @@ import os import shutil import uuid -from typing import List, Optional +from typing import Optional -from common.params import Params +from openpilot.common.params import Params +from openpilot.system.hardware.hw import Paths -class OpenpilotPrefix(object): +class OpenpilotPrefix: def __init__(self, prefix: Optional[str] = None, clean_dirs_on_exit: bool = True): - self.prefix = prefix if prefix else str(uuid.uuid4()) + self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15]) self.msgq_path = os.path.join('/dev/shm', self.prefix) self.clean_dirs_on_exit = clean_dirs_on_exit @@ -18,13 +19,17 @@ class OpenpilotPrefix(object): os.mkdir(self.msgq_path) except FileExistsError: pass + os.makedirs(Paths.log_root(), exist_ok=True) return self def __exit__(self, exc_type, exc_obj, exc_tb): if self.clean_dirs_on_exit: self.clean_dirs() - del os.environ['OPENPILOT_PREFIX'] + try: + del os.environ['OPENPILOT_PREFIX'] + except KeyError: + pass return False def clean_dirs(self): @@ -33,17 +38,6 @@ class OpenpilotPrefix(object): shutil.rmtree(os.path.realpath(symlink_path), ignore_errors=True) os.remove(symlink_path) shutil.rmtree(self.msgq_path, ignore_errors=True) - - -class DummySocket: - def __init__(self): - self.data: List[bytes] = [] - - def receive(self, non_blocking: bool = False) -> Optional[bytes]: - if non_blocking: - return None - - return self.data.pop() - - def send(self, data: bytes): - self.data.append(data) + shutil.rmtree(Paths.log_root(), ignore_errors=True) + shutil.rmtree(Paths.download_cache_root(), ignore_errors=True) + shutil.rmtree(Paths.comma_home(), ignore_errors=True) diff --git a/common/ratekeeper.cc b/common/ratekeeper.cc new file mode 100644 index 0000000000..7e63815168 --- /dev/null +++ b/common/ratekeeper.cc @@ -0,0 +1,40 @@ +#include "common/ratekeeper.h" + +#include + +#include "common/swaglog.h" +#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)) { + interval = 1 / rate; + last_monitor_time = seconds_since_boot(); + next_frame_time = last_monitor_time + interval; +} + +bool RateKeeper::keepTime() { + bool lagged = monitorTime(); + if (remaining_ > 0) { + util::sleep_for(remaining_ * 1000); + } + return lagged; +} + +bool RateKeeper::monitorTime() { + ++frame_; + last_monitor_time = seconds_since_boot(); + remaining_ = next_frame_time - last_monitor_time; + + bool lagged = remaining_ < 0; + if (lagged) { + if (print_delay_threshold > 0 && remaining_ < -print_delay_threshold) { + LOGW("%s lagging by %.2f ms", name.c_str(), -remaining_ * 1000); + } + next_frame_time = last_monitor_time + interval; + } else { + next_frame_time += interval; + } + return lagged; +} diff --git a/common/ratekeeper.h b/common/ratekeeper.h new file mode 100644 index 0000000000..b6e8ac66a6 --- /dev/null +++ b/common/ratekeeper.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class RateKeeper { +public: + RateKeeper(const std::string &name, float rate, float print_delay_threshold = 0); + ~RateKeeper() {} + bool keepTime(); + bool monitorTime(); + inline double frame() const { return frame_; } + inline double remaining() const { return remaining_; } + +private: + double interval; + double next_frame_time; + double last_monitor_time; + double remaining_ = 0; + float print_delay_threshold = 0; + uint64_t frame_ = 0; + std::string name; +}; diff --git a/common/realtime.py b/common/realtime.py index 7dd2eb98a6..05e19e770f 100644 --- a/common/realtime.py +++ b/common/realtime.py @@ -5,10 +5,9 @@ import time from collections import deque from typing import Optional, List, Union -from setproctitle import getproctitle # pylint: disable=no-name-in-module +from setproctitle import getproctitle -from common.clock import sec_since_boot # pylint: disable=no-name-in-module, import-error -from system.hardware import PC +from openpilot.system.hardware import PC # time step for each process @@ -31,12 +30,12 @@ class Priority: def set_realtime_priority(level: int) -> None: if not PC: - os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level)) # pylint: disable=no-member + os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level)) def set_core_affinity(cores: List[int]) -> None: if not PC: - os.sched_setaffinity(0, cores) # pylint: disable=no-member + os.sched_setaffinity(0, cores) def config_realtime_process(cores: Union[int, List[int]], priority: int) -> None: @@ -50,13 +49,13 @@ class Ratekeeper: def __init__(self, rate: float, print_delay_threshold: Optional[float] = 0.0) -> None: """Rate in Hz for ratekeeping. print_delay_threshold must be nonnegative.""" self._interval = 1. / rate - self._next_frame_time = sec_since_boot() + self._interval + self._next_frame_time = time.monotonic() + self._interval self._print_delay_threshold = print_delay_threshold self._frame = 0 self._remaining = 0.0 self._process_name = getproctitle() self._dts = deque([self._interval], maxlen=100) - self._last_monitor_time = sec_since_boot() + self._last_monitor_time = time.monotonic() @property def frame(self) -> int: @@ -82,11 +81,11 @@ class Ratekeeper: # this only monitor the cumulative lag, but does not enforce a rate def monitor_time(self) -> bool: prev = self._last_monitor_time - self._last_monitor_time = sec_since_boot() + self._last_monitor_time = time.monotonic() self._dts.append(self._last_monitor_time - prev) lagged = False - remaining = self._next_frame_time - sec_since_boot() + remaining = self._next_frame_time - time.monotonic() self._next_frame_time += self._interval if self._print_delay_threshold is not None and remaining < -self._print_delay_threshold: print(f"{self._process_name} lagging by {-remaining * 1000:.2f} ms") diff --git a/common/spinner.py b/common/spinner.py index 34fd720ee9..43d4bb2cc2 100644 --- a/common/spinner.py +++ b/common/spinner.py @@ -1,6 +1,6 @@ import os import subprocess -from common.basedir import BASEDIR +from openpilot.common.basedir import BASEDIR class Spinner(): diff --git a/common/swaglog.cc b/common/swaglog.cc index 060090e18f..bcd597a257 100644 --- a/common/swaglog.cc +++ b/common/swaglog.cc @@ -12,7 +12,7 @@ #include #include -#include "json11.hpp" +#include "third_party/json11/json11.hpp" #include "common/util.h" #include "common/version.h" @@ -20,7 +20,7 @@ class SwaglogState : public LogState { public: - SwaglogState() : LogState("ipc:///tmp/logmessage") {} + SwaglogState() : LogState(Path::swaglog_ipc().c_str()) {} json11::Json::object ctx_j; @@ -64,8 +64,7 @@ static void log(int levelnum, const char* filename, int lineno, const char* func if (levelnum >= s.print_level) { printf("%s: %s\n", filename, msg); } - char levelnum_c = levelnum; - zmq_send(s.sock, (levelnum_c + log_s).c_str(), log_s.length() + 1, ZMQ_NOBLOCK); + zmq_send(s.sock, log_s.data(), log_s.length(), ZMQ_NOBLOCK); } static void cloudlog_common(int levelnum, const char* filename, int lineno, const char* func, @@ -87,8 +86,11 @@ static void cloudlog_common(int levelnum, const char* filename, int lineno, cons log_j["msg"] = msg_j; } - std::string log_s = ((json11::Json)log_j).dump(); + std::string log_s; + log_s += (char)levelnum; + ((json11::Json)log_j).dump(log_s); log(levelnum, filename, lineno, func, msg_buf, log_s); + free(msg_buf); } diff --git a/common/swaglog.h b/common/swaglog.h index 5a3434d624..06d45b1d98 100644 --- a/common/swaglog.h +++ b/common/swaglog.h @@ -44,7 +44,7 @@ void cloudlog_te(int levelnum, const char* filename, int lineno, const char* fun int __millis = (millis); \ uint64_t __ts = nanos_since_boot(); \ \ - if (!__begin) __begin = __ts; \ + if (!__begin) { __begin = __ts; } \ \ if (__begin + __millis*1000000ULL < __ts) { \ if (__missed) { \ diff --git a/common/tests/.gitignore b/common/tests/.gitignore index 1350b3b825..6cddfc7bdf 100644 --- a/common/tests/.gitignore +++ b/common/tests/.gitignore @@ -1,2 +1 @@ -test_util -test_swaglog +test_common diff --git a/common/tests/test_file_helpers.py b/common/tests/test_file_helpers.py index d39e66de13..e36df57a32 100644 --- a/common/tests/test_file_helpers.py +++ b/common/tests/test_file_helpers.py @@ -2,8 +2,8 @@ import os import unittest from uuid import uuid4 -from common.file_helpers import atomic_write_on_fs_tmp -from common.file_helpers import atomic_write_in_dir +from openpilot.common.file_helpers import atomic_write_on_fs_tmp +from openpilot.common.file_helpers import atomic_write_in_dir class TestFileHelpers(unittest.TestCase): diff --git a/common/tests/test_numpy_fast.py b/common/tests/test_numpy_fast.py index 2fb8a1cef3..de7bb972e7 100644 --- a/common/tests/test_numpy_fast.py +++ b/common/tests/test_numpy_fast.py @@ -1,7 +1,7 @@ import numpy as np import unittest -from common.numpy_fast import interp +from openpilot.common.numpy_fast import interp class InterpTest(unittest.TestCase): diff --git a/common/tests/test_params.py b/common/tests/test_params.py index d432218c8a..fb6f320ea4 100644 --- a/common/tests/test_params.py +++ b/common/tests/test_params.py @@ -1,21 +1,14 @@ import os import threading import time -import tempfile -import shutil import uuid import unittest -from common.params import Params, ParamKeyType, UnknownKeyName, put_nonblocking, put_bool_nonblocking +from openpilot.common.params import Params, ParamKeyType, UnknownKeyName, put_nonblocking, put_bool_nonblocking class TestParams(unittest.TestCase): def setUp(self): - self.tmpdir = tempfile.mkdtemp() - print("using", self.tmpdir) - self.params = Params(self.tmpdir) - - def tearDown(self): - shutil.rmtree(self.tmpdir) + self.params = Params() def test_params_put_and_get(self): self.params.put("DongleId", "cb38263377b873ee") @@ -90,19 +83,19 @@ class TestParams(unittest.TestCase): self.assertFalse(self.params.get_bool("IsMetric")) def test_put_non_blocking_with_get_block(self): - q = Params(self.tmpdir) + q = Params() def _delayed_writer(): time.sleep(0.1) - put_nonblocking("CarParams", "test", self.tmpdir) + put_nonblocking("CarParams", "test") threading.Thread(target=_delayed_writer).start() assert q.get("CarParams") is None assert q.get("CarParams", True) == b"test" def test_put_bool_non_blocking_with_get_block(self): - q = Params(self.tmpdir) + q = Params() def _delayed_writer(): time.sleep(0.1) - put_bool_nonblocking("CarParams", True, self.tmpdir) + put_bool_nonblocking("CarParams", True) threading.Thread(target=_delayed_writer).start() assert q.get("CarParams") is None assert q.get("CarParams", True) == b"1" diff --git a/common/tests/test_ratekeeper.cc b/common/tests/test_ratekeeper.cc new file mode 100644 index 0000000000..32c7dfe58c --- /dev/null +++ b/common/tests/test_ratekeeper.cc @@ -0,0 +1,23 @@ +#include "catch2/catch.hpp" +#include "common/ratekeeper.h" +#include "common/timing.h" +#include "common/util.h" + +TEST_CASE("RateKeeper") { + float freq = GENERATE(10, 50, 100); + RateKeeper rk("Test RateKeeper", freq); + + int lags = 0; + int bad_keep_times = 0; + for (int i = 0; i < freq; ++i) { + double begin = seconds_since_boot(); + util::sleep_for(util::random_int(0, 1000.0 / freq - 1)); + bool lagged = rk.keepTime(); + lags += lagged; + bad_keep_times += (seconds_since_boot() - begin - (1 / freq)) > 1e-3; + } + + // need a tolerance here due to scheduling + REQUIRE(lags < 5); + REQUIRE(bad_keep_times < 5); +} diff --git a/common/tests/test_runner.cc b/common/tests/test_runner.cc new file mode 100644 index 0000000000..62bf7476a1 --- /dev/null +++ b/common/tests/test_runner.cc @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" diff --git a/common/tests/test_swaglog.cc b/common/tests/test_swaglog.cc index 20455ec74c..09bc4c3795 100644 --- a/common/tests/test_swaglog.cc +++ b/common/tests/test_swaglog.cc @@ -1,15 +1,14 @@ #include + #include -#define CATCH_CONFIG_MAIN -#include "catch2/catch.hpp" -#include "json11.hpp" +#include "catch2/catch.hpp" #include "common/swaglog.h" #include "common/util.h" #include "common/version.h" #include "system/hardware/hw.h" +#include "third_party/json11/json11.hpp" -const char *SWAGLOG_ADDR = "ipc:///tmp/logmessage"; std::string daemon_name = "testy"; std::string dongle_id = "test_dongle_id"; int LINE_NO = 0; @@ -25,7 +24,7 @@ void log_thread(int thread_id, int msg_cnt) { void recv_log(int thread_cnt, int thread_msg_cnt) { void *zctx = zmq_ctx_new(); void *sock = zmq_socket(zctx, ZMQ_PULL); - zmq_bind(sock, SWAGLOG_ADDR); + zmq_bind(sock, Path::swaglog_ipc().c_str()); std::vector thread_msgs(thread_cnt); int total_count = 0; diff --git a/common/tests/test_util.cc b/common/tests/test_util.cc index 25ecf09aa9..de87fa3e06 100644 --- a/common/tests/test_util.cc +++ b/common/tests/test_util.cc @@ -5,10 +5,10 @@ #include #include +#include #include #include -#define CATCH_CONFIG_MAIN #include "catch2/catch.hpp" #include "common/util.h" @@ -38,7 +38,8 @@ TEST_CASE("util::read_file") { std::string content = random_bytes(64 * 1024); write(fd, content.c_str(), content.size()); std::string ret = util::read_file(filename); - REQUIRE(ret == content); + bool equal = (ret == content); + REQUIRE(equal); close(fd); } SECTION("read directory") { @@ -108,7 +109,8 @@ TEST_CASE("util::safe_fwrite") { REQUIRE(ret == 0); ret = fclose(f); REQUIRE(ret == 0); - REQUIRE(dat == util::read_file(filename)); + bool equal = (dat == util::read_file(filename)); + REQUIRE(equal); } TEST_CASE("util::create_directories") { diff --git a/common/text_window.py b/common/text_window.py index bea3a149f8..d2762ebf7d 100755 --- a/common/text_window.py +++ b/common/text_window.py @@ -2,7 +2,7 @@ import os import time import subprocess -from common.basedir import BASEDIR +from openpilot.common.basedir import BASEDIR class TextWindow: diff --git a/common/transformations/SConscript b/common/transformations/SConscript index ee9b9a2b73..4ac73a165e 100644 --- a/common/transformations/SConscript +++ b/common/transformations/SConscript @@ -1,6 +1,5 @@ Import('env', 'envCython') transformations = env.Library('transformations', ['orientation.cc', 'coordinates.cc']) -Export('transformations') - -envCython.Program('transformations.so', 'transformations.pyx') +transformations_python = envCython.Program('transformations.so', 'transformations.pyx') +Export('transformations', 'transformations_python') diff --git a/common/transformations/camera.py b/common/transformations/camera.py index eaed0e7afe..c643cb5702 100644 --- a/common/transformations/camera.py +++ b/common/transformations/camera.py @@ -1,6 +1,6 @@ import numpy as np -import common.transformations.orientation as orient +import openpilot.common.transformations.orientation as orient ## -- hardcoded hardware params -- eon_f_focal_length = 910.0 diff --git a/common/transformations/coordinates.cc b/common/transformations/coordinates.cc index b729ac3d87..5b00b53a4f 100644 --- a/common/transformations/coordinates.cc +++ b/common/transformations/coordinates.cc @@ -1,13 +1,11 @@ #define _USE_MATH_DEFINES +#include "common/transformations/coordinates.hpp" + #include #include #include -#include "coordinates.hpp" - - - double a = 6378137; // lgtm [cpp/short-global-name] double b = 6356752.3142; // lgtm [cpp/short-global-name] double esq = 6.69437999014 * 0.001; // lgtm [cpp/short-global-name] diff --git a/common/transformations/coordinates.hpp b/common/transformations/coordinates.hpp index f5ba0d3fe7..32ec2ff66e 100644 --- a/common/transformations/coordinates.hpp +++ b/common/transformations/coordinates.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #define DEG2RAD(x) ((x) * M_PI / 180.0) #define RAD2DEG(x) ((x) * 180.0 / M_PI) diff --git a/common/transformations/coordinates.py b/common/transformations/coordinates.py index 46cc0ded0d..696e7de2e5 100644 --- a/common/transformations/coordinates.py +++ b/common/transformations/coordinates.py @@ -1,8 +1,7 @@ -# pylint: skip-file -from common.transformations.orientation import numpy_wrap -from common.transformations.transformations import (ecef2geodetic_single, +from openpilot.common.transformations.orientation import numpy_wrap +from openpilot.common.transformations.transformations import (ecef2geodetic_single, geodetic2ecef_single) -from common.transformations.transformations import LocalCoord as LocalCoord_single +from openpilot.common.transformations.transformations import LocalCoord as LocalCoord_single class LocalCoord(LocalCoord_single): diff --git a/common/transformations/model.py b/common/transformations/model.py index 811a17eafe..7e40767f63 100644 --- a/common/transformations/model.py +++ b/common/transformations/model.py @@ -1,7 +1,9 @@ import numpy as np -from common.transformations.camera import (FULL_FRAME_SIZE, - get_view_frame_from_calib_frame) +from openpilot.common.transformations.orientation import rot_from_euler +from openpilot.common.transformations.camera import ( + FULL_FRAME_SIZE, get_view_frame_from_calib_frame, view_frame_from_device_frame, + eon_fcam_intrinsics, tici_ecam_intrinsics, tici_fcam_intrinsics) # segnet SEGNET_SIZE = (512, 384) @@ -57,61 +59,20 @@ medmodel_frame_from_calib_frame = np.dot(medmodel_intrinsics, medmodel_frame_from_bigmodel_frame = np.dot(medmodel_intrinsics, np.linalg.inv(bigmodel_intrinsics)) +calib_from_medmodel = np.linalg.inv(medmodel_frame_from_calib_frame[:, :3]) +calib_from_sbigmodel = np.linalg.inv(sbigmodel_frame_from_calib_frame[:, :3]) -### This function mimics the update_calibration logic in modeld.cc -### Manually verified to give similar results to xx.uncommon.utils.transform_img -def get_warp_matrix(rpy_calib, wide_cam=False, big_model=False, tici=True): - from common.transformations.orientation import rot_from_euler - from common.transformations.camera import view_frame_from_device_frame, eon_fcam_intrinsics, tici_ecam_intrinsics, tici_fcam_intrinsics - - if tici and wide_cam: - intrinsics = tici_ecam_intrinsics - elif tici: - intrinsics = tici_fcam_intrinsics - else: - intrinsics = eon_fcam_intrinsics - - if big_model: - sbigmodel_from_calib = sbigmodel_frame_from_calib_frame[:, (0,1,2)] - calib_from_model = np.linalg.inv(sbigmodel_from_calib) - else: - medmodel_from_calib = medmodel_frame_from_calib_frame[:, (0,1,2)] - calib_from_model = np.linalg.inv(medmodel_from_calib) - device_from_calib = rot_from_euler(rpy_calib) - camera_from_calib = intrinsics.dot(view_frame_from_device_frame.dot(device_from_calib)) - warp_matrix = camera_from_calib.dot(calib_from_model) - return warp_matrix - - -### This is old, just for debugging -def get_warp_matrix_old(rpy_calib, wide_cam=False, big_model=False, tici=True): - from common.transformations.orientation import rot_from_euler - from common.transformations.camera import view_frame_from_device_frame, eon_fcam_intrinsics, tici_ecam_intrinsics, tici_fcam_intrinsics - - - def get_view_frame_from_road_frame(roll, pitch, yaw, height): - device_from_road = rot_from_euler([roll, pitch, yaw]).dot(np.diag([1, -1, -1])) - view_from_road = view_frame_from_device_frame.dot(device_from_road) - return np.hstack((view_from_road, [[0], [height], [0]])) - - if tici and wide_cam: - intrinsics = tici_ecam_intrinsics +# This function is verified to give similar results to xx.uncommon.utils.transform_img +def get_warp_matrix(device_from_calib_euler: np.ndarray, wide_camera: bool = False, bigmodel_frame: bool = False, tici: bool = True) -> np.ndarray: + if tici and wide_camera: + cam_intrinsics = tici_ecam_intrinsics elif tici: - intrinsics = tici_fcam_intrinsics + cam_intrinsics = tici_fcam_intrinsics else: - intrinsics = eon_fcam_intrinsics + cam_intrinsics = eon_fcam_intrinsics - model_height = 1.22 - if big_model: - model_from_road = np.dot(sbigmodel_intrinsics, - get_view_frame_from_road_frame(0, 0, 0, model_height)) - else: - model_from_road = np.dot(medmodel_intrinsics, - get_view_frame_from_road_frame(0, 0, 0, model_height)) - ground_from_model = np.linalg.inv(model_from_road[:, (0, 1, 3)]) - - E = get_view_frame_from_road_frame(*rpy_calib, 1.22) - camera_frame_from_road_frame = intrinsics.dot(E) - camera_frame_from_ground = camera_frame_from_road_frame[:,(0,1,3)] - warp_matrix = camera_frame_from_ground .dot(ground_from_model) + calib_from_model = calib_from_sbigmodel if bigmodel_frame else calib_from_medmodel + device_from_calib = rot_from_euler(device_from_calib_euler) + camera_from_calib = cam_intrinsics @ view_frame_from_device_frame @ device_from_calib + warp_matrix: np.ndarray = camera_from_calib @ calib_from_model return warp_matrix diff --git a/common/transformations/orientation.cc b/common/transformations/orientation.cc index 7909c0affb..00888c3a92 100644 --- a/common/transformations/orientation.cc +++ b/common/transformations/orientation.cc @@ -4,8 +4,8 @@ #include #include -#include "orientation.hpp" -#include "coordinates.hpp" +#include "common/transformations/orientation.hpp" +#include "common/transformations/coordinates.hpp" Eigen::Quaterniond ensure_unique(Eigen::Quaterniond quat){ if (quat.w() > 0){ diff --git a/common/transformations/orientation.hpp b/common/transformations/orientation.hpp index ebd7da0aee..150b12cade 100644 --- a/common/transformations/orientation.hpp +++ b/common/transformations/orientation.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include "coordinates.hpp" +#include "common/transformations/coordinates.hpp" Eigen::Quaterniond ensure_unique(Eigen::Quaterniond quat); diff --git a/common/transformations/orientation.py b/common/transformations/orientation.py index 134442b624..ce4378738d 100644 --- a/common/transformations/orientation.py +++ b/common/transformations/orientation.py @@ -1,8 +1,7 @@ -# pylint: skip-file import numpy as np from typing import Callable -from common.transformations.transformations import (ecef_euler_from_ned_single, +from openpilot.common.transformations.transformations import (ecef_euler_from_ned_single, euler2quat_single, euler2rot_single, ned_euler_from_ecef_single, diff --git a/common/transformations/tests/test_coordinates.py b/common/transformations/tests/test_coordinates.py index dc70faed0b..7ae79403bd 100755 --- a/common/transformations/tests/test_coordinates.py +++ b/common/transformations/tests/test_coordinates.py @@ -3,7 +3,7 @@ import numpy as np import unittest -import common.transformations.coordinates as coord +import openpilot.common.transformations.coordinates as coord geodetic_positions = np.array([[37.7610403, -122.4778699, 115], [27.4840915, -68.5867592, 2380], diff --git a/common/transformations/tests/test_orientation.py b/common/transformations/tests/test_orientation.py index 50978e1a63..f77827d2f9 100755 --- a/common/transformations/tests/test_orientation.py +++ b/common/transformations/tests/test_orientation.py @@ -3,7 +3,7 @@ import numpy as np import unittest -from common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \ +from openpilot.common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \ rot2quat, quat2rot, \ ned_euler_from_ecef diff --git a/common/transformations/transformations.pyx b/common/transformations/transformations.pyx index ce80d90d29..c5cb9e0056 100644 --- a/common/transformations/transformations.pyx +++ b/common/transformations/transformations.pyx @@ -1,20 +1,20 @@ # distutils: language = c++ # cython: language_level = 3 -from common.transformations.transformations cimport Matrix3, Vector3, Quaternion -from common.transformations.transformations cimport ECEF, NED, Geodetic - -from common.transformations.transformations cimport euler2quat as euler2quat_c -from common.transformations.transformations cimport quat2euler as quat2euler_c -from common.transformations.transformations cimport quat2rot as quat2rot_c -from common.transformations.transformations cimport rot2quat as rot2quat_c -from common.transformations.transformations cimport euler2rot as euler2rot_c -from common.transformations.transformations cimport rot2euler as rot2euler_c -from common.transformations.transformations cimport rot_matrix as rot_matrix_c -from common.transformations.transformations cimport ecef_euler_from_ned as ecef_euler_from_ned_c -from common.transformations.transformations cimport ned_euler_from_ecef as ned_euler_from_ecef_c -from common.transformations.transformations cimport geodetic2ecef as geodetic2ecef_c -from common.transformations.transformations cimport ecef2geodetic as ecef2geodetic_c -from common.transformations.transformations cimport LocalCoord_c +from openpilot.common.transformations.transformations cimport Matrix3, Vector3, Quaternion +from openpilot.common.transformations.transformations cimport ECEF, NED, Geodetic + +from openpilot.common.transformations.transformations cimport euler2quat as euler2quat_c +from openpilot.common.transformations.transformations cimport quat2euler as quat2euler_c +from openpilot.common.transformations.transformations cimport quat2rot as quat2rot_c +from openpilot.common.transformations.transformations cimport rot2quat as rot2quat_c +from openpilot.common.transformations.transformations cimport euler2rot as euler2rot_c +from openpilot.common.transformations.transformations cimport rot2euler as rot2euler_c +from openpilot.common.transformations.transformations cimport rot_matrix as rot_matrix_c +from openpilot.common.transformations.transformations cimport ecef_euler_from_ned as ecef_euler_from_ned_c +from openpilot.common.transformations.transformations cimport ned_euler_from_ecef as ned_euler_from_ecef_c +from openpilot.common.transformations.transformations cimport geodetic2ecef as geodetic2ecef_c +from openpilot.common.transformations.transformations cimport ecef2geodetic as ecef2geodetic_c +from openpilot.common.transformations.transformations cimport LocalCoord_c import cython diff --git a/common/util.cc b/common/util.cc index 6ad619c35c..8af9f5884c 100644 --- a/common/util.cc +++ b/common/util.cc @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -253,6 +252,14 @@ std::string dir_name(std::string const &path) { return path.substr(0, pos); } +bool starts_with(const std::string &s1, const std::string &s2) { + return strncmp(s1.c_str(), s2.c_str(), s2.size()) == 0; +} + +bool ends_with(const std::string &s1, const std::string &s2) { + return strcmp(s1.c_str() + (s1.size() - s2.size()), s2.c_str()) == 0; +} + std::string check_output(const std::string& command) { char buffer[128]; std::string result; diff --git a/common/util.h b/common/util.h index aaa54d52a2..5c0ef7fec5 100644 --- a/common/util.h +++ b/common/util.h @@ -77,6 +77,8 @@ float getenv(const char* key, float default_val); std::string hexdump(const uint8_t* in, const size_t size); std::string dir_name(std::string const& path); +bool starts_with(const std::string &s1, const std::string &s2); +bool ends_with(const std::string &s1, const std::string &s2); // ***** random helpers ***** int random_int(int min, int max); @@ -115,7 +117,7 @@ public: #ifndef __APPLE__ std::signal(SIGPWR, (sighandler_t)set_do_exit); #endif - }; + } inline static std::atomic power_failure = false; inline static std::atomic signal = 0; inline operator bool() { return do_exit; } @@ -151,12 +153,18 @@ struct unique_fd { class FirstOrderFilter { public: - FirstOrderFilter(float x0, float ts, float dt) { + FirstOrderFilter(float x0, float ts, float dt, bool initialized = true) { k_ = (dt / ts) / (1.0 + dt / ts); x_ = x0; + initialized_ = initialized; } inline float update(float x) { - x_ = (1. - k_) * x_ + k_ * x; + if (initialized_) { + x_ = (1. - k_) * x_ + k_ * x; + } else { + initialized_ = true; + x_ = x; + } return x_; } inline void reset(float x) { x_ = x; } @@ -164,12 +172,13 @@ public: private: float x_, k_; + bool initialized_; }; template void update_max_atomic(std::atomic& max, T const& value) { T prev = max; - while(prev < value && !max.compare_exchange_weak(prev, value)) {} + while (prev < value && !max.compare_exchange_weak(prev, value)) {} } class LogState { @@ -179,9 +188,9 @@ class LogState { void *zctx = nullptr; void *sock = nullptr; int print_level; - const char* endpoint; + std::string endpoint; - LogState(const char* _endpoint) { + LogState(std::string _endpoint) { endpoint = _endpoint; } @@ -193,7 +202,7 @@ class LogState { int timeout = 100; zmq_setsockopt(sock, ZMQ_LINGER, &timeout, sizeof(timeout)); - zmq_connect(sock, endpoint); + zmq_connect(sock, endpoint.c_str()); initialized = true; } diff --git a/common/watchdog.cc b/common/watchdog.cc index 920df4030a..3483ad21c2 100644 --- a/common/watchdog.cc +++ b/common/watchdog.cc @@ -1,3 +1,5 @@ +#include + #include "common/watchdog.h" #include "common/util.h" diff --git a/common/window.py b/common/window.py index 613b3b201b..95886c0c92 100644 --- a/common/window.py +++ b/common/window.py @@ -1,6 +1,6 @@ import sys -import pygame # pylint: disable=import-error -import cv2 # pylint: disable=import-error +import pygame +import cv2 class Window: def __init__(self, w, h, caption="window", double=False, halve=False): diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000000..e139ab8400 --- /dev/null +++ b/conftest.py @@ -0,0 +1,10 @@ +import pytest + +from openpilot.common.prefix import OpenpilotPrefix + + +@pytest.fixture(scope="function", autouse=True) +def global_setup_and_teardown(): + # setup a clean environment for each test + with OpenpilotPrefix(): + yield diff --git a/docs/CARS.md b/docs/CARS.md index 9ec07a3e22..8bb318c13e 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -2,9 +2,9 @@ # Supported Cars -A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. +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. -# 257 Supported Cars +# 262 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -71,6 +71,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Honda|Passport 2019-23|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -80,15 +81,17 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq 5 (with HDA II) 2022-23[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq 5 (without HDA II) 2022-23[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Ioniq 6 (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Ioniq Electric 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Cruz 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -107,7 +110,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Carnival 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Carnival 2023-24[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|EV6 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -117,6 +120,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Forte 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|K5 Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|K8 Hybrid (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -124,15 +128,16 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Niro EV 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Hybrid 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Plug-in Hybrid 2018-19|All|openpilot available[1](#footnotes)|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Plug-in Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro Plug-in Hybrid 2020|All|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento 2021-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento Hybrid 2023[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sportage 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sportage Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -183,7 +188,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Škoda|Kamiq 2021[9,11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| |Škoda|Karoq 2019-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Škoda|Kodiaq 2017-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Octavia 2015, 2018-19[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Octavia 2015-19[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Škoda|Octavia RS 2016[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Škoda|Scala 2020-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| |Škoda|Superb 2015-22[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -267,7 +272,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| ### Footnotes -1Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
+1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
2By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
3Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
4Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
diff --git a/docs/conf.py b/docs/conf.py index beb5924496..9a8d646697 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ # type: ignore -# pylint: skip-file # Configuration file for the Sphinx documentation builder. # @@ -17,8 +16,8 @@ import os import sys from os.path import exists -from common.basedir import BASEDIR -from system.version import get_version +from openpilot.common.basedir import BASEDIR +from openpilot.system.version import get_version sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath('..')) diff --git a/docs/docker/Dockerfile b/docs/docker/Dockerfile index 63564a6fff..1bae2be24a 100644 --- a/docs/docker/Dockerfile +++ b/docs/docker/Dockerfile @@ -1,8 +1,8 @@ -FROM ghcr.io/commaai/openpilot-base:latest as openpilot-docs-base +FROM ghcr.io/commaai/openpilot-base:latest ENV PYTHONUNBUFFERED 1 -ENV OPENPILOT_PATH /home/batman/openpilot/ +ENV OPENPILOT_PATH /tmp/openpilot ENV PYTHONPATH ${OPENPILOT_PATH}:${PYTHONPATH} ENV POETRY_VIRUALENVS_CREATE false @@ -11,11 +11,12 @@ WORKDIR ${OPENPILOT_PATH} COPY SConstruct ${OPENPILOT_PATH} +COPY ./openpilot ${OPENPILOT_PATH}/openpilot COPY ./body ${OPENPILOT_PATH}/body COPY ./third_party ${OPENPILOT_PATH}/third_party COPY ./site_scons ${OPENPILOT_PATH}/site_scons -COPY ./laika ${OPENPILOT_PATH}/laika COPY ./laika_repo ${OPENPILOT_PATH}/laika_repo +RUN ln -s ${OPENPILOT_PATH}/laika_repo/laika/ ${OPENPILOT_PATH}/laika COPY ./rednose ${OPENPILOT_PATH}/rednose COPY ./rednose_repo ${OPENPILOT_PATH}/rednose_repo COPY ./tools ${OPENPILOT_PATH}/tools @@ -28,7 +29,7 @@ COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive COPY ./system ${OPENPILOT_PATH}/system COPY ./*.md ${OPENPILOT_PATH}/ -RUN scons -j$(nproc) +RUN --mount=type=bind,source=.ci_cache/scons_cache,target=/tmp/scons_cache,rw scons -j$(nproc) --cache-readonly RUN apt update && apt install doxygen -y COPY ./docs ${OPENPILOT_PATH}/docs @@ -37,5 +38,5 @@ WORKDIR ${OPENPILOT_PATH}/docs RUN make html FROM nginx:1.21 -COPY --from=0 /home/batman/openpilot/build/docs/html /usr/share/nginx/html +COPY --from=0 /tmp/openpilot/build/docs/html /usr/share/nginx/html COPY ./docs/docker/nginx.conf /etc/nginx/conf.d/default.conf diff --git a/laika_repo b/laika_repo index ef21c612f4..8861844c9b 160000 --- a/laika_repo +++ b/laika_repo @@ -1 +1 @@ -Subproject commit ef21c612f4b0d68c790b4b1a6d637cd5fce7732e +Subproject commit 8861844c9b577ff7de7d03fab9f4d7f560415fc9 diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 74d0ac2175..0000000000 --- a/mypy.ini +++ /dev/null @@ -1,16 +0,0 @@ -[mypy] -python_version = 3.11 -plugins = numpy.typing.mypy_plugin -files = body, common, docs, scripts, selfdrive, site_scons, system, tools -exclude = ^(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/) - -; third-party packages -ignore_missing_imports = True - -; helpful warnings -warn_redundant_casts = True -warn_unreachable = True -warn_unused_ignores = True - -; restrict dynamic typing -warn_return_any = True diff --git a/opendbc b/opendbc index a1582f5e28..4ab347baef 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit a1582f5e28fe0df23b6821c907188be477aac11c +Subproject commit 4ab347baefb7473771ada0723c969c50d0c28d01 diff --git a/panda b/panda index 5681f84f4e..dfbee695af 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 5681f84f4e79c45ac1387cedf70cf66be9729884 +Subproject commit dfbee695affcc37378b57a2e9a96e157eb41b7ab diff --git a/poetry.lock b/poetry.lock index 16bfe6f375..fe2beb4b35 100644 --- a/poetry.lock +++ b/poetry.lock @@ -313,47 +313,60 @@ files = [ ] [[package]] -name = "azure-common" -version = "1.1.28" -description = "Microsoft Azure Client Library for Python (Common)" +name = "azure-core" +version = "1.29.4" +description = "Microsoft Azure Core Library for Python" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "azure-common-1.1.28.zip", hash = "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3"}, - {file = "azure_common-1.1.28-py2.py3-none-any.whl", hash = "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad"}, + {file = "azure-core-1.29.4.tar.gz", hash = "sha256:500b3aa9bf2e90c5ccc88bb105d056114ca0ce7d0ce73afb8bc4d714b2fc7568"}, + {file = "azure_core-1.29.4-py3-none-any.whl", hash = "sha256:b03261bcba22c0b9290faf9999cedd23e849ed2577feee90515694cea6bc74bf"}, ] +[package.dependencies] +requests = ">=2.18.4" +six = ">=1.11.0" +typing-extensions = ">=4.6.0" + +[package.extras] +aio = ["aiohttp (>=3.0)"] + [[package]] -name = "azure-storage-blob" -version = "2.1.0" -description = "Microsoft Azure Storage Blob Client Library for Python" +name = "azure-identity" +version = "1.14.0" +description = "Microsoft Azure Identity Library for Python" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "azure-storage-blob-2.1.0.tar.gz", hash = "sha256:b90323aad60f207f9f90a0c4cf94c10acc313c20b39403398dfba51f25f7b454"}, - {file = "azure_storage_blob-2.1.0-py2.py3-none-any.whl", hash = "sha256:a8e91a51d4f62d11127c7fd8ba0077385c5b11022f0269f8a2a71b9fc36bef31"}, + {file = "azure-identity-1.14.0.zip", hash = "sha256:72441799f8c5c89bfe21026965e266672a7c5d050c2c65119ef899dd5362e2b1"}, + {file = "azure_identity-1.14.0-py3-none-any.whl", hash = "sha256:edabf0e010eb85760e1dd19424d5e8f97ba2c9caff73a16e7b30ccbdbcce369b"}, ] [package.dependencies] -azure-common = ">=1.1.5" -azure-storage-common = ">=2.1,<3.0" +azure-core = ">=1.11.0,<2.0.0" +cryptography = ">=2.5" +msal = ">=1.20.0,<2.0.0" +msal-extensions = ">=0.3.0,<2.0.0" [[package]] -name = "azure-storage-common" -version = "2.1.0" -description = "Microsoft Azure Storage Common Client Library for Python" +name = "azure-storage-blob" +version = "12.17.0" +description = "Microsoft Azure Blob Storage Client Library for Python" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "azure-storage-common-2.1.0.tar.gz", hash = "sha256:ccedef5c67227bc4d6670ffd37cec18fb529a1b7c3a5e53e4096eb0cf23dc73f"}, - {file = "azure_storage_common-2.1.0-py2.py3-none-any.whl", hash = "sha256:b01a491a18839b9d05a4fe3421458a0ddb5ab9443c14e487f40d16f9a1dc2fbe"}, + {file = "azure-storage-blob-12.17.0.zip", hash = "sha256:c14b785a17050b30fc326a315bdae6bc4a078855f4f94a4c303ad74a48dc8c63"}, + {file = "azure_storage_blob-12.17.0-py3-none-any.whl", hash = "sha256:0016e0c549a80282d7b4920c03f2f4ba35c53e6e3c7dbcd2a4a8c8eb3882c1e7"}, ] [package.dependencies] -azure-common = ">=1.1.5" -cryptography = "*" -python-dateutil = "*" -requests = "*" +azure-core = ">=1.28.0,<2.0.0" +cryptography = ">=2.1.4" +isodate = ">=0.6.1" +typing-extensions = ">=4.3.0" + +[package.extras] +aio = ["azure-core[aio] (>=1.28.0,<2.0.0)"] [[package]] name = "babel" @@ -634,18 +647,63 @@ files = [ [[package]] name = "click" -version = "8.1.6" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, - {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "click-plugins" +version = "1.1.1" +description = "An extension module for click to enable registering CLI commands via setuptools entry-points." +optional = false +python-versions = "*" +files = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] + +[package.dependencies] +click = ">=4.0" + +[package.extras] +dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] + +[[package]] +name = "cligj" +version = "0.7.2" +description = "Click params for commmand line interfaces to GeoJSON" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <4" +files = [ + {file = "cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df"}, + {file = "cligj-0.7.2.tar.gz", hash = "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27"}, +] + +[package.dependencies] +click = ">=4.0" + +[package.extras] +test = ["pytest-cov"] + +[[package]] +name = "cloudpickle" +version = "2.2.1" +description = "Extended pickling support for Python objects" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cloudpickle-2.2.1-py3-none-any.whl", hash = "sha256:61f594d1f4c295fa5cd9014ceb3a1fc4a70b0de1164b94fbc2d854ccba056f9f"}, + {file = "cloudpickle-2.2.1.tar.gz", hash = "sha256:d89684b8de9e34a2a43b3460fbca07d09d6e25ce858df4d5a44240403b6178f5"}, +] + [[package]] name = "colorama" version = "0.4.6" @@ -732,65 +790,86 @@ mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.2.0)", "types-Pill test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] test-no-images = ["pytest", "pytest-cov", "wurlitzer"] +[[package]] +name = "control" +version = "0.9.4" +description = "Python Control Systems Library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "control-0.9.4-py3-none-any.whl", hash = "sha256:ab68980abd8d35ae5015ffa090865cbbd926deea7e66d0b9a41cfd12577e63ff"}, + {file = "control-0.9.4.tar.gz", hash = "sha256:0fa57d2216b7ac4e9339c09eab6827660318a641779335864feee940bd19c9ce"}, +] + +[package.dependencies] +matplotlib = "*" +numpy = "*" +scipy = ">=1.3" + +[package.extras] +cvxopt = ["cvxopt (>=1.2.0)"] +slycot = ["slycot (>=0.4.0)"] +test = ["pytest", "pytest-timeout"] + [[package]] name = "coverage" -version = "7.3.0" +version = "7.3.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, - {file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, - {file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, - {file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, - {file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, - {file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, - {file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, - {file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, - {file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, - {file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, - {file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, - {file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, - {file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, - {file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, + {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, + {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, + {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, + {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, + {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, + {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, + {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, + {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, + {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, + {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, + {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, + {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, ] [package.extras] @@ -864,69 +943,69 @@ files = [ [[package]] name = "cython" -version = "3.0.0" +version = "3.0.2" description = "The Cython compiler for writing C extensions in the Python language." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "Cython-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c7d728e1a49ad01d41181e3a9ea80b8d14e825f4679e4dd837cbf7bca7998a5"}, - {file = "Cython-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:626a4a6ef4b7ced87c348ea805488e4bd39dad9d0b39659aa9e1040b62bbfedf"}, - {file = "Cython-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33c900d1ca9f622b969ac7d8fc44bdae140a4a6c7d8819413b51f3ccd0586a09"}, - {file = "Cython-3.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a65bc50dc1bc2faeafd9425defbdef6a468974f5c4192497ff7f14adccfdcd32"}, - {file = "Cython-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b71b399b10b038b056ad12dce1e317a8aa7a96e99de7e4fa2fa5d1c9415cfb9"}, - {file = "Cython-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f42f304c097cc53e9eb5f1a1d150380353d5018a3191f1b77f0de353c762181e"}, - {file = "Cython-3.0.0-cp310-cp310-win32.whl", hash = "sha256:3e234e2549e808d9259fdb23ebcfd145be30c638c65118326ec33a8d29248dc2"}, - {file = "Cython-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:829c8333195100448a23863cf64a07e1334fae6a275aefe871458937911531b6"}, - {file = "Cython-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06db81b1a01858fcc406616f8528e686ffb6cf7c3d78fb83767832bfecea8ad8"}, - {file = "Cython-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c93634845238645ce7abf63a56b1c5b6248189005c7caff898fd4a0dac1c5e1e"}, - {file = "Cython-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa606675c6bd23478b1d174e2a84e3c5a2c660968f97dc455afe0fae198f9d3d"}, - {file = "Cython-3.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3355e6f690184f984eeb108b0f5bbc4bcf8b9444f8168933acf79603abf7baf"}, - {file = "Cython-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93a34e1ca8afa4b7075b02ed14a7e4969256297029fb1bfd4cbe48f7290dbcff"}, - {file = "Cython-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb1165ca9e78823f9ad1efa5b3d83156f868eabd679a615d140a3021bb92cd65"}, - {file = "Cython-3.0.0-cp311-cp311-win32.whl", hash = "sha256:2fadde1da055944f5e1e17625055f54ddd11f451889110278ef30e07bd5e1695"}, - {file = "Cython-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:254ed1f03a6c237fa64f0c6e44862058de65bfa2e6a3b48ca3c205492e0653aa"}, - {file = "Cython-3.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4e212237b7531759befb92699c452cd65074a78051ae4ee36ff8b237395ecf3d"}, - {file = "Cython-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f29307463eba53747b31f71394ed087e3e3e264dcc433e62de1d51f5c0c966c"}, - {file = "Cython-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53328a8af0806bebbdb48a4191883b11ee9d9dfb084d84f58fa5a8ab58baefc9"}, - {file = "Cython-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5962e70b15e863e72bed6910e8c6ffef77d36cc98e2b31c474378f3b9e49b0e3"}, - {file = "Cython-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9e69139f4e60ab14c50767a568612ea64d6907e9c8e0289590a170eb495e005f"}, - {file = "Cython-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c40bdbcb2286f0aeeb5df9ce53d45da2d2a9b36a16b331cd0809d212d22a8fc7"}, - {file = "Cython-3.0.0-cp312-cp312-win32.whl", hash = "sha256:8abb8915eb2e57fa53d918afe641c05d1bcc6ed1913682ec1f28de71f4e3f398"}, - {file = "Cython-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:30a4bd2481e59bd7ab2539f835b78edc19fc455811e476916f56026b93afd28b"}, - {file = "Cython-3.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0e1e4b7e4bfbf22fecfa5b852f0e499c442d4853b7ebd33ae37cdec9826ed5d8"}, - {file = "Cython-3.0.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b00df42cdd1a285a64491ba23de08ab14169d3257c840428d40eb7e8e9979af"}, - {file = "Cython-3.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:650d03ddddc08b051b4659778733f0f173ca7d327415755c05d265a6c1ba02fb"}, - {file = "Cython-3.0.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4965f2ebade17166f21a508d66dd60d2a0b3a3b90abe3f72003baa17ae020dd6"}, - {file = "Cython-3.0.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4123c8d03167803df31da6b39de167cb9c04ac0aa4e35d4e5aa9d08ad511b84d"}, - {file = "Cython-3.0.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:296c53b6c0030cf82987eef163444e8d7631cc139d995f9d58679d9fd1ddbf31"}, - {file = "Cython-3.0.0-cp36-cp36m-win32.whl", hash = "sha256:0d2c1e172f1c81bafcca703093608e10dc16e3e2d24c5644c17606c7fdb1792c"}, - {file = "Cython-3.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:bc816d8eb3686d6f8d165f4156bac18c1147e1035dc28a76742d0b7fb5b7c032"}, - {file = "Cython-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8d86651347bbdbac1aca1824696c5e4c0a3b162946c422edcca2be12a03744d1"}, - {file = "Cython-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84176bd04ce9f3cc8799b47ec6d1959fa1ea5e71424507df7bbf0b0915bbedef"}, - {file = "Cython-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35abcf07b8277ec95bbe49a07b5c8760a2d941942ccfe759a94c8d2fe5602e9f"}, - {file = "Cython-3.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a44d6b9a29b2bff38bb648577b2fcf6a68cf8b1783eee89c2eb749f69494b98d"}, - {file = "Cython-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4dc6bbe7cf079db37f1ebb9b0f10d0d7f29e293bb8688e92d50b5ea7a91d82f3"}, - {file = "Cython-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e28763e75e380b8be62b02266a7995a781997c97c119efbdccb8fb954bcd7574"}, - {file = "Cython-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:edae615cb4af51d5173e76ba9aea212424d025c57012e9cdf2f131f774c5ba71"}, - {file = "Cython-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:20c604e974832aaf8b7a1f5455ee7274b34df62a35ee095cd7d2ed7e818e6c53"}, - {file = "Cython-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c85fd2b1cbd9400d60ebe074795bb9a9188752f1612be3b35b0831a24879b91f"}, - {file = "Cython-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:090256c687106932339f87f888b95f0d69c617bc9b18801555545b695d29d8ab"}, - {file = "Cython-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cec2a67a0a7d9d4399758c0657ca03e5912e37218859cfbf046242cc532bfb3b"}, - {file = "Cython-3.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1cdd01ce45333bc264a218c6e183700d6b998f029233f586a53c9b13455c2d2"}, - {file = "Cython-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecee663d2d50ca939fc5db81f2f8a219c2417b4651ad84254c50a03a9cb1aadd"}, - {file = "Cython-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:30f10e79393b411af7677c270ea69807acb9fc30205c8ff25561f4deef780ec1"}, - {file = "Cython-3.0.0-cp38-cp38-win32.whl", hash = "sha256:609777d3a7a0a23b225e84d967af4ad2485c8bdfcacef8037cf197e87d431ca0"}, - {file = "Cython-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:7f4a6dfd42ae0a45797f50fc4f6add702abf46ab3e7cd61811a6c6a97a40e1a2"}, - {file = "Cython-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2d8158277c8942c0b20ff4c074fe6a51c5b89e6ac60cef606818de8c92773596"}, - {file = "Cython-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54e34f99b2a8c1e11478541b2822e6408c132b98b6b8f5ed89411e5e906631ea"}, - {file = "Cython-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:877d1c8745df59dd2061a0636c602729e9533ba13f13aa73a498f68662e1cbde"}, - {file = "Cython-3.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204690be60f0ff32eb70b04f28ef0d1e50ffd7b3f77ba06a7dc2389ee3b848e0"}, - {file = "Cython-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06fcb4628ccce2ba5abc8630adbeaf4016f63a359b4c6c3827b2d80e0673981c"}, - {file = "Cython-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:090e24cfa31c926d0b13d8bb2ef48175acdd061ae1413343c94a2b12a4a4fa6f"}, - {file = "Cython-3.0.0-cp39-cp39-win32.whl", hash = "sha256:4cd00f2158dc00f7f93a92444d0f663eda124c9c29bbbd658964f4e89c357fe8"}, - {file = "Cython-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:5b4cc896d49ce2bae8d6a030f9a4c64965b59c38acfbf4617685e17f7fcf1731"}, - {file = "Cython-3.0.0-py2.py3-none-any.whl", hash = "sha256:ff1aef1a03cfe293237c7a86ae9625b0411b2df30c53d1a7f29a8d381f38a1df"}, - {file = "Cython-3.0.0.tar.gz", hash = "sha256:350b18f9673e63101dbbfcf774ee2f57c20ac4636d255741d76ca79016b1bd82"}, + {file = "Cython-3.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ccb91d2254e34724f1541b2a6fcdfacdb88284185b0097ae84e0ddf476c7a38"}, + {file = "Cython-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c298b1589205ecaaed0457ad05e0c8a43e7db2053607f48ed4a899cb6aa114df"}, + {file = "Cython-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e825e682cef76d0c33384f38b56b7e87c76152482a914dfc78faed6ff66ce05a"}, + {file = "Cython-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:77ec0134fc1b10aebef2013936a91c07bff2498ec283bc2eca099ee0cb94d12e"}, + {file = "Cython-3.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c90eeb94395315e65fd758a2f86b92904fce7b50060b4d45a878ef6767f9276e"}, + {file = "Cython-3.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:38085523fa7a299638d051ae08144222785639882f6291bd275c0b12db1034ff"}, + {file = "Cython-3.0.2-cp310-cp310-win32.whl", hash = "sha256:b032cb0c69082f0665b2c5fb416d041157062f1538336d0edf823b9ee500e39c"}, + {file = "Cython-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:067b2b9eb487bd61367b296f11b7c1c70a084b3eb7d5a572f607cd1fc5ca5586"}, + {file = "Cython-3.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:213ff9f95de319e54b520bf31edd6aa7a1fa4fbf617c2beb0f92362595e6476a"}, + {file = "Cython-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bebbca13078125a35937966137af4bd0300a0c66fd7ae4ce36adc049b13bdf3"}, + {file = "Cython-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e5587128e8c2423aefcffa4ded4ddf60d44898938fbb7c0f236636a750a94f"}, + {file = "Cython-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e2853d484643c6b7ac3bdb48392753442da1c71b689468fa3176b619bebe54"}, + {file = "Cython-3.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e722732e9aa9bde667ed6d87525234823eb7766ca234cfb19d7e0c095a2ef4"}, + {file = "Cython-3.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:989787fc24a95100a26918b6577d06e15a8868a3ed267009c5cfcf1a906179ac"}, + {file = "Cython-3.0.2-cp311-cp311-win32.whl", hash = "sha256:d21801981db44b7e9f9768f121317946461d56b51de1e6eff3c42e8914048696"}, + {file = "Cython-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:809617cf4825b2138ce0ec827e1f28e39668743c81ac8286373f8d148c05f088"}, + {file = "Cython-3.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5682293d344b7dbad97ce6eceb9e887aca6e53499709db9da726ca3424e5559d"}, + {file = "Cython-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e08ff5da5f5b969639784b1bffcd880a0c0f048d182aed7cba9945ee8b367c2"}, + {file = "Cython-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8850269ff59f77a1629e26d0576701925360d732011d6d3516ccdc5b2c2bc310"}, + {file = "Cython-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:550b3fbe9b3c555b44ded934f4822f9fcc04dfcee512167ebcbbd370ccede20e"}, + {file = "Cython-3.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4db017b104f47b1185237702f6ed2651839c8124614683efa7c489f3fa4e19d9"}, + {file = "Cython-3.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:75a2395cc7b78cff59be6e9b7f92bbb5d7b8d25203f6d3fb6f72bdb7d3f49777"}, + {file = "Cython-3.0.2-cp312-cp312-win32.whl", hash = "sha256:786b6034a91e886116bb562fe42f8bf0f97c3e00c02e56791d02675959ed65b1"}, + {file = "Cython-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc9d173ab8b167cae674f6deed8c65ba816574797a2bd6d8aa623277d1fa81ca"}, + {file = "Cython-3.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8948504338d7a140ce588333177dcabf0743a68dbc83b0174f214f5b959634d5"}, + {file = "Cython-3.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a51efba0e136b2af358e5a347bae09678b17460c35cf1eab24f0476820348991"}, + {file = "Cython-3.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05cb2a73810f045d328b7579cf98f550a9e601df5e282d1fea0512d8ad589011"}, + {file = "Cython-3.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22ba78e48bdb65977928ecb275ac8c82df7b0eefa075078a1363a5af4606b42e"}, + {file = "Cython-3.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:302281b927409b3e0ef8cd9251eab782cf1acd2578eab305519fbae5d184b7e9"}, + {file = "Cython-3.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:a1c3675394b81024aaf56e4f53c2b4f81d9a116c7049e9d4706f810899c9134e"}, + {file = "Cython-3.0.2-cp36-cp36m-win32.whl", hash = "sha256:34f7b014ebce5d325c8084e396c81cdafbd8d82be56780dffe6b67b28c891f1b"}, + {file = "Cython-3.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:477cd3549597f09a1608da7b05e16ba641e9aedd171b868533a5a07790ed886f"}, + {file = "Cython-3.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a49dde9f9e29ea82f29aaf3bb1a270b6eb90b75d627c7ff2f5dd3764540ae646"}, + {file = "Cython-3.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc1c8013fad0933f5201186eccc5f2be223cafd6a8dcd586d3f7bb6ba84dc845"}, + {file = "Cython-3.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b75e9c9d7ad7c9dd85d45241d1d4e3c5f66079c1f84eec91689c26d98bc3349"}, + {file = "Cython-3.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f43c4d3ecd9e3b8b7afe834e519f55cf4249b1088f96d11b96f02c55cbaeff7"}, + {file = "Cython-3.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:dab6a923e21e212aa3dc6dde9b22a190f5d7c449315a94e57ddc019ea74a979b"}, + {file = "Cython-3.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae453cfa933b919c0a19d2cc5dc9fb28486268e95dc2ab7a11ab7f99cf8c3883"}, + {file = "Cython-3.0.2-cp37-cp37m-win32.whl", hash = "sha256:b1f023d36a3829069ed11017c670128be3f135a9c17bd64c35d3b3442243b05c"}, + {file = "Cython-3.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:011c4e0b75baee1843334562487eb4fbc0c59ddb2cc32a978b972a81eedcbdcc"}, + {file = "Cython-3.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:832bbee87bca760efeae248ddf19ccd77f9a2355cb6f8a64f20cc377e56957b3"}, + {file = "Cython-3.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fe806d154b6b7f0ab746dac36c022889e2e7cf47546ff9afdc29a62cfa692d0"}, + {file = "Cython-3.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e486331a29e7700b1ad5f4f753bef483c81412a5e64a873df46d6cb66f9a65de"}, + {file = "Cython-3.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54d41a1dfbaab74449873e7f8e6cd4239850fe7a50f7f784dd99a560927f3bac"}, + {file = "Cython-3.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4dca13c86d6cd523c7d8bbf8db1b2bbf8faedd0addedb229158d8015ad1819e1"}, + {file = "Cython-3.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:10cbfb37f31938371a6213cc8b5459c639954aed053efeded3c012d4c5915db9"}, + {file = "Cython-3.0.2-cp38-cp38-win32.whl", hash = "sha256:e663c237579c033deaa2cb362b74651da7712f56e441c11382510a8c4c4f2dd7"}, + {file = "Cython-3.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:2f84bd6cefa5130750c492038170c44f1cbd6f42e9ed85e168fd9cb453f85160"}, + {file = "Cython-3.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f37e4287f520f3748a06ad5eaae09ba4ac68f52e155d70de5f75780d83575c43"}, + {file = "Cython-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd30826ca8b27b2955a63c8ffe8aacc9f0779582b4bd154cf7b441ac10dae2cb"}, + {file = "Cython-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08d67c7225a09eeb77e090c8d4f60677165b052ccf76e3a57d8237064e5c2de2"}, + {file = "Cython-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e625eec8c5c9a8cb062a318b257cc469d301bed952c7daf86e38bbd3afe7c91"}, + {file = "Cython-3.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1b12a8f23270675b537d1c3b988f845bea4bbcc66ae0468857f5ede0526d4522"}, + {file = "Cython-3.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:62dd78afdf748a58dae9c9b9c42a1519ae30787b28ce5f84a0e1bb54144142ca"}, + {file = "Cython-3.0.2-cp39-cp39-win32.whl", hash = "sha256:d0d0cc4ecc05f41c5e02af14ac0083552d22efed976f79eb7bade55fed63b25d"}, + {file = "Cython-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:147cc1d3dda8b06de9d86df5e59cdf15f0a522620168b7349a5ec88b48104d7d"}, + {file = "Cython-3.0.2-py2.py3-none-any.whl", hash = "sha256:8f1c9e4b8e413da211dd7942440cf410ff0eafb081309e04e81f4fafbb146bf2"}, + {file = "Cython-3.0.2.tar.gz", hash = "sha256:9594818dca8bb22ae6580c5222da2bc5cc32334350bd2d294a00d8669bcc61b5"}, ] [[package]] @@ -1001,20 +1080,74 @@ files = [ [package.extras] testing = ["hatch", "pre-commit", "pytest", "tox"] +[[package]] +name = "farama-notifications" +version = "0.0.4" +description = "Notifications for all Farama Foundation maintained libraries." +optional = false +python-versions = "*" +files = [ + {file = "Farama-Notifications-0.0.4.tar.gz", hash = "sha256:13fceff2d14314cf80703c8266462ebf3733c7d165336eee998fc58e545efd18"}, + {file = "Farama_Notifications-0.0.4-py3-none-any.whl", hash = "sha256:14de931035a41961f7c056361dc7f980762a143d05791ef5794a751a2caf05ae"}, +] + [[package]] name = "filelock" -version = "3.12.2" +version = "3.12.3" description = "A platform independent file lock." optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.12.3-py3-none-any.whl", hash = "sha256:f067e40ccc40f2b48395a80fcbd4728262fab54e232e090a4063ab804179efeb"}, + {file = "filelock-3.12.3.tar.gz", hash = "sha256:0ecc1dd2ec4672a10c8550a8182f1bd0c0a5088470ecd5a125e45f49472fac3d"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "fiona" +version = "1.9.4.post1" +description = "Fiona reads and writes spatial data files" +optional = false python-versions = ">=3.7" files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {file = "Fiona-1.9.4.post1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:d6483a20037db2209c8e9a0c6f1e552f807d03c8f42ed0c865ab500945a37c4d"}, + {file = "Fiona-1.9.4.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbe158947099a83ad16f9acd3a21f50ff01114c64e2de67805e382e6b6e0083a"}, + {file = "Fiona-1.9.4.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2c7b09eecee3bb074ef8aa518cd6ab30eb663c6fdd0eff3c88d454a9746eaa"}, + {file = "Fiona-1.9.4.post1-cp310-cp310-win_amd64.whl", hash = "sha256:1da8b954f6f222c3c782bc285586ea8dd9d7e55e1bc7861da9cd772bca671660"}, + {file = "Fiona-1.9.4.post1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c671d8832287cda397621d79c5a635d52e4631f33a8f0e6fdc732a79a93cb96c"}, + {file = "Fiona-1.9.4.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b633a2e550e083805c638d2ab8059c283ca112aaea8241e170c012d2ee0aa905"}, + {file = "Fiona-1.9.4.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1faa625d5202b8403471bbc9f9c96b1bf9099cfcb0ee02a80a3641d3d02383e"}, + {file = "Fiona-1.9.4.post1-cp311-cp311-win_amd64.whl", hash = "sha256:39baf11ff0e4318397e2b2197de427b4eebdc49d4a9a7c1366f8a7ed682978a4"}, + {file = "Fiona-1.9.4.post1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d93c993265f6378b23f47708c83bddb3377ca6814a1f0b5a0ae0bee9c8d72cf8"}, + {file = "Fiona-1.9.4.post1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:b0387cae39e27f338fd948b3b50b6e6ce198cc4cec257fc91660849697c69dc3"}, + {file = "Fiona-1.9.4.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:450561d308d3ce7c7e30294822b1de3f4f942033b703ddd4a91a7f7f5f506ca0"}, + {file = "Fiona-1.9.4.post1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:71b023ef5248ebfa5524e7a875033f7db3bbfaf634b1b5c1ae36958d1eb82083"}, + {file = "Fiona-1.9.4.post1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74511d3755695d75cea0f4ff6f5e0c6c5d5be8e0d46dafff124c6a219e99b1eb"}, + {file = "Fiona-1.9.4.post1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:285f3dd4f96aa0a3955ed469f0543375b20989731b2dddc85124453f11ac62bc"}, + {file = "Fiona-1.9.4.post1-cp38-cp38-win_amd64.whl", hash = "sha256:a670ea4262cb9140445bcfc97cbfd2f508a058be342f4a97e966b8ce7696601f"}, + {file = "Fiona-1.9.4.post1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:ea7c44c15b3a653452b9b3173181490b7afc5f153b0473c145c43c0fbf90448b"}, + {file = "Fiona-1.9.4.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7bfb1f49e0e53f6cd7ad64ae809d72646266b37a7b9881205977408b443a8d79"}, + {file = "Fiona-1.9.4.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a585002a6385cc8ab0f66ddf3caf18711f531901906abd011a67a0cc89ab7b0"}, + {file = "Fiona-1.9.4.post1-cp39-cp39-win_amd64.whl", hash = "sha256:f5da66b723a876142937e683431bbaa5c3d81bb2ed3ec98941271bc99b7f8cd0"}, + {file = "Fiona-1.9.4.post1.tar.gz", hash = "sha256:5679d3f7e0d513035eb72e59527bb90486859af4405755dfc739138633106120"}, ] +[package.dependencies] +attrs = ">=19.2.0" +certifi = "*" +click = ">=8.0,<9.0" +click-plugins = ">=1.0" +cligj = ">=0.5" +six = "*" + [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +all = ["Fiona[calc,s3,test]"] +calc = ["shapely"] +s3 = ["boto3 (>=1.3.1)"] +test = ["Fiona[s3]", "pytest (>=7)", "pytest-cov", "pytz"] [[package]] name = "flatbuffers" @@ -1029,45 +1162,45 @@ files = [ [[package]] name = "fonttools" -version = "4.42.0" +version = "4.42.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9c456d1f23deff64ffc8b5b098718e149279abdea4d8692dba69172fb6a0d597"}, - {file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:150122ed93127a26bc3670ebab7e2add1e0983d30927733aec327ebf4255b072"}, - {file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48e82d776d2e93f88ca56567509d102266e7ab2fb707a0326f032fe657335238"}, - {file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58c1165f9b2662645de9b19a8c8bdd636b36294ccc07e1b0163856b74f10bafc"}, - {file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d6dc3fa91414ff4daa195c05f946e6a575bd214821e26d17ca50f74b35b0fe4"}, - {file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fae4e801b774cc62cecf4a57b1eae4097903fced00c608d9e2bc8f84cd87b54a"}, - {file = "fonttools-4.42.0-cp310-cp310-win32.whl", hash = "sha256:b8600ae7dce6ec3ddfb201abb98c9d53abbf8064d7ac0c8a0d8925e722ccf2a0"}, - {file = "fonttools-4.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:57b68eab183fafac7cd7d464a7bfa0fcd4edf6c67837d14fb09c1c20516cf20b"}, - {file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0a1466713e54bdbf5521f2f73eebfe727a528905ff5ec63cda40961b4b1eea95"}, - {file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3fb2a69870bfe143ec20b039a1c8009e149dd7780dd89554cc8a11f79e5de86b"}, - {file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae881e484702efdb6cf756462622de81d4414c454edfd950b137e9a7352b3cb9"}, - {file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27ec3246a088555629f9f0902f7412220c67340553ca91eb540cf247aacb1983"}, - {file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ece1886d12bb36c48c00b2031518877f41abae317e3a55620d38e307d799b7e"}, - {file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10dac980f2b975ef74532e2a94bb00e97a95b4595fb7f98db493c474d5f54d0e"}, - {file = "fonttools-4.42.0-cp311-cp311-win32.whl", hash = "sha256:83b98be5d291e08501bd4fc0c4e0f8e6e05b99f3924068b17c5c9972af6fff84"}, - {file = "fonttools-4.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:e35bed436726194c5e6e094fdfb423fb7afaa0211199f9d245e59e11118c576c"}, - {file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c36c904ce0322df01e590ba814d5d69e084e985d7e4c2869378671d79662a7d4"}, - {file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d54e600a2bcfa5cdaa860237765c01804a03b08404d6affcd92942fa7315ffba"}, - {file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01cfe02416b6d416c5c8d15e30315cbcd3e97d1b50d3b34b0ce59f742ef55258"}, - {file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f81ed9065b4bd3f4f3ce8e4873cd6a6b3f4e92b1eddefde35d332c6f414acc3"}, - {file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:685a4dd6cf31593b50d6d441feb7781a4a7ef61e19551463e14ed7c527b86f9f"}, - {file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:329341ba3d86a36e482610db56b30705384cb23bd595eac8cbb045f627778e9d"}, - {file = "fonttools-4.42.0-cp38-cp38-win32.whl", hash = "sha256:4655c480a1a4d706152ff54f20e20cf7609084016f1df3851cce67cef768f40a"}, - {file = "fonttools-4.42.0-cp38-cp38-win_amd64.whl", hash = "sha256:6bd7e4777bff1dcb7c4eff4786998422770f3bfbef8be401c5332895517ba3fa"}, - {file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9b55d2a3b360e0c7fc5bd8badf1503ca1c11dd3a1cd20f2c26787ffa145a9c7"}, - {file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0df8ef75ba5791e873c9eac2262196497525e3f07699a2576d3ab9ddf41cb619"}, - {file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd2363ea7728496827658682d049ffb2e98525e2247ca64554864a8cc945568"}, - {file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d40673b2e927f7cd0819c6f04489dfbeb337b4a7b10fc633c89bf4f34ecb9620"}, - {file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c8bf88f9e3ce347c716921804ef3a8330cb128284eb6c0b6c4b3574f3c580023"}, - {file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:703101eb0490fae32baf385385d47787b73d9ea55253df43b487c89ec767e0d7"}, - {file = "fonttools-4.42.0-cp39-cp39-win32.whl", hash = "sha256:f0290ea7f9945174bd4dfd66e96149037441eb2008f3649094f056201d99e293"}, - {file = "fonttools-4.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:ae7df0ae9ee2f3f7676b0ff6f4ebe48ad0acaeeeaa0b6839d15dbf0709f2c5ef"}, - {file = "fonttools-4.42.0-py3-none-any.whl", hash = "sha256:dfe7fa7e607f7e8b58d0c32501a3a7cac148538300626d1b930082c90ae7f6bd"}, - {file = "fonttools-4.42.0.tar.gz", hash = "sha256:614b1283dca88effd20ee48160518e6de275ce9b5456a3134d5f235523fc5065"}, + {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed1a13a27f59d1fc1920394a7f596792e9d546c9ca5a044419dca70c37815d7c"}, + {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9b1ce7a45978b821a06d375b83763b27a3a5e8a2e4570b3065abad240a18760"}, + {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f720fa82a11c0f9042376fd509b5ed88dab7e3cd602eee63a1af08883b37342b"}, + {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db55cbaea02a20b49fefbd8e9d62bd481aaabe1f2301dabc575acc6b358874fa"}, + {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a35981d90feebeaef05e46e33e6b9e5b5e618504672ca9cd0ff96b171e4bfff"}, + {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68a02bbe020dc22ee0540e040117535f06df9358106d3775e8817d826047f3fd"}, + {file = "fonttools-4.42.1-cp310-cp310-win32.whl", hash = "sha256:12a7c247d1b946829bfa2f331107a629ea77dc5391dfd34fdcd78efa61f354ca"}, + {file = "fonttools-4.42.1-cp310-cp310-win_amd64.whl", hash = "sha256:a398bdadb055f8de69f62b0fc70625f7cbdab436bbb31eef5816e28cab083ee8"}, + {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:689508b918332fb40ce117131633647731d098b1b10d092234aa959b4251add5"}, + {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e36344e48af3e3bde867a1ca54f97c308735dd8697005c2d24a86054a114a71"}, + {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19b7db825c8adee96fac0692e6e1ecd858cae9affb3b4812cdb9d934a898b29e"}, + {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113337c2d29665839b7d90b39f99b3cac731f72a0eda9306165a305c7c31d341"}, + {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37983b6bdab42c501202500a2be3a572f50d4efe3237e0686ee9d5f794d76b35"}, + {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6ed2662a3d9c832afa36405f8748c250be94ae5dfc5283d668308391f2102861"}, + {file = "fonttools-4.42.1-cp311-cp311-win32.whl", hash = "sha256:179737095eb98332a2744e8f12037b2977f22948cf23ff96656928923ddf560a"}, + {file = "fonttools-4.42.1-cp311-cp311-win_amd64.whl", hash = "sha256:f2b82f46917d8722e6b5eafeefb4fb585d23babd15d8246c664cd88a5bddd19c"}, + {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:62f481ac772fd68901573956231aea3e4b1ad87b9b1089a61613a91e2b50bb9b"}, + {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2f806990160d1ce42d287aa419df3ffc42dfefe60d473695fb048355fe0c6a0"}, + {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db372213d39fa33af667c2aa586a0c1235e88e9c850f5dd5c8e1f17515861868"}, + {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d18fc642fd0ac29236ff88ecfccff229ec0386090a839dd3f1162e9a7944a40"}, + {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8708b98c278012ad267ee8a7433baeb809948855e81922878118464b274c909d"}, + {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c95b0724a6deea2c8c5d3222191783ced0a2f09bd6d33f93e563f6f1a4b3b3a4"}, + {file = "fonttools-4.42.1-cp38-cp38-win32.whl", hash = "sha256:4aa79366e442dbca6e2c8595645a3a605d9eeabdb7a094d745ed6106816bef5d"}, + {file = "fonttools-4.42.1-cp38-cp38-win_amd64.whl", hash = "sha256:acb47f6f8680de24c1ab65ebde39dd035768e2a9b571a07c7b8da95f6c8815fd"}, + {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb289b7a815638a7613d46bcf324c9106804725b2bb8ad913c12b6958ffc4ec"}, + {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:53eb5091ddc8b1199330bb7b4a8a2e7995ad5d43376cadce84523d8223ef3136"}, + {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46a0ec8adbc6ff13494eb0c9c2e643b6f009ce7320cf640de106fb614e4d4360"}, + {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cc7d685b8eeca7ae69dc6416833fbfea61660684b7089bca666067cb2937dcf"}, + {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:be24fcb80493b2c94eae21df70017351851652a37de514de553435b256b2f249"}, + {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:515607ec756d7865f23070682622c49d922901943697871fc292277cf1e71967"}, + {file = "fonttools-4.42.1-cp39-cp39-win32.whl", hash = "sha256:0eb79a2da5eb6457a6f8ab904838454accc7d4cccdaff1fd2bd3a0679ea33d64"}, + {file = "fonttools-4.42.1-cp39-cp39-win_amd64.whl", hash = "sha256:7286aed4ea271df9eab8d7a9b29e507094b51397812f7ce051ecd77915a6e26b"}, + {file = "fonttools-4.42.1-py3-none-any.whl", hash = "sha256:9398f244e28e0596e2ee6024f808b06060109e33ed38dcc9bded452fd9bbb853"}, + {file = "fonttools-4.42.1.tar.gz", hash = "sha256:c391cd5af88aacaf41dd7cfb96eeedfad297b5899a39e12f4c2c3706d0a3329d"}, ] [package.extras] @@ -1206,6 +1339,24 @@ files = [ [package.extras] rewrite = ["tokenize-rt (>=3)"] +[[package]] +name = "geopandas" +version = "0.13.2" +description = "Geographic pandas extensions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "geopandas-0.13.2-py3-none-any.whl", hash = "sha256:101cfd0de54bcf9e287a55b5ea17ebe0db53a5e25a28bacf100143d0507cabd9"}, + {file = "geopandas-0.13.2.tar.gz", hash = "sha256:e5b56d9c20800c77bcc0c914db3f27447a37b23b2cd892be543f5001a694a968"}, +] + +[package.dependencies] +fiona = ">=1.8.19" +packaging = "*" +pandas = ">=1.1.0" +pyproj = ">=3.0.1" +shapely = ">=1.7.1" + [[package]] name = "google-crc32c" version = "1.5.0" @@ -1286,6 +1437,37 @@ files = [ [package.extras] testing = ["pytest"] +[[package]] +name = "gymnasium" +version = "0.28.1" +description = "A standard API for reinforcement learning and a diverse set of reference environments (formerly Gym)." +optional = false +python-versions = ">=3.7" +files = [ + {file = "gymnasium-0.28.1-py3-none-any.whl", hash = "sha256:7bc9a5bce1022f997d1dbc152fc91d1ac977bad9cc7794cdc25437010867cabf"}, + {file = "gymnasium-0.28.1.tar.gz", hash = "sha256:4c2c745808792c8f45c6e88ad0a5504774394e0c126f6e3db555e720d3da6f24"}, +] + +[package.dependencies] +cloudpickle = ">=1.2.0" +farama-notifications = ">=0.0.1" +jax-jumpy = ">=1.0.0" +numpy = ">=1.21.0" +typing-extensions = ">=4.3.0" + +[package.extras] +accept-rom-license = ["autorom[accept-rom-license] (>=0.4.2,<0.5.0)"] +all = ["box2d-py (==2.3.5)", "imageio (>=2.14.1)", "jax (==0.3.24)", "jaxlib (==0.3.24)", "lz4 (>=3.1.0)", "matplotlib (>=3.0)", "moviepy (>=1.0.0)", "mujoco (>=2.3.2)", "mujoco-py (>=2.1,<2.2)", "opencv-python (>=3.0)", "pygame (==2.1.3)", "shimmy[atari] (>=0.1.0,<1.0)", "swig (==4.*)", "torch (>=1.0.0)"] +atari = ["shimmy[atari] (>=0.1.0,<1.0)"] +box2d = ["box2d-py (==2.3.5)", "pygame (==2.1.3)", "swig (==4.*)"] +classic-control = ["pygame (==2.1.3)", "pygame (==2.1.3)"] +jax = ["jax (==0.3.24)", "jaxlib (==0.3.24)"] +mujoco = ["imageio (>=2.14.1)", "mujoco (>=2.3.2)"] +mujoco-py = ["mujoco-py (>=2.1,<2.2)", "mujoco-py (>=2.1,<2.2)"] +other = ["lz4 (>=3.1.0)", "matplotlib (>=3.0)", "moviepy (>=1.0.0)", "opencv-python (>=3.0)", "torch (>=1.0.0)"] +testing = ["pytest (==7.1.3)", "scipy (==1.7.3)"] +toy-text = ["pygame (==2.1.3)", "pygame (==2.1.3)"] + [[package]] name = "h3" version = "3.7.6" @@ -1413,13 +1595,13 @@ zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.1)"] [[package]] name = "identify" -version = "2.5.26" +version = "2.5.28" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.26-py2.py3-none-any.whl", hash = "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54"}, - {file = "identify-2.5.26.tar.gz", hash = "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f"}, + {file = "identify-2.5.28-py2.py3-none-any.whl", hash = "sha256:87816de144bf46d161bd5b3e8f5596b16cade3b80be537087334b26bc5c177f3"}, + {file = "identify-2.5.28.tar.gz", hash = "sha256:94bb59643083ebd60dc996d043497479ee554381fbc5307763915cda49b0e78f"}, ] [package.extras] @@ -1458,6 +1640,25 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] +[[package]] +name = "importlib-metadata" +version = "6.8.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + [[package]] name = "importlib-resources" version = "6.0.1" @@ -1505,6 +1706,38 @@ files = [ {file = "ioctl-opt-1.3.tar.gz", hash = "sha256:5ed4f9a80d2e02e152a43d3648d7ed8821a0aac5ea88ecc5fcc14460ff7cf2f9"}, ] +[[package]] +name = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = false +python-versions = "*" +files = [ + {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, + {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "jax-jumpy" +version = "1.0.0" +description = "Common backend for Jax or Numpy." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jax-jumpy-1.0.0.tar.gz", hash = "sha256:195fb955cc4c2b7f0b1453e3cb1fb1c414a51a407ffac7a51e69a73cb30d59ad"}, + {file = "jax_jumpy-1.0.0-py3-none-any.whl", hash = "sha256:ab7e01454bba462de3c4d098e3e585c302a8f06bc36d9182ab4e7e4aa7067c5e"}, +] + +[package.dependencies] +numpy = ">=1.18.0" + +[package.extras] +jax = ["jax (>=0.3.24)", "jaxlib (>=0.3.24)"] +testing = ["pytest (==7.1.3)"] + [[package]] name = "jinja2" version = "3.1.2" @@ -1535,79 +1768,115 @@ files = [ [[package]] name = "kiwisolver" -version = "1.4.4" +version = "1.4.5" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.7" files = [ - {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, - {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, - {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"}, - {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"}, - {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"}, - {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"}, - {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"}, - {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"}, - {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"}, - {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"}, - {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"}, - {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"}, - {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"}, - {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"}, - {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"}, - {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"}, - {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"}, - {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"}, - {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"}, - {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"}, - {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"}, - {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"}, - {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"}, - {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"}, - {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"}, - {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"}, - {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"}, - {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"}, - {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"}, - {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"}, - {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"}, - {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"}, - {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"}, - {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"}, - {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"}, - {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"}, - {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"}, - {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"}, - {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"}, - {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"}, - {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"}, - {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"}, - {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"}, - {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"}, - {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"}, - {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"}, - {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"}, - {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"}, - {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"}, - {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"}, - {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"}, - {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"}, - {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"}, - {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"}, - {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"}, - {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"}, - {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"}, - {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"}, - {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"}, - {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"}, - {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"}, - {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"}, - {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"}, - {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"}, - {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"}, - {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"}, - {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, - {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, + {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, ] [[package]] @@ -1717,6 +1986,113 @@ files = [ [package.extras] test = ["pytest"] +[[package]] +name = "lxml" +version = "4.9.3" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +files = [ + {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, + {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, + {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, + {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, + {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, + {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, + {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, + {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, + {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, + {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, + {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, + {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, + {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, + {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, + {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, + {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, + {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, + {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, + {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, + {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, + {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, + {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, + {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, + {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, + {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.35)"] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -1802,52 +2178,58 @@ files = [ [[package]] name = "matplotlib" -version = "3.7.2" +version = "3.7.3" description = "Python plotting package" optional = false python-versions = ">=3.8" files = [ - {file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:2699f7e73a76d4c110f4f25be9d2496d6ab4f17345307738557d345f099e07de"}, - {file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a8035ba590658bae7562786c9cc6ea1a84aa49d3afab157e414c9e2ea74f496d"}, - {file = "matplotlib-3.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f8e4a49493add46ad4a8c92f63e19d548b2b6ebbed75c6b4c7f46f57d36cdd1"}, - {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71667eb2ccca4c3537d9414b1bc00554cb7f91527c17ee4ec38027201f8f1603"}, - {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:152ee0b569a37630d8628534c628456b28686e085d51394da6b71ef84c4da201"}, - {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070f8dddd1f5939e60aacb8fa08f19551f4b0140fab16a3669d5cd6e9cb28fc8"}, - {file = "matplotlib-3.7.2-cp310-cp310-win32.whl", hash = "sha256:fdbb46fad4fb47443b5b8ac76904b2e7a66556844f33370861b4788db0f8816a"}, - {file = "matplotlib-3.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:23fb1750934e5f0128f9423db27c474aa32534cec21f7b2153262b066a581fd1"}, - {file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:30e1409b857aa8a747c5d4f85f63a79e479835f8dffc52992ac1f3f25837b544"}, - {file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:50e0a55ec74bf2d7a0ebf50ac580a209582c2dd0f7ab51bc270f1b4a0027454e"}, - {file = "matplotlib-3.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ac60daa1dc83e8821eed155796b0f7888b6b916cf61d620a4ddd8200ac70cd64"}, - {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305e3da477dc8607336ba10bac96986d6308d614706cae2efe7d3ffa60465b24"}, - {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c308b255efb9b06b23874236ec0f10f026673ad6515f602027cc8ac7805352d"}, - {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60c521e21031632aa0d87ca5ba0c1c05f3daacadb34c093585a0be6780f698e4"}, - {file = "matplotlib-3.7.2-cp311-cp311-win32.whl", hash = "sha256:26bede320d77e469fdf1bde212de0ec889169b04f7f1179b8930d66f82b30cbc"}, - {file = "matplotlib-3.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:af4860132c8c05261a5f5f8467f1b269bf1c7c23902d75f2be57c4a7f2394b3e"}, - {file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:a1733b8e84e7e40a9853e505fe68cc54339f97273bdfe6f3ed980095f769ddc7"}, - {file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d9881356dc48e58910c53af82b57183879129fa30492be69058c5b0d9fddf391"}, - {file = "matplotlib-3.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f081c03f413f59390a80b3e351cc2b2ea0205839714dbc364519bcf51f4b56ca"}, - {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1cd120fca3407a225168238b790bd5c528f0fafde6172b140a2f3ab7a4ea63e9"}, - {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c1590b90aa7bd741b54c62b78de05d4186271e34e2377e0289d943b3522273"}, - {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d2ff3c984b8a569bc1383cd468fc06b70d7b59d5c2854ca39f1436ae8394117"}, - {file = "matplotlib-3.7.2-cp38-cp38-win32.whl", hash = "sha256:5dea00b62d28654b71ca92463656d80646675628d0828e08a5f3b57e12869e13"}, - {file = "matplotlib-3.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f506a1776ee94f9e131af1ac6efa6e5bc7cb606a3e389b0ccb6e657f60bb676"}, - {file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:6515e878f91894c2e4340d81f0911857998ccaf04dbc1bba781e3d89cbf70608"}, - {file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:71f7a8c6b124e904db550f5b9fe483d28b896d4135e45c4ea381ad3b8a0e3256"}, - {file = "matplotlib-3.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12f01b92ecd518e0697da4d97d163b2b3aa55eb3eb4e2c98235b3396d7dad55f"}, - {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7e28d6396563955f7af437894a36bf2b279462239a41028323e04b85179058b"}, - {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbcf59334ff645e6a67cd5f78b4b2cdb76384cdf587fa0d2dc85f634a72e1a3e"}, - {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:318c89edde72ff95d8df67d82aca03861240512994a597a435a1011ba18dbc7f"}, - {file = "matplotlib-3.7.2-cp39-cp39-win32.whl", hash = "sha256:ce55289d5659b5b12b3db4dc9b7075b70cef5631e56530f14b2945e8836f2d20"}, - {file = "matplotlib-3.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:2ecb5be2b2815431c81dc115667e33da0f5a1bcf6143980d180d09a717c4a12e"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fdcd28360dbb6203fb5219b1a5658df226ac9bebc2542a9e8f457de959d713d0"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3cca3e842b11b55b52c6fb8bd6a4088693829acbfcdb3e815fa9b7d5c92c1b"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebf577c7a6744e9e1bd3fee45fc74a02710b214f94e2bde344912d85e0c9af7c"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:936bba394682049919dda062d33435b3be211dc3dcaa011e09634f060ec878b2"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bc221ffbc2150458b1cd71cdd9ddd5bb37962b036e41b8be258280b5b01da1dd"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35d74ebdb3f71f112b36c2629cf32323adfbf42679e2751252acd468f5001c07"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717157e61b3a71d3d26ad4e1770dc85156c9af435659a25ee6407dc866cb258d"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:20f844d6be031948148ba49605c8b96dfe7d3711d1b63592830d650622458c11"}, - {file = "matplotlib-3.7.2.tar.gz", hash = "sha256:a8cdb91dddb04436bd2f098b8fdf4b81352e68cf4d2c6756fcc414791076569b"}, + {file = "matplotlib-3.7.3-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:085c33b27561d9c04386789d5aa5eb4a932ddef43cfcdd0e01735f9a6e85ce0c"}, + {file = "matplotlib-3.7.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c568e80e1c17f68a727f30f591926751b97b98314d8e59804f54f86ae6fa6a22"}, + {file = "matplotlib-3.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7baf98c5ad59c5c4743ea884bb025cbffa52dacdfdac0da3e6021a285a90377e"}, + {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:236024f582e40dac39bca592258888b38ae47a9fed7b8de652d68d3d02d47d2b"}, + {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12b4f6795efea037ce2d41e7c417ad8bd02d5719c6ad4a8450a0708f4a1cfb89"}, + {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b2136cc6c5415b78977e0e8c608647d597204b05b1d9089ccf513c7d913733"}, + {file = "matplotlib-3.7.3-cp310-cp310-win32.whl", hash = "sha256:122dcbf9be0086e2a95d9e5e0632dbf3bd5b65eaa68c369363310a6c87753059"}, + {file = "matplotlib-3.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:4aab27d9e33293389e3c1d7c881d414a72bdfda0fedc3a6bf46c6fa88d9b8015"}, + {file = "matplotlib-3.7.3-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:d5adc743de91e8e0b13df60deb1b1c285b8effea3d66223afceb14b63c9b05de"}, + {file = "matplotlib-3.7.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:55de4cf7cd0071b8ebf203981b53ab64f988a0a1f897a2dff300a1124e8bcd8b"}, + {file = "matplotlib-3.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ac03377fd908aaee2312d0b11735753e907adb6f4d1d102de5e2425249693f6c"}, + {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:755bafc10a46918ce9a39980009b54b02dd249594e5adf52f9c56acfddb5d0b7"}, + {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a6094c6f8e8d18db631754df4fe9a34dec3caf074f6869a7db09f18f9b1d6b2"}, + {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:272dba2f1b107790ed78ebf5385b8d14b27ad9e90419de340364b49fe549a993"}, + {file = "matplotlib-3.7.3-cp311-cp311-win32.whl", hash = "sha256:591c123bed1cb4b9996fb60b41a6d89c2ec4943244540776c5f1283fb6960a53"}, + {file = "matplotlib-3.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:3bf3a178c6504694cee8b88b353df0051583f2f6f8faa146f67115c27c856881"}, + {file = "matplotlib-3.7.3-cp312-cp312-macosx_10_12_universal2.whl", hash = "sha256:edf54cac8ee3603f3093616b40a931e8c063969756a4d78a86e82c2fea9659f7"}, + {file = "matplotlib-3.7.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:91e36a85ea639a1ba9f91427041eac064b04829945fe331a92617b6cb21d27e5"}, + {file = "matplotlib-3.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:caf5eaaf7c68f8d7df269dfbcaf46f48a70ff482bfcebdcc97519671023f2a7d"}, + {file = "matplotlib-3.7.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74bf57f505efea376097e948b7cdd87191a7ce8180616390aef496639edf601f"}, + {file = "matplotlib-3.7.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee152a88a0da527840a426535514b6ed8ac4240eb856b1da92cf48124320e346"}, + {file = "matplotlib-3.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:67a410a9c9e07cbc83581eeea144bbe298870bf0ac0ee2f2e10a015ab7efee19"}, + {file = "matplotlib-3.7.3-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:259999c05285cb993d7f2a419cea547863fa215379eda81f7254c9e932963729"}, + {file = "matplotlib-3.7.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3f4e7fd5a6157e1d018ce2166ec8e531a481dd4a36f035b5c23edfe05a25419a"}, + {file = "matplotlib-3.7.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:faa3d12d8811d08d14080a8b7b9caea9a457dc495350166b56df0db4b9909ef5"}, + {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:336e88900c11441e458da01c8414fc57e04e17f9d3bb94958a76faa2652bcf6b"}, + {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:12f4c0dd8aa280d796c8772ea8265a14f11a04319baa3a16daa5556065e8baea"}, + {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1990955b11e7918d256cf3b956b10997f405b7917a3f1c7d8e69c1d15c7b1930"}, + {file = "matplotlib-3.7.3-cp38-cp38-win32.whl", hash = "sha256:e78707b751260b42b721507ad7aa60fe4026d7f51c74cca6b9cd8b123ebb633a"}, + {file = "matplotlib-3.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:e594ee43c59ea39ca5c6244667cac9d017a3527febc31f5532ad9135cf7469ec"}, + {file = "matplotlib-3.7.3-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:6eaa1cf0e94c936a26b78f6d756c5fbc12e0a58c8a68b7248a2a31456ce4e234"}, + {file = "matplotlib-3.7.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0a97af9d22e8ebedc9f00b043d9bbd29a375e9e10b656982012dded44c10fd77"}, + {file = "matplotlib-3.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f9c6c16597af660433ab330b59ee2934b832ee1fabcaf5cbde7b2add840f31e"}, + {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7240259b4b9cbc62381f6378cff4d57af539162a18e832c1e48042fabc40b6b"}, + {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747c6191d2e88ae854809e69aa358dbf852ff1a5738401b85c1cc9012309897a"}, + {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec726b08a5275d827aa91bb951e68234a4423adb91cf65bc0fcdc0f2777663f7"}, + {file = "matplotlib-3.7.3-cp39-cp39-win32.whl", hash = "sha256:40e3b9b450c6534f07278310c4e34caff41c2a42377e4b9d47b0f8d3ac1083a2"}, + {file = "matplotlib-3.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfc118642903a23e309b1da32886bb39a4314147d013e820c86b5fb4cb2e36d0"}, + {file = "matplotlib-3.7.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:165c8082bf8fc0360c24aa4724a22eaadbfd8c28bf1ccf7e94d685cad48261e4"}, + {file = "matplotlib-3.7.3-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebd8470cc2a3594746ff0513aecbfa2c55ff6f58e6cef2efb1a54eb87c88ffa2"}, + {file = "matplotlib-3.7.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7153453669c9672b52095119fd21dd032d19225d48413a2871519b17db4b0fde"}, + {file = "matplotlib-3.7.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:498a08267dc69dd8f24c4b5d7423fa584d7ce0027ba71f7881df05fc09b89bb7"}, + {file = "matplotlib-3.7.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48999c4b19b5a0c058c9cd828ff6fc7748390679f6cf9a2ad653a3e802c87d3"}, + {file = "matplotlib-3.7.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22d65d18b4ee8070a5fea5761d59293f1f9e2fac37ec9ce090463b0e629432fd"}, + {file = "matplotlib-3.7.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c40cde976c36693cc0767e27cf5f443f91c23520060bd9496678364adfafe9c"}, + {file = "matplotlib-3.7.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:39018a2b17592448fbfdf4b8352955e6c3905359939791d4ff429296494d1a0c"}, + {file = "matplotlib-3.7.3.tar.gz", hash = "sha256:f09b3dd6bdeb588de91f853bbb2d6f0ff8ab693485b0c49035eaa510cb4f142e"}, ] [package.dependencies] @@ -1855,11 +2237,12 @@ contourpy = ">=1.0.1" cycler = ">=0.10" fonttools = ">=4.22.0" kiwisolver = ">=1.0.1" -numpy = ">=1.20" +numpy = ">=1.20,<2" packaging = ">=20.0" pillow = ">=6.2.0" -pyparsing = ">=2.3.1,<3.1" +pyparsing = ">=2.3.1" python-dateutil = ">=2.7" +setuptools_scm = ">=7" [[package]] name = "mdit-py-plugins" @@ -1891,6 +2274,51 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "metadrive-simulator" +version = "0.4.1.2" +description = "An open-ended driving simulator with infinite scenes" +optional = false +python-versions = ">=3.6, <3.12" +files = [] +develop = false + +[package.dependencies] +geopandas = "*" +gymnasium = ">=0.28,<0.29" +lxml = "*" +matplotlib = "*" +numpy = ">=1.21.6,<=1.24.2" +opencv-python = "*" +panda3d = "1.10.13" +panda3d-gltf = "0.13" +panda3d-simplepbr = "*" +pandas = "*" +pillow = "*" +progressbar = "*" +psutil = "*" +pygame = "*" +pytest = "*" +requests = "*" +scipy = "*" +seaborn = "*" +shapely = "*" +tqdm = "*" +yapf = "*" + +[package.extras] +all = ["PyOpenGL (==3.1.6)", "PyOpenGL-accelerate (==3.1.6)", "aioboto3", "aiofiles", "bokeh (==2.4)", "boto3", "chardet", "cuda-python (==12.0.0)", "glfw", "hydra-core", "pyarrow", "pyrr (==0.10.3)", "retry"] +cuda = ["PyOpenGL (==3.1.6)", "PyOpenGL-accelerate (==3.1.6)", "cuda-python (==12.0.0)", "glfw", "pyrr (==0.10.3)"] +gym = ["gym (>=0.19.0,<=0.26.0)"] +nuplan = ["aioboto3", "aiofiles", "bokeh (==2.4)", "boto3", "chardet", "hydra-core", "pyarrow", "retry"] +waymo = ["waymo-open-dataset-tf-2.11.0 (==1.5.0)"] + +[package.source] +type = "git" +url = "https://github.com/metadriverse/metadrive.git" +reference = "7d6f8ef707bfff67c6b88f3b4a98f8b1d58bf8e6" +resolved_reference = "7d6f8ef707bfff67c6b88f3b4a98f8b1d58bf8e6" + [[package]] name = "mpld3" version = "0.5.9" @@ -1923,6 +2351,43 @@ docs = ["sphinx"] gmpy = ["gmpy2 (>=2.1.0a4)"] tests = ["pytest (>=4.6)"] +[[package]] +name = "msal" +version = "1.23.0" +description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." +optional = false +python-versions = "*" +files = [ + {file = "msal-1.23.0-py2.py3-none-any.whl", hash = "sha256:3342e0837a047007f9d479e814b559c3219767453d57920dc40a31986862048b"}, + {file = "msal-1.23.0.tar.gz", hash = "sha256:25c9a33acf84301f93d1fdbe9f1a9c60cd38af0d5fffdbfa378138fc7bc1e86b"}, +] + +[package.dependencies] +cryptography = ">=0.6,<44" +PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} +requests = ">=2.0.0,<3" + +[package.extras] +broker = ["pymsalruntime (>=0.13.2,<0.14)"] + +[[package]] +name = "msal-extensions" +version = "1.0.0" +description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." +optional = false +python-versions = "*" +files = [ + {file = "msal-extensions-1.0.0.tar.gz", hash = "sha256:c676aba56b0cce3783de1b5c5ecfe828db998167875126ca4b47dc6436451354"}, + {file = "msal_extensions-1.0.0-py2.py3-none-any.whl", hash = "sha256:91e3db9620b822d0ed2b4d1850056a0f133cba04455e62f11612e40f5502f2ee"}, +] + +[package.dependencies] +msal = ">=0.4.1,<2.0.0" +portalocker = [ + {version = ">=1.0,<3", markers = "python_version >= \"3.5\" and platform_system != \"Windows\""}, + {version = ">=1.6,<3", markers = "python_version >= \"3.5\" and platform_system == \"Windows\""}, +] + [[package]] name = "multidict" version = "6.0.4" @@ -2008,33 +2473,38 @@ files = [ [[package]] name = "mypy" -version = "1.5.0" +version = "1.5.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ad3109bec37cc33654de8db30fe8ff3a1bb57ea65144167d68185e6dced9868d"}, - {file = "mypy-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b4ea3a0241cb005b0ccdbd318fb99619b21ae51bcf1660b95fc22e0e7d3ba4a1"}, - {file = "mypy-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fe816e26e676c1311b9e04fd576543b873576d39439f7c24c8e5c7728391ecf"}, - {file = "mypy-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42170e68adb1603ccdc55a30068f72bcfcde2ce650188e4c1b2a93018b826735"}, - {file = "mypy-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d145b81a8214687cfc1f85c03663a5bbe736777410e5580e54d526e7e904f564"}, - {file = "mypy-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c36011320e452eb30bec38b9fd3ba20569dc9545d7d4540d967f3ea1fab9c374"}, - {file = "mypy-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3940cf5845b2512b3ab95463198b0cdf87975dfd17fdcc6ce9709a9abe09e69"}, - {file = "mypy-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9166186c498170e1ff478a7f540846b2169243feb95bc228d39a67a1a450cdc6"}, - {file = "mypy-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:725b57a19b7408ef66a0fd9db59b5d3e528922250fb56e50bded27fea9ff28f0"}, - {file = "mypy-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:eec5c927aa4b3e8b4781840f1550079969926d0a22ce38075f6cfcf4b13e3eb4"}, - {file = "mypy-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79c520aa24f21852206b5ff2cf746dc13020113aa73fa55af504635a96e62718"}, - {file = "mypy-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:769ddb6bfe55c2bd9c7d6d7020885a5ea14289619db7ee650e06b1ef0852c6f4"}, - {file = "mypy-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbf18f8db7e5f060d61c91e334d3b96d6bb624ddc9ee8a1cde407b737acbca2c"}, - {file = "mypy-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a2500ad063413bc873ae102cf655bf49889e0763b260a3a7cf544a0cbbf7e70a"}, - {file = "mypy-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:84cf9f7d8a8a22bb6a36444480f4cbf089c917a4179fbf7eea003ea931944a7f"}, - {file = "mypy-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a551ed0fc02455fe2c1fb0145160df8336b90ab80224739627b15ebe2b45e9dc"}, - {file = "mypy-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:372fd97293ed0076d52695849f59acbbb8461c4ab447858cdaeaf734a396d823"}, - {file = "mypy-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8a7444d6fcac7e2585b10abb91ad900a576da7af8f5cffffbff6065d9115813"}, - {file = "mypy-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:35b13335c6c46a386577a51f3d38b2b5d14aa619e9633bb756bd77205e4bd09f"}, - {file = "mypy-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:2c9d570f53908cbea326ad8f96028a673b814d9dca7515bf71d95fa662c3eb6f"}, - {file = "mypy-1.5.0-py3-none-any.whl", hash = "sha256:69b32d0dedd211b80f1b7435644e1ef83033a2af2ac65adcdc87c38db68a86be"}, - {file = "mypy-1.5.0.tar.gz", hash = "sha256:f3460f34b3839b9bc84ee3ed65076eb827cd99ed13ed08d723f9083cada4a212"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, + {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, + {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, + {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, + {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, + {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, + {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, + {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, + {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, + {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, + {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, + {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, + {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, + {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, + {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, + {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, + {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, + {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, ] [package.dependencies] @@ -2178,76 +2648,79 @@ setuptools = "*" [[package]] name = "numpy" -version = "1.25.2" +version = "1.24.2" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" files = [ - {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, - {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, - {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, - {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, - {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, - {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, - {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, - {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, - {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, - {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, - {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, - {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, + {file = "numpy-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef70b4fc1e872ebddc38cddacc87c19a3709c0e3e5d20bf3954c147b1dd941d"}, + {file = "numpy-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d2859428712785e8a8b7d2b3ef0a1d1565892367b32f915c4a4df44d0e64f5"}, + {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6524630f71631be2dabe0c541e7675db82651eb998496bbe16bc4f77f0772253"}, + {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a51725a815a6188c662fb66fb32077709a9ca38053f0274640293a14fdd22978"}, + {file = "numpy-1.24.2-cp310-cp310-win32.whl", hash = "sha256:2620e8592136e073bd12ee4536149380695fbe9ebeae845b81237f986479ffc9"}, + {file = "numpy-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:97cf27e51fa078078c649a51d7ade3c92d9e709ba2bfb97493007103c741f1d0"}, + {file = "numpy-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7de8fdde0003f4294655aa5d5f0a89c26b9f22c0a58790c38fae1ed392d44a5a"}, + {file = "numpy-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4173bde9fa2a005c2c6e2ea8ac1618e2ed2c1c6ec8a7657237854d42094123a0"}, + {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cecaed30dc14123020f77b03601559fff3e6cd0c048f8b5289f4eeabb0eb281"}, + {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a23f8440561a633204a67fb44617ce2a299beecf3295f0d13c495518908e910"}, + {file = "numpy-1.24.2-cp311-cp311-win32.whl", hash = "sha256:e428c4fbfa085f947b536706a2fc349245d7baa8334f0c5723c56a10595f9b95"}, + {file = "numpy-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:557d42778a6869c2162deb40ad82612645e21d79e11c1dc62c6e82a2220ffb04"}, + {file = "numpy-1.24.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d0a2db9d20117bf523dde15858398e7c0858aadca7c0f088ac0d6edd360e9ad2"}, + {file = "numpy-1.24.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c72a6b2f4af1adfe193f7beb91ddf708ff867a3f977ef2ec53c0ffb8283ab9f5"}, + {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29e6bd0ec49a44d7690ecb623a8eac5ab8a923bce0bea6293953992edf3a76a"}, + {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eabd64ddb96a1239791da78fa5f4e1693ae2dadc82a76bc76a14cbb2b966e96"}, + {file = "numpy-1.24.2-cp38-cp38-win32.whl", hash = "sha256:e3ab5d32784e843fc0dd3ab6dcafc67ef806e6b6828dc6af2f689be0eb4d781d"}, + {file = "numpy-1.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756"}, + {file = "numpy-1.24.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4199e7cfc307a778f72d293372736223e39ec9ac096ff0a2e64853b866a8e18a"}, + {file = "numpy-1.24.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adbdce121896fd3a17a77ab0b0b5eedf05a9834a18699db6829a64e1dfccca7f"}, + {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889b2cc88b837d86eda1b17008ebeb679d82875022200c6e8e4ce6cf549b7acb"}, + {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f64bb98ac59b3ea3bf74b02f13836eb2e24e48e0ab0145bbda646295769bd780"}, + {file = "numpy-1.24.2-cp39-cp39-win32.whl", hash = "sha256:63e45511ee4d9d976637d11e6c9864eae50e12dc9598f531c035265991910468"}, + {file = "numpy-1.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:a77d3e1163a7770164404607b7ba3967fb49b24782a6ef85d9b5f54126cc39e5"}, + {file = "numpy-1.24.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:92011118955724465fb6853def593cf397b4a1367495e0b59a7e69d40c4eb71d"}, + {file = "numpy-1.24.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9006288bcf4895917d02583cf3411f98631275bc67cce355a7f39f8c14338fa"}, + {file = "numpy-1.24.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:150947adbdfeceec4e5926d956a06865c1c690f2fd902efede4ca6fe2e657c3f"}, + {file = "numpy-1.24.2.tar.gz", hash = "sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22"}, ] [[package]] name = "onnx" -version = "1.14.0" +version = "1.14.1" description = "Open Neural Network Exchange" optional = false python-versions = "*" files = [ - {file = "onnx-1.14.0-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:fb35c2c347486416f87f41557242c05d7ee804d3676c6c8c98eef6f5b1889e7b"}, - {file = "onnx-1.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd683d4aa6d55365582055a6c1e10a55d6c08a59e9216cbb67e37ad3a5b2b980"}, - {file = "onnx-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00b0d2620c10dcb9ec33441e807dc5851d2843d445e0faab5e22c8ad6874a67a"}, - {file = "onnx-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01893a4a2d70b68e8ee20269ccde4069a6fd243dc9e296643e2afeb0050527bc"}, - {file = "onnx-1.14.0-cp310-cp310-win32.whl", hash = "sha256:0753b0f118be71ff109dd994a3d6769e5871e9feaddfada77931c63f9de534b3"}, - {file = "onnx-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:d8c3a2354d9d997c7a4a5e467b5373c98dc549d4a33c77d5723e1eda7e87559c"}, - {file = "onnx-1.14.0-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:5e780fd1ed25493596a141e93303d0b2897acb9ebfdee7047a916d8f8e525ab3"}, - {file = "onnx-1.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9d28d64cbac3ebdc0c9761a300340c60ec60316099906e354e5059e90335fb3b"}, - {file = "onnx-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba92fed1aa27cba385bc3890fbbe6484603e837e67c957b22899f93c70990cc4"}, - {file = "onnx-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fab7e6e1c2d9d6479edad8e9088cdfd87ea293cb08f31565adabfb33c6e5789"}, - {file = "onnx-1.14.0-cp311-cp311-win32.whl", hash = "sha256:6e966f5ef38a0521595cad6a1d14d9ae205c593d2824d8c1fa044fa5ba15370d"}, - {file = "onnx-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:1fe8ba794d261d722018bd1385f02f966aace0fcb5448881ab5dd55ab0ebb81b"}, - {file = "onnx-1.14.0-cp37-cp37m-macosx_10_12_universal2.whl", hash = "sha256:c16dacf577700ff9cb076c61c880d1a4bc612eed96280396a54ee1e1bd7e2d68"}, - {file = "onnx-1.14.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:bbdca51da9fa9ec43eebd8c640bf71c05daa2afbeaa2c6478466470e28e41111"}, - {file = "onnx-1.14.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3315c304d23a06ebd07fffe2456ab7f1e0a8dba317393d5c17a671ae2da6645e"}, - {file = "onnx-1.14.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1545159f2e7fbc5b4a3ae032cd4d9ddeafc62c4f27fe22cbc3ecff49338992"}, - {file = "onnx-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:18cd98f7e234e268cb60c47a1f8ea5f6ffba50fe11de924b17498b1571d0cd2c"}, - {file = "onnx-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a8f7454acded506b6359ee0837c8527c64964973d7d25ed6b16b7d4314599502"}, - {file = "onnx-1.14.0-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:a9702e7dd120bca421a820020151cbb1003077e17ded29cc8d44ff32a9a57ad8"}, - {file = "onnx-1.14.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:369c3ecace7e8c7df6efbcbc712b262626796ae4a83decd29111afafa025a30c"}, - {file = "onnx-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fbcdc1a0c1057785bc5f7254aca0cf0b49d19c74696f1ade107638054157315"}, - {file = "onnx-1.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed099fbdada4accead109a4479d5f73fb974566cce8d3c6fca94774f9645934c"}, - {file = "onnx-1.14.0-cp38-cp38-win32.whl", hash = "sha256:296e689aa54a9ae4e560b2bb149a64e96775699a0624af5f631665b9cda90482"}, - {file = "onnx-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:e1607f97007515df303c1f40b77363545af99a1f32d2f73240c8aa526cdbd109"}, - {file = "onnx-1.14.0-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:7800b6ec74b1fe3fbb3bf4a2380e2f4007c1a7f2d6927599ad40eead6eae5e19"}, - {file = "onnx-1.14.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:45d3effe59e20d0a9fdc51f5bb8f38299086c79576b894ed945e6a058c4b210a"}, - {file = "onnx-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a593b46015326feb949781d030cb1d0d5d388cca52bff2e2995badf55d56b38d"}, - {file = "onnx-1.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54614942574415ef3f0bce0800c6f41ecea8201f8042754e204ee8c0a8e473e1"}, - {file = "onnx-1.14.0-cp39-cp39-win32.whl", hash = "sha256:dcfaeb2d15e93c456003fac13ffa35144ba9d2666a83e2cef650dd5c90a2b768"}, - {file = "onnx-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:0639427ac61e5a0181f4f7c89f9fc82b3c9715c95071f9c3de79bbe303a4ae65"}, - {file = "onnx-1.14.0.tar.gz", hash = "sha256:43b85087c6b919de66872a043c7f4899fe6f840e11ffca7e662b2ce9e4cc2927"}, + {file = "onnx-1.14.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:05d8609b4148f8ee4bd5d8186875ccb288300106242fc5201b8b575681bbd5c4"}, + {file = "onnx-1.14.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f131c2fd36f7848437be9de3b1fa5449a94245e16c6f275f66ac7cf8f183ec26"}, + {file = "onnx-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8d7abe048d0e9e31541dc62e9e40b8411b11377d2a22ed842e678802b4e1aa"}, + {file = "onnx-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:921ad325b17484698d9d65978e123b1f351328ea50de6f84f25d09d5c7dde361"}, + {file = "onnx-1.14.1-cp310-cp310-win32.whl", hash = "sha256:6c8156be97762814c7c835d597320ef1f6630f034344fbc672cd6edddbbf78ee"}, + {file = "onnx-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:776ab461515c20cc4e24dbd75af32b6b1e64de931dc5873b049f13bfec1c96e9"}, + {file = "onnx-1.14.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:93e614edaf87ea1adba24663780ac62e30f421c117d695379daa9ff816de821b"}, + {file = "onnx-1.14.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:65672ae827ea5f0e59dc0d1cef1c0ed5083d5e8348946f98f1715ebb123573e9"}, + {file = "onnx-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efa7375d91b1da10badd1d2701a94b0e9b111a5e1a227be1bf877450cea84ac"}, + {file = "onnx-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9cd91b85cfbb0d6478f4a1a0aee4d95cf8839adc48c69130a0cf8452f21db4"}, + {file = "onnx-1.14.1-cp311-cp311-win32.whl", hash = "sha256:1072baf93e04bbbed45f8f997cbbe96e179080b4cd95bc676882fe64aa709dd6"}, + {file = "onnx-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:16a6667aff34431ab828b393ed8153c0a0cf15152d76f8d93aa48fb206217827"}, + {file = "onnx-1.14.1-cp37-cp37m-macosx_10_12_universal2.whl", hash = "sha256:3fde9e1854e525aae93b403c1174bf68dc86ac92b6f8fb4af0fe3ec0d1440631"}, + {file = "onnx-1.14.1-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:58e6eb27c99dbefc84b4234388f5f668b49a1aaeced1580cb96f5fe05800a77c"}, + {file = "onnx-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84653e8e19f5d051f9e7ed9cf7285527fd34e093e3b50554121849664e97c254"}, + {file = "onnx-1.14.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b4a0e029b3604dc5294a7333f622d8c04d6a6a1bc4f51054195074f61b8f41a"}, + {file = "onnx-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:f5046bfbe7f9bab59fc53984aaa5b47a35c8f8e98787053e1650049a1aaf12de"}, + {file = "onnx-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:b37e7bd8baf75efa78ecce713273e2aa29c8c06f69cee6107b413cd03bf59b20"}, + {file = "onnx-1.14.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:758dc585885e997f1086019f098e7ce0a4b3ab7d5a89bb2093572bb68ea906c1"}, + {file = "onnx-1.14.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:486ced7588437ff08a03914ac110d64caa686ff7fa766123d15c8d8eeec29210"}, + {file = "onnx-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:498ecc3e545b80685501c26b62eeeda0b8ae2f2ba8ff3f650ce1f526924aa699"}, + {file = "onnx-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e991e867b799df0d7ed4cdad94c6a3ed9bebaceef3e574ac9eed314e1bfca0ef"}, + {file = "onnx-1.14.1-cp38-cp38-win32.whl", hash = "sha256:a8c3b1398b156f8bae9882ed8c602e1aa5171180fffcbeb1f9a337fe307c1df4"}, + {file = "onnx-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:cf20e7a346d22468a128a40c5cc1f4d20c3939e21e74fc8e3be8ba66c6f82444"}, + {file = "onnx-1.14.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:17f78637d2f6c3c9afad0611fe4c583b6ba4839ac724af0846e5db24dc8dadc0"}, + {file = "onnx-1.14.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:60ad73263a06056f9aa288b082887c6330be08475471c3a009f62439b2a67dca"}, + {file = "onnx-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030aa47e28337fd81f4d884032660e40912a4763ce4e5a4b4144380271390e82"}, + {file = "onnx-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b113fa0183034743e6477fec928e478a6d94eee8d9a4376c144d20d736cdc45"}, + {file = "onnx-1.14.1-cp39-cp39-win32.whl", hash = "sha256:b9c28a99d4a620cb1d31120d35e0fab54073b9725ed50c3cd3ec7beb876e8dba"}, + {file = "onnx-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:bdb15fc4b7f2a8a19abb52ac9672db876f9505e7219e206bcb7530e7c1274e55"}, + {file = "onnx-1.14.1.tar.gz", hash = "sha256:70903afe163643bd71195c78cedcc3f4fa05a2af651fd950ef3acbb15175b2d1"}, ] [package.dependencies] @@ -2283,6 +2756,32 @@ packaging = "*" protobuf = "*" sympy = "*" +[[package]] +name = "opencv-python" +version = "4.8.0.76" +description = "Wrapper package for OpenCV python bindings." +optional = false +python-versions = ">=3.6" +files = [ + {file = "opencv-python-4.8.0.76.tar.gz", hash = "sha256:56d84c43ce800938b9b1ec74b33942b2edbcef3f70c2754eb9bfe5dff1ee3ace"}, + {file = "opencv_python-4.8.0.76-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:67bce4b9aad307c98a9a07c6afb7de3a4e823c1f4991d6d8e88e229e7dfeee59"}, + {file = "opencv_python-4.8.0.76-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:48eb3121d809a873086d6677565e3ac963e6946110d13cd115533fa70e2aa2eb"}, + {file = "opencv_python-4.8.0.76-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93871871b1c9d6b125cddd45b0638a2fa01ee9fd37f5e428823f750e404f2f15"}, + {file = "opencv_python-4.8.0.76-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcb4944211acf13742dbfd9d3a11dc4e36353ffa1746f2c7dcd6a01c32d1376"}, + {file = "opencv_python-4.8.0.76-cp37-abi3-win32.whl", hash = "sha256:b2349dc9f97ed6c9ba163d0a7a24bcef9695a3e216cd143e92f1b9659c5d9a49"}, + {file = "opencv_python-4.8.0.76-cp37-abi3-win_amd64.whl", hash = "sha256:ba32cfa75a806abd68249699d34420737d27b5678553387fc5768747a6492147"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, + {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, + {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, +] + [[package]] name = "opencv-python-headless" version = "4.8.0.76" @@ -2320,71 +2819,180 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "panda3d" +version = "1.10.13" +description = "Panda3D is a framework for 3D rendering and game development for Python and C++ programs." +optional = false +python-versions = "*" +files = [ + {file = "panda3d-1.10.13-cp27-cp27m-macosx_10_6_i386.whl", hash = "sha256:697555595a01c3f5468d49a1ee1f05f57df31e63a251a9ba47a8f534faf0ba15"}, + {file = "panda3d-1.10.13-cp27-cp27m-macosx_10_6_x86_64.whl", hash = "sha256:ccfa288a3575c2996ad7f409f14e8ceb8541a9b1b39bb0162e30f73b1fa0884b"}, + {file = "panda3d-1.10.13-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b2d9870c044e8ac0f6a65e96b6814d122a560adfe045b6ad500bc107aa361726"}, + {file = "panda3d-1.10.13-cp27-cp27m-win32.whl", hash = "sha256:1c35d8676caa1a2c11f91592a6abd8cf18c3ac9496437082e79b4b0671603857"}, + {file = "panda3d-1.10.13-cp27-cp27m-win_amd64.whl", hash = "sha256:f7423bf86d3369f5f7578ec70199f6674ba9f81f80f3eea83d0b27ec7d2522fc"}, + {file = "panda3d-1.10.13-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:017c2a9d40c024de3d68359926dfbb0fef51e219bd2bc6d021464051bba9c80e"}, + {file = "panda3d-1.10.13-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8028fd67c0fc524eae3b7dbfba0b433e3d811e386ba51adbccf1f95a79ef32cb"}, + {file = "panda3d-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50427b08d6dcbe91e95c719b3eb32abe5e806fe265a2cfa66f5b704d22776409"}, + {file = "panda3d-1.10.13-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:597332377b09cb97a1699fed2514c99bcc4241924ce7c21e7577685164576afc"}, + {file = "panda3d-1.10.13-cp310-cp310-manylinux2010_i686.whl", hash = "sha256:fd64c7995fc9d3f07100d4fe225498106cf3851f77bd54be3131193965768041"}, + {file = "panda3d-1.10.13-cp310-cp310-manylinux2010_x86_64.whl", hash = "sha256:acbcdf204ff75abe75b66af5c025b2f42e7777c2acb6cb17375cf7c66751a062"}, + {file = "panda3d-1.10.13-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:73905d348b486675706ac53e454b34138a3ab12faa1058720b6bf40110973be1"}, + {file = "panda3d-1.10.13-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:870da8fb7654f60d04ff5a85c75e80f89f14406c7f8812f4916f5781d1caeb25"}, + {file = "panda3d-1.10.13-cp310-cp310-win32.whl", hash = "sha256:5f75c18323ba3d36236e4756a98cba68268b0dd4ac6e4b889d87904a65b4fbbc"}, + {file = "panda3d-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:7d8a28b10167990d55ac696275b08b21b06fb35e7f5f64d482c9b76410373e96"}, + {file = "panda3d-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52f4b9014235ef5abbd6b5714389dc887ad45828316c42c83af53937179360d8"}, + {file = "panda3d-1.10.13-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d4cbd179bf442fab572bd9a486f866147ce38579e78dd5c01e1f59887bea64b8"}, + {file = "panda3d-1.10.13-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b4419faa2ca1747fccca8e76ca37700c1e011a38a8bad5be104fc703d1216bc3"}, + {file = "panda3d-1.10.13-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:68e9e215c4192ca14ab1304be79f86657f43d6d663b3dbe3af209caf8eb9793c"}, + {file = "panda3d-1.10.13-cp311-cp311-win32.whl", hash = "sha256:3a4df33d18fad4bfbc70907e6f7d6ef27f20ef4d0f3dfb3c17d7e4d46abd78f5"}, + {file = "panda3d-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:55efa8ae9a04260358257e94f29c506e338af0197c5b75dae29b0112dfdd67ac"}, + {file = "panda3d-1.10.13-cp34-cp34m-macosx_10_6_i386.whl", hash = "sha256:f6f016870c22ba0a250958fb09c49843eb17d5620dd1bfa86f38dfd200af9401"}, + {file = "panda3d-1.10.13-cp34-cp34m-macosx_10_6_x86_64.whl", hash = "sha256:fd901d3a822824a9b288392ba6ade77143c8024e208d3ce73a554e364f03cadb"}, + {file = "panda3d-1.10.13-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:4e46ad41febad38f57e4948912f9642838f5bff196c7c336194904e13165172d"}, + {file = "panda3d-1.10.13-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:dc7cd2ee0bd8ca9f7e957b2672f2d8ee1132cd6ddc2f8287d9579efa682b8b01"}, + {file = "panda3d-1.10.13-cp34-cp34m-win32.whl", hash = "sha256:190bd5c58184cae94cf158900106215f20d579f84a1b7ce1474bfbcb797b4a0b"}, + {file = "panda3d-1.10.13-cp34-cp34m-win_amd64.whl", hash = "sha256:372ac3770ada07f1c92848f36f13359c2c00f3eefa525b0b3cad4804ec64c6ca"}, + {file = "panda3d-1.10.13-cp35-cp35m-macosx_10_6_i386.whl", hash = "sha256:496147561f15a738a9a212a69ce3dda340ff1d8ae8bfd0501507945a94153bd8"}, + {file = "panda3d-1.10.13-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:b78acc42a2ee7006dd9618d52ea2657e03d6e39fe4d8c9b4c48342a1d866bbfd"}, + {file = "panda3d-1.10.13-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:35bb1a847a9012e218b1fa768632e4e52644e87ac8daf08fbd4c49feff0672b4"}, + {file = "panda3d-1.10.13-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:fdd564b7f154a78310d6d08920a9a6f405d9e7e9f74fd4b53c9eb73a007d2dea"}, + {file = "panda3d-1.10.13-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:730e5c6b8fb20448478ac1af117cc4aadd8e9dfa2558b5f9e89a69f83ce5f284"}, + {file = "panda3d-1.10.13-cp35-cp35m-win32.whl", hash = "sha256:4da16ba0a2c9615c9d68afdee47681454ece21bf252039078b4662dca856b8f0"}, + {file = "panda3d-1.10.13-cp35-cp35m-win_amd64.whl", hash = "sha256:526a1db71fcf1a2b4eaafb36642c710eb5d2425c9ec96d1afb7fadde4b1af619"}, + {file = "panda3d-1.10.13-cp36-cp36m-macosx_10_6_i386.whl", hash = "sha256:3298dcbfc8a9e87565d35129e3942ba0d076c6c7a504b32f656ccea39b876eb4"}, + {file = "panda3d-1.10.13-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:36c55f8aeb1b4eb305c3f76e0704678bdad80e37d46db71d3f723cbe4aa1eecf"}, + {file = "panda3d-1.10.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e4a7549e3583cd009a1a3ce33f84ed68f9ffd5b3f34f67606af6e8d6dafa32d5"}, + {file = "panda3d-1.10.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1711c7edc7620a8ca2f4fee2e6e448522726678cc5318d59de00d0d1e3f35ea6"}, + {file = "panda3d-1.10.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4c9da46324813e23b1108c0c3906460aa73a052c2934643a0e4a406a1a93dc03"}, + {file = "panda3d-1.10.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c1c77315e2a998f9173fe86d3af63b0affc1e7399a03226881d39ff758cc6b2d"}, + {file = "panda3d-1.10.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:017fdcb16c1a786f12b45a39bd33666423689a94060d1fc6135539d826146eb3"}, + {file = "panda3d-1.10.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b112c8d0ecdc85b14a93f6bae57dbb31043a3bbf1cd3ce3905b143e76ffbfc84"}, + {file = "panda3d-1.10.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:1e18194c78b9b519da885620ddcc7f6709e139645f665f08309b7eff58a5a0ee"}, + {file = "panda3d-1.10.13-cp36-cp36m-win32.whl", hash = "sha256:8eda3bd2402d75fb52962051c157f9495742f1f85c49c38b9bc47091fc0639b0"}, + {file = "panda3d-1.10.13-cp36-cp36m-win_amd64.whl", hash = "sha256:39652709ae0b8167aaf253edacc78898109cce96e872d9e7be33257dd5f98485"}, + {file = "panda3d-1.10.13-cp37-cp37m-macosx_10_6_i386.whl", hash = "sha256:d8c9668f257da744f301bf83ff44b2cdc8bd635e732fadf2f9b09200655cd3e4"}, + {file = "panda3d-1.10.13-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:ece8eb268a0be1fa45b6ef1e6cfa1ae89f25ee926e866893baf8bf391708c02d"}, + {file = "panda3d-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:508df5128cb63163eeee8f3cb6cb2984aefe473ee7e8e328f25a8bf205d54de2"}, + {file = "panda3d-1.10.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:d8a7beb7b88f1fc4ec0061be28018f6da1511783eb6a9911442d2a3435e50f7d"}, + {file = "panda3d-1.10.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a1cae74294394aa4afe93414c799798be77777e5bba77a45368bb1973ca65d2e"}, + {file = "panda3d-1.10.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:94d59874924de7b6765d427ea833ffea38fbb1b72a35679f81ef76c34ddf584d"}, + {file = "panda3d-1.10.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:23806bb1b289ad31f1ec1c206ddbdddd484ecdf8fdb09a4f89bc1c61af6b19ba"}, + {file = "panda3d-1.10.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:6013bf0049bb6b30b0ff17ca5dc6bde621b553f0d1815481ced186ba710aa3b6"}, + {file = "panda3d-1.10.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:e208d1ac385acb48e24f557badfdb6244b0122bc25511c512b893c55ccb1bed3"}, + {file = "panda3d-1.10.13-cp37-cp37m-win32.whl", hash = "sha256:1dea56806643077e863abd8735e7056019ffd2b935990670607b44f996ef8456"}, + {file = "panda3d-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:08f7729e3b13d7417a07b14a3532ee93e4c50293de45392d9783038dcc24cfd4"}, + {file = "panda3d-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8099b2bc211a98ff1d26a91c26d126d538896a59ad29d4de50dea19ccaf31b1b"}, + {file = "panda3d-1.10.13-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:bc41bd97e3a2dd04d0f664ae0ea1a7677ce7c6a718ab4d3b3ff79ef06f0a22c9"}, + {file = "panda3d-1.10.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2027d6d8ac86db0989fbcbafa9f4f1e1a4de79a94f087325e43ccf9e65da12a9"}, + {file = "panda3d-1.10.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:dbfccaf3ff52b4e5de66d33bf06aa489af43cb139367b77fa6bc79ac774d23dc"}, + {file = "panda3d-1.10.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c0785bfa3ad3e87f837433737679dce5e0d1a33accbfcb0dcb0393ce3a7a68f2"}, + {file = "panda3d-1.10.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:e384ed11490d479095cbad2f27ad61f536ca75aeb702557ce83b67381d4448bd"}, + {file = "panda3d-1.10.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8cd5e47e18f56dd6dab7ed1acb21c07f356b7220db72b820f02363050ee3bdfd"}, + {file = "panda3d-1.10.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:094ccf20cdfb2a2f44cde56a1ab9a1287a09cfd33dd4fe094cb255aaacfb678f"}, + {file = "panda3d-1.10.13-cp38-cp38-win32.whl", hash = "sha256:83c39308ee95951616dd5ab1e2ee730853fca9b4331a7c9ea9d92c9af3321aa9"}, + {file = "panda3d-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:2ae1bd477a8882c80bdb6d63d6738ae2c4c335f0d88af592a051a9705434c75d"}, + {file = "panda3d-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:df904a0ca374fd7667727a3d3743c386e913e6e80b8efe67fe0fcd01f904e4c3"}, + {file = "panda3d-1.10.13-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:db8cc33163112722cdf313d06939ff30b260c7a5e2a404b38bddc7558e6e8a26"}, + {file = "panda3d-1.10.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:029d7c698114da30d0d5ecd57e0c92423d319848e4197e6a86ac54b9ae43c424"}, + {file = "panda3d-1.10.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4927f23e8c1469e6a17c3f05a81ac0c6fa7c23b2d39bffee5277ecabfb2da10f"}, + {file = "panda3d-1.10.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2683fc8ad1e98a98ae692dbd82896c3f055743351e7a61a8ecd22557ddef643e"}, + {file = "panda3d-1.10.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:809881853087d36211f380245499778a4838f2f1748a4f8d90546debed294f8b"}, + {file = "panda3d-1.10.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:05be45eeddc81fb4879b0e6b206422b810e2de0f6639ab04522f660270eb202a"}, + {file = "panda3d-1.10.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:a00823d1ae2490dba0643109f10e7347b15e6c2554de0b1bed0a085950fa6ed0"}, + {file = "panda3d-1.10.13-cp39-cp39-win32.whl", hash = "sha256:2d5d3e2160d8fa667ef548171695be84e7de25f51817ae31c61cf2e869a2f6c6"}, + {file = "panda3d-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:839d8b2724a149d56a31f621c6397a1fed597a7dacee478922c711426160990e"}, +] + +[[package]] +name = "panda3d-gltf" +version = "0.13" +description = "glTF utilities for Panda3D" +optional = false +python-versions = ">=3.6" +files = [ + {file = "panda3d-gltf-0.13.tar.gz", hash = "sha256:d06d373bdd91cf530909b669f43080e599463bbf6d3ef00c3558bad6c6b19675"}, + {file = "panda3d_gltf-0.13-py3-none-any.whl", hash = "sha256:02d1a980f447bb1895ff4b48c667f289ba78f07a28ef308d8839b665a621efe2"}, +] + +[package.dependencies] +panda3d = ">=1.10.8" +panda3d-simplepbr = ">=0.6" + +[[package]] +name = "panda3d-simplepbr" +version = "0.10" +description = "A simple, lightweight PBR render pipeline for Panda3D" +optional = false +python-versions = ">=3.6" +files = [ + {file = "panda3d-simplepbr-0.10.tar.gz", hash = "sha256:b6bd902671b02c8794eb60d8039f256a4115faef067c1368cc70147f8ed83a31"}, + {file = "panda3d_simplepbr-0.10-py3-none-any.whl", hash = "sha256:c526a0fa5b2af8a2f16ee2837dcedbf5987ca917ee95699830675204bf765fb4"}, +] + +[package.dependencies] +panda3d = ">=1.10.8" + +[package.extras] +test = ["pylint (==2.4.*)", "pytest", "pytest-pylint"] + [[package]] name = "pandas" -version = "2.0.3" +version = "2.1.0" description = "Powerful data structures for data analysis, time series, and statistics" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, - {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, - {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, - {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, - {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, - {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, - {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, - {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"}, - {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"}, - {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"}, - {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"}, + {file = "pandas-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:40dd20439ff94f1b2ed55b393ecee9cb6f3b08104c2c40b0cb7186a2f0046242"}, + {file = "pandas-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4f38e4fedeba580285eaac7ede4f686c6701a9e618d8a857b138a126d067f2f"}, + {file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e6a0fe052cf27ceb29be9429428b4918f3740e37ff185658f40d8702f0b3e09"}, + {file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d81e1813191070440d4c7a413cb673052b3b4a984ffd86b8dd468c45742d3cc"}, + {file = "pandas-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eb20252720b1cc1b7d0b2879ffc7e0542dd568f24d7c4b2347cb035206936421"}, + {file = "pandas-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:38f74ef7ebc0ffb43b3d633e23d74882bce7e27bfa09607f3c5d3e03ffd9a4a5"}, + {file = "pandas-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cda72cc8c4761c8f1d97b169661f23a86b16fdb240bdc341173aee17e4d6cedd"}, + {file = "pandas-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d97daeac0db8c993420b10da4f5f5b39b01fc9ca689a17844e07c0a35ac96b4b"}, + {file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8c58b1113892e0c8078f006a167cc210a92bdae23322bb4614f2f0b7a4b510f"}, + {file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:629124923bcf798965b054a540f9ccdfd60f71361255c81fa1ecd94a904b9dd3"}, + {file = "pandas-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:70cf866af3ab346a10debba8ea78077cf3a8cd14bd5e4bed3d41555a3280041c"}, + {file = "pandas-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d53c8c1001f6a192ff1de1efe03b31a423d0eee2e9e855e69d004308e046e694"}, + {file = "pandas-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86f100b3876b8c6d1a2c66207288ead435dc71041ee4aea789e55ef0e06408cb"}, + {file = "pandas-2.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28f330845ad21c11db51e02d8d69acc9035edfd1116926ff7245c7215db57957"}, + {file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9a6ccf0963db88f9b12df6720e55f337447aea217f426a22d71f4213a3099a6"}, + {file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99e678180bc59b0c9443314297bddce4ad35727a1a2656dbe585fd78710b3b9"}, + {file = "pandas-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b31da36d376d50a1a492efb18097b9101bdbd8b3fbb3f49006e02d4495d4c644"}, + {file = "pandas-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0164b85937707ec7f70b34a6c3a578dbf0f50787f910f21ca3b26a7fd3363437"}, + {file = "pandas-2.1.0.tar.gz", hash = "sha256:62c24c7fc59e42b775ce0679cfa7b14a5f9bfb7643cfbe708c960699e05fb918"}, ] [package.dependencies] -numpy = [ - {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, - {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, -] +numpy = {version = ">=1.23.2", markers = "python_version >= \"3.11\""} python-dateutil = ">=2.8.2" pytz = ">=2020.1" tzdata = ">=2022.1" [package.extras] -all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] -aws = ["s3fs (>=2021.08.0)"] -clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] -compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] -computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] +all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] +aws = ["s3fs (>=2022.05.0)"] +clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] +compression = ["zstandard (>=0.17.0)"] +computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] feather = ["pyarrow (>=7.0.0)"] -fss = ["fsspec (>=2021.07.0)"] -gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] -hdf5 = ["tables (>=3.6.1)"] -html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] -mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] +fss = ["fsspec (>=2022.05.0)"] +gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] +hdf5 = ["tables (>=3.7.0)"] +html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] +mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] parquet = ["pyarrow (>=7.0.0)"] -performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] +performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] plot = ["matplotlib (>=3.6.1)"] -postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] -spss = ["pyreadstat (>=1.1.2)"] -sql-other = ["SQLAlchemy (>=1.4.16)"] -test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.6.3)"] +postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] +spss = ["pyreadstat (>=1.1.5)"] +sql-other = ["SQLAlchemy (>=1.4.36)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.8.0)"] [[package]] name = "panflute" @@ -2506,13 +3114,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] @@ -2534,6 +3142,25 @@ files = [ dev = ["pylint (>=2.15.10,<2.16.0)", "pytest (>=7.0,<8.0)", "pytest-cov (>=4.0,<5.0)", "sphinx (>=4.2.0,<4.3.0)", "sphinx-rtd-theme (>=1.0.0,<1.1.0)", "toml (>=0.10.2,<0.11.0)"] publish = ["build (>=0.8,<1.0)", "twine (>=4.0,<5.0)"] +[[package]] +name = "portalocker" +version = "2.7.0" +description = "Wraps the portalocker recipe for easy usage" +optional = false +python-versions = ">=3.5" +files = [ + {file = "portalocker-2.7.0-py2.py3-none-any.whl", hash = "sha256:a07c5b4f3985c3cf4798369631fb7011adb498e2a46d8440efc75a8f29a0f983"}, + {file = "portalocker-2.7.0.tar.gz", hash = "sha256:032e81d534a88ec1736d03f780ba073f047a06c478b06e2937486f334e955c51"}, +] + +[package.dependencies] +pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} + +[package.extras] +docs = ["sphinx (>=1.7.1)"] +redis = ["redis"] +tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)"] + [[package]] name = "pprofile" version = "2.1.0" @@ -2546,13 +3173,13 @@ files = [ [[package]] name = "pre-commit" -version = "3.3.3" +version = "3.4.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, - {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, + {file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"}, + {file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"}, ] [package.dependencies] @@ -2562,26 +3189,36 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "progressbar" +version = "2.5" +description = "Text progress bar library for Python." +optional = false +python-versions = "*" +files = [ + {file = "progressbar-2.5.tar.gz", hash = "sha256:5d81cb529da2e223b53962afd6c8ca0f05c6670e40309a7219eacc36af9b6c63"}, +] + [[package]] name = "protobuf" -version = "4.24.0" +version = "4.24.3" description = "" optional = false python-versions = ">=3.7" files = [ - {file = "protobuf-4.24.0-cp310-abi3-win32.whl", hash = "sha256:81cb9c4621d2abfe181154354f63af1c41b00a4882fb230b4425cbaed65e8f52"}, - {file = "protobuf-4.24.0-cp310-abi3-win_amd64.whl", hash = "sha256:6c817cf4a26334625a1904b38523d1b343ff8b637d75d2c8790189a4064e51c3"}, - {file = "protobuf-4.24.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ae97b5de10f25b7a443b40427033e545a32b0e9dda17bcd8330d70033379b3e5"}, - {file = "protobuf-4.24.0-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:567fe6b0647494845d0849e3d5b260bfdd75692bf452cdc9cb660d12457c055d"}, - {file = "protobuf-4.24.0-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:a6b1ca92ccabfd9903c0c7dde8876221dc7d8d87ad5c42e095cc11b15d3569c7"}, - {file = "protobuf-4.24.0-cp37-cp37m-win32.whl", hash = "sha256:a38400a692fd0c6944c3c58837d112f135eb1ed6cdad5ca6c5763336e74f1a04"}, - {file = "protobuf-4.24.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5ab19ee50037d4b663c02218a811a5e1e7bb30940c79aac385b96e7a4f9daa61"}, - {file = "protobuf-4.24.0-cp38-cp38-win32.whl", hash = "sha256:e8834ef0b4c88666ebb7c7ec18045aa0f4325481d724daa624a4cf9f28134653"}, - {file = "protobuf-4.24.0-cp38-cp38-win_amd64.whl", hash = "sha256:8bb52a2be32db82ddc623aefcedfe1e0eb51da60e18fcc908fb8885c81d72109"}, - {file = "protobuf-4.24.0-cp39-cp39-win32.whl", hash = "sha256:ae7a1835721086013de193311df858bc12cd247abe4ef9710b715d930b95b33e"}, - {file = "protobuf-4.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:44825e963008f8ea0d26c51911c30d3e82e122997c3c4568fd0385dd7bacaedf"}, - {file = "protobuf-4.24.0-py3-none-any.whl", hash = "sha256:82e6e9ebdd15b8200e8423676eab38b774624d6a1ad696a60d86a2ac93f18201"}, - {file = "protobuf-4.24.0.tar.gz", hash = "sha256:5d0ceb9de6e08311832169e601d1fc71bd8e8c779f3ee38a97a78554945ecb85"}, + {file = "protobuf-4.24.3-cp310-abi3-win32.whl", hash = "sha256:20651f11b6adc70c0f29efbe8f4a94a74caf61b6200472a9aea6e19898f9fcf4"}, + {file = "protobuf-4.24.3-cp310-abi3-win_amd64.whl", hash = "sha256:3d42e9e4796a811478c783ef63dc85b5a104b44aaaca85d4864d5b886e4b05e3"}, + {file = "protobuf-4.24.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:6e514e8af0045be2b56e56ae1bb14f43ce7ffa0f68b1c793670ccbe2c4fc7d2b"}, + {file = "protobuf-4.24.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:ba53c2f04798a326774f0e53b9c759eaef4f6a568ea7072ec6629851c8435959"}, + {file = "protobuf-4.24.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:f6ccbcf027761a2978c1406070c3788f6de4a4b2cc20800cc03d52df716ad675"}, + {file = "protobuf-4.24.3-cp37-cp37m-win32.whl", hash = "sha256:1b182c7181a2891e8f7f3a1b5242e4ec54d1f42582485a896e4de81aa17540c2"}, + {file = "protobuf-4.24.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b0271a701e6782880d65a308ba42bc43874dabd1a0a0f41f72d2dac3b57f8e76"}, + {file = "protobuf-4.24.3-cp38-cp38-win32.whl", hash = "sha256:e29d79c913f17a60cf17c626f1041e5288e9885c8579832580209de8b75f2a52"}, + {file = "protobuf-4.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:067f750169bc644da2e1ef18c785e85071b7c296f14ac53e0900e605da588719"}, + {file = "protobuf-4.24.3-cp39-cp39-win32.whl", hash = "sha256:2da777d34b4f4f7613cdf85c70eb9a90b1fbef9d36ae4a0ccfe014b0b07906f1"}, + {file = "protobuf-4.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:f631bb982c5478e0c1c70eab383af74a84be66945ebf5dd6b06fc90079668d0b"}, + {file = "protobuf-4.24.3-py3-none-any.whl", hash = "sha256:f6f8dc65625dadaad0c8545319c2e2f0424fede988368893ca3844261342c11a"}, + {file = "protobuf-4.24.3.tar.gz", hash = "sha256:12e9ad2ec079b833176d2921be2cb24281fa591f0b119b208b788adc48c2561d"}, ] [[package]] @@ -2765,66 +3402,66 @@ dev = ["black", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", [[package]] name = "pygame" -version = "2.5.0" +version = "2.5.1" description = "Python Game Development" optional = false python-versions = ">=3.6" files = [ - {file = "pygame-2.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e34a2b5660acc298d0a66ce16f13a7ca1c56c2a685e40afef3a0cf6eaf3f44b3"}, - {file = "pygame-2.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875dbde88b899fb7f48d6f0e87f70c3dcc8ee87a947c3df817d949a9741dbcf5"}, - {file = "pygame-2.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:854dc9106210d1a3a83914af53fc234c0bed65a39f5e6098a8eb489da354ad0c"}, - {file = "pygame-2.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e1898db0fd7b868a31c29204813f447c59390350fd806904d80bebde094f3f8"}, - {file = "pygame-2.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22d5eac9b9936c7dc2813a750bc8efd53234ad1afc32eb99d6f64bb403c2b9aa"}, - {file = "pygame-2.5.0-cp310-cp310-win32.whl", hash = "sha256:e9eed550b8921080a3c7524202822fc2cf7226e0ffd3c4e4d16510ba44b24e6f"}, - {file = "pygame-2.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:93128beb1154c443f05a66bfbf3f1d4eb8659157ab3b45e4a0454e5905440431"}, - {file = "pygame-2.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c71d5b3ec232113cbd2e23a19eb01eef1818db41892d0d5efbac3901f940da66"}, - {file = "pygame-2.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b763062b1996de26a28600e7a8f138d5b36ba0ddd63c65ccbd06124cd95bab70"}, - {file = "pygame-2.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5b6a42109f922352c524565fceb22bf8f8b6e4b00d38306e6f5b4c673048f4a"}, - {file = "pygame-2.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bcb19c8ee3fc794ab3a7cb5b5fb1ad38da6866dfbba4af3699a84a828c8a4b9"}, - {file = "pygame-2.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c66b7abc38511c7ca08c5bb58a3bfc14fa51b4e5f85a1786777afc9e584a14dd"}, - {file = "pygame-2.5.0-cp311-cp311-win32.whl", hash = "sha256:46cf1c9b20fb11c7d836c02dd5fc2ca843b699c0e2bc4130cf4ad2f855db5f7f"}, - {file = "pygame-2.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:f7b77b5019a9a6342535f53c75cef912b218cd24e98505828418f135aacc0a1b"}, - {file = "pygame-2.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ddec0c823fd0869fe4a75ba906dcb7889db0e0c289ce8c03d4ce0a67351ab66"}, - {file = "pygame-2.5.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bae93ce29b8337a5e02507603094c51740c9f496272ef070e2624e9647776568"}, - {file = "pygame-2.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c80a1ad38d11102b4dfa0519aa2a26fea534503b259872609acc9adb1860884e"}, - {file = "pygame-2.5.0-cp312-cp312-win32.whl", hash = "sha256:8ffebcafda0add8072f82999498113be37494694fa36e02155cfaf1a0ba56fe2"}, - {file = "pygame-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:574e310ba708da0c34b71c4158aa7cdca3cf3e16c4100dcd1d3c931a9c6705b4"}, - {file = "pygame-2.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:275e4fab379620c3b262cd58c457eea80001e91bc2e04d306ddb0ba548c969bf"}, - {file = "pygame-2.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c116a96a2784bd1724476dbf9c48bfea466ee493a736bdfa04ecbc3f193de0bc"}, - {file = "pygame-2.5.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4a0787ade8723323a3ba874bb725010bb08990a77327fc85f42474f3a840447"}, - {file = "pygame-2.5.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb39b660da1b56a1704ec4aa72bac538030786e23607fb25b8a66f357ffe3a"}, - {file = "pygame-2.5.0-cp36-cp36m-win32.whl", hash = "sha256:d051420667dd9fc8103b3cf994c03e46ee90b1c4a72c174737b8c14729ddf68e"}, - {file = "pygame-2.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6b58356510b7c38836eb81cf08983b58f280da99580d4f17e89ed0ddb707c29c"}, - {file = "pygame-2.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:80f167d8fcec7cd3107f829784ad721b1b7532c19fdf42b3aabbb51f7347850f"}, - {file = "pygame-2.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef96e9a2d8fd9526b89657d192c42dd7c551bfa381fa98ec52d45443e9713818"}, - {file = "pygame-2.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e21d53279fb504b267ae06b565b72d9f95ecbf1f2dd8c705329b287f38295d98"}, - {file = "pygame-2.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147cc0256a5df1316590f351febf6205ef2907564fb0d902935834b91e183486"}, - {file = "pygame-2.5.0-cp37-cp37m-win32.whl", hash = "sha256:e47696154d689180e4eea8c1d6f2bac923986119219db6ad0d2e60ab1f525e8c"}, - {file = "pygame-2.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4c87fa8fa1f3ea94069119accd6d4b5fbf869c2b2954a19b45162dfb3b7c885e"}, - {file = "pygame-2.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:143550078ab10f290cd7c8715a46853e0dc598fd6cdd1561ecb4d6e3116a6b26"}, - {file = "pygame-2.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:969de806bed49b28972862acba652f05ece9420bbdf5f925c970c6a18a9fd1f9"}, - {file = "pygame-2.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5d3b0da31ea341b86ef96d6b13c0ddcb25f5320186b7215bc870f08119d2f80"}, - {file = "pygame-2.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23effd50121468f1dc41022230485bff515154191a9d343224850aa3ed3b7f0"}, - {file = "pygame-2.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce4116db6924b544ff8ff03f7ef681c8baf9c6e039a1ec21e26b4ebdaa0e994"}, - {file = "pygame-2.5.0-cp38-cp38-win32.whl", hash = "sha256:50a89c15412506d95e98792435f49a73101788db30ad9c562f611c7aa7b437fa"}, - {file = "pygame-2.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:df1d8affdbe9f417cc7141581e3d08e4b09f708060d3127d2016fd591b2e4f68"}, - {file = "pygame-2.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:99965da24d0bf138d9ac6b7494b9a12781c1510cf936616d1d0c46a736777f6a"}, - {file = "pygame-2.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:daa81057c1beb71a9fb96253457197ad03ee988ba546a166f253bd92a98a9a11"}, - {file = "pygame-2.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ca85da605f6621c99c05f272a5dcf85bf0badcdca45f16ff2bee9a9d41ae042"}, - {file = "pygame-2.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:603d403997d46b07022097861c8b0ff76c6192f8a2f5f89f1a6a978d4411b715"}, - {file = "pygame-2.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7babaeac11544f3e4d7a15756a27f943dc5fff276481fdc9d90415a903ad31a9"}, - {file = "pygame-2.5.0-cp39-cp39-win32.whl", hash = "sha256:9d2126f91699223f0c36845d1c7b2cdfe2f1753ef85b8410ea613e8bd212ca33"}, - {file = "pygame-2.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:8062adc409f0b2742d7996b9b470494025c5e3b73d0d03e3798708dcf5d195cd"}, - {file = "pygame-2.5.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:1bd14adf6151b6ac2f617a8fd71621f1c125209c41a359d3c1cf4bf3904dba5f"}, - {file = "pygame-2.5.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b11708f1c7b1671db15246275adcb18cf384f5f7e73532e26999968876c5099"}, - {file = "pygame-2.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6830e431575697f7a11f7731798445242e37eb07ae9007f7be33083f700e9b1e"}, - {file = "pygame-2.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7dd80addfdf7dc1f0e04f81c98acb96580726783172256f2ebc955a967e84124"}, - {file = "pygame-2.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c2cbd9d1a0a3969d6e1c6b0741279c843b4a36ef3804d324874d0a2f0e49816"}, - {file = "pygame-2.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef373b9865c740f18236f2324e17e7f2111e27c6a4a5b67c490c72a8a8b8de77"}, - {file = "pygame-2.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3959038a3e2034cee3f15471786a3bac35baeaa1f7503dc7402bb49d25b5ddbc"}, - {file = "pygame-2.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:583d9c8ad033ad51da8485427139d047afb649f49e42d4fa88477f73734ad4b0"}, - {file = "pygame-2.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b650e925d2e8c82c16bdeae6e7fc5d6ca4ac659a1412da4ecd923ef9d688cb"}, - {file = "pygame-2.5.0.tar.gz", hash = "sha256:edd5745b79435976d92c0a7318aedcafcb7ac4567125ac6ba88aa473559ef9ab"}, + {file = "pygame-2.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:afc3d7d125baba727785fd4a05b2a99a0ee1c0cff6db7321e65607a3f644724d"}, + {file = "pygame-2.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:423168a16e89e02b4addfd3c2b68cfa9826bd675aa18141435de16da85143afe"}, + {file = "pygame-2.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8722cae725c49493603509087d5c4e6b9200993ab998663a553df3279237c52d"}, + {file = "pygame-2.5.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:757f74c029c6d0fbed8f778034c7fa5be77f3d1cf217e9eda48d8508b7672d14"}, + {file = "pygame-2.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f597d1c57d4297b518865d95539fa70decbba77809bda66278baf0df98b6e2cf"}, + {file = "pygame-2.5.1-cp310-cp310-win32.whl", hash = "sha256:65d1983837fd1f8a9fee3576500e666fcdd4efb20c4b8d0edab2ff6921c67aa8"}, + {file = "pygame-2.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:30fe97290f0fcc6a6f45a939a95ae44477a9b36cd89f93a7435bdad6d6a2bedd"}, + {file = "pygame-2.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8fbf01919feea464b57b505973f68386d73d2cd08cf52078c578e3bffcf03c84"}, + {file = "pygame-2.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f0b237cee5147f95bcbc98d8c1f13da4a6a637256a274021313db6a11ab8cebf"}, + {file = "pygame-2.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57bf8237fa2dcc16ddb4cd2eddaa52009e6c84f219c051b118d8863ea7f05433"}, + {file = "pygame-2.5.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93de41a54909f5622f01671d04e22c4dbc889ae8c3d1b1ee1b25d4e83077683f"}, + {file = "pygame-2.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6621baf985d8aec2b1089d86dcbf7b53ed1b235d9b372b1083e385f8d6ef9ee1"}, + {file = "pygame-2.5.1-cp311-cp311-win32.whl", hash = "sha256:1f90e3e6677cfc56bb04b13fb6c6e61e5f24d9373b27d33942ba7e4da0255c8d"}, + {file = "pygame-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:b06122f17e5c6ec7316c454613a34d697abfed94ed1029f26b804d20ef6ba550"}, + {file = "pygame-2.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63f7b0cd0ea631161b72056ce6d8ee0eb6af809e1a1f96c5fd338ec8b7e9eb33"}, + {file = "pygame-2.5.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b0ab1e29459f5e6d88c3c586f59113172846f8800f0a166d1605a0f459f8242"}, + {file = "pygame-2.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9c01780e8a11047c0a9f9b09db023d736c2221a292f2542b398f78a0bf15f32"}, + {file = "pygame-2.5.1-cp312-cp312-win32.whl", hash = "sha256:643a0556b563a9ae9448ffdec459bb84d200be666e4fd197d68be15b6c2b4826"}, + {file = "pygame-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:2cb3d56f34147c4c6653c4dac8d58e0791a298abff762aab147679e6935e6cd0"}, + {file = "pygame-2.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2377534fe2947ae6eaec1b7469f438ae4e4f3ff22c67b80e0c580215a52654ce"}, + {file = "pygame-2.5.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88d2a8880200035017696783475b6f32b0e551fbb23a52f0223264b24d680949"}, + {file = "pygame-2.5.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c6064bc879ef092194cf21ba25433500a0f8006979ef9ae145081ed1767de7"}, + {file = "pygame-2.5.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f983d62e659e7b2644605b690e2b72683b2d0d1124f09274ffa2cc8658648546"}, + {file = "pygame-2.5.1-cp36-cp36m-win32.whl", hash = "sha256:57c361a402db63224160082451721caafc138e6e27aba5f30f9268672fb8f3d1"}, + {file = "pygame-2.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cbcd03784a1858c2c5b91b6ba65c1905b9e30a2e150759f5a54af3241e008fbc"}, + {file = "pygame-2.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be1ec9bf870d155b978811d115363c89eb10418c78295bb58b7bbd4e679d0010"}, + {file = "pygame-2.5.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1b2d1fcb51e60ecacd18f2a03f2a6023453ab64298fd4f54d8c50a5b2a13bff"}, + {file = "pygame-2.5.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9212522069fca25cadf3a8044383d01f5451833b66f950cb9bc6d49406f88cee"}, + {file = "pygame-2.5.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4276c2688b11e098bb3732e7e243f522a58d0cd662bc72008727fc7671277771"}, + {file = "pygame-2.5.1-cp37-cp37m-win32.whl", hash = "sha256:9463b7c80994eabcb6f9460a7859241fe030c6654d041a2f26ce12366ae202f3"}, + {file = "pygame-2.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:11cb40204cad95b7c23aee32bac048a531e3528a6f12dd3601504ec0b6000c06"}, + {file = "pygame-2.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:426897ec90e91a05c3d41c44875793568d4b20065f086ac476729a9e557e8976"}, + {file = "pygame-2.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:184d8e3b025ca6cbd199235faece6a7b911725566f40c71e0773058f1e189da2"}, + {file = "pygame-2.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b378ba0756173e9dc8cd7e0c40cb6ba217b122a6bef2ce3479293d543cc9777"}, + {file = "pygame-2.5.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62f09b59b9702ccf9d7a6e7c6ebb1fca5f0072c30e99b4f786c5478876433f0c"}, + {file = "pygame-2.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbcc7284369118299ce935062ef0c79d67503ad3b86bd6b02a29211279a46244"}, + {file = "pygame-2.5.1-cp38-cp38-win32.whl", hash = "sha256:3df49bb58dcd70ed76e8da0e936a267c57245e5b057ceeaa9070340d7d49162a"}, + {file = "pygame-2.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:4c410bd1d0807820fef48f15452366d0ef100d966323835ced818f7833d16a5a"}, + {file = "pygame-2.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c0a4f415dffa0b9d9099ee01647b1d6794f6a96b2b909997e05acff85e745c"}, + {file = "pygame-2.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a0eb2cae526a39a8e213477ce6d4947d7b7fb50757fbdb0275c9e3ee1bca22df"}, + {file = "pygame-2.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a232083d54c5936f40cb493d9cced38702a03dd7918fefd9a6b6522875714e5"}, + {file = "pygame-2.5.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e234c4d58b76dfc0d32b337e46e2191fa4dad7b3b0c7110d5c97076f1bf8cbd4"}, + {file = "pygame-2.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e9816fa15aea99b6ea2e0b88ba07e83cc19a152c3724016d323823e6e713454"}, + {file = "pygame-2.5.1-cp39-cp39-win32.whl", hash = "sha256:7613ccb5c1800234c7e2d090f5f0be9d72f5fd76e49acc458bcc33797ab0de97"}, + {file = "pygame-2.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:0618d8648e8d890a1bfc04a2ffd043a9b20dbc2e175d1347402e044729a5515b"}, + {file = "pygame-2.5.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:ea3bce8488f2bdb83a874f7799e229be9e0a1b70d2cff9c47234c19a54ab868e"}, + {file = "pygame-2.5.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:581ef66dfbff883fc4a172f57395db4e8c97d3381860e81943261a5a5214fde7"}, + {file = "pygame-2.5.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e4bbe4b94cd0dbf592ee32be5561b0cad8327aff92c36e7a1a1372ed804229e"}, + {file = "pygame-2.5.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:05b13f5994efd61c3de50ef0b5062fa3396e3fd851e99e215a7e3da78da49561"}, + {file = "pygame-2.5.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e21a79fd5db155164ad3341f12c89e121ce53cb4ea1856527dfa9daeb3f6016d"}, + {file = "pygame-2.5.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8c5953767eb459d9a32ca52399c2f379af807a24e00163f1bcb1aacc1e81d59"}, + {file = "pygame-2.5.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:84e4a4da14235d812b65081a87dbe89a4d45a101d06a4f33cf386be825e8797e"}, + {file = "pygame-2.5.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a78eb81082bf460249f0e7267db8d2f401f37f538b673125719be1db504584c"}, + {file = "pygame-2.5.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfb90539845885c4bb7861d6c7fe84ffb19de466b6d55fc39b707e8ea261ff93"}, + {file = "pygame-2.5.1.tar.gz", hash = "sha256:b7f88720be5c740576fd988dc0375328dc1adb070869654a245531e03df46262"}, ] [[package]] @@ -2852,6 +3489,9 @@ files = [ {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, ] +[package.dependencies] +cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} + [package.extras] crypto = ["cryptography (>=3.4.0)"] dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] @@ -2922,40 +3562,42 @@ cffi = ">=1.0.0" [[package]] name = "pyopencl" -version = "2023.1.1" +version = "2023.1.2" description = "Python wrapper for OpenCL" optional = false python-versions = "~=3.8" files = [ - {file = "pyopencl-2023.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:57fe5635d1f34c64127c99bb652027d2f2955235426e162bd9ab1b43f75c989b"}, - {file = "pyopencl-2023.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:86ac050bd506b39a4fc92350d2a30f61d5a0eaffa12d51453e9e6dab27f4f5e6"}, - {file = "pyopencl-2023.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2cb8cb5b79c68b2916ed9f0710bc5f72aa18e53fb0fb4e9abdbde9ff0ba1f1"}, - {file = "pyopencl-2023.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c562081400998da0acec3ed408ff05dee6026050873b0876e069e9a3b28456ab"}, - {file = "pyopencl-2023.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:595d0b36a853bb853e542e98dd398321e92a51c0f67187981b336079ef26ad42"}, - {file = "pyopencl-2023.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:78353062ed6a78200e4a4b833bdc9532ead7eabb4028fee71aa13cb7a199345e"}, - {file = "pyopencl-2023.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c3a30db6a1533efd806674ecd2fdab8384f19dc454bb4934295ec9a78559125"}, - {file = "pyopencl-2023.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8101dcc6152d5b332b21690a43e7633839928c3c86135ceffb0b9828f024c792"}, - {file = "pyopencl-2023.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d9bb87606ff1762d95f5071551de75ef4d9203b77900cc08ba5494192fa3224"}, - {file = "pyopencl-2023.1.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7396461693dce54f93fcb20aaa8759f059358fe644e72c2c8267d48c5d7f05f7"}, - {file = "pyopencl-2023.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1e6537694cacf3c8c8b79f55ed07553c229a1ab453c29d9d817d6d1697b63dc5"}, - {file = "pyopencl-2023.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:8073b61ec027c25a12cb2df477206425d062cda2bc1ba275d2d91c3321168b20"}, - {file = "pyopencl-2023.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:10a47398b1a888fed5b8f31f456ee3ea11e5a03538818a9c5da609448649cbaf"}, - {file = "pyopencl-2023.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6f846358866178e6c27efccee9c3792e7b8ad7009261b2a522b777d10c0fcd6"}, - {file = "pyopencl-2023.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd43d5f33cdce303471e6bd935de7a4b953842187a341fdeb1dc5c168f30537"}, - {file = "pyopencl-2023.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d59722a7b2da7df1a379d9410a7be50411181124ca8aba38cd0b3b825753b002"}, - {file = "pyopencl-2023.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7a9726e80cabd8d2e5d93327e1757b151e011770fe879f2fb9cda8d6d331e6cc"}, - {file = "pyopencl-2023.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0b16d6ceff0c8c39eb41bdddb024e95744057d2b3e3ab009d417aabaf855e0c2"}, - {file = "pyopencl-2023.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f73d12fcbe080c5791906eb2fe1de51dcb970f3afff290e29dd97dcb162ce25e"}, - {file = "pyopencl-2023.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6695f2c36da2052e61a18654aa0bc7982650d00dd655d50cbfeaf850cb3a0153"}, - {file = "pyopencl-2023.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc5c21eab1b15f95afe55d22bb7c071ef1ce2d41e3eccf544f6fb65651157aef"}, - {file = "pyopencl-2023.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b518eeec8aff8a4f52a48efc4cc8844b2b397b8162ff769acda4ed030036b58f"}, - {file = "pyopencl-2023.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:32f89c321fcff28a9a7487559b8c422acefdac2a2673e5290e1c4a1178308336"}, - {file = "pyopencl-2023.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:c958a6d5f3aacc66c8ef60218b69504dd50a62cd613fd5cec2195112edef6a96"}, - {file = "pyopencl-2023.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ff92c98ee2e2cd90512b473c28e9db1f00bbde4aa1008ec0a2adb1e4c51d11"}, - {file = "pyopencl-2023.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f9d6d2e0f5961ae1c5a612203e6bae7a0361b88efec7dbc3b6c024fa6670a6"}, - {file = "pyopencl-2023.1.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b4cecc30bbf32cdc0bb0081119e6e0e8ae7bebf5041d371d2f9188e05541e5b"}, - {file = "pyopencl-2023.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1373f98d49d2dc452a2748c6cd38fad37a09032cf983013785715a530b0cafff"}, - {file = "pyopencl-2023.1.1.tar.gz", hash = "sha256:0ad92578a94a0be0dedd5ca4fcb6e27b5a75de4e5fac757f04c9044bd9d42444"}, + {file = "pyopencl-2023.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c69c7595e1bab949e4702dfdaad9ae97005cc071dc42eb67c2b0c3aed1cbaac"}, + {file = "pyopencl-2023.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cd95634f772c45b8cf48c2c3b1c5018e7e5aee35f7f3a3d3c514d92b19c13ae"}, + {file = "pyopencl-2023.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c34f4a912525819a2427bf0dca3b1b540b69321bd48c578c0fdfd0ff4ac98c94"}, + {file = "pyopencl-2023.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be7fbeee10b6f5de54043efda430d6b940efcd3976c14beea7740c0b6d2679ea"}, + {file = "pyopencl-2023.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ded99985c2169142f3ad6fd97ff2b173ab0755f8c83796d8182d89953a47622b"}, + {file = "pyopencl-2023.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:f36c9e4ca6f82f3d4d514ab256405595ded7a9d3a9615002ca270dd0e9690a04"}, + {file = "pyopencl-2023.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d5633e9081d4a08f7b93a0e79eae003c5deb48d30504ce33304090050ba2d54"}, + {file = "pyopencl-2023.1.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:deee455f432a95b8c649e1bc8f5c8228aaa94d359aa31416830521c6d7a0a264"}, + {file = "pyopencl-2023.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8834cedaf9b0fbef1bb0bdbe24d24262c61c4ee23f4363bb1aef5dd0753152af"}, + {file = "pyopencl-2023.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:77196615fc3b0c69ee9468e5e8132b1e0e8b727d709e3a193fd1c7f8944f2d34"}, + {file = "pyopencl-2023.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6917dc9cf7420df5d52280e2c6d5d95f3008c5c2f1e13842b825bbf4bf2a462e"}, + {file = "pyopencl-2023.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:056e5753ce48e8f5e3ac421c845b068afa8f11877098beab5093ad48fe7f7e27"}, + {file = "pyopencl-2023.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d6e822f2f91b9c6876ed672139ba531c7d8e530d10dd19961b0c7335890bbd74"}, + {file = "pyopencl-2023.1.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:367fa10516ed80c308f02efd75ec5f60604543710ea06cb1c0e3553574513602"}, + {file = "pyopencl-2023.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9756e06ce74919679b7c4c4b9e58a9e57928d47d77112a5bd2598b840dd49ccb"}, + {file = "pyopencl-2023.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a09ad6fddef2cb8179ddfdee3a041141912c0eef9677440ac84dff8a06d0b1ea"}, + {file = "pyopencl-2023.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b96c23353214cd12761963d5c866f2ff617ff746653ea36060f3a911bf7a020c"}, + {file = "pyopencl-2023.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:503e5d4a3c6bf7c258e0619eb30835e0e7e42565237e2f6e21a68d34d9d350ac"}, + {file = "pyopencl-2023.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:458272e79c74c9d12bbb3ae2b9ab97f5109f60ab97f2e99c6e492007ce01b9ed"}, + {file = "pyopencl-2023.1.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbd7a49ad050807ebbc1fa64815c9cf22fbd73b5f7638852fe68d9b9dde075be"}, + {file = "pyopencl-2023.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ea121d1307419c89ecb766e416228fc831b4dc1c936329d870e6e55cfd26f13"}, + {file = "pyopencl-2023.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a168d96b2e0c5ddb414a74d4d045b82b2f3d0630527a7922c03ac7739eb3632"}, + {file = "pyopencl-2023.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3631eefd30966ad35b07b6ceeb2c59ac8cee2e823534b1d3fa97acbb91a41399"}, + {file = "pyopencl-2023.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:9f11ede4c039c8a472f23be6f8e62704986cba019e8a13a8a5af4d8b606a783c"}, + {file = "pyopencl-2023.1.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e01b88b873ae644e6edc05d4a6d935e9da371ae46b1a78c4022c23046401a4bb"}, + {file = "pyopencl-2023.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:311b14ac9def1538fe7d4b01dd00d6e6e8b296fdfe6a7bacef739552bd477653"}, + {file = "pyopencl-2023.1.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffdb4cbcb795fca5a27be101f02e85aca24637471e9b53303f232e293646d72b"}, + {file = "pyopencl-2023.1.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b02ac162218cbabe5287244fb7cea906ca4d3d6716d8fc952526e1b55ad63d9"}, + {file = "pyopencl-2023.1.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d67b55c92c769da5b994eb2f56e712d48cfdf4b534f8df20c6ab74346129357d"}, + {file = "pyopencl-2023.1.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:068cd66ea53c98c745614697ccafd147e10bffaa761feccd3b1e90fa7cdc483c"}, + {file = "pyopencl-2023.1.2.tar.gz", hash = "sha256:eb00cd574049d592b679dcf8bfe7ab4a36c94a39fd1acb1a6b45d6c0d7be9a68"}, ] [package.dependencies] @@ -2988,13 +3630,13 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] name = "pyparsing" -version = "3.0.9" +version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, ] [package.extras] @@ -3010,6 +3652,43 @@ files = [ {file = "pyprof2calltree-1.4.5.tar.gz", hash = "sha256:a635672ff31677486350b2be9a823ef92f740e6354a6aeda8fa4a8a3768e8f2f"}, ] +[[package]] +name = "pyproj" +version = "3.6.0" +description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pyproj-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e600f6a2771d3b41aeb2cc1efd96771ae9a01451013da1dd48ff272e7c6e34ef"}, + {file = "pyproj-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d7f6cd045df29aae960391dfe06a575c110af598f1dea5add8be6ca42332b0f5"}, + {file = "pyproj-3.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:557e6592855111c84eda176ddf6b130f55d5e2b9cb1c017b8c91b69f37f474f5"}, + {file = "pyproj-3.6.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de6288b6ceabdeeac01abf627c74414822d322d8f55dc8efe4d29dedd27c5719"}, + {file = "pyproj-3.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e427ccdbb1763872416549bdfa9fa1f5f169054653c4daf674e71480cc39cf11"}, + {file = "pyproj-3.6.0-cp310-cp310-win32.whl", hash = "sha256:1283d3c1960edbb74828f5f3405b27578a9a27f7766ab6a3956f4bd851f08239"}, + {file = "pyproj-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:9de1aab71234bfd3fd648a1152519b5ee152c43113d7d8ea52590a0140129501"}, + {file = "pyproj-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:00fab048596c17572fa8980014ef117dbb2a445e6f7ba3b9ddfcc683efc598e7"}, + {file = "pyproj-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba5e7c8ddd6ed5a3f9fcf95ea80ba44c931913723de2ece841c94bb38b200c4a"}, + {file = "pyproj-3.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08dfc5c9533c78a97afae9d53b99b810a4a8f97c3be9eb2b8f323b726c736403"}, + {file = "pyproj-3.6.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18a8bdb87aeb41b60a2e91d32f623227de3569fb83b4c64b174c3a7c5b0ed3ae"}, + {file = "pyproj-3.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfe392dfc0eba2248dc08c976a72f52ff9da2bddfddfd9ff5dcf18e8e88200c7"}, + {file = "pyproj-3.6.0-cp311-cp311-win32.whl", hash = "sha256:78276c6b0c831255c97c56dff7313a3571f327a284d8ac63d6a56437a72ed0e0"}, + {file = "pyproj-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:8fbac2eb9a0e425d7d6b7c6f4ebacd675cf3bdef0c59887057b8b4b0374e7c12"}, + {file = "pyproj-3.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:95120d65cbc5983dfd877076f28dbc18b9b329cbee38ca6e217bb7a5a043c099"}, + {file = "pyproj-3.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:830e6de7cfe43853967afee5ef908dfd5aa72d1ec12af9b9e3fecc179886e346"}, + {file = "pyproj-3.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e342b3010b2b20134671564ff9a8c476e5e512bf589477480aded1a5813af7c8"}, + {file = "pyproj-3.6.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23787460fab85ba2f857ee60ffb2e8e21fd9bd5db9833c51c1c05b2a6d9f0be5"}, + {file = "pyproj-3.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595376e4d3bb72b7dceeccbce0f4c43053d47561f17a1ad0224407e9980ee849"}, + {file = "pyproj-3.6.0-cp39-cp39-win32.whl", hash = "sha256:4d8a9773503085eada59b6892c96ddf686ab8cf64cfdc18ad744d13ee76dfa6f"}, + {file = "pyproj-3.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:137a07404f937f264b11b7130cd4cfa00002dbe4333b222e8056db84849c2ea4"}, + {file = "pyproj-3.6.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2799499a4045e4fb73e44c31bdacab0593a253a7a4b6baae6fdd27d604cf9bc2"}, + {file = "pyproj-3.6.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f04f6297c615c3b17f835df2556ac8fb9b4f51f281e960437eaf0cd80e7ae26a"}, + {file = "pyproj-3.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a4d2d438b007cb1f8d5f6f308d53d7ff9a2508cff8f9da6e2a93b76ffd98aaf"}, + {file = "pyproj-3.6.0.tar.gz", hash = "sha256:a5b111865b3f0f8b77b3983f2fbe4dd6248fc09d3730295949977c8dcd988062"}, +] + +[package.dependencies] +certifi = "*" + [[package]] name = "pyqt5" version = "5.15.9" @@ -3087,13 +3766,13 @@ cp2110 = ["hidapi"] [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -3105,6 +3784,81 @@ pluggy = ">=0.12,<2.0" [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-cpp" +version = "2.4.0" +description = "Use pytest's runner to discover and execute C++ tests" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cpp-2.4.0.tar.gz", hash = "sha256:dcb8b09d4c714fc7f778dc40a7c2c47e73cc50280d7f9bba3bdd3ca98abd4685"}, + {file = "pytest_cpp-2.4.0-py3-none-any.whl", hash = "sha256:e7c5f40b70b00cc09d672a1584e35beb9b9553512cdd4b1d1f99468747b3bc31"}, +] + +[package.dependencies] +colorama = "*" +pytest = ">=7.0" + +[[package]] +name = "pytest-subtests" +version = "0.11.0" +description = "unittest subTest() support and subtests fixture" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-subtests-0.11.0.tar.gz", hash = "sha256:51865c88457545f51fb72011942f0a3c6901ee9e24cbfb6d1b9dc1348bafbe37"}, + {file = "pytest_subtests-0.11.0-py3-none-any.whl", hash = "sha256:453389984952eec85ab0ce0c4f026337153df79587048271c7fd0f49119c07e4"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +pytest = ">=7.0" + +[[package]] +name = "pytest-timeout" +version = "2.1.0" +description = "pytest plugin to abort hanging tests" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-timeout-2.1.0.tar.gz", hash = "sha256:c07ca07404c612f8abbe22294b23c368e2e5104b521c1790195561f37e1ac3d9"}, + {file = "pytest_timeout-2.1.0-py3-none-any.whl", hash = "sha256:f6f50101443ce70ad325ceb4473c4255e9d74e3c7cd0ef827309dfa4c0d975c6"}, +] + +[package.dependencies] +pytest = ">=5.0.0" + +[[package]] +name = "pytest-timeouts" +version = "1.2.1" +description = "Linux-only Pytest plugin to control durations of various test case execution phases" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pytest-timeouts-1.2.1.tar.gz", hash = "sha256:390351afc7ecb422ea0ec38081e0acd91cad416b383944a9a3358087de50c2fb"}, +] + +[package.dependencies] +pytest = ">=3.1" + [[package]] name = "pytest-xdist" version = "3.3.1" @@ -3158,13 +3912,36 @@ numpy = ["numpy (>=1.6.0)"] [[package]] name = "pytz" -version = "2023.3" +version = "2023.3.post1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, - {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] [[package]] @@ -3342,32 +4119,64 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "ruff" +version = "0.0.290" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.290-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:0e2b09ac4213b11a3520221083866a5816616f3ae9da123037b8ab275066fbac"}, + {file = "ruff-0.0.290-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:4ca6285aa77b3d966be32c9a3cd531655b3d4a0171e1f9bf26d66d0372186767"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35e3550d1d9f2157b0fcc77670f7bb59154f223bff281766e61bdd1dd854e0c5"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d748c8bd97874f5751aed73e8dde379ce32d16338123d07c18b25c9a2796574a"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:982af5ec67cecd099e2ef5e238650407fb40d56304910102d054c109f390bf3c"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bbd37352cea4ee007c48a44c9bc45a21f7ba70a57edfe46842e346651e2b995a"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d9be6351b7889462912e0b8185a260c0219c35dfd920fb490c7f256f1d8313e"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75cdc7fe32dcf33b7cec306707552dda54632ac29402775b9e212a3c16aad5e6"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb07f37f7aecdbbc91d759c0c09870ce0fb3eed4025eebedf9c4b98c69abd527"}, + {file = "ruff-0.0.290-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2ab41bc0ba359d3f715fc7b705bdeef19c0461351306b70a4e247f836b9350ed"}, + {file = "ruff-0.0.290-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:150bf8050214cea5b990945b66433bf9a5e0cef395c9bc0f50569e7de7540c86"}, + {file = "ruff-0.0.290-py3-none-musllinux_1_2_i686.whl", hash = "sha256:75386ebc15fe5467248c039f5bf6a0cfe7bfc619ffbb8cd62406cd8811815fca"}, + {file = "ruff-0.0.290-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ac93eadf07bc4ab4c48d8bb4e427bf0f58f3a9c578862eb85d99d704669f5da0"}, + {file = "ruff-0.0.290-py3-none-win32.whl", hash = "sha256:461fbd1fb9ca806d4e3d5c745a30e185f7cf3ca77293cdc17abb2f2a990ad3f7"}, + {file = "ruff-0.0.290-py3-none-win_amd64.whl", hash = "sha256:f1f49f5ec967fd5778813780b12a5650ab0ebcb9ddcca28d642c689b36920796"}, + {file = "ruff-0.0.290-py3-none-win_arm64.whl", hash = "sha256:ae5a92dfbdf1f0c689433c223f8dac0782c2b2584bd502dfdbc76475669f1ba1"}, + {file = "ruff-0.0.290.tar.gz", hash = "sha256:949fecbc5467bb11b8db810a7fa53c7e02633856ee6bd1302b2f43adcd71b88d"}, +] + [[package]] name = "scipy" -version = "1.11.1" +version = "1.11.2" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = "<3.13,>=3.9" files = [ - {file = "scipy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aec8c62fbe52914f9cf28d846cf0401dd80ab80788bbab909434eb336ed07c04"}, - {file = "scipy-1.11.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:3b9963798df1d8a52db41a6fc0e6fa65b1c60e85d73da27ae8bb754de4792481"}, - {file = "scipy-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e8eb42db36526b130dfbc417609498a6192381abc1975b91e3eb238e0b41c1a"}, - {file = "scipy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:366a6a937110d80dca4f63b3f5b00cc89d36f678b2d124a01067b154e692bab1"}, - {file = "scipy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:08d957ca82d3535b3b9ba6c8ff355d78fe975271874e2af267cb5add5bd78625"}, - {file = "scipy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:e866514bc2d660608447b6ba95c8900d591f2865c07cca0aa4f7ff3c4ca70f30"}, - {file = "scipy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba94eeef3c9caa4cea7b402a35bb02a5714ee1ee77eb98aca1eed4543beb0f4c"}, - {file = "scipy-1.11.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:512fdc18c65f76dadaca139348e525646d440220d8d05f6d21965b8d4466bccd"}, - {file = "scipy-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cce154372f0ebe88556ed06d7b196e9c2e0c13080ecb58d0f35062dc7cc28b47"}, - {file = "scipy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4bb943010203465ac81efa392e4645265077b4d9e99b66cf3ed33ae12254173"}, - {file = "scipy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:249cfa465c379c9bb2c20123001e151ff5e29b351cbb7f9c91587260602c58d0"}, - {file = "scipy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:ffb28e3fa31b9c376d0fb1f74c1f13911c8c154a760312fbee87a21eb21efe31"}, - {file = "scipy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:39154437654260a52871dfde852adf1b93b1d1bc5dc0ffa70068f16ec0be2624"}, - {file = "scipy-1.11.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:b588311875c58d1acd4ef17c983b9f1ab5391755a47c3d70b6bd503a45bfaf71"}, - {file = "scipy-1.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d51565560565a0307ed06fa0ec4c6f21ff094947d4844d6068ed04400c72d0c3"}, - {file = "scipy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b41a0f322b4eb51b078cb3441e950ad661ede490c3aca66edef66f4b37ab1877"}, - {file = "scipy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:396fae3f8c12ad14c5f3eb40499fd06a6fef8393a6baa352a652ecd51e74e029"}, - {file = "scipy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:be8c962a821957fdde8c4044efdab7a140c13294997a407eaee777acf63cbf0c"}, - {file = "scipy-1.11.1.tar.gz", hash = "sha256:fb5b492fa035334fd249f0973cc79ecad8b09c604b42a127a677b45a9a3d4289"}, + {file = "scipy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2b997a5369e2d30c97995dcb29d638701f8000d04df01b8e947f206e5d0ac788"}, + {file = "scipy-1.11.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:95763fbda1206bec41157582bea482f50eb3702c85fffcf6d24394b071c0e87a"}, + {file = "scipy-1.11.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e367904a0fec76433bf3fbf3e85bf60dae8e9e585ffd21898ab1085a29a04d16"}, + {file = "scipy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d690e1ca993c8f7ede6d22e5637541217fc6a4d3f78b3672a6fe454dbb7eb9a7"}, + {file = "scipy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d2b813bfbe8dec6a75164523de650bad41f4405d35b0fa24c2c28ae07fcefb20"}, + {file = "scipy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:afdb0d983f6135d50770dd979df50bf1c7f58b5b33e0eb8cf5c73c70600eae1d"}, + {file = "scipy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d9886f44ef8c9e776cb7527fb01455bf4f4a46c455c4682edc2c2cc8cd78562"}, + {file = "scipy-1.11.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1342ca385c673208f32472830c10110a9dcd053cf0c4b7d4cd7026d0335a6c1d"}, + {file = "scipy-1.11.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b133f237bd8ba73bad51bc12eb4f2d84cbec999753bf25ba58235e9fc2096d80"}, + {file = "scipy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aeb87661de987f8ec56fa6950863994cd427209158255a389fc5aea51fa7055"}, + {file = "scipy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90d3b1364e751d8214e325c371f0ee0dd38419268bf4888b2ae1040a6b266b2a"}, + {file = "scipy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:f73102f769ee06041a3aa26b5841359b1a93cc364ce45609657751795e8f4a4a"}, + {file = "scipy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa4909c6c20c3d91480533cddbc0e7c6d849e7d9ded692918c76ce5964997898"}, + {file = "scipy-1.11.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ac74b1512d38718fb6a491c439aa7b3605b96b1ed3be6599c17d49d6c60fca18"}, + {file = "scipy-1.11.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8425fa963a32936c9773ee3ce44a765d8ff67eed5f4ac81dc1e4a819a238ee9"}, + {file = "scipy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:542a757e2a6ec409e71df3d8fd20127afbbacb1c07990cb23c5870c13953d899"}, + {file = "scipy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea932570b1c2a30edafca922345854ff2cd20d43cd9123b6dacfdecebfc1a80b"}, + {file = "scipy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:4447ad057d7597476f9862ecbd9285bbf13ba9d73ce25acfa4e4b11c6801b4c9"}, + {file = "scipy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b0620240ef445b5ddde52460e6bc3483b7c9c750275369379e5f609a1050911c"}, + {file = "scipy-1.11.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f28f1f6cfeb48339c192efc6275749b2a25a7e49c4d8369a28b6591da02fbc9a"}, + {file = "scipy-1.11.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:214cdf04bbae7a54784f8431f976704ed607c4bc69ba0d5d5d6a9df84374df76"}, + {file = "scipy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10eb6af2f751aa3424762948e5352f707b0dece77288206f227864ddf675aca0"}, + {file = "scipy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0f3261f14b767b316d7137c66cc4f33a80ea05841b9c87ad83a726205b901423"}, + {file = "scipy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:2c91cf049ffb5575917f2a01da1da082fd24ed48120d08a6e7297dfcac771dcd"}, + {file = "scipy-1.11.2.tar.gz", hash = "sha256:b29318a5e39bd200ca4381d80b065cdf3076c7d7281c5e36569e99273867f61d"}, ] [package.dependencies] @@ -3395,9 +4204,9 @@ setuptools = "*" [[package]] name = "sconscontrib" version = "1.0" -description = "" +description = "Contributed builders and other useful logic for the SCons build system.," optional = false -python-versions = ">=3.6, <4" +python-versions = "<4,>=3.6" files = [] develop = false @@ -3413,6 +4222,27 @@ url = "https://github.com/SCons/scons-contrib.git" reference = "HEAD" resolved_reference = "f3b0100d3a628e4d18f496815903660a99489bae" +[[package]] +name = "seaborn" +version = "0.12.2" +description = "Statistical data visualization" +optional = false +python-versions = ">=3.7" +files = [ + {file = "seaborn-0.12.2-py3-none-any.whl", hash = "sha256:ebf15355a4dba46037dfd65b7350f014ceb1f13c05e814eda2c9f5fd731afc08"}, + {file = "seaborn-0.12.2.tar.gz", hash = "sha256:374645f36509d0dcab895cba5b47daf0586f77bfe3b36c97c607db7da5be0139"}, +] + +[package.dependencies] +matplotlib = ">=3.1,<3.6.1 || >3.6.1" +numpy = ">=1.17,<1.24.0 || >1.24.0" +pandas = ">=0.25" + +[package.extras] +dev = ["flake8", "flit", "mypy", "pandas-stubs", "pre-commit", "pytest", "pytest-cov", "pytest-xdist"] +docs = ["ipykernel", "nbconvert", "numpydoc", "pydata_sphinx_theme (==0.10.0rc2)", "pyyaml", "sphinx-copybutton", "sphinx-design", "sphinx-issues"] +stats = ["scipy (>=1.3)", "statsmodels (>=0.10)"] + [[package]] name = "sentry-sdk" version = "1.28.1" @@ -3541,19 +4371,93 @@ test = ["pytest"] [[package]] name = "setuptools" -version = "68.0.0" +version = "68.2.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.2.1-py3-none-any.whl", hash = "sha256:eff96148eb336377ab11beee0c73ed84f1709a40c0b870298b0d058828761bae"}, + {file = "setuptools-68.2.1.tar.gz", hash = "sha256:56ee14884fd8d0cd015411f4a13f40b4356775a0aefd9ebc1d3bfb9a1acb32f1"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "setuptools-scm" +version = "7.1.0" +description = "the blessed package to manage your versions by scm tags" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools_scm-7.1.0-py3-none-any.whl", hash = "sha256:73988b6d848709e2af142aa48c986ea29592bbcfca5375678064708205253d8e"}, + {file = "setuptools_scm-7.1.0.tar.gz", hash = "sha256:6c508345a771aad7d56ebff0e70628bf2b0ec7573762be9960214730de278f27"}, +] + +[package.dependencies] +packaging = ">=20.0" +setuptools = "*" +typing-extensions = "*" + +[package.extras] +test = ["pytest (>=6.2)", "virtualenv (>20)"] +toml = ["setuptools (>=42)"] + +[[package]] +name = "shapely" +version = "2.0.1" +description = "Manipulation and analysis of geometric objects" +optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, + {file = "shapely-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b06d031bc64149e340448fea25eee01360a58936c89985cf584134171e05863f"}, + {file = "shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9a6ac34c16f4d5d3c174c76c9d7614ec8fe735f8f82b6cc97a46b54f386a86bf"}, + {file = "shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:865bc3d7cc0ea63189d11a0b1120d1307ed7a64720a8bfa5be2fde5fc6d0d33f"}, + {file = "shapely-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45b4833235b90bc87ee26c6537438fa77559d994d2d3be5190dd2e54d31b2820"}, + {file = "shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce88ec79df55430e37178a191ad8df45cae90b0f6972d46d867bf6ebbb58cc4d"}, + {file = "shapely-2.0.1-cp310-cp310-win32.whl", hash = "sha256:01224899ff692a62929ef1a3f5fe389043e262698a708ab7569f43a99a48ae82"}, + {file = "shapely-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:da71de5bf552d83dcc21b78cc0020e86f8d0feea43e202110973987ffa781c21"}, + {file = "shapely-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:502e0a607f1dcc6dee0125aeee886379be5242c854500ea5fd2e7ac076b9ce6d"}, + {file = "shapely-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7d3bbeefd8a6a1a1017265d2d36f8ff2d79d0162d8c141aa0d37a87063525656"}, + {file = "shapely-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f470a130d6ddb05b810fc1776d918659407f8d025b7f56d2742a596b6dffa6c7"}, + {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4641325e065fd3e07d55677849c9ddfd0cf3ee98f96475126942e746d55b17c8"}, + {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90cfa4144ff189a3c3de62e2f3669283c98fb760cfa2e82ff70df40f11cadb39"}, + {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70a18fc7d6418e5aea76ac55dce33f98e75bd413c6eb39cfed6a1ba36469d7d4"}, + {file = "shapely-2.0.1-cp311-cp311-win32.whl", hash = "sha256:09d6c7763b1bee0d0a2b84bb32a4c25c6359ad1ac582a62d8b211e89de986154"}, + {file = "shapely-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:d8f55f355be7821dade839df785a49dc9f16d1af363134d07eb11e9207e0b189"}, + {file = "shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:83a8ec0ee0192b6e3feee9f6a499d1377e9c295af74d7f81ecba5a42a6b195b7"}, + {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a529218e72a3dbdc83676198e610485fdfa31178f4be5b519a8ae12ea688db14"}, + {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91575d97fd67391b85686573d758896ed2fc7476321c9d2e2b0c398b628b961c"}, + {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8b0d834b11be97d5ab2b4dceada20ae8e07bcccbc0f55d71df6729965f406ad"}, + {file = "shapely-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:b4f0711cc83734c6fad94fc8d4ec30f3d52c1787b17d9dca261dc841d4731c64"}, + {file = "shapely-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:05c51a29336e604c084fb43ae5dbbfa2c0ef9bd6fedeae0a0d02c7b57a56ba46"}, + {file = "shapely-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b519cf3726ddb6c67f6a951d1bb1d29691111eaa67ea19ddca4d454fbe35949c"}, + {file = "shapely-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:193a398d81c97a62fc3634a1a33798a58fd1dcf4aead254d080b273efbb7e3ff"}, + {file = "shapely-2.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e55698e0ed95a70fe9ff9a23c763acfe0bf335b02df12142f74e4543095e9a9b"}, + {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32a748703e7bf6e92dfa3d2936b2fbfe76f8ce5f756e24f49ef72d17d26ad02"}, + {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a34a23d6266ca162499e4a22b79159dc0052f4973d16f16f990baa4d29e58b6"}, + {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d173d24e85e51510e658fb108513d5bc11e3fd2820db6b1bd0522266ddd11f51"}, + {file = "shapely-2.0.1-cp38-cp38-win32.whl", hash = "sha256:3cb256ae0c01b17f7bc68ee2ffdd45aebf42af8992484ea55c29a6151abe4386"}, + {file = "shapely-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:c7eed1fb3008a8a4a56425334b7eb82651a51f9e9a9c2f72844a2fb394f38a6c"}, + {file = "shapely-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac1dfc397475d1de485e76de0c3c91cc9d79bd39012a84bb0f5e8a199fc17bef"}, + {file = "shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33403b8896e1d98aaa3a52110d828b18985d740cc9f34f198922018b1e0f8afe"}, + {file = "shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2569a4b91caeef54dd5ae9091ae6f63526d8ca0b376b5bb9fd1a3195d047d7d4"}, + {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a70a614791ff65f5e283feed747e1cc3d9e6c6ba91556e640636bbb0a1e32a71"}, + {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43755d2c46b75a7b74ac6226d2cc9fa2a76c3263c5ae70c195c6fb4e7b08e79"}, + {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad81f292fffbd568ae71828e6c387da7eb5384a79db9b4fde14dd9fdeffca9a"}, + {file = "shapely-2.0.1-cp39-cp39-win32.whl", hash = "sha256:b50c401b64883e61556a90b89948297f1714dbac29243d17ed9284a47e6dd731"}, + {file = "shapely-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:bca57b683e3d94d0919e2f31e4d70fdfbb7059650ef1b431d9f4e045690edcd5"}, + {file = "shapely-2.0.1.tar.gz", hash = "sha256:66a6b1a3e72ece97fc85536a281476f9b7794de2e646ca8a4517e2e3c1446893"}, ] +[package.dependencies] +numpy = ">=1.14" + [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] +test = ["pytest", "pytest-cov"] [[package]] name = "six" @@ -3568,13 +4472,13 @@ files = [ [[package]] name = "smbus2" -version = "0.4.2" +version = "0.4.3" description = "smbus2 is a drop-in replacement for smbus-cffi/smbus-python in pure Python" optional = false python-versions = "*" files = [ - {file = "smbus2-0.4.2-py2.py3-none-any.whl", hash = "sha256:50f3c78e436b42a9583948be06961a8104cf020ebad5edfaaf2657528bef0818"}, - {file = "smbus2-0.4.2.tar.gz", hash = "sha256:634541ed794068a822fe7499f1577468b9d4641b68dd9bfb6d0eb7270f4d2a32"}, + {file = "smbus2-0.4.3-py2.py3-none-any.whl", hash = "sha256:a2fc29cfda4081ead2ed61ef2c4fc041d71dd40a8d917e85216f44786fca2d1d"}, + {file = "smbus2-0.4.3.tar.gz", hash = "sha256:36f2288a8e1a363cb7a7b2244ec98d880eb5a728a2494ac9c71e9de7bf6a803a"}, ] [package.extras] @@ -3626,24 +4530,24 @@ numpy = ["NumPy"] [[package]] name = "sphinx" -version = "6.2.1" +version = "7.2.5" description = "Python documentation generator" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b"}, - {file = "sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912"}, + {file = "sphinx-7.2.5-py3-none-any.whl", hash = "sha256:9269f9ed2821c9ebd30e4204f5c2339f5d4980e377bc89cb2cb6f9b17409c20a"}, + {file = "sphinx-7.2.5.tar.gz", hash = "sha256:1a9290001b75c497fd087e92b0334f1bbfa1a1ae7fddc084990c4b7bd1130b88"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.20" +docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" Jinja2 = ">=3.0" packaging = ">=21.0" -Pygments = ">=2.13" +Pygments = ">=2.14" requests = ">=2.25.0" snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" @@ -3651,27 +4555,27 @@ sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" +sphinxcontrib-serializinghtml = ">=1.1.9" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] +test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] [[package]] name = "sphinx-rtd-theme" -version = "1.2.2" +version = "1.3.0" description = "Read the Docs theme for Sphinx" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "sphinx_rtd_theme-1.2.2-py2.py3-none-any.whl", hash = "sha256:6a7e7d8af34eb8fc57d52a09c6b6b9c46ff44aea5951bc831eeb9245378f3689"}, - {file = "sphinx_rtd_theme-1.2.2.tar.gz", hash = "sha256:01c5c5a72e2d025bd23d1f06c59a4831b06e6ce6c01fdd5ebfe9986c0a880fc7"}, + {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, + {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, ] [package.dependencies] docutils = "<0.19" -sphinx = ">=1.6,<7" +sphinx = ">=1.6,<8" sphinxcontrib-jquery = ">=4,<5" [package.extras] @@ -3679,13 +4583,13 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinx-sitemap" -version = "2.5.0" +version = "2.5.1" description = "Sitemap generator for Sphinx" optional = false python-versions = "*" files = [ - {file = "sphinx-sitemap-2.5.0.tar.gz", hash = "sha256:95101f622d0d594161720cbe92a39d353efae9382f7f3563f06d150b1146fef6"}, - {file = "sphinx_sitemap-2.5.0-py3-none-any.whl", hash = "sha256:98a7e3bb25acb467037b56f3585fc38d53d5a274542b1497393a66f71b79b125"}, + {file = "sphinx-sitemap-2.5.1.tar.gz", hash = "sha256:984bef068bbdbc26cfae209a8b61392e9681abc9191b477cd30da406e3a60ee5"}, + {file = "sphinx_sitemap-2.5.1-py3-none-any.whl", hash = "sha256:0b7bce2835f287687f75584d7695e4eb8efaec028e5e7b36e9f791de3c344686"}, ] [package.dependencies] @@ -3693,13 +4597,13 @@ sphinx = ">=1.2" [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.6" +version = "1.0.7" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_applehelp-1.0.6-py3-none-any.whl", hash = "sha256:c0578efa23cab5a2f3aaa8af5691b952433f4fdfaac255befd3452448e7ea4a4"}, - {file = "sphinxcontrib_applehelp-1.0.6.tar.gz", hash = "sha256:a59274de7a952a99af36b8a5092352d9249279c0e3280b7dceaae8e15873c942"}, + {file = "sphinxcontrib_applehelp-1.0.7-py3-none-any.whl", hash = "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d"}, + {file = "sphinxcontrib_applehelp-1.0.7.tar.gz", hash = "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa"}, ] [package.dependencies] @@ -3711,13 +4615,13 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "1.0.4" +version = "1.0.5" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_devhelp-1.0.4-py3-none-any.whl", hash = "sha256:d4e20a17f78865d4096733989b5efa0d5e7743900e98e1f6ecd6f489380febc8"}, - {file = "sphinxcontrib_devhelp-1.0.4.tar.gz", hash = "sha256:4fd751c63dc40895ac8740948f26bf1a3c87e4e441cc008672abd1cb2bc8a3d1"}, + {file = "sphinxcontrib_devhelp-1.0.5-py3-none-any.whl", hash = "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f"}, + {file = "sphinxcontrib_devhelp-1.0.5.tar.gz", hash = "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212"}, ] [package.dependencies] @@ -3729,13 +4633,13 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.3" +version = "2.0.4" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_htmlhelp-2.0.3-py3-none-any.whl", hash = "sha256:abee4e6c5471203ad2fc40dc6a16ed99884a5d6b15a6f79c9269a7e82cf04149"}, - {file = "sphinxcontrib_htmlhelp-2.0.3.tar.gz", hash = "sha256:14358d0f88ccf58447f2b54343cdcc0012f32de2f8d27cf934fdbc0b362f9597"}, + {file = "sphinxcontrib_htmlhelp-2.0.4-py3-none-any.whl", hash = "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9"}, + {file = "sphinxcontrib_htmlhelp-2.0.4.tar.gz", hash = "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a"}, ] [package.dependencies] @@ -3775,13 +4679,13 @@ test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" -version = "1.0.5" +version = "1.0.6" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_qthelp-1.0.5-py3-none-any.whl", hash = "sha256:962730a6ad15d21fd6760b14c9e95c00a097413595aa6ee871dd9dfa4b002a16"}, - {file = "sphinxcontrib_qthelp-1.0.5.tar.gz", hash = "sha256:d31d1a1beaf3894866bb318fb712f1edc82687f1c06235a01e5b2c50c36d5c40"}, + {file = "sphinxcontrib_qthelp-1.0.6-py3-none-any.whl", hash = "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4"}, + {file = "sphinxcontrib_qthelp-1.0.6.tar.gz", hash = "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d"}, ] [package.dependencies] @@ -3793,13 +4697,13 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.7" +version = "1.1.9" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_serializinghtml-1.1.7-py3-none-any.whl", hash = "sha256:424164fc3a8b4355a29d5ea8b7f18199022d160c8f7b96e68bb6c50217729b87"}, - {file = "sphinxcontrib_serializinghtml-1.1.7.tar.gz", hash = "sha256:ca31afee32e1508cff4034e258060ce2c81a3b1c49e77da60fdb61f0e7a73c22"}, + {file = "sphinxcontrib_serializinghtml-1.1.9-py3-none-any.whl", hash = "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1"}, + {file = "sphinxcontrib_serializinghtml-1.1.9.tar.gz", hash = "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54"}, ] [package.dependencies] @@ -3863,13 +4767,13 @@ widechars = ["wcwidth"] [[package]] name = "tenacity" -version = "8.2.2" +version = "8.2.3" description = "Retry code until it succeeds" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "tenacity-8.2.2-py3-none-any.whl", hash = "sha256:2f277afb21b851637e8f52e6a613ff08734c347dc19ade928e519d7d2d8569b0"}, - {file = "tenacity-8.2.2.tar.gz", hash = "sha256:43af037822bd0029025877f3b2d97cc4d7bb0c2991000a3d59d71517c5c969e0"}, + {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, + {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, ] [package.extras] @@ -3896,6 +4800,17 @@ setuptools = ">=65.5" numba = ["numba (>=0.56,<1)"] pytz = ["pytz (>=2022.7.1)"] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "tqdm" version = "4.66.1" @@ -3940,13 +4855,13 @@ files = [ [[package]] name = "types-pycurl" -version = "7.45.2.4" +version = "7.45.2.5" description = "Typing stubs for pycurl" optional = false python-versions = "*" files = [ - {file = "types-pycurl-7.45.2.4.tar.gz", hash = "sha256:02c1611e7862f5ce02127746f079cd20dff511b049c7030cf24bb3d3ef41ceff"}, - {file = "types_pycurl-7.45.2.4-py3-none-any.whl", hash = "sha256:d10efc8b71bfcd2fc4745a2f08967baa441a8c74bc6028829e5b65899affc40b"}, + {file = "types-pycurl-7.45.2.5.tar.gz", hash = "sha256:cc07e4b1e388b98e1a935bc12ef33eec8e7294496d0a81ed50c46a31bf5df5ee"}, + {file = "types_pycurl-7.45.2.5-py3-none-any.whl", hash = "sha256:e433f13a266245be770018eabc6a50139ea26150f75df5b0ee698d291575582b"}, ] [[package]] @@ -4037,13 +4952,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.24.3" +version = "20.24.5" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.3-py3-none-any.whl", hash = "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02"}, - {file = "virtualenv-20.24.3.tar.gz", hash = "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc"}, + {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, + {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, ] [package.dependencies] @@ -4052,25 +4967,41 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<4" [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "websocket-client" -version = "1.6.1" +version = "1.6.3" description = "WebSocket client for Python with low level API options" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "websocket-client-1.6.1.tar.gz", hash = "sha256:c951af98631d24f8df89ab1019fc365f2227c0892f12fd150e935607c79dd0dd"}, - {file = "websocket_client-1.6.1-py3-none-any.whl", hash = "sha256:f1f9f2ad5291f0225a49efad77abf9e700b6fef553900623060dad6e26503b9d"}, + {file = "websocket-client-1.6.3.tar.gz", hash = "sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f"}, + {file = "websocket_client-1.6.3-py3-none-any.whl", hash = "sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03"}, ] [package.extras] -docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] +docs = ["Sphinx (>=6.0)", "sphinx-rtd-theme (>=1.1.0)"] optional = ["python-socks", "wsaccel"] test = ["websockets"] +[[package]] +name = "yapf" +version = "0.40.1" +description = "A formatter for Python code." +optional = false +python-versions = ">=3.7" +files = [ + {file = "yapf-0.40.1-py3-none-any.whl", hash = "sha256:b8bfc1f280949153e795181768ca14ef43d7312629a06c43e7abd279323af313"}, + {file = "yapf-0.40.1.tar.gz", hash = "sha256:958587eb5c8ec6c860119a9c25d02addf30a44f75aa152a4220d30e56a98037c"}, +] + +[package.dependencies] +importlib-metadata = ">=6.6.0" +platformdirs = ">=3.5.1" +tomli = ">=2.0.1" + [[package]] name = "yarl" version = "1.9.2" @@ -4158,7 +5089,22 @@ files = [ idna = ">=2.0" multidict = ">=4.0" +[[package]] +name = "zipp" +version = "3.16.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "4b2510f1465520a9dc757f64861f4fcea9d9663c0ecdebd23b329ef5e0205863" +content-hash = "80bd9226bb8fc61c75fe8047c55ba2da3919bc8d9b32a8154ec0a40f9fe2a18e" diff --git a/pyproject.toml b/pyproject.toml index d9cc49bfd0..bc28ed1da1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,62 @@ [tool.pytest.ini_options] minversion = "6.0" -addopts = "--ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=laika_repo/" +addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=laika_repo/ -Werror --strict-config --strict-markers --durations=10" +#cpp_files = "test_*" # uncomment when agnos has pytest-cpp and remove from CI python_files = "test_*.py" -timeout = "30" # you get this long by default +#timeout = "30" # you get this long by default +markers = [ + "parallel: mark tests as parallelizable (tests with no global state, so can be run in parallel)" +] +testpaths = [ + "common", + "selfdrive/athena", + "selfdrive/boardd", + "selfdrive/car", + "selfdrive/controls", + "selfdrive/locationd", + "selfdrive/monitoring", + "selfdrive/thermald", + "selfdrive/test/longitudinal_maneuvers", + "system/hardware/tici", + "system/loggerd", + "system/proclogd", + "system/tests", + "system/ubloxd", + "tools/lib/tests", + "tools/replay", + "tools/cabana" +] + +[tool.mypy] +python_version = "3.11" +plugins = [ + "numpy.typing.mypy_plugin", +] +exclude = [ + "body/", + "cereal/", + "opendbc/", + "panda/", + "laika/", + "laika_repo/", + "rednose/", + "rednose_repo/", + "tinygrad/", + "tinygrad_repo/", + "third_party/", +] + +# third-party packages +ignore_missing_imports=true + +# helpful warnings +warn_redundant_casts=true +warn_unreachable=true +warn_unused_ignores=true + +# restrict dynamic typing +warn_return_any=true + [tool.poetry] name = "openpilot" @@ -22,6 +76,7 @@ aiohttp = "*" aiortc = "*" casadi = "==3.6.3" cffi = "*" +control = "*" crcmod = "*" cryptography = "*" Cython = "*" @@ -39,6 +94,7 @@ psutil = "*" pyaudio = "*" pycapnp = "*" pycryptodome = "*" +pycurl = "*" pydub = "*" PyJWT = "*" pyopencl = "*" @@ -64,7 +120,8 @@ sconscontrib = {git = "https://github.com/SCons/scons-contrib.git"} [tool.poetry.group.dev.dependencies] av = "*" -azure-storage-blob = "~2.1" +azure-identity = "*" +azure-storage-blob = "*" breathe = "*" carla = { url = "https://github.com/commaai/carla/releases/download/3.11.4/carla-0.9.14-cp311-cp311-linux_x86_64.whl", platform = "linux", markers = "platform_machine == 'x86_64'" } coverage = "*" @@ -75,6 +132,7 @@ inputs = "*" lru-dict = "*" markdown-it-py = "*" matplotlib = "*" +metadrive-simulator = { git = "https://github.com/metadriverse/metadrive.git", rev ="7d6f8ef707bfff67c6b88f3b4a98f8b1d58bf8e6", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies mpld3 = "*" mypy = "*" myst-parser = "*" @@ -84,11 +142,16 @@ pandas = "*" parameterized = "^0.8" pprofile = "*" pre-commit = "*" -pycurl = "*" pygame = "*" pyprof2calltree = "*" pytest = "*" +pytest-cov = "*" +pytest-cpp = "*" +pytest-subtests = "*" pytest-xdist = "*" +pytest-timeout = "*" +pytest-timeouts = "*" +ruff = "*" scipy = "*" sphinx = "*" sphinx-rtd-theme = "*" @@ -110,7 +173,7 @@ build-backend = "poetry.core.masonry.api" # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml [tool.ruff] -select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF100", "A", "B"] +select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF008", "RUF100", "A", "B", "TID251"] ignore = ["W292", "E741", "E402", "C408", "ISC003", "B027", "B024"] line-length = 160 target-version="py311" @@ -123,3 +186,9 @@ exclude = [ "third_party", ] flake8-implicit-str-concat.allow-multiline=false +[tool.ruff.flake8-tidy-imports.banned-api] +"selfdrive".msg = "Use openpilot.selfdrive" +"common".msg = "Use openpilot.common" +"system".msg = "Use openpilot.system" +"third_party".msg = "Use openpilot.third_party" +"tools".msg = "Use openpilot.tools" diff --git a/rednose_repo b/rednose_repo index 22f02dd650..8658bed296 160000 --- a/rednose_repo +++ b/rednose_repo @@ -1 +1 @@ -Subproject commit 22f02dd650361e091f09f1ae94c5c96a9419c2d4 +Subproject commit 8658bed29686b2ddae191fd18302986c85542431 diff --git a/release/files_common b/release/files_common index 8381546b03..2d0b5f0514 100644 --- a/release/files_common +++ b/release/files_common @@ -15,12 +15,14 @@ docs/INTEGRATION.md docs/LIMITATIONS.md site_scons/site_tools/cython.py +openpilot/__init__.py +openpilot/** + common/.gitignore common/__init__.py common/conversions.py common/gpio.py common/realtime.py -common/clock.pyx common/timeout.py common/ffi_wrapper.py common/file_helpers.py @@ -160,6 +162,8 @@ common/clutil.cc common/clutil.h common/params.h common/params.cc +common/ratekeeper.cc +common/ratekeeper.h common/watchdog.cc common/watchdog.h @@ -205,6 +209,7 @@ system/hardware/__init__.py system/hardware/base.h system/hardware/base.py system/hardware/hw.h +system/hardware/hw.py system/hardware/tici/__init__.py system/hardware/tici/hardware.h system/hardware/tici/hardware.py @@ -314,6 +319,8 @@ selfdrive/ui/tests/test_translations.py selfdrive/ui/qt/*.cc selfdrive/ui/qt/*.h +selfdrive/ui/qt/network/*.cc +selfdrive/ui/qt/network/*.h selfdrive/ui/qt/offroad/*.cc selfdrive/ui/qt/offroad/*.h selfdrive/ui/qt/offroad/*.qml @@ -350,15 +357,18 @@ selfdrive/manager/process.py selfdrive/manager/test/__init__.py selfdrive/manager/test/test_manager.py +selfdrive/modeld/.gitignore selfdrive/modeld/__init__.py selfdrive/modeld/SConscript -selfdrive/modeld/modeld.cc -selfdrive/modeld/navmodeld.cc -selfdrive/modeld/dmonitoringmodeld.cc +selfdrive/modeld/modeld.py +selfdrive/modeld/navmodeld.py +selfdrive/modeld/dmonitoringmodeld.py selfdrive/modeld/constants.py selfdrive/modeld/modeld -selfdrive/modeld/navmodeld -selfdrive/modeld/dmonitoringmodeld + +selfdrive/modeld/models/__init__.py +selfdrive/modeld/models/*.pxd +selfdrive/modeld/models/*.pyx selfdrive/modeld/models/commonmodel.cc selfdrive/modeld/models/commonmodel.h @@ -367,12 +377,8 @@ selfdrive/modeld/models/driving.cc selfdrive/modeld/models/driving.h selfdrive/modeld/models/supercombo.onnx -selfdrive/modeld/models/dmonitoring.cc -selfdrive/modeld/models/dmonitoring.h selfdrive/modeld/models/dmonitoring_model_q.dlc -selfdrive/modeld/models/nav.cc -selfdrive/modeld/models/nav.h selfdrive/modeld/models/navmodel_q.dlc selfdrive/modeld/transforms/loadyuv.cc @@ -388,12 +394,12 @@ selfdrive/modeld/thneed/thneed_common.cc selfdrive/modeld/thneed/thneed_qcom2.cc selfdrive/modeld/thneed/serialize.cc -selfdrive/modeld/runners/snpemodel.cc -selfdrive/modeld/runners/snpemodel.h -selfdrive/modeld/runners/thneedmodel.cc -selfdrive/modeld/runners/thneedmodel.h -selfdrive/modeld/runners/runmodel.h -selfdrive/modeld/runners/run.h +selfdrive/modeld/runners/__init__.py +selfdrive/modeld/runners/*.pxd +selfdrive/modeld/runners/*.pyx +selfdrive/modeld/runners/*.cc +selfdrive/modeld/runners/*.h +selfdrive/modeld/runners/*.py selfdrive/monitoring/dmonitoringd.py selfdrive/monitoring/driver_monitor.py @@ -565,6 +571,7 @@ opendbc/nissan_x_trail_2017_generated.dbc opendbc/nissan_leaf_2018_generated.dbc opendbc/subaru_global_2017_generated.dbc +opendbc/subaru_global_2020_hybrid_generated.dbc opendbc/subaru_outback_2015_generated.dbc opendbc/subaru_outback_2019_generated.dbc opendbc/subaru_forester_2017_generated.dbc diff --git a/release/files_pc b/release/files_pc index a331a45c0f..13f1b52166 100644 --- a/release/files_pc +++ b/release/files_pc @@ -1,5 +1,3 @@ -selfdrive/modeld/runners/onnx* - third_party/mapbox-gl-native-qt/x86_64/*.so third_party/libyuv/x86_64/** diff --git a/scripts/count_cars.py b/scripts/count_cars.py index 25bad2c9b4..8c0892bb82 100755 --- a/scripts/count_cars.py +++ b/scripts/count_cars.py @@ -2,7 +2,7 @@ from collections import Counter from pprint import pprint -from selfdrive.car.docs import get_all_car_info +from openpilot.selfdrive.car.docs import get_all_car_info if __name__ == "__main__": cars = get_all_car_info() diff --git a/scripts/disable-powersave.py b/scripts/disable-powersave.py index 93688504f3..367b4108b0 100755 --- a/scripts/disable-powersave.py +++ b/scripts/disable-powersave.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from system.hardware import HARDWARE +from openpilot.system.hardware import HARDWARE if __name__ == "__main__": HARDWARE.set_power_save(False) diff --git a/scripts/dump_pll.c b/scripts/dump_pll.c index 325ee2b4c0..3e2190235a 100644 --- a/scripts/dump_pll.c +++ b/scripts/dump_pll.c @@ -39,9 +39,9 @@ void hexdump(uint32_t *d, int l) { int main() { int fd = open("/dev/mem", O_RDWR); - volatile uint32_t *mb = (uint32_t*)mmap(0,0x1000,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0x06400000); - volatile uint32_t *mc = (uint32_t*)mmap(0,0x1000,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0x06480000); - volatile uint32_t *md = (uint32_t*)mmap(0,0x1000,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0x09A20000); + volatile uint32_t *mb = (uint32_t *)mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x06400000); + volatile uint32_t *mc = (uint32_t *)mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x06480000); + volatile uint32_t *md = (uint32_t *)mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x09A20000); while (1) { printf("PLL MODE:%x L_VAL:%x ALPHA:%x USER_CTL:%x CONFIG_CTL:%x CONFIG_CTL_HI:%x STATUS:%x TEST_CTL_LO:%x TEST_CTL_HI:%x\n", mb[C0_PLL_MODE/4], mb[C0_PLL_L_VAL/4], mb[C0_PLL_ALPHA/4], diff --git a/scripts/pyqt_demo.py b/scripts/pyqt_demo.py index 43716fbeb2..783728bdb8 100755 --- a/scripts/pyqt_demo.py +++ b/scripts/pyqt_demo.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -from PyQt5.QtWidgets import QApplication, QLabel # pylint: disable=no-name-in-module, import-error -from selfdrive.ui.qt.python_helpers import set_main_window +from PyQt5.QtWidgets import QApplication, QLabel +from openpilot.selfdrive.ui.qt.python_helpers import set_main_window if __name__ == "__main__": diff --git a/scripts/waste.py b/scripts/waste.py index d3c96bf198..0764ff77c3 100755 --- a/scripts/waste.py +++ b/scripts/waste.py @@ -1,23 +1,23 @@ #!/usr/bin/env python3 import os +import time import numpy as np -from common.realtime import sec_since_boot from multiprocessing import Process -from setproctitle import setproctitle # pylint: disable=no-name-in-module +from setproctitle import setproctitle def waste(core): - os.sched_setaffinity(0, [core,]) # pylint: disable=no-member + os.sched_setaffinity(0, [core,]) m1 = np.zeros((200, 200)) + 0.8 m2 = np.zeros((200, 200)) + 1.2 i = 1 - st = sec_since_boot() + st = time.monotonic() j = 0 while 1: if (i % 100) == 0: setproctitle("%3d: %8d" % (core, i)) - lt = sec_since_boot() + lt = time.monotonic() print("%3d: %8d %f %.2f" % (core, i, lt-st, j)) st = lt i += 1 diff --git a/selfdrive/assets/.gitignore b/selfdrive/assets/.gitignore index 283034ca8b..1f90a2a932 100644 --- a/selfdrive/assets/.gitignore +++ b/selfdrive/assets/.gitignore @@ -1 +1,2 @@ *.cc +translations_assets.qrc diff --git a/selfdrive/assets/assets.qrc b/selfdrive/assets/assets.qrc index 79a1a1e272..6d8d8df334 100644 --- a/selfdrive/assets/assets.qrc +++ b/selfdrive/assets/assets.qrc @@ -14,5 +14,7 @@ offroad/icon_wifi_strength_medium.svg offroad/icon_wifi_strength_high.svg offroad/icon_wifi_strength_full.svg + + ../ui/translations/languages.json diff --git a/selfdrive/assets/navigation/direction_notificaiton_right.png b/selfdrive/assets/navigation/direction_notification_right.png similarity index 100% rename from selfdrive/assets/navigation/direction_notificaiton_right.png rename to selfdrive/assets/navigation/direction_notification_right.png diff --git a/selfdrive/assets/navigation/direction_notificaiton_sharp_right.png b/selfdrive/assets/navigation/direction_notification_sharp_right.png similarity index 100% rename from selfdrive/assets/navigation/direction_notificaiton_sharp_right.png rename to selfdrive/assets/navigation/direction_notification_sharp_right.png diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index d7ef064072..899605d989 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -30,17 +30,17 @@ from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutExce import cereal.messaging as messaging from cereal import log from cereal.services import service_list -from common.api import Api -from common.basedir import PERSIST -from common.file_helpers import CallbackReader -from common.params import Params -from common.realtime import sec_since_boot, set_core_affinity -from system.hardware import HARDWARE, PC, AGNOS -from system.loggerd.config import ROOT -from system.loggerd.xattr_cache import getxattr, setxattr -from selfdrive.statsd import STATS_DIR -from system.swaglog import SWAGLOG_DIR, cloudlog -from system.version import get_commit, get_origin, get_short_branch, get_version +from openpilot.common.api import Api +from openpilot.common.basedir import PERSIST +from openpilot.common.file_helpers import CallbackReader +from openpilot.common.params import Params +from openpilot.common.realtime import set_core_affinity +from openpilot.system.hardware import HARDWARE, PC, AGNOS +from openpilot.system.loggerd.xattr_cache import getxattr, setxattr +from openpilot.selfdrive.statsd import STATS_DIR +from openpilot.system.swaglog import cloudlog +from openpilot.system.version import get_commit, get_origin, get_short_branch, get_version +from openpilot.selfdrive.hardware.hw import Paths # TODO: use socket constant when mypy recognizes this as a valid attribute @@ -75,7 +75,7 @@ class UploadFile: allow_cellular: bool @classmethod - def from_dict(cls, d: Dict) -> UploadFile: + def from_dict(cls, d: dict) -> UploadFile: return cls(d.get("fn", ""), d.get("url", ""), d.get("headers", {}), d.get("allow_cellular", False)) @@ -92,7 +92,7 @@ class UploadItem: allow_cellular: bool = False @classmethod - def from_dict(cls, d: Dict) -> UploadItem: + def from_dict(cls, d: dict) -> UploadItem: return cls(d["path"], d["url"], d["headers"], d["created_at"], d["id"], d["retry_count"], d["current"], d["progress"], d["allow_cellular"]) @@ -119,12 +119,11 @@ class AbortTransferException(Exception): class UploadQueueCache: - params = Params() @staticmethod def initialize(upload_queue: Queue[UploadItem]) -> None: try: - upload_queue_json = UploadQueueCache.params.get("AthenadUploadQueue") + upload_queue_json = Params().get("AthenadUploadQueue") if upload_queue_json is not None: for item in json.loads(upload_queue_json): upload_queue.put(UploadItem.from_dict(item)) @@ -136,7 +135,7 @@ class UploadQueueCache: try: queue: List[Optional[UploadItem]] = list(upload_queue.queue) items = [asdict(i) for i in queue if i is not None and (i.id not in cancelled_uploads)] - UploadQueueCache.params.put("AthenadUploadQueue", json.dumps(items)) + Params().put("AthenadUploadQueue", json.dumps(items)) except Exception: cloudlog.exception("athena.UploadQueueCache.cache.exception") @@ -309,7 +308,7 @@ def _do_upload(upload_item: UploadItem, callback: Optional[Callable] = None) -> # security: user should be able to request any message from their car @dispatcher.add_method -def getMessage(service: str, timeout: int = 1000) -> Dict: +def getMessage(service: str, timeout: int = 1000) -> dict: if service is None or service not in service_list: raise Exception("invalid service") @@ -320,7 +319,7 @@ def getMessage(service: str, timeout: int = 1000) -> Dict: raise TimeoutError # this is because capnp._DynamicStructReader doesn't have typing information - return cast(Dict, ret.to_dict()) + return cast(dict, ret.to_dict()) @dispatcher.add_method @@ -352,7 +351,7 @@ def scan_dir(path: str, prefix: str) -> List[str]: # (glob and friends traverse entire dir tree) with os.scandir(path) as i: for e in i: - rel_path = os.path.relpath(e.path, ROOT) + rel_path = os.path.relpath(e.path, Paths.log_root()) if e.is_dir(follow_symlinks=False): # add trailing slash rel_path = os.path.join(rel_path, '') @@ -367,7 +366,7 @@ def scan_dir(path: str, prefix: str) -> List[str]: @dispatcher.add_method def listDataDirectory(prefix='') -> List[str]: - return scan_dir(ROOT, prefix) + return scan_dir(Paths.log_root(), prefix) @dispatcher.add_method @@ -408,7 +407,7 @@ def uploadFilesToUrls(files_data: List[UploadFileDict]) -> UploadFilesToUrlRespo failed.append(file.fn) continue - path = os.path.join(ROOT, file.fn) + path = os.path.join(Paths.log_root(), file.fn) if not os.path.exists(path) and not os.path.exists(strip_bz2_extension(path)): failed.append(file.fn) continue @@ -552,7 +551,7 @@ def getNetworks(): @dispatcher.add_method def takeSnapshot() -> Optional[Union[str, Dict[str, str]]]: - from system.camerad.snapshot.snapshot import jpeg_write, snapshot + from openpilot.system.camerad.snapshot.snapshot import jpeg_write, snapshot ret = snapshot() if ret is not None: def b64jpeg(x): @@ -572,8 +571,8 @@ def get_logs_to_send_sorted() -> List[str]: # TODO: scan once then use inotify to detect file creation/deletion curr_time = int(time.time()) logs = [] - for log_entry in os.listdir(SWAGLOG_DIR): - log_path = os.path.join(SWAGLOG_DIR, log_entry) + for log_entry in os.listdir(Paths.swaglog_root()): + log_path = os.path.join(Paths.swaglog_root(), log_entry) time_sent = 0 try: value = getxattr(log_path, LOG_ATTR_NAME) @@ -593,10 +592,10 @@ def log_handler(end_event: threading.Event) -> None: return log_files = [] - last_scan = 0 + last_scan = 0. while not end_event.is_set(): try: - curr_scan = sec_since_boot() + curr_scan = time.monotonic() if curr_scan - last_scan > 10: log_files = get_logs_to_send_sorted() last_scan = curr_scan @@ -608,7 +607,7 @@ def log_handler(end_event: threading.Event) -> None: cloudlog.debug(f"athena.log_handler.forward_request {log_entry}") try: curr_time = int(time.time()) - log_path = os.path.join(SWAGLOG_DIR, log_entry) + log_path = os.path.join(Paths.swaglog_root(), log_entry) setxattr(log_path, LOG_ATTR_NAME, int.to_bytes(curr_time, 4, sys.byteorder)) with open(log_path) as f: jsonrpc = { @@ -635,7 +634,7 @@ def log_handler(end_event: threading.Event) -> None: log_success = "result" in log_resp and log_resp["result"].get("success") cloudlog.debug(f"athena.log_handler.forward_response {log_entry} {log_success}") if log_entry and log_success: - log_path = os.path.join(SWAGLOG_DIR, log_entry) + log_path = os.path.join(Paths.swaglog_root(), log_entry) try: setxattr(log_path, LOG_ATTR_NAME, LOG_ATTR_VALUE_MAX_UNIX_TIME) except OSError: @@ -652,8 +651,8 @@ def log_handler(end_event: threading.Event) -> None: def stat_handler(end_event: threading.Event) -> None: while not end_event.is_set(): - last_scan = 0 - curr_scan = sec_since_boot() + last_scan = 0. + curr_scan = time.monotonic() try: if curr_scan - last_scan > 10: stat_filenames = list(filter(lambda name: not name.startswith(tempfile.gettempprefix()), os.listdir(STATS_DIR))) @@ -721,7 +720,7 @@ def ws_proxy_send(ws: WebSocket, local_sock: socket.socket, signal_sock: socket. def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: - last_ping = int(sec_since_boot() * 1e9) + last_ping = int(time.monotonic() * 1e9) while not end_event.is_set(): try: opcode, data = ws.recv_data(control_frame=True) @@ -730,10 +729,10 @@ def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: data = data.decode("utf-8") recv_queue.put_nowait(data) elif opcode == ABNF.OPCODE_PING: - last_ping = int(sec_since_boot() * 1e9) + last_ping = int(time.monotonic() * 1e9) Params().put("LastAthenaPingTime", str(last_ping)) except WebSocketTimeoutException: - ns_since_last_ping = int(sec_since_boot() * 1e9) - last_ping + ns_since_last_ping = int(time.monotonic() * 1e9) - last_ping if ns_since_last_ping > RECONNECT_TIMEOUT_S * 1e9: cloudlog.exception("athenad.ws_recv.timeout") end_event.set() diff --git a/selfdrive/athena/manage_athenad.py b/selfdrive/athena/manage_athenad.py index 59ca2430ce..877d8aca03 100755 --- a/selfdrive/athena/manage_athenad.py +++ b/selfdrive/athena/manage_athenad.py @@ -3,10 +3,10 @@ import time from multiprocessing import Process -from common.params import Params -from selfdrive.manager.process import launcher -from system.swaglog import cloudlog -from system.version import get_version, is_dirty +from openpilot.common.params import Params +from openpilot.selfdrive.manager.process import launcher +from openpilot.system.swaglog import cloudlog +from openpilot.system.version import get_version, is_dirty ATHENA_MGR_PID_PARAM = "AthenadPid" diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py index 32bc92059c..0ab69371c2 100755 --- a/selfdrive/athena/registration.py +++ b/selfdrive/athena/registration.py @@ -6,13 +6,13 @@ from pathlib import Path from typing import Optional from datetime import datetime, timedelta -from common.api import api_get -from common.params import Params -from common.spinner import Spinner -from common.basedir import PERSIST -from selfdrive.controls.lib.alertmanager import set_offroad_alert -from system.hardware import HARDWARE, PC -from system.swaglog import cloudlog +from openpilot.common.api import api_get +from openpilot.common.params import Params +from openpilot.common.spinner import Spinner +from openpilot.common.basedir import PERSIST +from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert +from openpilot.system.hardware import HARDWARE, PC +from openpilot.system.swaglog import cloudlog UNREGISTERED_DONGLE_ID = "UnregisteredDevice" diff --git a/selfdrive/athena/tests/helpers.py b/selfdrive/athena/tests/helpers.py index 5087012bcd..34edeb2de5 100644 --- a/selfdrive/athena/tests/helpers.py +++ b/selfdrive/athena/tests/helpers.py @@ -6,7 +6,7 @@ import time from functools import wraps from multiprocessing import Process -from common.timeout import Timeout +from openpilot.common.timeout import Timeout class MockResponse: diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 7ce6932f2a..f4c229188e 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -3,7 +3,6 @@ import json import os import requests import shutil -import tempfile import time import threading import queue @@ -18,11 +17,11 @@ from unittest import mock from websocket import ABNF from websocket._exceptions import WebSocketConnectionClosedException -from system import swaglog -from selfdrive.athena import athenad -from selfdrive.athena.athenad import MAX_RETRY_COUNT, dispatcher -from selfdrive.athena.tests.helpers import MockWebsocket, MockParams, MockApi, EchoSocket, with_http_server +from openpilot.selfdrive.athena import athenad +from openpilot.selfdrive.athena.athenad import MAX_RETRY_COUNT, dispatcher +from openpilot.selfdrive.athena.tests.helpers import MockWebsocket, MockParams, MockApi, EchoSocket, with_http_server from cereal import messaging +from openpilot.selfdrive.hardware.hw import Paths class TestAthenadMethods(unittest.TestCase): @@ -30,8 +29,6 @@ class TestAthenadMethods(unittest.TestCase): def setUpClass(cls): cls.SOCKET_PORT = 45454 athenad.Params = MockParams - athenad.ROOT = tempfile.mkdtemp() - athenad.SWAGLOG_DIR = swaglog.SWAGLOG_DIR = tempfile.mkdtemp() athenad.Api = MockApi athenad.LOCAL_PORT_WHITELIST = {cls.SOCKET_PORT} @@ -41,13 +38,14 @@ class TestAthenadMethods(unittest.TestCase): athenad.cur_upload_items.clear() athenad.cancelled_uploads.clear() - for i in os.listdir(athenad.ROOT): - p = os.path.join(athenad.ROOT, i) + for i in os.listdir(Paths.log_root()): + p = os.path.join(Paths.log_root(), i) if os.path.isdir(p): shutil.rmtree(p) else: os.unlink(p) + dispatcher["listUploadQueue"]() # ensure queue is empty at start # *** test helpers *** @@ -60,7 +58,7 @@ class TestAthenadMethods(unittest.TestCase): @staticmethod def _create_file(file: str, parent: Optional[str] = None) -> str: - fn = os.path.join(athenad.ROOT if parent is None else parent, file) + fn = os.path.join(Paths.log_root() if parent is None else parent, file) os.makedirs(os.path.dirname(fn), exist_ok=True) Path(fn).touch() return fn @@ -351,7 +349,7 @@ class TestAthenadMethods(unittest.TestCase): self.assertEqual(athenad.upload_queue.qsize(), 1) self.assertDictEqual(asdict(athenad.upload_queue.queue[-1]), asdict(item1)) - @mock.patch('selfdrive.athena.athenad.create_connection') + @mock.patch('openpilot.selfdrive.athena.athenad.create_connection') def test_startLocalProxy(self, mock_create_connection): end_event = threading.Event() @@ -417,7 +415,7 @@ class TestAthenadMethods(unittest.TestCase): fl = list() for i in range(10): file = f'swaglog.{i:010}' - self._create_file(file, athenad.SWAGLOG_DIR) + self._create_file(file, Paths.swaglog_root()) fl.append(file) # ensure the list is all logs except most recent diff --git a/selfdrive/athena/tests/test_athenad_ping.py b/selfdrive/athena/tests/test_athenad_ping.py index e1fa00a76f..3ec7cb115c 100755 --- a/selfdrive/athena/tests/test_athenad_ping.py +++ b/selfdrive/athena/tests/test_athenad_ping.py @@ -6,11 +6,11 @@ import unittest from typing import Callable, cast, Optional from unittest.mock import MagicMock -from common.params import Params -from common.timeout import Timeout -from selfdrive.athena import athenad -from selfdrive.manager.helpers import write_onroad_params -from system.hardware import TICI +from openpilot.common.params import Params +from openpilot.common.timeout import Timeout +from openpilot.selfdrive.athena import athenad +from openpilot.selfdrive.manager.helpers import write_onroad_params +from openpilot.system.hardware import TICI def wifi_radio(on: bool) -> None: diff --git a/selfdrive/athena/tests/test_registration.py b/selfdrive/athena/tests/test_registration.py index 7a38477305..195fca2df9 100755 --- a/selfdrive/athena/tests/test_registration.py +++ b/selfdrive/athena/tests/test_registration.py @@ -7,9 +7,9 @@ from Crypto.PublicKey import RSA from pathlib import Path from unittest import mock -from common.params import Params -from selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID -from selfdrive.athena.tests.helpers import MockResponse +from openpilot.common.params import Params +from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID +from openpilot.selfdrive.athena.tests.helpers import MockResponse class TestRegistration(unittest.TestCase): @@ -23,7 +23,7 @@ class TestRegistration(unittest.TestCase): os.mkdir(os.path.join(self.persist.name, "comma")) self.priv_key = Path(os.path.join(self.persist.name, "comma/id_rsa")) self.pub_key = Path(os.path.join(self.persist.name, "comma/id_rsa.pub")) - self.persist_patcher = mock.patch("selfdrive.athena.registration.PERSIST", self.persist.name) + self.persist_patcher = mock.patch("openpilot.selfdrive.athena.registration.PERSIST", self.persist.name) self.persist_patcher.start() def tearDown(self): @@ -44,7 +44,7 @@ class TestRegistration(unittest.TestCase): self.params.put("HardwareSerial", "serial") self._generate_keys() - with mock.patch("selfdrive.athena.registration.api_get", autospec=True) as m: + with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m: dongle = "DONGLE_ID_123" self.params.put("DongleId", dongle) self.assertEqual(register(), dongle) @@ -52,7 +52,7 @@ class TestRegistration(unittest.TestCase): def test_no_keys(self): # missing pubkey - with mock.patch("selfdrive.athena.registration.api_get", autospec=True) as m: + with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m: dongle = register() self.assertEqual(m.call_count, 0) self.assertEqual(dongle, UNREGISTERED_DONGLE_ID) @@ -61,7 +61,7 @@ class TestRegistration(unittest.TestCase): def test_missing_cache(self): # keys exist but no dongle id self._generate_keys() - with mock.patch("selfdrive.athena.registration.api_get", autospec=True) as m: + with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m: dongle = "DONGLE_ID_123" m.return_value = MockResponse(json.dumps({'dongle_id': dongle}), 200) self.assertEqual(register(), dongle) @@ -75,7 +75,7 @@ class TestRegistration(unittest.TestCase): def test_unregistered(self): # keys exist, but unregistered self._generate_keys() - with mock.patch("selfdrive.athena.registration.api_get", autospec=True) as m: + with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m: m.return_value = MockResponse(None, 402) dongle = register() self.assertEqual(m.call_count, 1) diff --git a/selfdrive/boardd/SConscript b/selfdrive/boardd/SConscript index 2fe4591484..666763d9b0 100644 --- a/selfdrive/boardd/SConscript +++ b/selfdrive/boardd/SConscript @@ -7,5 +7,5 @@ env.Program('boardd', ['main.cc', 'boardd.cc'], LIBS=[panda] + libs) env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) -if GetOption('test'): +if GetOption('extras'): env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc'], LIBS=[panda] + libs) diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 31ae0a4102..b64a81296e 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -18,11 +18,13 @@ #include #include #include +#include #include #include "cereal/gen/cpp/car.capnp.h" #include "cereal/messaging/messaging.h" #include "common/params.h" +#include "common/ratekeeper.h" #include "common/swaglog.h" #include "common/timing.h" #include "common/util.h" @@ -248,8 +250,7 @@ void can_recv_thread(std::vector pandas) { PubMaster pm({"can"}); // run at 100Hz - const uint64_t dt = 10000000ULL; - uint64_t next_frame_time = nanos_since_boot() + dt; + RateKeeper rk("boardd_can_recv", 100); std::vector raw_can_data; while (!do_exit && check_all_connected(pandas)) { @@ -271,18 +272,7 @@ void can_recv_thread(std::vector pandas) { } pm.send("can", msg); - uint64_t cur_time = nanos_since_boot(); - int64_t remaining = next_frame_time - cur_time; - if (remaining > 0) { - std::this_thread::sleep_for(std::chrono::nanoseconds(remaining)); - } else { - if (ignition) { - LOGW("missed cycles (%lu) %lld", (unsigned long)(-1*remaining/dt), (long long)remaining); - } - next_frame_time = cur_time; - } - - next_frame_time += dt; + rk.keepTime(); } } @@ -483,14 +473,16 @@ void panda_state_thread(std::vector pandas, bool spoofing_started) { LOGD("start panda state thread"); // run at 2hz - while (!do_exit && check_all_connected(pandas)) { - uint64_t start_time = nanos_since_boot(); + RateKeeper rk("panda_state_thread", 2); + while (!do_exit && check_all_connected(pandas)) { // send out peripheralState send_peripheral_state(&pm, peripheral_panda); auto ignition_opt = send_panda_states(&pm, pandas, spoofing_started); if (!ignition_opt) { + LOGE("Failed to get ignition_opt"); + rk.keepTime(); continue; } @@ -543,8 +535,7 @@ void panda_state_thread(std::vector pandas, bool spoofing_started) { panda->send_heartbeat(engaged); } - uint64_t dt = nanos_since_boot() - start_time; - util::sleep_for(500 - dt / 1000000ULL); + rk.keepTime(); } } diff --git a/selfdrive/boardd/boardd.h b/selfdrive/boardd/boardd.h index d3c9e1f94a..0646fc6189 100644 --- a/selfdrive/boardd/boardd.h +++ b/selfdrive/boardd/boardd.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "selfdrive/boardd/panda.h" bool safety_setter_thread(std::vector pandas); diff --git a/selfdrive/boardd/boardd.py b/selfdrive/boardd/boardd.py index 527f1f4f52..0cdaf5e912 100644 --- a/selfdrive/boardd/boardd.py +++ b/selfdrive/boardd/boardd.py @@ -1,7 +1,5 @@ -# pylint: skip-file - # Cython, now uses scons to build -from selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp +from openpilot.selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp assert can_list_to_can_capnp def can_capnp_to_can_list(can, src_filter=None): diff --git a/selfdrive/boardd/can_list_to_can_capnp.cc b/selfdrive/boardd/can_list_to_can_capnp.cc index c1778c51a2..72ca72688a 100644 --- a/selfdrive/boardd/can_list_to_can_capnp.cc +++ b/selfdrive/boardd/can_list_to_can_capnp.cc @@ -1,5 +1,5 @@ #include "cereal/messaging/messaging.h" -#include "panda.h" +#include "selfdrive/boardd/panda.h" void can_list_to_can_capnp_cpp(const std::vector &can_list, std::string &out, bool sendCan, bool valid) { MessageBuilder msg; diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index 8849a46bd8..e075887a4d 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -4,6 +4,7 @@ #include #include +#include #include "cereal/messaging/messaging.h" #include "common/swaglog.h" diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index 5edca04419..9d4b1b2092 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "cereal/gen/cpp/car.capnp.h" diff --git a/selfdrive/boardd/panda_comms.cc b/selfdrive/boardd/panda_comms.cc index 120d2f67d5..bc4e5f5867 100644 --- a/selfdrive/boardd/panda_comms.cc +++ b/selfdrive/boardd/panda_comms.cc @@ -199,7 +199,7 @@ int PandaUsbHandle::bulk_write(unsigned char endpoint, unsigned char* data, int } else if (err != 0 || length != transferred) { handle_usb_issue(err, __func__); } - } while(err != 0 && connected); + } while (err != 0 && connected); return transferred; } @@ -226,7 +226,7 @@ int PandaUsbHandle::bulk_read(unsigned char endpoint, unsigned char* data, int l handle_usb_issue(err, __func__); } - } while(err != 0 && connected); + } while (err != 0 && connected); return transferred; } diff --git a/selfdrive/boardd/panda_comms.h b/selfdrive/boardd/panda_comms.h index c102642e59..e61d25402f 100644 --- a/selfdrive/boardd/panda_comms.h +++ b/selfdrive/boardd/panda_comms.h @@ -20,8 +20,8 @@ // comms base class class PandaCommsHandle { public: - PandaCommsHandle(std::string serial) {}; - virtual ~PandaCommsHandle() {}; + PandaCommsHandle(std::string serial) {} + virtual ~PandaCommsHandle() {} virtual void cleanup() = 0; std::string hw_serial; diff --git a/selfdrive/boardd/pandad.py b/selfdrive/boardd/pandad.py index be2ed3c4cc..e614432fad 100755 --- a/selfdrive/boardd/pandad.py +++ b/selfdrive/boardd/pandad.py @@ -9,11 +9,11 @@ from typing import List, NoReturn from functools import cmp_to_key from panda import Panda, PandaDFU, PandaProtocolMismatch, FW_PATH -from common.basedir import BASEDIR -from common.params import Params -from selfdrive.boardd.set_time import set_time -from system.hardware import HARDWARE -from system.swaglog import cloudlog +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.selfdrive.boardd.set_time import set_time +from openpilot.system.hardware import HARDWARE +from openpilot.system.swaglog import cloudlog def get_expected_signature(panda: Panda) -> bytes: @@ -129,6 +129,7 @@ def main() -> NoReturn: count = 0 first_run = True params = Params() + no_internal_panda_count = 0 while True: try: @@ -136,6 +137,16 @@ def main() -> NoReturn: cloudlog.event("pandad.flash_and_connect", count=count) params.remove("PandaSignatures") + # Handle missing internal panda + if no_internal_panda_count > 0: + if no_internal_panda_count == 3: + cloudlog.info("No pandas found, putting internal panda into DFU") + HARDWARE.recover_internal_panda() + else: + cloudlog.info("No pandas found, resetting internal panda") + HARDWARE.reset_internal_panda() + time.sleep(3) # wait to come back up + # Flash all Pandas in DFU mode dfu_serials = PandaDFU.list() if len(dfu_serials) > 0: @@ -146,10 +157,7 @@ def main() -> NoReturn: panda_serials = Panda.list() if len(panda_serials) == 0: - if first_run: - cloudlog.info("No pandas found, resetting internal panda") - HARDWARE.reset_internal_panda() - time.sleep(2) # wait to come back up + no_internal_panda_count += 1 continue cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}") @@ -162,10 +170,10 @@ def main() -> NoReturn: # 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: - cloudlog.error("Internal panda is missing, resetting") - HARDWARE.reset_internal_panda() - time.sleep(2) # wait to come back up + 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 pandas.sort(key=cmp_to_key(panda_sort_cmp)) diff --git a/selfdrive/boardd/set_time.py b/selfdrive/boardd/set_time.py index 93453dcd97..fe17f64e82 100755 --- a/selfdrive/boardd/set_time.py +++ b/selfdrive/boardd/set_time.py @@ -3,7 +3,7 @@ import os import datetime from panda import Panda -from common.time import MIN_DATE +from openpilot.common.time import MIN_DATE def set_time(logger): sys_time = datetime.datetime.today() diff --git a/selfdrive/boardd/spi.cc b/selfdrive/boardd/spi.cc index 1732e902d5..d11e955c49 100644 --- a/selfdrive/boardd/spi.cc +++ b/selfdrive/boardd/spi.cc @@ -43,7 +43,7 @@ public: LockEx(int fd, std::recursive_mutex &m) : fd(fd), m(m) { m.lock(); flock(fd, LOCK_EX); - }; + } ~LockEx() { flock(fd, LOCK_UN); diff --git a/selfdrive/boardd/tests/test_boardd_loopback.py b/selfdrive/boardd/tests/test_boardd_loopback.py index 74393c6eb1..dfce0e3710 100755 --- a/selfdrive/boardd/tests/test_boardd_loopback.py +++ b/selfdrive/boardd/tests/test_boardd_loopback.py @@ -9,12 +9,12 @@ from pprint import pprint import cereal.messaging as messaging from cereal import car, log -from common.params import Params -from common.timeout import Timeout -from selfdrive.boardd.boardd import can_list_to_can_capnp -from selfdrive.car import make_can_msg -from system.hardware import TICI -from selfdrive.test.helpers import phone_only, with_processes +from openpilot.common.params import Params +from openpilot.common.timeout import Timeout +from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp +from openpilot.selfdrive.car import make_can_msg +from openpilot.system.hardware import TICI +from openpilot.selfdrive.test.helpers import phone_only, with_processes class TestBoardd(unittest.TestCase): diff --git a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc index 52cb11ec32..86476d05cd 100644 --- a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc +++ b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc @@ -82,7 +82,7 @@ void PandaTest::test_can_recv(uint32_t rx_chunk_size) { this->receive_buffer_size = 0; uint32_t pos = 0; - while(pos < size) { + while (pos < size) { uint32_t chunk_size = std::min(rx_chunk_size, size - pos); memcpy(&this->receive_buffer[this->receive_buffer_size], &data[pos], chunk_size); this->receive_buffer_size += chunk_size; diff --git a/selfdrive/boardd/tests/test_pandad.py b/selfdrive/boardd/tests/test_pandad.py index 1d49446bf5..30a4a9b868 100755 --- a/selfdrive/boardd/tests/test_pandad.py +++ b/selfdrive/boardd/tests/test_pandad.py @@ -5,13 +5,13 @@ import unittest import cereal.messaging as messaging from cereal import log -from common.gpio import gpio_set, gpio_init -from common.params import Params +from openpilot.common.gpio import gpio_set, gpio_init +from openpilot.common.params import Params from panda import Panda, PandaDFU, PandaProtocolMismatch -from selfdrive.test.helpers import phone_only -from selfdrive.manager.process_config import managed_processes -from system.hardware import HARDWARE -from system.hardware.tici.pins import GPIO +from openpilot.selfdrive.test.helpers import phone_only +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.hardware import HARDWARE +from openpilot.system.hardware.tici.pins import GPIO HERE = os.path.dirname(os.path.realpath(__file__)) @@ -25,13 +25,17 @@ class TestPandad(unittest.TestCase): def tearDown(self): managed_processes['pandad'].stop() - def _wait_for_boardd(self, timeout=30): + def _run_test(self, timeout=30): + managed_processes['pandad'].start() + sm = messaging.SubMaster(['peripheralState']) for _ in range(timeout*10): sm.update(100) if sm['peripheralState'].pandaType != log.PandaState.PandaType.unknown: break + managed_processes['pandad'].stop() + if sm['peripheralState'].pandaType == log.PandaState.PandaType.unknown: raise Exception("boardd failed to start") @@ -43,7 +47,11 @@ class TestPandad(unittest.TestCase): HARDWARE.recover_internal_panda() assert Panda.wait_for_dfu(None, 10) - def _flash_and_test(self, fn, expect_mismatch=False): + def _assert_no_panda(self): + assert not Panda.wait_for_dfu(None, 3) + assert not Panda.wait_for_panda(None, 3) + + def _flash_bootstub_and_test(self, fn, expect_mismatch=False): self._go_to_dfu() pd = PandaDFU(None) if fn is None: @@ -61,22 +69,19 @@ class TestPandad(unittest.TestCase): with Panda() as p: assert p.bootstub - managed_processes['pandad'].start() - self._wait_for_boardd(45) + self._run_test(45) @phone_only def test_in_dfu(self): HARDWARE.recover_internal_panda() - managed_processes['pandad'].start() - self._wait_for_boardd(60) + self._run_test(60) @phone_only def test_in_bootstub(self): with Panda() as p: p.reset(enter_bootstub=True) assert p.bootstub - managed_processes['pandad'].start() - self._wait_for_boardd() + self._run_test() @phone_only def test_internal_panda_reset(self): @@ -84,22 +89,17 @@ class TestPandad(unittest.TestCase): gpio_set(GPIO.STM_RST_N, 1) time.sleep(0.5) assert all(not Panda(s).is_internal() for s in Panda.list()) - - managed_processes['pandad'].start() - self._wait_for_boardd() + self._run_test() assert any(Panda(s).is_internal() for s in Panda.list()) @phone_only def test_best_case_startup_time(self): # run once so we're setup - managed_processes['pandad'].start() - self._wait_for_boardd() - managed_processes['pandad'].stop() + self._run_test() # should be fast this time - managed_processes['pandad'].start() - self._wait_for_boardd(8) + self._run_test(8) @phone_only def test_protocol_version_check(self): @@ -108,11 +108,23 @@ class TestPandad(unittest.TestCase): # flash old fw fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin") - self._flash_and_test(fn, expect_mismatch=True) + self._flash_bootstub_and_test(fn, expect_mismatch=True) @phone_only def test_release_to_devel_bootstub(self): - self._flash_and_test(None) + self._flash_bootstub_and_test(None) + + @phone_only + def test_recover_from_bad_bootstub(self): + self._go_to_dfu() + with PandaDFU(None) as pd: + pd.program_bootstub(b"\x00"*1024) + pd.reset() + HARDWARE.reset_internal_panda() + self._assert_no_panda() + + self._run_test(60) + if __name__ == "__main__": unittest.main() diff --git a/selfdrive/car/CARS_template.md b/selfdrive/car/CARS_template.md index ef64f6aa1e..9d045e7627 100644 --- a/selfdrive/car/CARS_template.md +++ b/selfdrive/car/CARS_template.md @@ -10,7 +10,7 @@ # Supported Cars -A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. +A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. # {{all_car_info | length}} Supported Cars diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 156161c796..c90ae50ab9 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -1,11 +1,11 @@ # functions common among cars from collections import namedtuple -from typing import Dict, Optional +from typing import Dict, List, Optional import capnp from cereal import car -from common.numpy_fast import clip, interp +from openpilot.common.numpy_fast import clip, interp # kg of standard extra cargo to count for drive, gas, etc... @@ -24,20 +24,23 @@ def apply_hysteresis(val: float, val_steady: float, hyst_gap: float) -> float: return val_steady -def create_button_event(cur_but: int, prev_but: int, buttons_dict: Dict[int, capnp.lib.capnp._EnumModule], - unpressed: int = 0) -> capnp.lib.capnp._DynamicStructBuilder: - if cur_but != unpressed: - be = car.CarState.ButtonEvent(pressed=True) - but = cur_but - else: - be = car.CarState.ButtonEvent(pressed=False) - but = prev_but - be.type = buttons_dict.get(but, ButtonType.unknown) - return be +def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: Dict[int, capnp.lib.capnp._EnumModule], + unpressed_btn: int = 0) -> List[capnp.lib.capnp._DynamicStructBuilder]: + events: List[capnp.lib.capnp._DynamicStructBuilder] = [] + + if cur_btn == prev_btn: + return events + + # Add events for button presses, multiple when a button switches without going to unpressed + for pressed, btn in ((False, prev_btn), (True, cur_btn)): + if btn != unpressed_btn: + events.append(car.CarState.ButtonEvent(pressed=pressed, + type=buttons_dict.get(btn, ButtonType.unknown))) + return events def gen_empty_fingerprint(): - return {i: {} for i in range(0, 8)} + return {i: {} for i in range(8)} # these params were derived for the Civic and used to calculate params for other cars @@ -232,4 +235,4 @@ class CanSignalRateCalculator: self.previous_counter = current_counter self.previous_value = current_value - return self.rate \ No newline at end of file + return self.rate diff --git a/selfdrive/car/body/carcontroller.py b/selfdrive/car/body/carcontroller.py index 4993b1865f..1dad8e796a 100644 --- a/selfdrive/car/body/carcontroller.py +++ b/selfdrive/car/body/carcontroller.py @@ -1,11 +1,11 @@ import numpy as np -from common.params import Params -from common.realtime import DT_CTRL +from openpilot.common.params import Params +from openpilot.common.realtime import DT_CTRL from opendbc.can.packer import CANPacker -from selfdrive.car.body import bodycan -from selfdrive.car.body.values import SPEED_FROM_RPM -from selfdrive.controls.lib.pid import PIDController +from openpilot.selfdrive.car.body import bodycan +from openpilot.selfdrive.car.body.values import SPEED_FROM_RPM +from openpilot.selfdrive.controls.lib.pid import PIDController MAX_TORQUE = 500 diff --git a/selfdrive/car/body/carstate.py b/selfdrive/car/body/carstate.py index a1ef17360f..fca9bcc627 100644 --- a/selfdrive/car/body/carstate.py +++ b/selfdrive/car/body/carstate.py @@ -1,7 +1,7 @@ from cereal import car from opendbc.can.parser import CANParser -from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.body.values import DBC +from openpilot.selfdrive.car.interfaces import CarStateBase +from openpilot.selfdrive.car.body.values import DBC STARTUP_TICKS = 100 diff --git a/selfdrive/car/body/interface.py b/selfdrive/car/body/interface.py index 4d583badae..12a2d5f304 100644 --- a/selfdrive/car/body/interface.py +++ b/selfdrive/car/body/interface.py @@ -1,10 +1,9 @@ -#!/usr/bin/env python3 import math from cereal import car -from common.realtime import DT_CTRL -from selfdrive.car import get_safety_config -from selfdrive.car.interfaces import CarInterfaceBase -from selfdrive.car.body.values import SPEED_FROM_RPM +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.car.body.values import SPEED_FROM_RPM class CarInterface(CarInterfaceBase): @staticmethod diff --git a/selfdrive/car/body/radar_interface.py b/selfdrive/car/body/radar_interface.py index b2f7651136..e654bd61fd 100644 --- a/selfdrive/car/body/radar_interface.py +++ b/selfdrive/car/body/radar_interface.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -from selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase class RadarInterface(RadarInterfaceBase): pass diff --git a/selfdrive/car/body/values.py b/selfdrive/car/body/values.py index 4fef966374..56d9723818 100644 --- a/selfdrive/car/body/values.py +++ b/selfdrive/car/body/values.py @@ -1,9 +1,9 @@ from typing import Dict from cereal import car -from selfdrive.car import dbc_dict -from selfdrive.car.docs_definitions import CarInfo -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries +from openpilot.selfdrive.car import dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarInfo +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 652862de47..7017580368 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -1,17 +1,18 @@ import os +import time from typing import Callable, Dict, List, Optional, Tuple from cereal import car -from common.params import Params -from common.basedir import BASEDIR -from system.version import is_comma_remote, is_tested_branch -from selfdrive.car.interfaces import get_interface_attr -from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars -from selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN -from selfdrive.car.fw_versions import get_fw_versions_ordered, get_present_ecus, match_fw_to_car, set_obd_multiplexing -from system.swaglog import cloudlog +from openpilot.common.params import Params +from openpilot.common.basedir import BASEDIR +from openpilot.system.version import is_comma_remote, is_tested_branch +from openpilot.selfdrive.car.interfaces import get_interface_attr +from openpilot.selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars +from openpilot.selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN +from openpilot.selfdrive.car.fw_versions import get_fw_versions_ordered, get_present_ecus, match_fw_to_car, set_obd_multiplexing +from openpilot.system.swaglog import cloudlog import cereal.messaging as messaging -from selfdrive.car import gen_empty_fingerprint +from openpilot.selfdrive.car import gen_empty_fingerprint FRAME_FINGERPRINT = 100 # 1s @@ -44,7 +45,7 @@ def get_one_can(logcan): def load_interfaces(brand_names): ret = {} for brand_name in brand_names: - path = f'selfdrive.car.{brand_name}' + path = f'openpilot.selfdrive.car.{brand_name}' CarInterface = __import__(path + '.interface', fromlist=['CarInterface']).CarInterface if os.path.exists(BASEDIR + '/' + path.replace('.', '/') + '/carstate.py'): @@ -125,6 +126,7 @@ def fingerprint(logcan, sendcan, num_pandas): ecu_rx_addrs = set() params = Params() + start_time = time.monotonic() if not skip_fw_query: # Vin query only reliably works through OBDII bus = 1 @@ -165,6 +167,8 @@ def fingerprint(logcan, sendcan, num_pandas): set_obd_multiplexing(params, False) params.put_bool("FirmwareQueryDone", True) + fw_query_time = time.monotonic() - start_time + # CAN fingerprint # drain CAN socket so we get the latest messages messaging.drain_sock_raw(logcan) @@ -185,7 +189,7 @@ def fingerprint(logcan, sendcan, num_pandas): cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, source=source, fuzzy=not exact_match, cached=cached, fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), vin_rx_addr=vin_rx_addr, fingerprints=finger, - error=True) + fw_query_time=fw_query_time, error=True) return car_fingerprint, finger, vin, car_fw, source, exact_match diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py index b418179e0e..050eb41b1a 100644 --- a/selfdrive/car/chrysler/carcontroller.py +++ b/selfdrive/car/chrysler/carcontroller.py @@ -1,8 +1,8 @@ from opendbc.can.packer import CANPacker -from common.realtime import DT_CTRL -from selfdrive.car import apply_meas_steer_torque_limits -from selfdrive.car.chrysler.chryslercan import create_lkas_hud, create_lkas_command, create_cruise_buttons -from selfdrive.car.chrysler.values import RAM_CARS, CarControllerParams, ChryslerFlags +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car import apply_meas_steer_torque_limits +from openpilot.selfdrive.car.chrysler import chryslercan +from openpilot.selfdrive.car.chrysler.values import RAM_CARS, CarControllerParams, ChryslerFlags class CarController: @@ -31,17 +31,18 @@ class CarController: # ACC cancellation if CC.cruiseControl.cancel: self.last_button_frame = self.frame - can_sends.append(create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, cancel=True)) + can_sends.append(chryslercan.create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, cancel=True)) # ACC resume from standstill elif CC.cruiseControl.resume: self.last_button_frame = self.frame - can_sends.append(create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, resume=True)) + can_sends.append(chryslercan.create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, resume=True)) # HUD alerts if self.frame % 25 == 0: if CS.lkas_car_model != -1: - can_sends.append(create_lkas_hud(self.packer, self.CP, lkas_active, CC.hudControl.visualAlert, self.hud_count, CS.lkas_car_model, CS.auto_high_beam)) + can_sends.append(chryslercan.create_lkas_hud(self.packer, self.CP, lkas_active, CC.hudControl.visualAlert, + self.hud_count, CS.lkas_car_model, CS.auto_high_beam)) self.hud_count += 1 # steering @@ -72,7 +73,7 @@ class CarController: apply_steer = 0 self.apply_steer_last = apply_steer - can_sends.append(create_lkas_command(self.packer, self.CP, int(apply_steer), lkas_control_bit)) + can_sends.append(chryslercan.create_lkas_command(self.packer, self.CP, int(apply_steer), lkas_control_bit)) self.frame += 1 diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index 7b250ddf9b..eb1cf7e7d5 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -1,9 +1,9 @@ from cereal import car -from common.conversions import Conversions as CV +from openpilot.common.conversions import Conversions as CV from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine -from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.chrysler.values import DBC, STEER_THRESHOLD, RAM_CARS +from openpilot.selfdrive.car.interfaces import CarStateBase +from openpilot.selfdrive.car.chrysler.values import DBC, STEER_THRESHOLD, RAM_CARS class CarState(CarStateBase): diff --git a/selfdrive/car/chrysler/chryslercan.py b/selfdrive/car/chrysler/chryslercan.py index 10ed73e9f2..96439f35d8 100644 --- a/selfdrive/car/chrysler/chryslercan.py +++ b/selfdrive/car/chrysler/chryslercan.py @@ -1,5 +1,5 @@ from cereal import car -from selfdrive.car.chrysler.values import RAM_CARS +from openpilot.selfdrive.car.chrysler.values import RAM_CARS GearShifter = car.CarState.GearShifter VisualAlert = car.CarControl.HUDControl.VisualAlert diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index a3c0b0c7a1..a87759910b 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from selfdrive.car import get_safety_config -from selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags -from selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags +from openpilot.selfdrive.car.interfaces import CarInterfaceBase class CarInterface(CarInterfaceBase): diff --git a/selfdrive/car/chrysler/radar_interface.py b/selfdrive/car/chrysler/radar_interface.py index fc795a36ca..d982958422 100755 --- a/selfdrive/car/chrysler/radar_interface.py +++ b/selfdrive/car/chrysler/radar_interface.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 from opendbc.can.parser import CANParser from cereal import car -from selfdrive.car.interfaces import RadarInterfaceBase -from selfdrive.car.chrysler.values import DBC +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.chrysler.values import DBC RADAR_MSGS_C = list(range(0x2c2, 0x2d4+2, 2)) # c_ messages 706,...,724 RADAR_MSGS_D = list(range(0x2a2, 0x2b4+2, 2)) # d_ messages @@ -46,7 +46,7 @@ class RadarInterface(RadarInterfaceBase): def update(self, can_strings): if self.rcp is None or self.CP.radarUnavailable: - return None + return super().update(None) vls = self.rcp.update_strings(can_strings) self.updated_messages.update(vls) diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 172d3a08cc..657ada2706 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -5,9 +5,9 @@ from typing import Dict, List, Optional, Union from cereal import car from panda.python import uds -from selfdrive.car import dbc_dict -from selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 +from openpilot.selfdrive.car import dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu diff --git a/selfdrive/car/disable_ecu.py b/selfdrive/car/disable_ecu.py index 36ebe12fa8..c11075342c 100755 --- a/selfdrive/car/disable_ecu.py +++ b/selfdrive/car/disable_ecu.py @@ -1,5 +1,6 @@ -from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery -from system.swaglog import cloudlog +#!/usr/bin/env python3 +from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery +from openpilot.system.swaglog import cloudlog EXT_DIAG_REQUEST = b'\x10\x03' EXT_DIAG_RESPONSE = b'\x50\x03' diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py index 02075a5e11..8475a69d8a 100755 --- a/selfdrive/car/docs.py +++ b/selfdrive/car/docs.py @@ -8,10 +8,10 @@ from natsort import natsorted from typing import Dict, List from cereal import car -from common.basedir import BASEDIR -from selfdrive.car import gen_empty_fingerprint -from selfdrive.car.docs_definitions import CarInfo, Column, CommonFootnote, PartType -from selfdrive.car.car_helpers import interfaces, get_interface_attr +from openpilot.common.basedir import BASEDIR +from openpilot.selfdrive.car import gen_empty_fingerprint +from openpilot.selfdrive.car.docs_definitions import CarInfo, Column, CommonFootnote, PartType +from openpilot.selfdrive.car.car_helpers import interfaces, get_interface_attr def get_all_footnotes() -> Dict[Enum, int]: diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index 2220205e8f..60f9494708 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -6,7 +6,7 @@ from enum import Enum from typing import Dict, List, Optional, Tuple, Union from cereal import car -from common.conversions import Conversions as CV +from openpilot.common.conversions import Conversions as CV GOOD_TORQUE_THRESHOLD = 1.0 # m/s^2 MODEL_YEARS_RE = r"(?<= )((\d{4}-\d{2})|(\d{4}))(,|$)" @@ -85,6 +85,7 @@ class CarHarness(EnumBase): bosch_a = BaseCarHarness("Honda Bosch A connector") bosch_b = BaseCarHarness("Honda Bosch B connector") toyota_a = BaseCarHarness("Toyota A connector") + toyota_b = BaseCarHarness("Toyota B connector") subaru_a = BaseCarHarness("Subaru A connector") subaru_b = BaseCarHarness("Subaru B connector") subaru_c = BaseCarHarness("Subaru C connector") @@ -110,6 +111,7 @@ class CarHarness(EnumBase): hyundai_o = BaseCarHarness("Hyundai O connector") hyundai_p = BaseCarHarness("Hyundai P connector") hyundai_q = BaseCarHarness("Hyundai Q connector") + hyundai_r = BaseCarHarness("Hyundai R connector") custom = BaseCarHarness("Developer connector") obd_ii = BaseCarHarness("OBD-II connector", parts=[Cable.long_obdc_cable, Cable.long_obdc_cable], has_connector=False) gm = BaseCarHarness("GM connector") @@ -174,8 +176,8 @@ CarFootnote = namedtuple("CarFootnote", ["text", "column", "docs_only", "shop_fo class CommonFootnote(Enum): EXP_LONG_AVAIL = CarFootnote( - "Experimental openpilot longitudinal control is available behind a toggle; " + - "the toggle is only available in non-release branches such as `devel` or `master-ci`. ", + "openpilot Longitudinal Control (Alpha) is available behind a toggle; " + + "the toggle is only available in non-release branches such as `devel` or `master-ci`.", Column.LONGITUDINAL, docs_only=True) EXP_LONG_DSU = CarFootnote( "By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. " + diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py index 25938f0d3c..cff1df79a5 100755 --- a/selfdrive/car/ecu_addrs.py +++ b/selfdrive/car/ecu_addrs.py @@ -5,10 +5,10 @@ from typing import Optional, Set import cereal.messaging as messaging from panda.python.uds import SERVICE_TYPE -from selfdrive.car import make_can_msg -from selfdrive.car.fw_query_definitions import EcuAddrBusType -from selfdrive.boardd.boardd import can_list_to_can_capnp -from system.swaglog import cloudlog +from openpilot.selfdrive.car import make_can_msg +from openpilot.selfdrive.car.fw_query_definitions import EcuAddrBusType +from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp +from openpilot.system.swaglog import cloudlog def make_tester_present_msg(addr, bus, subaddr=None): diff --git a/selfdrive/car/fingerprints.py b/selfdrive/car/fingerprints.py index 09ed30cc19..1d627e4b37 100644 --- a/selfdrive/car/fingerprints.py +++ b/selfdrive/car/fingerprints.py @@ -1,4 +1,4 @@ -from selfdrive.car.interfaces import get_interface_attr +from openpilot.selfdrive.car.interfaces import get_interface_attr FW_VERSIONS = get_interface_attr('FW_VERSIONS', combine_brands=True, ignore_none=True) diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index dd30bc57e1..fe9ee404a4 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -1,10 +1,9 @@ from cereal import car -from common.numpy_fast import clip +from openpilot.common.numpy_fast import clip from opendbc.can.packer import CANPacker -from selfdrive.car import apply_std_steer_angle_limits -from selfdrive.car.ford.fordcan import CanBus, create_acc_msg, create_acc_ui_msg, create_button_msg, \ - create_lat_ctl_msg, create_lat_ctl2_msg, create_lka_msg, create_lkas_ui_msg -from selfdrive.car.ford.values import CANFD_CAR, CarControllerParams +from openpilot.selfdrive.car import apply_std_steer_angle_limits +from openpilot.selfdrive.car.ford import fordcan +from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams LongCtrlState = car.CarControl.Actuators.LongControlState VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -27,7 +26,7 @@ class CarController: self.CP = CP self.VM = VM self.packer = CANPacker(dbc_name) - self.CAN = CanBus(CP) + self.CAN = fordcan.CanBus(CP) self.frame = 0 self.apply_curvature_last = 0 @@ -47,15 +46,15 @@ class CarController: ### acc buttons ### if CC.cruiseControl.cancel: - can_sends.append(create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, cancel=True)) - can_sends.append(create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, cancel=True)) + can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, cancel=True)) + can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, cancel=True)) elif CC.cruiseControl.resume and (self.frame % CarControllerParams.BUTTONS_STEP) == 0: - can_sends.append(create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, resume=True)) - can_sends.append(create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, resume=True)) + can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, resume=True)) + can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, resume=True)) # if stock lane centering isn't off, send a button press to toggle it off # the stock system checks for steering pressed, and eventually disengages cruise control elif CS.acc_tja_status_stock_values["Tja_D_Stat"] != 0 and (self.frame % CarControllerParams.ACC_UI_STEP) == 0: - can_sends.append(create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, tja_toggle=True)) + can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, tja_toggle=True)) ### lateral control ### # send steer msg at 20Hz @@ -73,13 +72,13 @@ class CarController: # TODO: extended mode mode = 1 if CC.latActive else 0 counter = (self.frame // CarControllerParams.STEER_STEP) % 0xF - can_sends.append(create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -apply_curvature, 0., counter)) + can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -apply_curvature, 0., counter)) else: - can_sends.append(create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -apply_curvature, 0.)) + can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -apply_curvature, 0.)) # send lka msg at 33Hz if (self.frame % CarControllerParams.LKA_STEP) == 0: - can_sends.append(create_lka_msg(self.packer, self.CAN)) + can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN)) ### longitudinal control ### # send acc msg at 50Hz @@ -91,16 +90,16 @@ class CarController: gas = CarControllerParams.INACTIVE_GAS stopping = CC.actuators.longControlState == LongCtrlState.stopping - can_sends.append(create_acc_msg(self.packer, self.CAN, CC.longActive, gas, accel, stopping)) + can_sends.append(fordcan.create_acc_msg(self.packer, self.CAN, CC.longActive, gas, accel, stopping)) ### ui ### send_ui = (self.main_on_last != main_on) or (self.lkas_enabled_last != CC.latActive) or (self.steer_alert_last != steer_alert) # send lkas ui msg at 1Hz or if ui state changes if (self.frame % CarControllerParams.LKAS_UI_STEP) == 0 or send_ui: - can_sends.append(create_lkas_ui_msg(self.packer, self.CAN, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values)) + can_sends.append(fordcan.create_lkas_ui_msg(self.packer, self.CAN, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values)) # send acc ui msg at 5Hz or if ui state changes if (self.frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui: - can_sends.append(create_acc_ui_msg(self.packer, self.CAN, self.CP, main_on, CC.latActive, + can_sends.append(fordcan.create_acc_ui_msg(self.packer, self.CAN, self.CP, main_on, CC.latActive, fcw_alert, CS.out.cruiseState.standstill, hud_control, CS.acc_tja_status_stock_values)) diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 824d929930..5c787b787a 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -1,10 +1,10 @@ from cereal import car -from common.conversions import Conversions as CV +from openpilot.common.conversions import Conversions as CV from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser -from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.ford.fordcan import CanBus -from selfdrive.car.ford.values import CANFD_CAR, CarControllerParams, DBC +from openpilot.selfdrive.car.interfaces import CarStateBase +from openpilot.selfdrive.car.ford.fordcan import CanBus +from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams, DBC GearShifter = car.CarState.GearShifter TransmissionType = car.CarParams.TransmissionType diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py index a49d7ad85d..e0086ecbb5 100644 --- a/selfdrive/car/ford/fordcan.py +++ b/selfdrive/car/ford/fordcan.py @@ -1,5 +1,5 @@ from cereal import car -from selfdrive.car import CanBusBase +from openpilot.selfdrive.car import CanBusBase HUDControl = car.CarControl.HUDControl diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 990d14de93..3045c317ff 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -1,11 +1,10 @@ -#!/usr/bin/env python3 from cereal import car from panda import Panda -from common.conversions import Conversions as CV -from selfdrive.car import get_safety_config -from selfdrive.car.ford.fordcan import CanBus -from selfdrive.car.ford.values import CANFD_CAR, CAR, Ecu -from selfdrive.car.interfaces import CarInterfaceBase +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car.ford.fordcan import CanBus +from openpilot.selfdrive.car.ford.values import CANFD_CAR, CAR, Ecu +from openpilot.selfdrive.car.interfaces import CarInterfaceBase TransmissionType = car.CarParams.TransmissionType GearShifter = car.CarState.GearShifter diff --git a/selfdrive/car/ford/radar_interface.py b/selfdrive/car/ford/radar_interface.py index 39a62b9e43..716c9b6e58 100644 --- a/selfdrive/car/ford/radar_interface.py +++ b/selfdrive/car/ford/radar_interface.py @@ -1,11 +1,10 @@ -#!/usr/bin/env python3 from math import cos, sin from cereal import car from opendbc.can.parser import CANParser -from common.conversions import Conversions as CV -from selfdrive.car.ford.fordcan import CanBus -from selfdrive.car.ford.values import DBC, RADAR -from selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car.ford.fordcan import CanBus +from openpilot.selfdrive.car.ford.values import DBC, RADAR +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase DELPHI_ESR_RADAR_MSGS = list(range(0x500, 0x540)) @@ -51,7 +50,7 @@ class RadarInterface(RadarInterfaceBase): def update(self, can_strings): if self.rcp is None: - return None + return super().update(None) vls = self.rcp.update_strings(can_strings) self.updated_messages.update(vls) diff --git a/selfdrive/car/ford/tests/__init__.py b/selfdrive/car/ford/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/car/ford/tests/test_ford.py b/selfdrive/car/ford/tests/test_ford.py new file mode 100755 index 0000000000..e645b4cbe6 --- /dev/null +++ b/selfdrive/car/ford/tests/test_ford.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +import unittest +from parameterized import parameterized +from typing import Dict, Iterable, Optional, Tuple + +import capnp + +from cereal import car +from openpilot.selfdrive.car.ford.values import FW_QUERY_CONFIG, FW_VERSIONS + +Ecu = car.CarParams.Ecu + + +ECU_ADDRESSES = { + Ecu.eps: 0x730, # Power Steering Control Module (PSCM) + Ecu.abs: 0x760, # Anti-Lock Brake System (ABS) + Ecu.fwdRadar: 0x764, # Cruise Control Module (CCM) + Ecu.fwdCamera: 0x706, # Image Processing Module A (IPMA) + Ecu.engine: 0x7E0, # Powertrain Control Module (PCM) + Ecu.shiftByWire: 0x732, # Gear Shift Module (GSM) +} + + +ECU_FW_CORE = { + Ecu.eps: [ + b"14D003", + ], + Ecu.abs: [ + b"2D053", + ], + Ecu.fwdRadar: [ + b"14D049", + ], + Ecu.fwdCamera: [ + b"14F397", # Ford Q3 + b"14H102", # Ford Q4 + ], + Ecu.engine: [ + b"14C204", + ], +} + + +class TestFordFW(unittest.TestCase): + def test_fw_query_config(self): + for (ecu, addr, subaddr) in FW_QUERY_CONFIG.extra_ecus: + self.assertIn(ecu, ECU_ADDRESSES, "Unknown ECU") + self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch") + self.assertIsNone(subaddr, "Unexpected ECU subaddress") + + @parameterized.expand(FW_VERSIONS.items()) + def test_fw_versions(self, car_model: str, fw_versions: Dict[Tuple[capnp.lib.capnp._EnumModule, int, Optional[int]], Iterable[bytes]]): + for (ecu, addr, subaddr), fws in fw_versions.items(): + self.assertIn(ecu, ECU_ADDRESSES, "Unknown ECU") + self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch") + self.assertIsNone(subaddr, "Unexpected ECU subaddress") + + # Software part number takes the form: PREFIX-CORE-SUFFIX + # Prefix changes based on the family of part. It includes the model year + # and likely the platform. + # Core identifies the type of the item (e.g. 14D003 = PSCM, 14C204 = PCM). + # Suffix specifies the version of the part. -AA would be followed by -AB. + # Small increments in the suffix are usually compatible. + # Details: https://forscan.org/forum/viewtopic.php?p=70008#p70008 + for fw in fws: + self.assertEqual(len(fw), 24, "Expected ECU response to be 24 bytes") + + # TODO: parse with regex, don't need detailed error message + fw_parts = fw.rstrip(b'\x00').split(b'-') + self.assertEqual(len(fw_parts), 3, "Expected FW to be in format: prefix-core-suffix") + + prefix, core, suffix = fw_parts + self.assertEqual(len(prefix), 4, "Expected FW prefix to be 4 characters") + self.assertIn(len(core), (5, 6), "Expected FW core to be 5-6 characters") + self.assertIn(core, ECU_FW_CORE[ecu], f"Unexpected FW core for {ecu}") + self.assertIn(len(suffix), (2, 3), "Expected FW suffix to be 2-3 characters") + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 01d1ae7b8a..e6aaa2a952 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -4,10 +4,10 @@ from enum import Enum from typing import Dict, List, Union from cereal import car -from selfdrive.car import AngleRateLimit, dbc_dict -from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ +from openpilot.selfdrive.car import AngleRateLimit, dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ Device -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu @@ -108,7 +108,6 @@ FW_QUERY_CONFIG = FwQueryConfig( [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], # whitelist_ecus=[Ecu.engine], - auxiliary=True, ), Request( [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], @@ -266,6 +265,7 @@ FW_VERSIONS = { b'NZ6A-14C204-AAA\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6A-14C204-PA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PZ6A-14C204-BE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PZ6A-14C204-JC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 7fa47da998..45c4967cb8 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -6,13 +6,13 @@ import capnp import panda.python.uds as uds from cereal import car -from common.params import Params -from selfdrive.car.ecu_addrs import get_ecu_addrs -from selfdrive.car.fw_query_definitions import AddrType, EcuAddrBusType -from selfdrive.car.interfaces import get_interface_attr -from selfdrive.car.fingerprints import FW_VERSIONS -from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery -from system.swaglog import cloudlog +from openpilot.common.params import Params +from openpilot.selfdrive.car.ecu_addrs import get_ecu_addrs +from openpilot.selfdrive.car.fw_query_definitions import AddrType, EcuAddrBusType +from openpilot.selfdrive.car.interfaces import get_interface_attr +from openpilot.selfdrive.car.fingerprints import FW_VERSIONS +from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery +from openpilot.system.swaglog import cloudlog Ecu = car.CarParams.Ecu ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] @@ -338,7 +338,7 @@ if __name__ == "__main__": import time import argparse import cereal.messaging as messaging - from selfdrive.car.vin import get_vin + from openpilot.selfdrive.car.vin import get_vin parser = argparse.ArgumentParser(description='Get firmware version of ECUs') parser.add_argument('--scan', action='store_true') diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 1b2424afaf..f51cb75372 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -1,11 +1,11 @@ from cereal import car -from common.conversions import Conversions as CV -from common.numpy_fast import interp -from common.realtime import DT_CTRL +from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import interp +from openpilot.common.realtime import DT_CTRL from opendbc.can.packer import CANPacker -from selfdrive.car import apply_driver_steer_torque_limits -from selfdrive.car.gm import gmcan -from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons +from openpilot.selfdrive.car import apply_driver_steer_torque_limits +from openpilot.selfdrive.car.gm import gmcan +from openpilot.selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons VisualAlert = car.CarControl.HUDControl.VisualAlert NetworkLocation = car.CarParams.NetworkLocation diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 43cdee18d4..89c1a3596a 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -1,11 +1,11 @@ import copy from cereal import car -from common.conversions import Conversions as CV -from common.numpy_fast import mean +from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import mean from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser -from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD +from openpilot.selfdrive.car.interfaces import CarStateBase +from openpilot.selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD TransmissionType = car.CarParams.TransmissionType NetworkLocation = car.CarParams.NetworkLocation @@ -98,7 +98,7 @@ class CarState(CarStateBase): ret.leftBlinker = pt_cp.vl["BCMTurnSignals"]["TurnSignals"] == 1 ret.rightBlinker = pt_cp.vl["BCMTurnSignals"]["TurnSignals"] == 2 - ret.parkingBrake = pt_cp.vl["VehicleIgnitionAlt"]["ParkBrake"] == 1 + ret.parkingBrake = pt_cp.vl["BCMGeneralPlatformStatus"]["ParkBrakeSwActive"] == 1 ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0 ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1 ret.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or @@ -135,7 +135,7 @@ class CarState(CarStateBase): ("PSCMStatus", 10), ("ESPStatus", 10), ("BCMDoorBeltStatus", 10), - ("VehicleIgnitionAlt", 10), + ("BCMGeneralPlatformStatus", 10), ("EBCMWheelSpdFront", 20), ("EBCMWheelSpdRear", 20), ("EBCMFrictionBrakeStatus", 20), diff --git a/selfdrive/car/gm/gmcan.py b/selfdrive/car/gm/gmcan.py index 0de2066678..bd1e29ce3b 100644 --- a/selfdrive/car/gm/gmcan.py +++ b/selfdrive/car/gm/gmcan.py @@ -1,5 +1,5 @@ -from selfdrive.car import make_can_msg -from selfdrive.car.gm.values import CAR +from openpilot.selfdrive.car import make_can_msg +from openpilot.selfdrive.car.gm.values import CAR def create_buttons(packer, bus, idx, button): diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 8e139947a6..9b5cae0961 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -3,12 +3,12 @@ from cereal import car from math import fabs, exp from panda import Panda -from common.conversions import Conversions as CV -from selfdrive.car import create_button_event, get_safety_config -from selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG -from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus -from selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD -from selfdrive.controls.lib.drive_helpers import get_friction +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car import create_button_events, get_safety_config +from openpilot.selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG +from openpilot.selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus +from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD +from openpilot.selfdrive.controls.lib.drive_helpers import get_friction ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -21,7 +21,8 @@ BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.D NON_LINEAR_TORQUE_PARAMS = { CAR.BOLT_EUV: [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178], - CAR.ACADIA: [4.78003305, 1.0, 0.3122, 0.05591772] + CAR.ACADIA: [4.78003305, 1.0, 0.3122, 0.05591772], + CAR.SILVERADO: [3.29974374, 1.0, 0.25571356, 0.0465122] } @@ -252,13 +253,10 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback) - if self.CS.cruise_buttons != self.CS.prev_cruise_buttons and self.CS.prev_cruise_buttons != CruiseButtons.INIT: - buttonEvents = [create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)] - # Handle ACCButtons changing buttons mid-press - if self.CS.cruise_buttons != CruiseButtons.UNPRESS and self.CS.prev_cruise_buttons != CruiseButtons.UNPRESS: - buttonEvents.append(create_button_event(CruiseButtons.UNPRESS, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)) - - ret.buttonEvents = buttonEvents + # Don't add event if transitioning from INIT, unless it's to an actual button + if self.CS.cruise_buttons != CruiseButtons.UNPRESS or self.CS.prev_cruise_buttons != CruiseButtons.INIT: + ret.buttonEvents = create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, + unpressed_btn=CruiseButtons.UNPRESS) # The ECM allows enabling on falling edge of set, but only rising edge of resume events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low, diff --git a/selfdrive/car/gm/radar_interface.py b/selfdrive/car/gm/radar_interface.py index 0424b95a77..b893babd31 100755 --- a/selfdrive/car/gm/radar_interface.py +++ b/selfdrive/car/gm/radar_interface.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 import math from cereal import car -from common.conversions import Conversions as CV +from openpilot.common.conversions import Conversions as CV from opendbc.can.parser import CANParser -from selfdrive.car.gm.values import DBC, CanBus -from selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.gm.values import DBC, CanBus +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase RADAR_HEADER_MSG = 1120 SLOT_1_MSG = RADAR_HEADER_MSG + 1 @@ -44,7 +44,7 @@ class RadarInterface(RadarInterfaceBase): def update(self, can_strings): if self.rcp is None: - return None + return super().update(None) vls = self.rcp.update_strings(can_strings) self.updated_messages.update(vls) diff --git a/selfdrive/car/gm/tests/test_gm.py b/selfdrive/car/gm/tests/test_gm.py index 1fc8a25611..fce5fea720 100755 --- a/selfdrive/car/gm/tests/test_gm.py +++ b/selfdrive/car/gm/tests/test_gm.py @@ -2,7 +2,7 @@ from parameterized import parameterized import unittest -from selfdrive.car.gm.values import CAMERA_ACC_CAR, CAR, FINGERPRINTS, GM_RX_OFFSET +from openpilot.selfdrive.car.gm.values import CAMERA_ACC_CAR, CAR, FINGERPRINTS, GM_RX_OFFSET CAMERA_DIAGNOSTIC_ADDRESS = 0x24b diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index cfe518650a..d12c21dc23 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -5,8 +5,8 @@ from enum import Enum from typing import Dict, List, Union from cereal import car -from selfdrive.car import dbc_dict -from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column +from openpilot.selfdrive.car import dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column Ecu = car.CarParams.Ecu diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index 347c16c86f..056b47c4b3 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -1,13 +1,13 @@ from collections import namedtuple from cereal import car -from common.numpy_fast import clip, interp -from common.realtime import DT_CTRL +from openpilot.common.numpy_fast import clip, interp +from openpilot.common.realtime import DT_CTRL from opendbc.can.packer import CANPacker -from selfdrive.car import create_gas_interceptor_command -from selfdrive.car.honda import hondacan -from selfdrive.car.honda.values import CruiseButtons, VISUAL_HUD, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams -from selfdrive.controls.lib.drive_helpers import rate_limit +from openpilot.selfdrive.car import create_gas_interceptor_command +from openpilot.selfdrive.car.honda import hondacan +from openpilot.selfdrive.car.honda.values import CruiseButtons, VISUAL_HUD, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams +from openpilot.selfdrive.controls.lib.drive_helpers import rate_limit VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index d6d2b18a0a..03aedb31d2 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -1,13 +1,15 @@ from collections import defaultdict from cereal import car -from common.conversions import Conversions as CV -from common.numpy_fast import interp +from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import interp from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser -from selfdrive.car.honda.hondacan import get_cruise_speed_conversion, get_pt_bus -from selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL, HONDA_BOSCH_RADARLESS -from selfdrive.car.interfaces import CarStateBase +from openpilot.selfdrive.car.honda.hondacan import get_cruise_speed_conversion, get_pt_bus +from openpilot.selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, \ + HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL, \ + HONDA_BOSCH_RADARLESS +from openpilot.selfdrive.car.interfaces import CarStateBase TransmissionType = car.CarParams.TransmissionType diff --git a/selfdrive/car/honda/hondacan.py b/selfdrive/car/honda/hondacan.py index 1fe0a13767..a8cbad78ce 100644 --- a/selfdrive/car/honda/hondacan.py +++ b/selfdrive/car/honda/hondacan.py @@ -1,5 +1,5 @@ -from common.conversions import Conversions as CV -from selfdrive.car.honda.values import HondaFlags, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, CAR, CarControllerParams +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car.honda.values import HondaFlags, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, CAR, CarControllerParams # CAN bus layout with relay # 0 = ACC-CAN - radar side diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 1025eb6212..d1a287a76a 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from common.conversions import Conversions as CV -from common.numpy_fast import interp -from selfdrive.car.honda.values import CarControllerParams, CruiseButtons, HondaFlags, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, \ +from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import interp +from openpilot.selfdrive.car.honda.values import CarControllerParams, CruiseButtons, HondaFlags, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, \ HONDA_BOSCH_ALT_BRAKE_SIGNAL, HONDA_BOSCH_RADARLESS -from selfdrive.car import create_button_event, get_safety_config -from selfdrive.car.interfaces import CarInterfaceBase -from selfdrive.car.disable_ecu import disable_ecu +from openpilot.selfdrive.car import create_button_events, get_safety_config +from openpilot.selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.car.disable_ecu import disable_ecu ButtonType = car.CarState.ButtonEvent.Type @@ -308,15 +308,10 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam, self.cp_body) - buttonEvents = [] - - if self.CS.cruise_buttons != self.CS.prev_cruise_buttons: - buttonEvents.append(create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT)) - - if self.CS.cruise_setting != self.CS.prev_cruise_setting: - buttonEvents.append(create_button_event(self.CS.cruise_setting, self.CS.prev_cruise_setting, {1: ButtonType.altButton1})) - - ret.buttonEvents = buttonEvents + ret.buttonEvents = [ + *create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT), + *create_button_events(self.CS.cruise_setting, self.CS.prev_cruise_setting, {1: ButtonType.altButton1}), + ] # events events = self.create_common_events(ret, pcm_enable=False) diff --git a/selfdrive/car/honda/radar_interface.py b/selfdrive/car/honda/radar_interface.py index 43fe422026..8090f8e03e 100755 --- a/selfdrive/car/honda/radar_interface.py +++ b/selfdrive/car/honda/radar_interface.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 -from collections import defaultdict - from cereal import car from opendbc.can.parser import CANParser -from selfdrive.car.interfaces import RadarInterfaceBase -from selfdrive.car.honda.values import DBC +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.honda.values import DBC def _create_nidec_can_parser(car_fingerprint): @@ -30,61 +28,50 @@ class RadarInterface(RadarInterfaceBase): else: self.rcp = _create_nidec_can_parser(CP.carFingerprint) self.trigger_msg = 0x445 - self.updated_values = defaultdict(lambda: defaultdict(list)) + self.updated_messages = set() def update(self, can_strings): # in Bosch radar and we are only steering for now, so sleep 0.05s to keep # radard at 20Hz and return no points if self.radar_off_can: - return None + return super().update(None) - addresses = self.rcp.update_strings(can_strings) - for addr in addresses: - vals_dict = self.rcp.vl_all[addr] - for sig_name, vals in vals_dict.items(): - self.updated_values[addr][sig_name].extend(vals) + vls = self.rcp.update_strings(can_strings) + self.updated_messages.update(vls) - if self.trigger_msg not in self.updated_values: + if self.trigger_msg not in self.updated_messages: return None - radar_data = self._radar_msg_from_buffer(self.updated_values, self.rcp.can_valid) - self.updated_values.clear() + rr = self._update(self.updated_messages) + self.updated_messages.clear() + return rr - return radar_data - - def _radar_msg_from_buffer(self, updated_values, can_valid): + def _update(self, updated_messages): ret = car.RadarData.new_message() - for ii in sorted(updated_values): - msgs = updated_values[ii] - n_vals_per_addr = len(list(msgs.values())[0]) - cpts = [ - {k: v[i] for k, v in msgs.items()} - for i in range(n_vals_per_addr) - ] - - for cpt in cpts: - if ii == 0x400: - # check for radar faults - self.radar_fault = cpt['RADAR_STATE'] != 0x79 - self.radar_wrong_config = cpt['RADAR_STATE'] == 0x69 - elif cpt['LONG_DIST'] < 255: - if ii not in self.pts or cpt['NEW_TRACK']: - self.pts[ii] = car.RadarData.RadarPoint.new_message() - self.pts[ii].trackId = self.track_id - self.track_id += 1 - self.pts[ii].dRel = cpt['LONG_DIST'] # from front of car - self.pts[ii].yRel = -cpt['LAT_DIST'] # in car frame's y axis, left is positive - self.pts[ii].vRel = cpt['REL_SPEED'] - self.pts[ii].aRel = float('nan') - self.pts[ii].yvRel = float('nan') - self.pts[ii].measured = True - else: - if ii in self.pts: - del self.pts[ii] + for ii in sorted(updated_messages): + cpt = self.rcp.vl[ii] + if ii == 0x400: + # check for radar faults + self.radar_fault = cpt['RADAR_STATE'] != 0x79 + self.radar_wrong_config = cpt['RADAR_STATE'] == 0x69 + elif cpt['LONG_DIST'] < 255: + if ii not in self.pts or cpt['NEW_TRACK']: + self.pts[ii] = car.RadarData.RadarPoint.new_message() + self.pts[ii].trackId = self.track_id + self.track_id += 1 + self.pts[ii].dRel = cpt['LONG_DIST'] # from front of car + self.pts[ii].yRel = -cpt['LAT_DIST'] # in car frame's y axis, left is positive + self.pts[ii].vRel = cpt['REL_SPEED'] + self.pts[ii].aRel = float('nan') + self.pts[ii].yvRel = float('nan') + self.pts[ii].measured = True + else: + if ii in self.pts: + del self.pts[ii] errors = [] - if not can_valid: + if not self.rcp.can_valid: errors.append("canError") if self.radar_fault: errors.append("fault") diff --git a/selfdrive/car/honda/tests/test_honda.py b/selfdrive/car/honda/tests/test_honda.py index 7a8c86fb0a..da64b8f70b 100755 --- a/selfdrive/car/honda/tests/test_honda.py +++ b/selfdrive/car/honda/tests/test_honda.py @@ -2,7 +2,7 @@ import re import unittest -from selfdrive.car.honda.values import FW_VERSIONS +from openpilot.selfdrive.car.honda.values import FW_VERSIONS HONDA_FW_VERSION_RE = br"\d{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$" diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index b149fd2a02..403745bd42 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -3,11 +3,11 @@ from enum import Enum, IntFlag from typing import Dict, List, Optional, Union from cereal import car -from common.conversions import Conversions as CV +from openpilot.common.conversions import Conversions as CV from panda.python import uds -from selfdrive.car import dbc_dict -from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 +from openpilot.selfdrive.car import dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 Ecu = car.CarParams.Ecu VisualAlert = car.CarControl.HUDControl.VisualAlert diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 8c734583e4..0b5f724ab9 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -1,12 +1,12 @@ from cereal import car -from common.conversions import Conversions as CV -from common.numpy_fast import clip -from common.realtime import DT_CTRL +from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import clip +from openpilot.common.realtime import DT_CTRL from opendbc.can.packer import CANPacker -from selfdrive.car import apply_driver_steer_torque_limits, common_fault_avoidance -from selfdrive.car.hyundai import hyundaicanfd, hyundaican -from selfdrive.car.hyundai.hyundaicanfd import CanBus -from selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CANFD_CAR, CAR +from openpilot.selfdrive.car import apply_driver_steer_torque_limits, common_fault_avoidance +from openpilot.selfdrive.car.hyundai import hyundaicanfd, hyundaican +from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus +from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CANFD_CAR, CAR VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState @@ -110,9 +110,10 @@ class CarController: # steering control can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_steer)) - # disable LFA on HDA2 + # prevent LFA from activating on HDA2 by sending "no lane lines detected" to ADAS ECU if self.frame % 5 == 0 and hda2: - can_sends.append(hyundaicanfd.create_cam_0x2a4(self.packer, self.CAN, CS.cam_0x2a4)) + can_sends.append(hyundaicanfd.create_suppress_lfa(self.packer, self.CAN, CS.hda2_lfa_block_msg, + self.CP.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING)) # LFA and HDA icons if self.frame % 5 == 0 and (not hda2 or hda2_long): @@ -131,26 +132,7 @@ class CarController: self.accel_last = accel else: # button presses - if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: - # cruise cancel - if CC.cruiseControl.cancel: - if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: - can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info)) - self.last_button_frame = self.frame - else: - for _ in range(20): - can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter+1, Buttons.CANCEL)) - self.last_button_frame = self.frame - - # cruise standstill resume - elif CC.cruiseControl.resume: - if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: - # TODO: resume for alt button cars - pass - else: - for _ in range(20): - can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter+1, Buttons.RES_ACCEL)) - self.last_button_frame = self.frame + can_sends.extend(self.create_button_messages(CC, CS, use_clu11=False)) else: can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.car_fingerprint, apply_steer, apply_steer_req, torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled, @@ -158,21 +140,15 @@ class CarController: left_lane_warning, right_lane_warning)) if not self.CP.openpilotLongitudinalControl: - if CC.cruiseControl.cancel: - can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP.carFingerprint)) - elif CC.cruiseControl.resume: - # send resume at a max freq of 10Hz - if (self.frame - self.last_button_frame) * DT_CTRL > 0.1: - # send 25 messages at a time to increases the likelihood of resume being accepted - can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP.carFingerprint)] * 25) - if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15: - self.last_button_frame = self.frame + can_sends.extend(self.create_button_messages(CC, CS, use_clu11=True)) if self.frame % 2 == 0 and self.CP.openpilotLongitudinalControl: # TODO: unclear if this is needed jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0 + use_fca = self.CP.flags & HyundaiFlags.USE_FCA.value can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2), - hud_control.leadVisible, set_speed_in_units, stopping, CC.cruiseControl.override)) + hud_control.leadVisible, set_speed_in_units, stopping, + CC.cruiseControl.override, use_fca)) # 20 Hz LFA MFA message if self.frame % 5 == 0 and self.CP.flags & HyundaiFlags.SEND_LFA.value: @@ -193,3 +169,39 @@ class CarController: self.frame += 1 return new_actuators, can_sends + + def create_button_messages(self, CC: car.CarControl, CS: car.CarState, use_clu11: bool): + can_sends = [] + if use_clu11: + if CC.cruiseControl.cancel: + can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP.carFingerprint)) + elif CC.cruiseControl.resume: + # send resume at a max freq of 10Hz + if (self.frame - self.last_button_frame) * DT_CTRL > 0.1: + # send 25 messages at a time to increases the likelihood of resume being accepted + can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP.carFingerprint)] * 25) + if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15: + self.last_button_frame = self.frame + else: + if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: + # cruise cancel + if CC.cruiseControl.cancel: + if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: + can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info)) + self.last_button_frame = self.frame + else: + for _ in range(20): + can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter+1, Buttons.CANCEL)) + self.last_button_frame = self.frame + + # cruise standstill resume + elif CC.cruiseControl.resume: + if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: + # TODO: resume for alt button cars + pass + else: + for _ in range(20): + can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter+1, Buttons.RES_ACCEL)) + self.last_button_frame = self.frame + + return can_sends diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 2e3801c319..8090bbcd8f 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -3,12 +3,13 @@ import copy import math from cereal import car -from common.conversions import Conversions as CV +from openpilot.common.conversions import Conversions as CV from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine -from selfdrive.car.hyundai.hyundaicanfd import CanBus -from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CAN_GEARS, CAMERA_SCC_CAR, CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams -from selfdrive.car.interfaces import CarStateBase +from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus +from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CAN_GEARS, CAMERA_SCC_CAR, \ + CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams +from openpilot.selfdrive.car.interfaces import CarStateBase PREV_BUTTON_SAMPLES = 8 CLUSTER_SAMPLE_RATE = 20 # frames @@ -104,10 +105,12 @@ class CarState(CarStateBase): ret.cruiseState.available = cp.vl["TCS13"]["ACCEnable"] == 0 ret.cruiseState.enabled = cp.vl["TCS13"]["ACC_REQ"] == 1 ret.cruiseState.standstill = False + ret.cruiseState.nonAdaptive = False else: ret.cruiseState.available = cp_cruise.vl["SCC11"]["MainMode_ACC"] == 1 ret.cruiseState.enabled = cp_cruise.vl["SCC12"]["ACCMode"] != 0 ret.cruiseState.standstill = cp_cruise.vl["SCC11"]["SCCInfoDisplay"] == 4. + ret.cruiseState.nonAdaptive = cp_cruise.vl["SCC11"]["SCCInfoDisplay"] == 2. # Shows 'Cruise Control' on dash ret.cruiseState.speed = cp_cruise.vl["SCC11"]["VSetDis"] * speed_conv # TODO: Find brake pressure @@ -201,8 +204,12 @@ class CarState(CarStateBase): ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.params.STEER_THRESHOLD, 5) ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0 - ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"]["LEFT_LAMP"], - cp.vl["BLINKERS"]["RIGHT_LAMP"]) + # TODO: alt signal usage may be described by cp.vl['BLINKERS']['USE_ALT_LAMP'] + left_blinker_sig, right_blinker_sig = "LEFT_LAMP", "RIGHT_LAMP" + if self.CP.carFingerprint == CAR.KONA_EV_2ND_GEN: + left_blinker_sig, right_blinker_sig = "LEFT_LAMP_ALT", "RIGHT_LAMP_ALT" + ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"][left_blinker_sig], + cp.vl["BLINKERS"][right_blinker_sig]) if self.CP.enableBsm: ret.leftBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FL_INDICATOR"] != 0 ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FR_INDICATOR"] != 0 @@ -221,6 +228,13 @@ class CarState(CarStateBase): ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"]) + # Manual Speed Limit Assist is a feature that replaces non-adaptive cruise control on EV CAN FD platforms. + # It limits the vehicle speed, overridable by pressing the accelerator past a certain point. + # The car will brake, but does not respect positive acceleration commands in this mode + # TODO: find this message on ICE & HYBRID cars + cruise control signals (if exists) + if self.CP.carFingerprint in EV_CAR: + ret.cruiseState.nonAdaptive = cp.vl["MANUAL_SPEED_LIMIT_ASSIST"]["MSLA_ENABLED"] == 1 + self.prev_cruise_buttons = self.cruise_buttons[-1] self.cruise_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["CRUISE_BUTTONS"]) self.main_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["ADAPTIVE_CRUISE_MAIN_BTN"]) @@ -228,7 +242,8 @@ class CarState(CarStateBase): ret.accFaulted = cp.vl["TCS"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED if self.CP.flags & HyundaiFlags.CANFD_HDA2: - self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"]) + self.hda2_lfa_block_msg = copy.copy(cp_cam.vl["CAM_0x362"] if self.CP.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING + else cp_cam.vl["CAM_0x2a4"]) return ret @@ -304,7 +319,6 @@ class CarState(CarStateBase): def get_can_parser_canfd(self, CP): messages = [ (self.gear_msg_canfd, 100), - (self.cruise_btns_msg_canfd, 50), (self.accelerator_msg_canfd, 100), ("WHEEL_SPEEDS", 100), ("STEERING_SENSORS", 100), @@ -315,6 +329,16 @@ class CarState(CarStateBase): ("DOORS_SEATBELTS", 4), ] + if CP.carFingerprint in EV_CAR: + messages += [ + ("MANUAL_SPEED_LIMIT_ASSIST", 10), + ] + + if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS): + messages += [ + ("CRUISE_BUTTONS", 50) + ] + if CP.enableBsm: messages += [ ("BLINDSPOTS_REAR_CORNERS", 20), @@ -331,7 +355,8 @@ class CarState(CarStateBase): def get_cam_can_parser_canfd(CP): messages = [] if CP.flags & HyundaiFlags.CANFD_HDA2: - messages += [("CAM_0x2a4", 20)] + block_lfa_msg = "CAM_0x362" if CP.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING else "CAM_0x2a4" + messages += [(block_lfa_msg, 20)] elif CP.flags & HyundaiFlags.CANFD_CAMERA_SCC: messages += [ ("SCC_CONTROL", 50), diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index dc5a5b6286..3b98432615 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -1,5 +1,5 @@ import crcmod -from selfdrive.car.hyundai.values import CAR, CHECKSUM, CAMERA_SCC_CAR +from openpilot.selfdrive.car.hyundai.values import CAR, CHECKSUM, CAMERA_SCC_CAR hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf) @@ -37,7 +37,8 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, - CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED): + CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED, + CAR.AZERA_6TH_GEN): values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsOpt_USM"] = 2 @@ -125,7 +126,7 @@ def create_lfahda_mfc(packer, enabled, hda_set_speed=0): } return packer.make_can_msg("LFAHDA_MFC", 0, values) -def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, set_speed, stopping, long_override): +def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, set_speed, stopping, long_override, use_fca): commands = [] scc11_values = { @@ -148,6 +149,13 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, s "aReqValue": accel, # stock ramps up and down respecting jerk limit until it reaches aReqRaw "CR_VSM_Alive": idx % 0xF, } + + # show AEB disabled indicator on dash with SCC12 if not sending FCA messages. + # these signals also prevent a TCS fault on non-FCA cars with alpha longitudinal + if not use_fca: + scc12_values["CF_VSM_ConfMode"] = 1 + scc12_values["AEB_Status"] = 1 # AEB disabled + scc12_dat = packer.make_can_msg("SCC12", 0, scc12_values)[2] scc12_values["CR_VSM_ChkSum"] = 0x10 - sum(sum(divmod(i, 16)) for i in scc12_dat) % 0x10 @@ -163,17 +171,19 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, s } commands.append(packer.make_can_msg("SCC14", 0, scc14_values)) - # note that some vehicles most likely have an alternate checksum/counter definition - # https://github.com/commaai/opendbc/commit/9ddcdb22c4929baf310295e832668e6e7fcfa602 - fca11_values = { - "CR_FCA_Alive": idx % 0xF, - "PAINT1_Status": 1, - "FCA_DrvSetStatus": 1, - "FCA_Status": 1, # AEB disabled - } - fca11_dat = packer.make_can_msg("FCA11", 0, fca11_values)[2] - fca11_values["CR_FCA_ChkSum"] = hyundai_checksum(fca11_dat[:7]) - commands.append(packer.make_can_msg("FCA11", 0, fca11_values)) + # Only send FCA11 on cars where it exists on the bus + if use_fca: + # note that some vehicles most likely have an alternate checksum/counter definition + # https://github.com/commaai/opendbc/commit/9ddcdb22c4929baf310295e832668e6e7fcfa602 + fca11_values = { + "CR_FCA_Alive": idx % 0xF, + "PAINT1_Status": 1, + "FCA_DrvSetStatus": 1, + "FCA_Status": 1, # AEB disabled + } + fca11_dat = packer.make_can_msg("FCA11", 0, fca11_values)[2] + fca11_values["CR_FCA_ChkSum"] = hyundai_checksum(fca11_dat[:7]) + commands.append(packer.make_can_msg("FCA11", 0, fca11_values)) return commands @@ -187,6 +197,7 @@ def create_acc_opt(packer): } commands.append(packer.make_can_msg("SCC13", 0, scc13_values)) + # TODO: this needs to be detected and conditionally sent on unsupported long cars fca12_values = { "FCA_DrvSetState": 2, "FCA_USM": 1, # AEB disabled diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index e78e02ae50..a35fcb7779 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -1,6 +1,6 @@ -from common.numpy_fast import clip -from selfdrive.car import CanBusBase -from selfdrive.car.hyundai.values import HyundaiFlags +from openpilot.common.numpy_fast import clip +from openpilot.selfdrive.car import CanBusBase +from openpilot.selfdrive.car.hyundai.values import HyundaiFlags class CanBus(CanBusBase): @@ -46,25 +46,32 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_steer): "LKA_ASSIST": 0, "STEER_REQ": 1 if lat_active else 0, "STEER_MODE": 0, - "SET_ME_1": 0, + "HAS_LANE_SAFETY": 0, # hide LKAS settings "NEW_SIGNAL_1": 0, "NEW_SIGNAL_2": 0, } if CP.flags & HyundaiFlags.CANFD_HDA2: + hda2_lkas_msg = "LKAS_ALT" if CP.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING else "LKAS" if CP.openpilotLongitudinalControl: ret.append(packer.make_can_msg("LFA", CAN.ECAN, values)) - ret.append(packer.make_can_msg("LKAS", CAN.ACAN, values)) + ret.append(packer.make_can_msg(hda2_lkas_msg, CAN.ACAN, values)) else: ret.append(packer.make_can_msg("LFA", CAN.ECAN, values)) return ret -def create_cam_0x2a4(packer, CAN, cam_0x2a4): - values = {f"BYTE{i}": cam_0x2a4[f"BYTE{i}"] for i in range(3, 24)} - values['COUNTER'] = cam_0x2a4['COUNTER'] - values["BYTE7"] = 0 - return packer.make_can_msg("CAM_0x2a4", CAN.ACAN, values) +def create_suppress_lfa(packer, CAN, hda2_lfa_block_msg, hda2_alt_steering): + suppress_msg = "CAM_0x362" if hda2_alt_steering else "CAM_0x2a4" + msg_bytes = 32 if hda2_alt_steering else 24 + + values = {f"BYTE{i}": hda2_lfa_block_msg[f"BYTE{i}"] for i in range(3, msg_bytes) if i != 7} + values["COUNTER"] = hda2_lfa_block_msg["COUNTER"] + values["SET_ME_0"] = 0 + values["SET_ME_0_2"] = 0 + values["LEFT_LANE_LINE"] = 0 + values["RIGHT_LANE_LINE"] = 0 + return packer.make_can_msg(suppress_msg, CAN.ACAN, values) def create_buttons(packer, CP, CAN, cnt, btn): values = { diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index ed0dab54fc..c5537993df 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -1,14 +1,14 @@ -#!/usr/bin/env python3 from cereal import car from panda import Panda -from common.conversions import Conversions as CV -from selfdrive.car.hyundai.hyundaicanfd import CanBus -from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, \ - EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons -from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR -from selfdrive.car import create_button_event, get_safety_config -from selfdrive.car.interfaces import CarInterfaceBase -from selfdrive.car.disable_ecu import disable_ecu +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus +from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, \ + EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, UNSUPPORTED_LONGITUDINAL_CAR, \ + Buttons +from openpilot.selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR +from openpilot.selfdrive.car import create_button_events, get_safety_config +from openpilot.selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.car.disable_ecu import disable_ecu Ecu = car.CarParams.Ecu ButtonType = car.CarState.ButtonEvent.Type @@ -27,7 +27,8 @@ class CarInterface(CarInterfaceBase): # These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars likely still work fine. Once a user confirms each car works and a test route is # added to selfdrive/car/tests/routes.py, we can remove it from this list. - ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, CAR.IONIQ_6} + # FIXME: the Optima Hybrid uses a different SCC12 checksum + ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, } hda2 = Ecu.adas in [fw.ecu for fw in car_fw] CAN = CanBus(None, hda2, fingerprint) @@ -36,6 +37,8 @@ class CarInterface(CarInterfaceBase): # detect HDA2 with ADAS Driving ECU if hda2: ret.flags |= HyundaiFlags.CANFD_HDA2.value + if 0x110 in fingerprint[CAN.CAM]: + ret.flags |= HyundaiFlags.CANFD_HDA2_ALT_STEERING.value else: # non-HDA2 if 0x1cf not in fingerprint[CAN.ECAN]: @@ -61,7 +64,11 @@ class CarInterface(CarInterfaceBase): ret.steerLimitTimer = 0.4 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - if candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): + if candidate == CAR.AZERA_6TH_GEN: + ret.mass = 1540. # average + ret.wheelbase = 2.885 + ret.steerRatio = 14.5 + elif candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): ret.mass = 3982. * CV.LB_TO_KG ret.wheelbase = 2.766 # Values from optimizer @@ -102,10 +109,10 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 3.01 ret.steerRatio = 16.5 ret.minSteerSpeed = 60 * CV.KPH_TO_MS - elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): - ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425., CAR.KONA_EV_2022: 1743.}.get(candidate, 1275.) - ret.wheelbase = 2.6 - ret.steerRatio = 13.42 # Spec + elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, CAR.KONA_EV_2ND_GEN): + ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425., CAR.KONA_EV_2022: 1743., CAR.KONA_EV_2ND_GEN: 1740.}.get(candidate, 1275.) + ret.wheelbase = {CAR.KONA_EV_2ND_GEN: 2.66, }.get(candidate, 2.6) + ret.steerRatio = {CAR.KONA_EV_2ND_GEN: 13.6, }.get(candidate, 13.42) # Spec ret.tireStiffnessFactor = 0.385 elif candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV_2019, CAR.IONIQ_HEV_2022, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV): ret.mass = 1490. # weight per hyundai site https://www.hyundaiusa.com/ioniq-electric/specifications.aspx @@ -195,17 +202,23 @@ class CarInterface(CarInterfaceBase): ret.mass = 1767. # SX Prestige trim support only ret.wheelbase = 2.756 ret.steerRatio = 13.6 - elif candidate in (CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN): + elif candidate in (CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN): ret.wheelbase = 2.81 ret.steerRatio = 13.5 # average of the platforms if candidate == CAR.KIA_SORENTO_4TH_GEN: ret.mass = 3957 * CV.LB_TO_KG + elif candidate == CAR.KIA_SORENTO_HEV_4TH_GEN: + ret.mass = 4255 * CV.LB_TO_KG else: ret.mass = 4537 * CV.LB_TO_KG elif candidate == CAR.KIA_CARNIVAL_4TH_GEN: ret.mass = 2087. ret.wheelbase = 3.09 ret.steerRatio = 14.23 + elif candidate == CAR.KIA_K8_HEV_1ST_GEN: + ret.mass = 1630. # https://carprices.ae/brands/kia/2023/k8/1.6-turbo-hybrid + ret.wheelbase = 2.895 + ret.steerRatio = 13.27 # guesstimate from K5 platform # Genesis elif candidate == CAR.GENESIS_GV60_EV_1ST_GEN: @@ -247,7 +260,7 @@ class CarInterface(CarInterfaceBase): else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] - ret.experimentalLongitudinalAvailable = candidate not in (LEGACY_SAFETY_MODE_CAR | CAMERA_SCC_CAR) + ret.experimentalLongitudinalAvailable = candidate not in (UNSUPPORTED_LONGITUDINAL_CAR | CAMERA_SCC_CAR) ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable ret.pcmCruise = not ret.openpilotLongitudinalControl @@ -273,6 +286,8 @@ class CarInterface(CarInterfaceBase): if ret.flags & HyundaiFlags.CANFD_HDA2: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2 + if ret.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING: + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2_ALT_STEERING if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC: @@ -317,13 +332,8 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) - if self.CS.CP.openpilotLongitudinalControl and self.CS.cruise_buttons[-1] != self.CS.prev_cruise_buttons: - buttonEvents = [create_button_event(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT)] - # Handle CF_Clu_CruiseSwState changing buttons mid-press - if self.CS.cruise_buttons[-1] != 0 and self.CS.prev_cruise_buttons != 0: - buttonEvents.append(create_button_event(0, self.CS.prev_cruise_buttons, BUTTONS_DICT)) - - ret.buttonEvents = buttonEvents + if self.CS.CP.openpilotLongitudinalControl: + ret.buttonEvents = create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT) # On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state # To avoid re-engaging when openpilot cancels, check user engagement intention via buttons diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index 7301ad18ce..754993a459 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -1,10 +1,9 @@ -#!/usr/bin/env python3 import math from cereal import car from opendbc.can.parser import CANParser -from selfdrive.car.interfaces import RadarInterfaceBase -from selfdrive.car.hyundai.values import DBC +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.hyundai.values import DBC RADAR_START_ADDR = 0x500 RADAR_MSG_COUNT = 32 @@ -30,7 +29,7 @@ class RadarInterface(RadarInterfaceBase): def update(self, can_strings): if self.radar_off_can or (self.rcp is None): - return None + return super().update(None) vls = self.rcp.update_strings(can_strings) self.updated_messages.update(vls) diff --git a/selfdrive/car/hyundai/tests/print_platform_codes.py b/selfdrive/car/hyundai/tests/print_platform_codes.py index 1bc8a4e366..6fe4582f54 100755 --- a/selfdrive/car/hyundai/tests/print_platform_codes.py +++ b/selfdrive/car/hyundai/tests/print_platform_codes.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 from cereal import car -from selfdrive.car.hyundai.values import FW_VERSIONS, PLATFORM_CODE_ECUS, get_platform_codes +from openpilot.selfdrive.car.hyundai.values import FW_VERSIONS, PLATFORM_CODE_ECUS, get_platform_codes Ecu = car.CarParams.Ecu ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py index 3d2b704e55..11268913aa 100755 --- a/selfdrive/car/hyundai/tests/test_hyundai.py +++ b/selfdrive/car/hyundai/tests/test_hyundai.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 +from hypothesis import settings, given, strategies as st import unittest from cereal import car -from selfdrive.car.fw_versions import build_fw_dict -from selfdrive.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, \ +from openpilot.selfdrive.car.fw_versions import build_fw_dict +from openpilot.selfdrive.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, \ EV_CAR, FW_QUERY_CONFIG, FW_VERSIONS, LEGACY_SAFETY_MODE_CAR, \ - PLATFORM_CODE_ECUS, get_platform_codes + UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, get_platform_codes Ecu = car.CarParams.Ecu ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} @@ -37,7 +38,7 @@ NO_DATES_PLATFORMS = { class TestHyundaiFingerprint(unittest.TestCase): def test_canfd_not_in_can_features(self): - can_specific_feature_list = set.union(*CAN_GEARS.values(), *CHECKSUM.values(), LEGACY_SAFETY_MODE_CAR, CAMERA_SCC_CAR) + can_specific_feature_list = set.union(*CAN_GEARS.values(), *CHECKSUM.values(), LEGACY_SAFETY_MODE_CAR, UNSUPPORTED_LONGITUDINAL_CAR, CAMERA_SCC_CAR) for car_model in CANFD_CAR: self.assertNotIn(car_model, can_specific_feature_list, "CAN FD car unexpectedly found in a CAN feature list") @@ -66,11 +67,19 @@ class TestHyundaiFingerprint(unittest.TestCase): part = code.split(b"-")[1] self.assertFalse(part.startswith(b'CW'), "Car has bad part number") + @settings(max_examples=100) + @given(data=st.data()) + def test_platform_codes_fuzzy_fw(self, data): + """Ensure function doesn't raise an exception""" + fw_strategy = st.lists(st.binary()) + fws = data.draw(fw_strategy) + get_platform_codes(fws) + # Tests for platform codes, part numbers, and FW dates which Hyundai will use to fuzzy # fingerprint in the absence of full FW matches: def test_platform_code_ecus_available(self): # TODO: add queries for these non-CAN FD cars to get EPS - no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, + no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H, CAR.SONATA_LF, CAR.TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80} # Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 72408a6006..3239be6d76 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -6,10 +6,10 @@ from typing import Dict, List, Optional, Set, Tuple, Union from cereal import car from panda.python import uds -from common.conversions import Conversions as CV -from selfdrive.car import dbc_dict -from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car import dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu @@ -64,10 +64,12 @@ class HyundaiFlags(IntFlag): CANFD_ALT_GEARS_2 = 64 SEND_LFA = 128 USE_FCA = 256 + CANFD_HDA2_ALT_STEERING = 512 class CAR: # Hyundai + AZERA_6TH_GEN = "HYUNDAI AZERA 6TH GEN" ELANTRA = "HYUNDAI ELANTRA 2017" ELANTRA_2021 = "HYUNDAI ELANTRA 2021" ELANTRA_HEV_2021 = "HYUNDAI ELANTRA HYBRID 2021" @@ -81,6 +83,7 @@ class CAR: KONA = "HYUNDAI KONA 2020" KONA_EV = "HYUNDAI KONA ELECTRIC 2019" KONA_EV_2022 = "HYUNDAI KONA ELECTRIC 2022" + KONA_EV_2ND_GEN = "HYUNDAI KONA ELECTRIC 2ND GEN" KONA_HEV = "HYUNDAI KONA HYBRID 2020" SANTA_FE = "HYUNDAI SANTA FE 2019" SANTA_FE_2022 = "HYUNDAI SANTA FE 2022" @@ -102,6 +105,7 @@ class CAR: KIA_FORTE = "KIA FORTE E 2018 & GT 2021" KIA_K5_2021 = "KIA K5 2021" KIA_K5_HEV_2020 = "KIA K5 HYBRID 2020" + KIA_K8_HEV_1ST_GEN = "KIA K8 HYBRID 1ST GEN" KIA_NIRO_EV = "KIA NIRO EV 2020" KIA_NIRO_EV_2ND_GEN = "KIA NIRO EV 2ND GEN" KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019" @@ -114,6 +118,7 @@ class CAR: KIA_SPORTAGE_5TH_GEN = "KIA SPORTAGE 5TH GEN" KIA_SORENTO = "KIA SORENTO GT LINE 2018" KIA_SORENTO_4TH_GEN = "KIA SORENTO 4TH GEN" + KIA_SORENTO_HEV_4TH_GEN = "KIA SORENTO HYBRID 4TH GEN" KIA_SORENTO_PHEV_4TH_GEN = "KIA SORENTO PLUG-IN HYBRID 4TH GEN" KIA_SPORTAGE_HYBRID_5TH_GEN = "KIA SPORTAGE HYBRID 5TH GEN" KIA_STINGER = "KIA STINGER GT2 2018" @@ -136,7 +141,7 @@ class Footnote(Enum): CANFD = CarFootnote( "Requires a comma 3X or CAN FD panda kit " + "for this CAN FD car.", - Column.MODEL, shop_footnote=True) + Column.MODEL, shop_footnote=False) @dataclass @@ -149,6 +154,7 @@ class HyundaiCarInfo(CarInfo): CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { + CAR.AZERA_6TH_GEN: HyundaiCarInfo("Hyundai Azera 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), CAR.ELANTRA: [ HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b])), HyundaiCarInfo("Hyundai Elantra GT 2017-19", car_parts=CarParts.common([CarHarness.hyundai_e])), @@ -170,9 +176,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.IONIQ_PHEV: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2020-22", "All", car_parts=CarParts.common([CarHarness.hyundai_h])), CAR.KONA: HyundaiCarInfo("Hyundai Kona 2020", car_parts=CarParts.common([CarHarness.hyundai_b])), CAR.KONA_EV: HyundaiCarInfo("Hyundai Kona Electric 2018-21", car_parts=CarParts.common([CarHarness.hyundai_g])), - CAR.KONA_EV_2022: HyundaiCarInfo("Hyundai Kona Electric 2022", car_parts=CarParts.common([CarHarness.hyundai_o])), + CAR.KONA_EV_2022: HyundaiCarInfo("Hyundai Kona Electric 2022-23", car_parts=CarParts.common([CarHarness.hyundai_o])), CAR.KONA_HEV: HyundaiCarInfo("Hyundai Kona Hybrid 2020", video_link="https://youtu.be/0dwpAHiZgFo", car_parts=CarParts.common([CarHarness.hyundai_i])), # TODO: check packages + CAR.KONA_EV_2ND_GEN: HyundaiCarInfo("Hyundai Kona Electric (with HDA II, Korea only) 2023", video_link="https://www.youtube.com/watch?v=U2fOCmcQ8hw", car_parts=CarParts.common([CarHarness.hyundai_r])), CAR.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", car_parts=CarParts.common([CarHarness.hyundai_d])), CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-23", "All", video_link="https://youtu.be/VnHzSTygTS4", car_parts=CarParts.common([CarHarness.hyundai_l])), @@ -197,8 +204,6 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q])), ], CAR.IONIQ_6: [ - # TODO: unknown - HyundaiCarInfo("Hyundai Ioniq 6 (without HDA II) 2023", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_k])), HyundaiCarInfo("Hyundai Ioniq 6 (with HDA II) 2023", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p])), ], CAR.TUCSON_4TH_GEN: [ @@ -215,6 +220,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { ], CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", car_parts=CarParts.common([CarHarness.hyundai_a])), CAR.KIA_K5_HEV_2020: HyundaiCarInfo("Kia K5 Hybrid 2020", car_parts=CarParts.common([CarHarness.hyundai_a])), + CAR.KIA_K8_HEV_1ST_GEN: HyundaiCarInfo("Kia K8 Hybrid (with HDA II) 2023", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q])), CAR.KIA_NIRO_EV: [ HyundaiCarInfo("Kia Niro EV 2019", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_h])), HyundaiCarInfo("Kia Niro EV 2020", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_f])), @@ -245,7 +251,8 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", car_parts=CarParts.common([CarHarness.hyundai_e])), ], CAR.KIA_SORENTO_4TH_GEN: HyundaiCarInfo("Kia Sorento 2021-23", car_parts=CarParts.common([CarHarness.hyundai_k])), - CAR.KIA_SORENTO_PHEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", car_parts=CarParts.common([CarHarness.hyundai_a])), + CAR.KIA_SORENTO_HEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Hybrid 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), + CAR.KIA_SORENTO_PHEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", car_parts=CarParts.common([CarHarness.hyundai_n])), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", car_parts=CarParts.common([CarHarness.hyundai_c])), @@ -257,7 +264,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia EV6 (with HDA II) 2022-23", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p])) ], CAR.KIA_CARNIVAL_4TH_GEN: [ - HyundaiCarInfo("Kia Carnival 2023", car_parts=CarParts.common([CarHarness.hyundai_a])), + HyundaiCarInfo("Kia Carnival 2023-24", car_parts=CarParts.common([CarHarness.hyundai_a])), HyundaiCarInfo("Kia Carnival (China only) 2023", car_parts=CarParts.common([CarHarness.hyundai_k])) ], @@ -386,6 +393,7 @@ def match_fw_to_car_fuzzy(live_fw_versions) -> Set[str]: # Non-electric CAN FD platforms often do not have platform code specifiers needed # to distinguish between hybrid and ICE. All EVs so far are either exclusively # electric or specify electric in the platform code. + # TODO: whitelist platforms that we've seen hybrid and ICE versions of that have these specifiers fuzzy_platform_blacklist = set(CANFD_CAR - EV_CAR) candidates = set() @@ -455,6 +463,7 @@ PART_NUMBER_FW_PATTERN = re.compile(b'(?<=[0-9][.,][0-9]{2} )([0-9]{5}[-/]?[A-Z] # TODO: use abs, it has the platform code and part number on many platforms PLATFORM_CODE_ECUS = [Ecu.fwdRadar, Ecu.fwdCamera, Ecu.eps] # So far we've only seen dates in fwdCamera +# TODO: there are date codes in the ABS firmware versions in hex DATE_FW_ECUS = [Ecu.fwdCamera] FW_QUERY_CONFIG = FwQueryConfig( @@ -518,6 +527,23 @@ FW_QUERY_CONFIG = FwQueryConfig( ) FW_VERSIONS = { + CAR.AZERA_6TH_GEN: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00IG__ SCC F-CU- 1.00 1.00 99110-G8100 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00IG MDPS C 1.00 1.02 56310G8510\x00 4IGSC103', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00IG MFC AT MES LHD 1.00 1.04 99211-G8100 200511', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 U912\x00\x00\x00\x00\x00\x00SIG0M35MH0\xa4 |.', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81641KA051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, CAR.HYUNDAI_GENESIS: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DH LKAS 1.1 -150210', @@ -572,11 +598,13 @@ FW_VERSIONS = { b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2510 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2560 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', + b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2310 4APHC101', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEP MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2700 201027', + b'\xf1\x00AEP MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -589,6 +617,7 @@ FW_VERSIONS = { b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00', b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x00\x00\x00\x00', b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt', + b'\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', ], }, CAR.IONIQ_EV_2020: { @@ -604,6 +633,7 @@ FW_VERSIONS = { b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.03 95740-G2500 190516', b'\xf1\x00AEE MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2600 190730', ], }, CAR.IONIQ_EV_LTD: { @@ -1406,26 +1436,35 @@ FW_VERSIONS = { b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010', b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', b'\xf1\x00OS IEB \r 102"\x05\x16 58520-K4010', - # TODO: these return from the MULTI request, above return from LONG - b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xd3\x00\x00\x00\x00\xff\xb7\xff\xee\xff\xe0\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xffP\xf5\xff\xfd\x00\x00\x00\x00\xfc\x00\x01', - b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xdb\x00\x00\x00\x00\xff\xb1\xff\xd9\xff\xd2\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xff\xd6\xf5\x00\x06\x00\x00\x00\x14\xfd\x00\x04', - b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xd3\x00\x00\x00\x00\xff\xb7\xff\xf4\xff\xd9\x00\xc0', + b'\xf1\x00OS IEB \x02 102"\x05\x16 58520-K4010', ], (Ecu.fwdCamera, 0x7C4, None): [ b'\xf1\x00OSP LKA AT CND LHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT AUS RHD 1.00 1.04 99211-J9200 904', b'\xf1\x00OSP LKA AT EUR LHD 1.00 1.04 99211-J9200 904', + b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.04 99211-J9200 904', + b'\xf1\x00OSP LKA AT USA LHD 1.00 1.04 99211-J9200 904', ], (Ecu.eps, 0x7D4, None): [ b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4260\x00 4OEPC102', b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4970 4OEPC102', b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4271 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4971\x00 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4261\x00 4OEPC102', ], (Ecu.fwdRadar, 0x7D0, None): [ b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00', ], }, + CAR.KONA_EV_2ND_GEN: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00SXev RDR ----- 1.00 1.00 99110-BF000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00SX2EMFC AT KOR LHD 1.00 1.00 99211-BF000 230410', + ], + }, CAR.KIA_NIRO_EV: { (Ecu.fwdRadar, 0x7D0, None): [ b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', @@ -1574,6 +1613,14 @@ FW_VERSIONS = { b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B8051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B8051\x00\x00TJFSG24NH27\xa7\xc2\xb4', ], }, + CAR.KIA_OPTIMA_H: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JFhe SCC FNCUP 1.00 1.00 96400-A8000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JFP LKAS AT EUR LHD 1.00 1.03 95895-A8100 160711', + ], + }, CAR.ELANTRA: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54', @@ -1786,6 +1833,7 @@ FW_VERSIONS = { b'\xf1\x00CE__ RDR ----- 1.00 1.01 99110-KL000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CE MFC AT EUR LHD 1.00 1.03 99211-KL000 221011', b'\xf1\x00CE MFC AT USA LHD 1.00 1.04 99211-KL000 221213', ], }, @@ -1805,10 +1853,12 @@ FW_VERSIONS = { b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9100 14A', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9250 14W', + b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 2.02 99211-N9000 14E', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', + b'\xf1\x00NX4__ 1.00 1.01 99110-N9000 ', ], }, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: { @@ -1867,10 +1917,12 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.05 99210-R5000 210623', b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.03 99210-R5000 200903', + b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.00 99210-R5100 221019', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.06 99110-P2000 ', b'\xf1\x00MQ4_ SCC F-CUP 1.00 1.06 99110-P2000 ', + b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.08 99110-P2000 ', ], }, CAR.KIA_NIRO_HEV_2ND_GEN: { @@ -1894,12 +1946,30 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.06 99210-R0000 220221', b'\xf1\x00KA4CMFC AT CHN LHD 1.00 1.01 99211-I4000 210525', + b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.00 99210-R0100 230105', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00KA4_ SCC FHCUP 1.00 1.03 99110-R0000 ', b'\xf1\x00KA4c SCC FHCUP 1.00 1.01 99110-I4000 ', ], }, + CAR.KIA_SORENTO_HEV_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.12 99210-P2000 230331', + b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00MQhe SCC FHCUP 1.00 1.07 99110-P4000 ', + ], + }, + CAR.KIA_K8_HEV_1ST_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00GL3HMFC AT KOR LHD 1.00 1.03 99211-L8000 210907', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00GL3_ RDR ----- 1.00 1.02 99110-L8000 ', + ], + }, } CHECKSUM = { @@ -1921,10 +1991,11 @@ CAN_GEARS = { CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_EV_2ND_GEN, - CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN} + CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN, CAR.KIA_K8_HEV_1ST_GEN} # The radar does SCC on these cars when HDA I, rather than the camera -CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN} +CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.GENESIS_GV80, + CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN, CAR.IONIQ_6} # The camera does SCC on these cars, rather than the radar CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } @@ -1932,19 +2003,24 @@ CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } # these cars use a different gas signal HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, - CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020, CAR.KIA_NIRO_HEV_2ND_GEN} + CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020, CAR.KIA_NIRO_HEV_2ND_GEN, + CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_OPTIMA_H, CAR.KIA_K8_HEV_1ST_GEN} EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KONA_EV_2022, - CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.GENESIS_GV60_EV_1ST_GEN} + CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KONA_EV_2ND_GEN} # these cars require a special panda safety mode due to missing counters and checksums in the messages -LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, - CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER, - CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022} +LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_LTD, CAR.KIA_OPTIMA_G4, + CAR.VELOSTER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022, + CAR.KIA_OPTIMA_H} + +# these cars have not been verified to work with longitudinal yet - radar disable, sending correct messages, etc. +UNSUPPORTED_LONGITUDINAL_CAR = LEGACY_SAFETY_MODE_CAR | {CAR.KIA_NIRO_PHEV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4_FL} # If 0x500 is present on bus 1 it probably has a Mando radar outputting radar points. # If no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py DBC = { + CAR.AZERA_6TH_GEN: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA_2021: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA_HEV_2021: dbc_dict('hyundai_kia_generic', None), @@ -2003,4 +2079,7 @@ DBC = { CAR.KIA_NIRO_EV_2ND_GEN: dbc_dict('hyundai_canfd', None), CAR.GENESIS_GV80: dbc_dict('hyundai_canfd', None), CAR.KIA_CARNIVAL_4TH_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_SORENTO_HEV_4TH_GEN: dbc_dict('hyundai_canfd', None), + CAR.KONA_EV_2ND_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_K8_HEV_1ST_GEN: dbc_dict('hyundai_canfd', None), } diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index a9966c2bb7..1b68b1dbcf 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -1,18 +1,20 @@ import yaml import os +import time +import numpy as np from abc import abstractmethod, ABC from typing import Any, Dict, Optional, Tuple, List, Callable from cereal import car -from common.basedir import BASEDIR -from common.conversions import Conversions as CV -from common.kalman.simple_kalman import KF1D -from common.numpy_fast import clip -from common.realtime import DT_CTRL -from selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, STD_CARGO_KG -from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, get_friction -from selfdrive.controls.lib.events import Events -from selfdrive.controls.lib.vehicle_model import VehicleModel +from openpilot.common.basedir import BASEDIR +from openpilot.common.conversions import Conversions as CV +from openpilot.common.kalman.simple_kalman import KF1D, get_kalman_gain +from openpilot.common.numpy_fast import clip +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, STD_CARGO_KG +from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, get_friction +from openpilot.selfdrive.controls.lib.events import Events +from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel ButtonType = car.CarState.ButtonEvent.Type GearShifter = car.CarState.GearShifter @@ -309,9 +311,14 @@ class RadarInterfaceBase(ABC): self.rcp = None self.pts = {} self.delay = 0 + self.radar_ts = CP.radarTimeStep + self.no_radar_sleep = 'NO_RADAR_SLEEP' in os.environ def update(self, can_strings): - pass + ret = car.RadarData.new_message() + if not self.no_radar_sleep: + time.sleep(self.radar_ts) # radard runs on RI updates + return ret class CarStateBase(ABC): @@ -329,12 +336,13 @@ class CarStateBase(ABC): self.cluster_speed_hyst_gap = 0.0 self.cluster_min_speed = 0.0 # min speed before dropping to 0 - # Q = np.matrix([[0.0, 0.0], [0.0, 100.0]]) - # R = 0.3 - self.v_ego_kf = KF1D(x0=[[0.0], [0.0]], - A=[[1.0, DT_CTRL], [0.0, 1.0]], - C=[1.0, 0.0], - K=[[0.17406039], [1.65925647]]) + Q = [[0.0, 0.0], [0.0, 100.0]] + R = 0.3 + A = [[1.0, DT_CTRL], [0.0, 1.0]] + C = [[1.0, 0.0]] + x0=[[0.0], [0.0]] + K = get_kalman_gain(DT_CTRL, np.array(A), np.array(C), np.array(Q), R) + self.v_ego_kf = KF1D(x0=x0, A=A, C=C[0], K=K) def update_speed_kf(self, v_ego_raw): if abs(v_ego_raw - self.v_ego_kf.x[0][0]) > 2.0: # Prevent large accelerations when car starts at non zero speed @@ -435,7 +443,7 @@ def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: boo for car_folder in sorted([x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]): try: brand_name = car_folder.split('/')[-1] - brand_values = __import__(f'selfdrive.car.{brand_name}.values', fromlist=[attr]) + brand_values = __import__(f'openpilot.selfdrive.car.{brand_name}.values', fromlist=[attr]) if hasattr(brand_values, attr) or not ignore_none: attr_data = getattr(brand_values, attr, None) else: diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 93033126a0..adbc598ea6 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -3,8 +3,8 @@ from collections import defaultdict from functools import partial import cereal.messaging as messaging -from system.swaglog import cloudlog -from selfdrive.boardd.boardd import can_list_to_can_capnp +from openpilot.system.swaglog import cloudlog +from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp from panda.python.uds import CanClient, IsoTpMessage, FUNCTIONAL_ADDRS, get_rx_addr_for_tx_addr diff --git a/selfdrive/car/mazda/carcontroller.py b/selfdrive/car/mazda/carcontroller.py index cb401a8abd..320ad19bb4 100644 --- a/selfdrive/car/mazda/carcontroller.py +++ b/selfdrive/car/mazda/carcontroller.py @@ -1,8 +1,8 @@ from cereal import car from opendbc.can.packer import CANPacker -from selfdrive.car import apply_driver_steer_torque_limits -from selfdrive.car.mazda import mazdacan -from selfdrive.car.mazda.values import CarControllerParams, Buttons +from openpilot.selfdrive.car import apply_driver_steer_torque_limits +from openpilot.selfdrive.car.mazda import mazdacan +from openpilot.selfdrive.car.mazda.values import CarControllerParams, Buttons VisualAlert = car.CarControl.HUDControl.VisualAlert diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py index d782e59dee..1f7846ca06 100644 --- a/selfdrive/car/mazda/carstate.py +++ b/selfdrive/car/mazda/carstate.py @@ -1,9 +1,9 @@ from cereal import car -from common.conversions import Conversions as CV +from openpilot.common.conversions import Conversions as CV from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser -from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.mazda.values import DBC, LKAS_LIMITS, GEN1 +from openpilot.selfdrive.car.interfaces import CarStateBase +from openpilot.selfdrive.car.mazda.values import DBC, LKAS_LIMITS, GEN1 class CarState(CarStateBase): def __init__(self, CP): diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index cb89c4c448..7ac93d9dee 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 from cereal import car -from common.conversions import Conversions as CV -from selfdrive.car.mazda.values import CAR, LKAS_LIMITS -from selfdrive.car import get_safety_config -from selfdrive.car.interfaces import CarInterfaceBase +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car.mazda.values import CAR, LKAS_LIMITS +from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName diff --git a/selfdrive/car/mazda/mazdacan.py b/selfdrive/car/mazda/mazdacan.py index 58a505f917..e350c5587f 100644 --- a/selfdrive/car/mazda/mazdacan.py +++ b/selfdrive/car/mazda/mazdacan.py @@ -1,4 +1,4 @@ -from selfdrive.car.mazda.values import GEN1, Buttons +from openpilot.selfdrive.car.mazda.values import GEN1, Buttons def create_steering_control(packer, car_fingerprint, frame, apply_steer, lkas): diff --git a/selfdrive/car/mazda/radar_interface.py b/selfdrive/car/mazda/radar_interface.py index b2f7651136..b461fcd5f8 100755 --- a/selfdrive/car/mazda/radar_interface.py +++ b/selfdrive/car/mazda/radar_interface.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase class RadarInterface(RadarInterfaceBase): pass diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index fbdf8961af..0230be2297 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -2,9 +2,9 @@ from dataclasses import dataclass, field from typing import Dict, List, Union from cereal import car -from selfdrive.car import dbc_dict -from selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries +from openpilot.selfdrive.car import dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py index 7986adb8f1..c7821bdb97 100755 --- a/selfdrive/car/mock/interface.py +++ b/selfdrive/car/mock/interface.py @@ -1,61 +1,35 @@ #!/usr/bin/env python3 from cereal import car -from system.swaglog import cloudlog import cereal.messaging as messaging -from selfdrive.car import get_safety_config -from selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.car.interfaces import CarInterfaceBase - -# mocked car interface to work with chffrplus +# mocked car interface for dashcam mode class CarInterface(CarInterfaceBase): def __init__(self, CP, CarController, CarState): super().__init__(CP, CarController, CarState) - cloudlog.debug("Using Mock Car Interface") - - self.sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal']) - self.speed = 0. - self.prev_speed = 0. + self.sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal']) @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "mock" - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput)] ret.mass = 1700. ret.wheelbase = 2.70 ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 13. # reasonable - + ret.steerRatio = 13. return ret - # returns a car.CarState def _update(self, c): self.sm.update(0) gps_sock = 'gpsLocationExternal' if self.sm.rcv_frame['gpsLocationExternal'] > 1 else 'gpsLocation' - if self.sm.updated[gps_sock]: - self.prev_speed = self.speed - self.speed = self.sm[gps_sock].speed - # create message ret = car.CarState.new_message() - - # speeds - ret.vEgo = self.speed - ret.vEgoRaw = self.speed - - ret.aEgo = self.speed - self.prev_speed - ret.brakePressed = ret.aEgo < -0.5 - - ret.standstill = self.speed < 0.01 - ret.wheelSpeeds.fl = self.speed - ret.wheelSpeeds.fr = self.speed - ret.wheelSpeeds.rl = self.speed - ret.wheelSpeeds.rr = self.speed + ret.vEgo = self.sm[gps_sock].speed + ret.vEgoRaw = self.sm[gps_sock].speed return ret def apply(self, c, now_nanos): - # in mock no carcontrols actuators = car.CarControl.Actuators.new_message() return actuators, [] diff --git a/selfdrive/car/mock/radar_interface.py b/selfdrive/car/mock/radar_interface.py old mode 100755 new mode 100644 index b2f7651136..e654bd61fd --- a/selfdrive/car/mock/radar_interface.py +++ b/selfdrive/car/mock/radar_interface.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -from selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase class RadarInterface(RadarInterfaceBase): pass diff --git a/selfdrive/car/mock/values.py b/selfdrive/car/mock/values.py index dfc7902e41..8426041b32 100644 --- a/selfdrive/car/mock/values.py +++ b/selfdrive/car/mock/values.py @@ -1,6 +1,6 @@ from typing import Dict, List, Optional, Union -from selfdrive.car.docs_definitions import CarInfo +from openpilot.selfdrive.car.docs_definitions import CarInfo class CAR: diff --git a/selfdrive/car/nissan/carcontroller.py b/selfdrive/car/nissan/carcontroller.py index 2c628b102d..2da518bbf1 100644 --- a/selfdrive/car/nissan/carcontroller.py +++ b/selfdrive/car/nissan/carcontroller.py @@ -1,8 +1,8 @@ from cereal import car from opendbc.can.packer import CANPacker -from selfdrive.car import apply_std_steer_angle_limits -from selfdrive.car.nissan import nissancan -from selfdrive.car.nissan.values import CAR, CarControllerParams +from openpilot.selfdrive.car import apply_std_steer_angle_limits +from openpilot.selfdrive.car.nissan import nissancan +from openpilot.selfdrive.car.nissan.values import CAR, CarControllerParams VisualAlert = car.CarControl.HUDControl.VisualAlert diff --git a/selfdrive/car/nissan/carstate.py b/selfdrive/car/nissan/carstate.py index f17e2d8dc4..b2ba9ce290 100644 --- a/selfdrive/car/nissan/carstate.py +++ b/selfdrive/car/nissan/carstate.py @@ -2,10 +2,10 @@ import copy from collections import deque from cereal import car from opendbc.can.can_define import CANDefine -from selfdrive.car.interfaces import CarStateBase -from common.conversions import Conversions as CV +from openpilot.selfdrive.car.interfaces import CarStateBase +from openpilot.common.conversions import Conversions as CV from opendbc.can.parser import CANParser -from selfdrive.car.nissan.values import CAR, DBC, CarControllerParams +from openpilot.selfdrive.car.nissan.values import CAR, DBC, CarControllerParams TORQUE_SAMPLES = 12 diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 423a1bf8de..aedcaa1887 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -1,8 +1,8 @@ -#!/usr/bin/env python3 from cereal import car -from selfdrive.car import get_safety_config -from selfdrive.car.interfaces import CarInterfaceBase -from selfdrive.car.nissan.values import CAR +from panda import Panda +from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.car.nissan.values import CAR class CarInterface(CarInterfaceBase): @@ -31,7 +31,7 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.44 elif candidate == CAR.ALTIMA: # Altima has EPS on C-CAN unlike the others that have it on V-CAN - ret.safetyConfigs[0].safetyParam = 1 # EPS is on alternate bus + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_NISSAN_ALT_EPS_BUS ret.mass = 1492 ret.wheelbase = 2.824 ret.centerToFront = ret.wheelbase * 0.44 diff --git a/selfdrive/car/nissan/nissancan.py b/selfdrive/car/nissan/nissancan.py index 89754775b1..49dcd6fe93 100644 --- a/selfdrive/car/nissan/nissancan.py +++ b/selfdrive/car/nissan/nissancan.py @@ -1,5 +1,5 @@ import crcmod -from selfdrive.car.nissan.values import CAR +from openpilot.selfdrive.car.nissan.values import CAR # TODO: add this checksum to the CANPacker nissan_checksum = crcmod.mkCrcFun(0x11d, initCrc=0x00, rev=False, xorOut=0xff) diff --git a/selfdrive/car/nissan/radar_interface.py b/selfdrive/car/nissan/radar_interface.py index b2f7651136..e654bd61fd 100644 --- a/selfdrive/car/nissan/radar_interface.py +++ b/selfdrive/car/nissan/radar_interface.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -from selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase class RadarInterface(RadarInterfaceBase): pass diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index e1970ded48..979e09eb66 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -4,9 +4,9 @@ from typing import Dict, List, Optional, Union from cereal import car from panda.python import uds -from selfdrive.car import AngleRateLimit, dbc_dict -from selfdrive.car.docs_definitions import CarInfo, CarHarness, CarParts -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries +from openpilot.selfdrive.car import AngleRateLimit, dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarInfo, CarHarness, CarParts +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu @@ -80,9 +80,14 @@ FINGERPRINTS = { ] } +# Default diagnostic session NISSAN_DIAGNOSTIC_REQUEST_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, 0x81]) NISSAN_DIAGNOSTIC_RESPONSE_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40, 0x81]) +# Manufacturer specific +NISSAN_DIAGNOSTIC_REQUEST_KWP_2 = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, 0xda]) +NISSAN_DIAGNOSTIC_RESPONSE_KWP_2 = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40, 0xda]) + NISSAN_VERSION_REQUEST_KWP = b'\x21\x83' NISSAN_VERSION_RESPONSE_KWP = b'\x61\x83' @@ -99,6 +104,11 @@ FW_QUERY_CONFIG = FwQueryConfig( [NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP], rx_offset=NISSAN_RX_OFFSET, ), + # Rogue's engine solely responds to this + Request( + [NISSAN_DIAGNOSTIC_REQUEST_KWP_2, NISSAN_VERSION_REQUEST_KWP], + [NISSAN_DIAGNOSTIC_RESPONSE_KWP_2, NISSAN_VERSION_RESPONSE_KWP], + ), Request( [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], @@ -121,6 +131,20 @@ FW_VERSIONS = { (Ecu.gateway, 0x18dad0f1, None): [ b'284U29HE0A', ], + }, + CAR.LEAF: { + (Ecu.abs, 0x740, None): [ + b'476606WK9B', + ], + (Ecu.eps, 0x742, None): [ + b'5SN2A\xb7A\x05\x02N126F\x15\xb2\x00\x00\x00\x00\x00\x00\x00\x80', + ], + (Ecu.fwdCamera, 0x707, None): [ + b'6WK2CDB\x04\x18\x00\x00\x00\x00\x00R=1\x18\x99\x10\x00\x00\x00\x80', + ], + (Ecu.gateway, 0x18dad0f1, None): [ + b'284U26WK0C', + ], }, CAR.LEAF_IC: { (Ecu.fwdCamera, 0x707, None): [ diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py index 80634bf261..19304efdb1 100644 --- a/selfdrive/car/subaru/carcontroller.py +++ b/selfdrive/car/subaru/carcontroller.py @@ -1,8 +1,13 @@ -from common.numpy_fast import clip, interp +from openpilot.common.numpy_fast import clip, interp from opendbc.can.packer import CANPacker -from selfdrive.car import apply_driver_steer_torque_limits -from selfdrive.car.subaru import subarucan -from selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, CanBus, CarControllerParams, SubaruFlags +from openpilot.selfdrive.car import apply_driver_steer_torque_limits, common_fault_avoidance +from openpilot.selfdrive.car.subaru import subarucan +from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, STEER_RATE_LIMITED, CanBus, CarControllerParams, SubaruFlags + +# FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and +# involves the total steering angle change rather than rate, but these limits work well for now +MAX_STEER_RATE = 25 # deg/s +MAX_STEER_RATE_FRAMES = 7 # tx control frames needed before torque can be cut class CarController: @@ -12,7 +17,7 @@ class CarController: self.frame = 0 self.cruise_button_prev = 0 - self.last_cancel_frame = 0 + self.steer_rate_counter = 0 self.p = CarControllerParams(CP) self.packer = CANPacker(DBC[CP.carFingerprint]['pt']) @@ -37,9 +42,17 @@ class CarController: apply_steer = 0 if self.CP.carFingerprint in PREGLOBAL_CARS: - can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer, CC.latActive)) + can_sends.append(subarucan.create_preglobal_steering_control(self.packer, self.frame // self.p.STEER_STEP, apply_steer, CC.latActive)) else: - can_sends.append(subarucan.create_steering_control(self.packer, apply_steer, CC.latActive)) + apply_steer_req = CC.latActive + + if self.CP.carFingerprint in STEER_RATE_LIMITED: + # Steering rate fault prevention + self.steer_rate_counter, apply_steer_req = \ + common_fault_avoidance(abs(CS.out.steeringRateDeg) > MAX_STEER_RATE, apply_steer_req, + self.steer_rate_counter, MAX_STEER_RATE_FRAMES) + + can_sends.append(subarucan.create_steering_control(self.packer, apply_steer, apply_steer_req)) self.apply_steer_last = apply_steer @@ -56,7 +69,7 @@ class CarController: cruise_brake = clip(apply_brake, CarControllerParams.BRAKE_MIN, CarControllerParams.BRAKE_MAX) else: cruise_throttle = CarControllerParams.THROTTLE_INACTIVE - cruise_rpm = CarControllerParams.RPM_INACTIVE + cruise_rpm = CarControllerParams.RPM_MIN cruise_brake = CarControllerParams.BRAKE_MIN # *** alerts and pcm cancel *** @@ -81,29 +94,30 @@ class CarController: else: if self.frame % 10 == 0: - can_sends.append(subarucan.create_es_dashstatus(self.packer, CS.es_dashstatus_msg, CC.enabled, self.CP.openpilotLongitudinalControl, + can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled, self.CP.openpilotLongitudinalControl, CC.longActive, hud_control.leadVisible)) - can_sends.append(subarucan.create_es_lkas_state(self.packer, CS.es_lkas_state_msg, CC.enabled, hud_control.visualAlert, + can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.enabled, hud_control.visualAlert, hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart)) if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT: - can_sends.append(subarucan.create_es_infotainment(self.packer, CS.es_infotainment_msg, hud_control.visualAlert)) + can_sends.append(subarucan.create_es_infotainment(self.packer, self.frame // 10, CS.es_infotainment_msg, hud_control.visualAlert)) if self.CP.openpilotLongitudinalControl: if self.frame % 5 == 0: - can_sends.append(subarucan.create_es_status(self.packer, CS.es_status_msg, self.CP.openpilotLongitudinalControl, CC.longActive, cruise_rpm)) + can_sends.append(subarucan.create_es_status(self.packer, self.frame // 5, CS.es_status_msg, + self.CP.openpilotLongitudinalControl, CC.longActive, cruise_rpm)) - can_sends.append(subarucan.create_es_brake(self.packer, CS.es_brake_msg, CC.enabled, cruise_brake)) + can_sends.append(subarucan.create_es_brake(self.packer, self.frame // 5, CS.es_brake_msg, CC.enabled, cruise_brake)) - can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg, 0, pcm_cancel_cmd, + can_sends.append(subarucan.create_es_distance(self.packer, self.frame // 5, CS.es_distance_msg, 0, pcm_cancel_cmd, self.CP.openpilotLongitudinalControl, cruise_brake > 0, cruise_throttle)) else: - if pcm_cancel_cmd and (self.frame - self.last_cancel_frame) > 0.2: - bus = CanBus.alt if self.CP.carFingerprint in GLOBAL_GEN2 else CanBus.main - can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg, bus, pcm_cancel_cmd)) - self.last_cancel_frame = self.frame + if pcm_cancel_cmd: + if self.CP.carFingerprint not in HYBRID_CARS: + bus = CanBus.alt if self.CP.carFingerprint in GLOBAL_GEN2 else CanBus.main + can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg["COUNTER"] + 1, CS.es_distance_msg, bus, pcm_cancel_cmd)) new_actuators = actuators.copy() new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index 84c1285af1..c8a6dfe1e6 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -1,11 +1,11 @@ import copy from cereal import car from opendbc.can.can_define import CANDefine -from common.conversions import Conversions as CV -from selfdrive.car.interfaces import CarStateBase +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser -from selfdrive.car.subaru.values import DBC, CAR, GLOBAL_GEN2, PREGLOBAL_CARS, CanBus, SubaruFlags -from selfdrive.car import CanSignalRateCalculator +from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, CanBus, SubaruFlags +from openpilot.selfdrive.car import CanSignalRateCalculator class CarState(CarStateBase): @@ -19,10 +19,12 @@ class CarState(CarStateBase): def update(self, cp, cp_cam, cp_body): ret = car.CarState.new_message() - ret.gas = cp.vl["Throttle"]["Throttle_Pedal"] / 255. + throttle_msg = cp.vl["Throttle"] if self.car_fingerprint not in HYBRID_CARS else cp_body.vl["Throttle_Hybrid"] + ret.gas = throttle_msg["Throttle_Pedal"] / 255. + ret.gasPressed = ret.gas > 1e-5 if self.car_fingerprint in PREGLOBAL_CARS: - ret.brakePressed = cp.vl["Brake_Pedal"]["Brake_Pedal"] > 2 + ret.brakePressed = cp.vl["Brake_Pedal"]["Brake_Pedal"] > 0 else: cp_brakes = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp ret.brakePressed = cp_brakes.vl["Brake_Status"]["Brake"] == 1 @@ -46,7 +48,8 @@ class CarState(CarStateBase): ret.leftBlindspot = (cp.vl["BSD_RCTA"]["L_ADJACENT"] == 1) or (cp.vl["BSD_RCTA"]["L_APPROACHING"] == 1) ret.rightBlindspot = (cp.vl["BSD_RCTA"]["R_ADJACENT"] == 1) or (cp.vl["BSD_RCTA"]["R_APPROACHING"] == 1) - can_gear = int(cp.vl["Transmission"]["Gear"]) + cp_transmission = cp_body if self.car_fingerprint in HYBRID_CARS else cp + can_gear = int(cp_transmission.vl["Transmission"]["Gear"]) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None)) ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"] @@ -62,8 +65,12 @@ class CarState(CarStateBase): ret.steeringPressed = abs(ret.steeringTorque) > steer_threshold cp_cruise = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp - ret.cruiseState.enabled = cp_cruise.vl["CruiseControl"]["Cruise_Activated"] != 0 - ret.cruiseState.available = cp_cruise.vl["CruiseControl"]["Cruise_On"] != 0 + if self.car_fingerprint in HYBRID_CARS: + ret.cruiseState.enabled = cp_cam.vl["ES_DashStatus"]['Cruise_Activated'] != 0 + ret.cruiseState.available = cp_cam.vl["ES_DashStatus"]['Cruise_On'] != 0 + else: + ret.cruiseState.enabled = cp_cruise.vl["CruiseControl"]["Cruise_Activated"] != 0 + ret.cruiseState.available = cp_cruise.vl["CruiseControl"]["Cruise_On"] != 0 ret.cruiseState.speed = cp_cam.vl["ES_DashStatus"]["Cruise_Set_Speed"] * CV.KPH_TO_MS if (self.car_fingerprint in PREGLOBAL_CARS and cp.vl["Dash_State2"]["UNITS"] == 1) or \ @@ -77,7 +84,7 @@ class CarState(CarStateBase): cp.vl["BodyInfo"]["DOOR_OPEN_FL"]]) ret.steerFaultPermanent = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1 - cp_es_distance = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam + cp_es_distance = cp_body if self.car_fingerprint in (GLOBAL_GEN2 | HYBRID_CARS) else cp_cam if self.car_fingerprint in PREGLOBAL_CARS: self.cruise_button = cp_cam.vl["ES_Distance"]["Cruise_Button"] self.ready = not cp_cam.vl["ES_DashStatus"]["Not_Ready_Startup"] @@ -87,18 +94,24 @@ class CarState(CarStateBase): ret.cruiseState.standstill = cp_cam.vl["ES_DashStatus"]["Cruise_State"] == 3 ret.stockFcw = (cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 1) or \ (cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 2) - # 8 is known AEB, there are a few other values related to AEB we ignore - ret.stockAeb = (cp_es_distance.vl["ES_Brake"]["AEB_Status"] == 8) and \ - (cp_es_distance.vl["ES_Brake"]["Brake_Pressure"] != 0) + self.es_lkas_state_msg = copy.copy(cp_cam.vl["ES_LKAS_State"]) cp_es_brake = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam self.es_brake_msg = copy.copy(cp_es_brake.vl["ES_Brake"]) cp_es_status = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam - self.es_status_msg = copy.copy(cp_es_status.vl["ES_Status"]) - self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"]) - self.brake_status_msg = copy.copy(cp_brakes.vl["Brake_Status"]) - self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"]) + # TODO: Hybrid cars don't have ES_Distance, need a replacement + if self.car_fingerprint not in HYBRID_CARS: + # 8 is known AEB, there are a few other values related to AEB we ignore + ret.stockAeb = (cp_es_distance.vl["ES_Brake"]["AEB_Status"] == 8) and \ + (cp_es_distance.vl["ES_Brake"]["Brake_Pressure"] != 0) + + self.es_status_msg = copy.copy(cp_es_status.vl["ES_Status"]) + self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"]) + + if self.car_fingerprint not in HYBRID_CARS: + self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"]) + self.es_dashstatus_msg = copy.copy(cp_cam.vl["ES_DashStatus"]) if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT: self.es_infotainment_msg = copy.copy(cp_cam.vl["ES_Infotainment"]) @@ -106,22 +119,37 @@ class CarState(CarStateBase): return ret @staticmethod - def get_common_global_body_messages(): + def get_common_global_body_messages(CP): messages = [ - ("CruiseControl", 20), ("Wheel_Speeds", 50), ("Brake_Status", 50), ] + if CP.carFingerprint not in HYBRID_CARS: + messages.append(("CruiseControl", 20)) + return messages @staticmethod - def get_common_global_es_messages(): + def get_common_global_es_messages(CP): messages = [ ("ES_Brake", 20), - ("ES_Distance", 20), - ("ES_Status", 20), - ("ES_Brake", 20), + ] + + if CP.carFingerprint not in HYBRID_CARS: + messages += [ + ("ES_Distance", 20), + ("ES_Status", 20) + ] + + return messages + + @staticmethod + def get_common_preglobal_body_messages(): + messages = [ + ("CruiseControl", 50), + ("Wheel_Speeds", 50), + ("Dash_State2", 1), ] return messages @@ -130,43 +158,26 @@ class CarState(CarStateBase): def get_can_parser(CP): messages = [ # sig_address, frequency - ("Throttle", 100), ("Dashlights", 10), - ("Brake_Pedal", 50), - ("Transmission", 100), ("Steering_Torque", 50), ("BodyInfo", 1), + ("Brake_Pedal", 50), ] + if CP.carFingerprint not in HYBRID_CARS: + messages += [ + ("Throttle", 100), + ("Transmission", 100) + ] + if CP.enableBsm: messages.append(("BSD_RCTA", 17)) if CP.carFingerprint not in PREGLOBAL_CARS: if CP.carFingerprint not in GLOBAL_GEN2: - messages += CarState.get_common_global_body_messages() - - messages += [ - ("Dashlights", 10), - ("BodyInfo", 10), - ] + messages += CarState.get_common_global_body_messages(CP) else: - messages += [ - ("Wheel_Speeds", 50), - ("Dash_State2", 1), - ] - - if CP.carFingerprint == CAR.FORESTER_PREGLOBAL: - messages += [ - ("Dashlights", 20), - ("BodyInfo", 1), - ("CruiseControl", 50), - ] - - if CP.carFingerprint in (CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018): - messages += [ - ("Dashlights", 10), - ("CruiseControl", 50), - ] + messages += CarState.get_common_preglobal_body_messages() return CANParser(DBC[CP.carFingerprint]["pt"], messages, CanBus.main) @@ -184,7 +195,7 @@ class CarState(CarStateBase): ] if CP.carFingerprint not in GLOBAL_GEN2: - messages += CarState.get_common_global_es_messages() + messages += CarState.get_common_global_es_messages(CP) if CP.flags & SubaruFlags.SEND_INFOTAINMENT: messages.append(("ES_Infotainment", 10)) @@ -193,9 +204,17 @@ class CarState(CarStateBase): @staticmethod def get_body_can_parser(CP): + messages = [] + if CP.carFingerprint in GLOBAL_GEN2: - messages = CarState.get_common_global_body_messages() - messages += CarState.get_common_global_es_messages() - return CANParser(DBC[CP.carFingerprint]["pt"], messages, CanBus.alt) + messages += CarState.get_common_global_body_messages(CP) + messages += CarState.get_common_global_es_messages(CP) + + if CP.carFingerprint in HYBRID_CARS: + messages += [ + ("Throttle_Hybrid", 40), + ("Transmission", 100) + ] + + return CANParser(DBC[CP.carFingerprint]["pt"], messages, CanBus.alt) - return None diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index eab39420cc..75bbc8ae91 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -1,9 +1,8 @@ -#!/usr/bin/env python3 from cereal import car from panda import Panda -from selfdrive.car import get_safety_config -from selfdrive.car.interfaces import CarInterfaceBase -from selfdrive.car.subaru.values import CAR, LKAS_ANGLE, GLOBAL_GEN2, PREGLOBAL_CARS, SubaruFlags +from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.car.subaru.values import CAR, LKAS_ANGLE, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, SubaruFlags class CarInterface(CarInterfaceBase): @@ -12,7 +11,11 @@ class CarInterface(CarInterfaceBase): def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "subaru" ret.radarUnavailable = True - ret.dashcamOnly = candidate in (PREGLOBAL_CARS | LKAS_ANGLE) + # for HYBRID CARS to be upstreamed, we need: + # - replacement for ES_Distance so we can cancel the cruise control + # - to find the Cruise_Activated bit from the car + # - proper panda safety setup (use the correct cruise_activated bit, throttle from Throttle_Hybrid, etc) + ret.dashcamOnly = candidate in (PREGLOBAL_CARS | LKAS_ANGLE | HYBRID_CARS) ret.autoResumeSng = False # Detect infotainment message sent from the camera @@ -36,7 +39,7 @@ class CarInterface(CarInterfaceBase): else: CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - if candidate == CAR.ASCENT: + if candidate in (CAR.ASCENT, CAR.ASCENT_2023): ret.mass = 2031. ret.wheelbase = 2.89 ret.centerToFront = ret.wheelbase * 0.5 @@ -68,7 +71,14 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.045, 0.042, 0.20], [0.04, 0.035, 0.045]] - elif candidate in (CAR.FORESTER, CAR.FORESTER_2022): + elif candidate == CAR.CROSSTREK_HYBRID: + ret.mass = 1668. + ret.wheelbase = 2.67 + ret.centerToFront = ret.wheelbase * 0.5 + ret.steerRatio = 17 + ret.steerActuatorDelay = 0.1 + + elif candidate in (CAR.FORESTER, CAR.FORESTER_2022, CAR.FORESTER_HYBRID): ret.mass = 1568. ret.wheelbase = 2.67 ret.centerToFront = ret.wheelbase * 0.5 @@ -107,7 +117,7 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"unknown car: {candidate}") - #ret.experimentalLongitudinalAvailable = candidate not in (GLOBAL_GEN2 | PREGLOBAL_CARS | LKAS_ANGLE) + #ret.experimentalLongitudinalAvailable = candidate not in (GLOBAL_GEN2 | PREGLOBAL_CARS | LKAS_ANGLE | HYBRID_CARS) ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable if ret.openpilotLongitudinalControl: diff --git a/selfdrive/car/subaru/radar_interface.py b/selfdrive/car/subaru/radar_interface.py index b2f7651136..e654bd61fd 100644 --- a/selfdrive/car/subaru/radar_interface.py +++ b/selfdrive/car/subaru/radar_interface.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -from selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase class RadarInterface(RadarInterfaceBase): pass diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py index df2718c764..57c1ae7741 100644 --- a/selfdrive/car/subaru/subarucan.py +++ b/selfdrive/car/subaru/subarucan.py @@ -1,5 +1,5 @@ from cereal import car -from selfdrive.car.subaru.values import CanBus +from openpilot.selfdrive.car.subaru.values import CanBus VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -16,16 +16,15 @@ def create_steering_control(packer, apply_steer, steer_req): def create_steering_status(packer): return packer.make_can_msg("ES_LKAS_State", 0, {}) -def create_es_distance(packer, es_distance_msg, bus, pcm_cancel_cmd, long_enabled = False, brake_cmd = False, cruise_throttle = 0): +def create_es_distance(packer, frame, es_distance_msg, bus, pcm_cancel_cmd, long_enabled = False, brake_cmd = False, cruise_throttle = 0): values = {s: es_distance_msg[s] for s in [ "CHECKSUM", - "COUNTER", "Signal1", "Cruise_Fault", "Cruise_Throttle", "Signal2", "Car_Follow", - "Signal3", + "Low_Speed_Follow", "Cruise_Soft_Disable", "Signal7", "Cruise_Brake_Active", @@ -39,7 +38,8 @@ def create_es_distance(packer, es_distance_msg, bus, pcm_cancel_cmd, long_enable "Cruise_Resume", "Signal6", ]} - values["COUNTER"] = (values["COUNTER"] + 1) % 0x10 + + values["COUNTER"] = frame % 0x10 if long_enabled: values["Cruise_Throttle"] = cruise_throttle @@ -57,10 +57,9 @@ def create_es_distance(packer, es_distance_msg, bus, pcm_cancel_cmd, long_enable return packer.make_can_msg("ES_Distance", bus, values) -def create_es_lkas_state(packer, es_lkas_state_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart): +def create_es_lkas_state(packer, frame, es_lkas_state_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart): values = {s: es_lkas_state_msg[s] for s in [ "CHECKSUM", - "COUNTER", "LKAS_Alert_Msg", "Signal1", "LKAS_ACTIVE", @@ -77,6 +76,8 @@ def create_es_lkas_state(packer, es_lkas_state_msg, enabled, visual_alert, left_ "Signal3", ]} + values["COUNTER"] = frame % 0x10 + # Filter the stock LKAS "Keep hands on wheel" alert if values["LKAS_Alert_Msg"] == 1: values["LKAS_Alert_Msg"] = 0 @@ -108,18 +109,20 @@ def create_es_lkas_state(packer, es_lkas_state_msg, enabled, visual_alert, left_ elif right_lane_depart: values["LKAS_Alert"] = 11 # Right lane departure dash alert - values["LKAS_ACTIVE"] = 1 # Show LKAS lane lines - values["LKAS_Dash_State"] = 2 if enabled else 0 # Green enabled indicator + if enabled: + values["LKAS_ACTIVE"] = 1 # Show LKAS lane lines + values["LKAS_Dash_State"] = 2 # Green enabled indicator + else: + values["LKAS_Dash_State"] = 0 # LKAS Not enabled values["LKAS_Left_Line_Visible"] = int(left_line) values["LKAS_Right_Line_Visible"] = int(right_line) return packer.make_can_msg("ES_LKAS_State", CanBus.main, values) -def create_es_dashstatus(packer, dashstatus_msg, enabled, long_enabled, long_active, lead_visible): +def create_es_dashstatus(packer, frame, dashstatus_msg, enabled, long_enabled, long_active, lead_visible): values = {s: dashstatus_msg[s] for s in [ "CHECKSUM", - "COUNTER", "PCB_Off", "LDW_Off", "Signal1", @@ -147,6 +150,8 @@ def create_es_dashstatus(packer, dashstatus_msg, enabled, long_enabled, long_act "Cruise_State", ]} + values["COUNTER"] = frame % 0x10 + if enabled and long_active: values["Cruise_State"] = 0 values["Cruise_Activated"] = 1 @@ -162,10 +167,9 @@ def create_es_dashstatus(packer, dashstatus_msg, enabled, long_enabled, long_act return packer.make_can_msg("ES_DashStatus", CanBus.main, values) -def create_es_brake(packer, es_brake_msg, enabled, brake_value): +def create_es_brake(packer, frame, es_brake_msg, enabled, brake_value): values = {s: es_brake_msg[s] for s in [ "CHECKSUM", - "COUNTER", "Signal1", "Brake_Pressure", "AEB_Status", @@ -176,6 +180,8 @@ def create_es_brake(packer, es_brake_msg, enabled, brake_value): "Signal3", ]} + values["COUNTER"] = frame % 0x10 + if enabled: values["Cruise_Activated"] = 1 @@ -187,10 +193,9 @@ def create_es_brake(packer, es_brake_msg, enabled, brake_value): return packer.make_can_msg("ES_Brake", CanBus.main, values) -def create_es_status(packer, es_status_msg, long_enabled, long_active, cruise_rpm): +def create_es_status(packer, frame, es_status_msg, long_enabled, long_active, cruise_rpm): values = {s: es_status_msg[s] for s in [ "CHECKSUM", - "COUNTER", "Signal1", "Cruise_Fault", "Cruise_RPM", @@ -201,6 +206,8 @@ def create_es_status(packer, es_status_msg, long_enabled, long_active, cruise_rp "Signal3", ]} + values["COUNTER"] = frame % 0x10 + if long_enabled: values["Cruise_RPM"] = cruise_rpm @@ -210,16 +217,18 @@ def create_es_status(packer, es_status_msg, long_enabled, long_active, cruise_rp return packer.make_can_msg("ES_Status", CanBus.main, values) -def create_es_infotainment(packer, es_infotainment_msg, visual_alert): +def create_es_infotainment(packer, frame, es_infotainment_msg, visual_alert): # Filter stock LKAS disabled and Keep hands on steering wheel OFF alerts values = {s: es_infotainment_msg[s] for s in [ "CHECKSUM", - "COUNTER", "LKAS_State_Infotainment", "LKAS_Blue_Lines", "Signal1", "Signal2", ]} + + values["COUNTER"] = frame % 0x10 + if values["LKAS_State_Infotainment"] in (3, 4): values["LKAS_State_Infotainment"] = 0 @@ -236,13 +245,14 @@ def create_es_infotainment(packer, es_infotainment_msg, visual_alert): # *** Subaru Pre-global *** -def subaru_preglobal_checksum(packer, values, addr): +def subaru_preglobal_checksum(packer, values, addr, checksum_byte=7): dat = packer.make_can_msg(addr, 0, values)[2] - return (sum(dat[:7])) % 256 + return (sum(dat[:checksum_byte]) + sum(dat[checksum_byte+1:])) % 256 -def create_preglobal_steering_control(packer, apply_steer, steer_req): +def create_preglobal_steering_control(packer, frame, apply_steer, steer_req): values = { + "COUNTER": frame % 0x08, "LKAS_Command": apply_steer, "LKAS_Active": steer_req, } @@ -257,7 +267,7 @@ def create_preglobal_es_distance(packer, cruise_button, es_distance_msg): "Signal1", "Car_Follow", "Signal2", - "Brake_On", + "Cruise_Brake_Active", "Distance_Swap", "Standstill", "Signal3", diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index dc25f31f73..7f66069514 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -4,9 +4,9 @@ from typing import Dict, List, Union from cereal import car from panda.python import uds -from selfdrive.car import dbc_dict -from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Tool, Column -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 +from openpilot.selfdrive.car import dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Tool, Column +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 Ecu = car.CarParams.Ecu @@ -66,10 +66,13 @@ class CanBus: class CAR: # Global platform ASCENT = "SUBARU ASCENT LIMITED 2019" + ASCENT_2023 = "SUBARU ASCENT 2023" IMPREZA = "SUBARU IMPREZA LIMITED 2019" IMPREZA_2020 = "SUBARU IMPREZA SPORT 2020" FORESTER = "SUBARU FORESTER 2019" OUTBACK = "SUBARU OUTBACK 6TH GEN" + CROSSTREK_HYBRID = "SUBARU CROSSTREK HYBRID 2020" + FORESTER_HYBRID = "SUBARU FORESTER HYBRID 2020" LEGACY = "SUBARU LEGACY 7TH GEN" FORESTER_2022 = "SUBARU FORESTER 2022" OUTBACK_2023 = "SUBARU OUTBACK 7TH GEN" @@ -110,6 +113,9 @@ CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { SubaruCarInfo("Subaru Crosstrek 2020-23"), SubaruCarInfo("Subaru XV 2020-21"), ], + # TODO: is there an XV and Impreza too? + CAR.CROSSTREK_HYBRID: SubaruCarInfo("Subaru Crosstrek Hybrid 2020"), + CAR.FORESTER_HYBRID: SubaruCarInfo("Subaru Forester Hybrid 2020"), CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-21", "All"), CAR.FORESTER_PREGLOBAL: SubaruCarInfo("Subaru Forester 2017-18"), CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"), @@ -117,6 +123,7 @@ CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { CAR.OUTBACK_PREGLOBAL_2018: SubaruCarInfo("Subaru Outback 2018-19"), CAR.FORESTER_2022: SubaruCarInfo("Subaru Forester 2022", "All", car_parts=CarParts.common([CarHarness.subaru_c])), CAR.OUTBACK_2023: SubaruCarInfo("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), + CAR.ASCENT_2023: SubaruCarInfo("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), } SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ @@ -131,6 +138,7 @@ FW_QUERY_CONFIG = FwQueryConfig( [StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], ), # Some Eyesight modules fail on TESTER_PRESENT_REQUEST + # TODO: check if this resolves the fingerprinting issue for the 2023 Ascent and other new Subaru cars Request( [SUBARU_VERSION_REQUEST], [SUBARU_VERSION_RESPONSE], @@ -172,6 +180,23 @@ FW_VERSIONS = { b'\x01\xfe\xfa\x00\x00', ], }, + CAR.ASCENT_2023: { + (Ecu.abs, 0x7b0, None): [ + b'\xa5 #\x03\x00', + ], + (Ecu.eps, 0x746, None): [ + b'%\xc0\xd0\x11', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x05!\x08\x1dK\x05!\x08\x01/', + ], + (Ecu.engine, 0x7a2, None): [ + b'\xe5,\xa0P\x07', + ], + (Ecu.transmission, 0x7a3, None): [ + b'\x04\xfe\xf3\x00\x00', + ], + }, CAR.LEGACY: { (Ecu.abs, 0x7b0, None): [ b'\xa1\\ x04\x01', @@ -285,6 +310,7 @@ FW_VERSIONS = { b'\xa2 !`\000', b'\xf1\x00\xb2\x06\x04', b'\xa2 `\x00', + b'\xa2 !3\x00', ], (Ecu.eps, 0x746, None): [ b'\x9a\xc0\000\000', @@ -298,6 +324,7 @@ FW_VERSIONS = { b'\x00\x00eq\x1f@ "', b'\x00\x00eq\x00\x00\x00\x00', b'\x00\x00e\x8f\x00\x00\x00\x00', + b'\x00\x00e\xa4\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\xca!ap\a', @@ -312,6 +339,7 @@ FW_VERSIONS = { b'\xf3"fp\x07', b'\xe6"f0\x07', b'\xe6"fp\x07', + b'\xe6!`@\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xe6\xf5\004\000\000', @@ -323,6 +351,25 @@ FW_VERSIONS = { b'\xe9\xf6F0\x00', b'\xe9\xf5B0\x00', b'\xe9\xf6B0\x00', + b'\xe9\xf5"\x00\x00', + ], + }, + CAR.CROSSTREK_HYBRID: { + (Ecu.abs, 0x7b0, None): [ + b'\xa2 \x19e\x01', + b'\xa2 !e\x01', + ], + (Ecu.eps, 0x746, None): [ + b'\x9a\xc2\x01\x00', + b'\n\xc2\x01\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00el\x1f@ #', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xd7!`@\x07', + b'\xd7!`p\a', + b'\xf4!`0\x07', ], }, CAR.FORESTER: { @@ -368,6 +415,23 @@ FW_VERSIONS = { b'\x1a\xe6F1\x00', ], }, + CAR.FORESTER_HYBRID: { + (Ecu.abs, 0x7b0, None): [ + b'\xa3 \x19T\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x8d\xc2\x00\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00eY\x1f@ !', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xd2\xa1`r\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\x1b\xa7@a\x00', + ], + }, CAR.FORESTER_PREGLOBAL: { (Ecu.abs, 0x7b0, None): [ b'\x7d\x97\x14\x40', @@ -623,11 +687,14 @@ FW_VERSIONS = { DBC = { CAR.ASCENT: dbc_dict('subaru_global_2017_generated', None), + CAR.ASCENT_2023: dbc_dict('subaru_global_2017_generated', None), CAR.IMPREZA: dbc_dict('subaru_global_2017_generated', None), CAR.IMPREZA_2020: dbc_dict('subaru_global_2017_generated', None), CAR.FORESTER: dbc_dict('subaru_global_2017_generated', None), CAR.FORESTER_2022: dbc_dict('subaru_global_2017_generated', None), CAR.OUTBACK: dbc_dict('subaru_global_2017_generated', None), + CAR.FORESTER_HYBRID: dbc_dict('subaru_global_2020_hybrid_generated', None), + CAR.CROSSTREK_HYBRID: dbc_dict('subaru_global_2020_hybrid_generated', None), CAR.OUTBACK_2023: dbc_dict('subaru_global_2017_generated', None), CAR.LEGACY: dbc_dict('subaru_global_2017_generated', None), CAR.FORESTER_PREGLOBAL: dbc_dict('subaru_forester_2017_generated', None), @@ -636,6 +703,11 @@ DBC = { CAR.OUTBACK_PREGLOBAL_2018: dbc_dict('subaru_outback_2019_generated', None), } -LKAS_ANGLE = {CAR.FORESTER_2022, CAR.OUTBACK_2023} -GLOBAL_GEN2 = {CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023} +LKAS_ANGLE = {CAR.FORESTER_2022, CAR.OUTBACK_2023, CAR.ASCENT_2023} +GLOBAL_GEN2 = {CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023, CAR.ASCENT_2023} PREGLOBAL_CARS = {CAR.FORESTER_PREGLOBAL, CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018} +HYBRID_CARS = {CAR.CROSSTREK_HYBRID, CAR.FORESTER_HYBRID} + +# Cars that temporarily fault when steering angle rate is greater than some threshold. +# Appears to be all cars that started production after 2020 +STEER_RATE_LIMITED = GLOBAL_GEN2 | {CAR.IMPREZA_2020} diff --git a/selfdrive/car/tesla/carcontroller.py b/selfdrive/car/tesla/carcontroller.py index aeaaba88e7..95a248a614 100644 --- a/selfdrive/car/tesla/carcontroller.py +++ b/selfdrive/car/tesla/carcontroller.py @@ -1,8 +1,8 @@ -from common.numpy_fast import clip +from openpilot.common.numpy_fast import clip from opendbc.can.packer import CANPacker -from selfdrive.car import apply_std_steer_angle_limits -from selfdrive.car.tesla.teslacan import TeslaCAN -from selfdrive.car.tesla.values import DBC, CANBUS, CarControllerParams +from openpilot.selfdrive.car import apply_std_steer_angle_limits +from openpilot.selfdrive.car.tesla.teslacan import TeslaCAN +from openpilot.selfdrive.car.tesla.values import DBC, CANBUS, CarControllerParams class CarController: diff --git a/selfdrive/car/tesla/carstate.py b/selfdrive/car/tesla/carstate.py index 61a6526f07..2cb4f09d79 100644 --- a/selfdrive/car/tesla/carstate.py +++ b/selfdrive/car/tesla/carstate.py @@ -1,9 +1,9 @@ import copy from collections import deque from cereal import car -from common.conversions import Conversions as CV -from selfdrive.car.tesla.values import DBC, CANBUS, GEAR_MAP, DOORS, BUTTONS -from selfdrive.car.interfaces import CarStateBase +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car.tesla.values import DBC, CANBUS, GEAR_MAP, DOORS, BUTTONS +from openpilot.selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine diff --git a/selfdrive/car/tesla/interface.py b/selfdrive/car/tesla/interface.py index 209f00873a..e06139729c 100755 --- a/selfdrive/car/tesla/interface.py +++ b/selfdrive/car/tesla/interface.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from selfdrive.car.tesla.values import CANBUS, CAR -from selfdrive.car import get_safety_config -from selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.car.tesla.values import CANBUS, CAR +from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car.interfaces import CarInterfaceBase class CarInterface(CarInterfaceBase): diff --git a/selfdrive/car/tesla/radar_interface.py b/selfdrive/car/tesla/radar_interface.py index cf76e213e0..b3e7c7fcb1 100755 --- a/selfdrive/car/tesla/radar_interface.py +++ b/selfdrive/car/tesla/radar_interface.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 from cereal import car from opendbc.can.parser import CANParser -from selfdrive.car.tesla.values import DBC, CANBUS -from selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.tesla.values import DBC, CANBUS +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase RADAR_MSGS_A = list(range(0x310, 0x36E, 3)) RADAR_MSGS_B = list(range(0x311, 0x36F, 3)) @@ -36,7 +36,7 @@ class RadarInterface(RadarInterfaceBase): def update(self, can_strings): if self.rcp is None: - return None + return super().update(None) values = self.rcp.update_strings(can_strings) self.updated_messages.update(values) diff --git a/selfdrive/car/tesla/teslacan.py b/selfdrive/car/tesla/teslacan.py index a491c030f8..6bb27b995f 100644 --- a/selfdrive/car/tesla/teslacan.py +++ b/selfdrive/car/tesla/teslacan.py @@ -1,7 +1,7 @@ import crcmod -from common.conversions import Conversions as CV -from selfdrive.car.tesla.values import CANBUS, CarControllerParams +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car.tesla.values import CANBUS, CarControllerParams class TeslaCAN: diff --git a/selfdrive/car/tesla/values.py b/selfdrive/car/tesla/values.py index 3333455d1b..15283ae4b2 100644 --- a/selfdrive/car/tesla/values.py +++ b/selfdrive/car/tesla/values.py @@ -3,9 +3,9 @@ from collections import namedtuple from typing import Dict, List, Union from cereal import car -from selfdrive.car import AngleRateLimit, dbc_dict -from selfdrive.car.docs_definitions import CarInfo -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries +from openpilot.selfdrive.car import AngleRateLimit, dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarInfo +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py old mode 100644 new mode 100755 index 857ba8e4f4..1e37ddf945 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 from typing import NamedTuple, Optional -from selfdrive.car.chrysler.values import CAR as CHRYSLER -from selfdrive.car.gm.values import CAR as GM -from selfdrive.car.ford.values import CAR as FORD -from selfdrive.car.honda.values import CAR as HONDA -from selfdrive.car.hyundai.values import CAR as HYUNDAI -from selfdrive.car.nissan.values import CAR as NISSAN -from selfdrive.car.mazda.values import CAR as MAZDA -from selfdrive.car.subaru.values import CAR as SUBARU -from selfdrive.car.toyota.values import CAR as TOYOTA -from selfdrive.car.volkswagen.values import CAR as VOLKSWAGEN -from selfdrive.car.tesla.values import CAR as TESLA -from selfdrive.car.body.values import CAR as COMMA +from openpilot.selfdrive.car.chrysler.values import CAR as CHRYSLER +from openpilot.selfdrive.car.gm.values import CAR as GM +from openpilot.selfdrive.car.ford.values import CAR as FORD +from openpilot.selfdrive.car.honda.values import CAR as HONDA +from openpilot.selfdrive.car.hyundai.values import CAR as HYUNDAI +from openpilot.selfdrive.car.nissan.values import CAR as NISSAN +from openpilot.selfdrive.car.mazda.values import CAR as MAZDA +from openpilot.selfdrive.car.subaru.values import CAR as SUBARU +from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA +from openpilot.selfdrive.car.volkswagen.values import CAR as VOLKSWAGEN +from openpilot.selfdrive.car.tesla.values import CAR as TESLA +from openpilot.selfdrive.car.body.values import CAR as COMMA # TODO: add routes for these cars non_tested_cars = [ @@ -22,12 +22,11 @@ non_tested_cars = [ GM.MALIBU, GM.EQUINOX, HYUNDAI.GENESIS_G90, - HYUNDAI.IONIQ_6, - HYUNDAI.KIA_OPTIMA_H, HONDA.ODYSSEY_CHN, VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter TOYOTA.RAV4_TSS2_2023, TOYOTA.RAV4H_TSS2_2023, + SUBARU.FORESTER_HYBRID, ] @@ -95,6 +94,7 @@ routes = [ CarTestRoute("2d5808fae0b38ac6|2021-09-01--17-14-11", HONDA.HONDA_E), CarTestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.CIVIC_2022), + CarTestRoute("87d7f06ade479c2e|2023-09-11--23-30-11", HYUNDAI.AZERA_6TH_GEN), CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS), CarTestRoute("b5d6dc830ad63071|2022-12-12--21-28-25", HYUNDAI.GENESIS_GV60_EV_1ST_GEN, segment=12), CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70), @@ -110,6 +110,7 @@ routes = [ CarTestRoute("e0e98335f3ebc58f|2021-03-07--16-38-29", HYUNDAI.KIA_CEED), CarTestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA_G4), CarTestRoute("018654717bc93d7d|2022-09-19--23-11-10", HYUNDAI.KIA_OPTIMA_G4_FL, segment=0), + CarTestRoute("f9716670b2481438|2023-08-23--14-49-50", HYUNDAI.KIA_OPTIMA_H), CarTestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS), CarTestRoute("b3537035ffe6a7d6|2022-10-17--15-23-49", HYUNDAI.KIA_SPORTAGE_HYBRID_5TH_GEN), CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA), @@ -119,18 +120,24 @@ routes = [ CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_HYBRID_4TH_GEN), CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO), CarTestRoute("1d0d000db3370fd0|2023-01-04--22-28-42", HYUNDAI.KIA_SORENTO_4TH_GEN, segment=5), + CarTestRoute("fc19648042eb6896|2023-08-16--11-43-27", HYUNDAI.KIA_SORENTO_HEV_4TH_GEN, segment=14), CarTestRoute("628935d7d3e5f4f7|2022-11-30--01-12-46", HYUNDAI.KIA_SORENTO_PHEV_4TH_GEN), CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.PALISADE), CarTestRoute("05a8f0197fdac372|2022-10-19--14-14-09", HYUNDAI.IONIQ_5), # HDA2 + CarTestRoute("eb4eae1476647463|2023-08-26--18-07-04", HYUNDAI.IONIQ_6, segment=6), # HDA2 CarTestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.IONIQ_PHEV_2019), CarTestRoute("fa8db5869167f821|2021-06-10--22-50-10", HYUNDAI.IONIQ_PHEV), + CarTestRoute("e1107f9d04dfb1e2|2023-09-05--22-32-12", HYUNDAI.IONIQ_PHEV), # openpilot longitudinal enabled CarTestRoute("2c5cf2dd6102e5da|2020-12-17--16-06-44", HYUNDAI.IONIQ_EV_2020), CarTestRoute("610ebb9faaad6b43|2020-06-13--15-28-36", HYUNDAI.IONIQ_EV_LTD), CarTestRoute("2c5cf2dd6102e5da|2020-06-26--16-00-08", HYUNDAI.IONIQ), + CarTestRoute("012c95f06918eca4|2023-01-15--11-19-36", HYUNDAI.IONIQ), # openpilot longitudinal enabled CarTestRoute("ab59fe909f626921|2021-10-18--18-34-28", HYUNDAI.IONIQ_HEV_2022), CarTestRoute("22d955b2cd499c22|2020-08-10--19-58-21", HYUNDAI.KONA), CarTestRoute("efc48acf44b1e64d|2021-05-28--21-05-04", HYUNDAI.KONA_EV), + CarTestRoute("f90d3cd06caeb6fa|2023-09-06--17-15-47", HYUNDAI.KONA_EV), # openpilot longitudinal enabled CarTestRoute("ff973b941a69366f|2022-07-28--22-01-19", HYUNDAI.KONA_EV_2022, segment=11), + CarTestRoute("1618132d68afc876|2023-08-27--09-32-14", HYUNDAI.KONA_EV_2ND_GEN, segment=13), CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV), CarTestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER), CarTestRoute("5b50b883a4259afb|2022-11-09--15-00-42", HYUNDAI.KIA_STINGER_2022), @@ -140,6 +147,7 @@ routes = [ CarTestRoute("9b25e8c1484a1b67|2023-04-13--10-41-45", HYUNDAI.KIA_EV6), CarTestRoute("007d5e4ad9f86d13|2021-09-30--15-09-23", HYUNDAI.KIA_K5_2021), CarTestRoute("c58dfc9fc16590e0|2023-01-14--13-51-48", HYUNDAI.KIA_K5_HEV_2020), + CarTestRoute("78ad5150de133637|2023-09-13--16-15-57", HYUNDAI.KIA_K8_HEV_1ST_GEN), CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV), CarTestRoute("b153671049a867b3|2023-04-05--10-00-30", HYUNDAI.KIA_NIRO_EV_2ND_GEN), CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV), @@ -163,6 +171,7 @@ routes = [ CarTestRoute("e9966711cfb04ce3|2022-01-11--07-59-43", TOYOTA.AVALON_TSS2), CarTestRoute("eca1080a91720a54|2022-03-17--13-32-29", TOYOTA.AVALONH_TSS2), CarTestRoute("6cdecc4728d4af37|2020-02-23--15-44-18", TOYOTA.CAMRY), + CarTestRoute("2f37c007683e85ba|2023-09-02--14-39-44", TOYOTA.CAMRY), # openpilot longitudinal, with radar CAN filter CarTestRoute("3456ad0cd7281b24|2020-12-13--17-45-56", TOYOTA.CAMRY_TSS2), CarTestRoute("ffccc77938ddbc44|2021-01-04--16-55-41", TOYOTA.CAMRYH_TSS2), CarTestRoute("54034823d30962f5|2021-05-24--06-37-34", TOYOTA.CAMRYH), @@ -203,6 +212,7 @@ routes = [ CarTestRoute("ea8fbe72b96a185c|2023-02-22--09-20-34", TOYOTA.CHR_TSS2), # openpilot longitudinal, with smartDSU CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.CHRH), CarTestRoute("6719965b0e1d1737|2023-02-09--22-44-05", TOYOTA.CHRH_TSS2), + # CarTestRoute("6719965b0e1d1737|2023-08-29--06-40-05", TOYOTA.CHRH_TSS2), # openpilot longitudinal, radar disabled CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V), CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1), @@ -237,10 +247,13 @@ routes = [ CarTestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER), CarTestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA), CarTestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020), + # CarTestRoute("8de015561e1ea4a0|2023-08-29--17-08-31", SUBARU.IMPREZA), # openpilot longitudinal CarTestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.OUTBACK, segment=10), CarTestRoute("c56e69bbc74b8fad|2022-08-18--09-43-51", SUBARU.LEGACY, segment=3), + CarTestRoute("f4e3a0c511a076f4|2022-08-04--16-16-48", SUBARU.CROSSTREK_HYBRID, segment=2), CarTestRoute("7fd1e4f3a33c1673|2022-12-04--15-09-53", SUBARU.FORESTER_2022, segment=4), CarTestRoute("f3b34c0d2632aa83|2023-07-23--20-43-25", SUBARU.OUTBACK_2023, segment=7), + CarTestRoute("99437cef6d5ff2ee|2023-03-13--21-21-38", SUBARU.ASCENT_2023, segment=7), # Pre-global, dashcam CarTestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL), CarTestRoute("df5ca7660000fba8|2020-06-16--17-37-19", SUBARU.LEGACY_PREGLOBAL), diff --git a/selfdrive/car/tests/test_can_fingerprint.py b/selfdrive/car/tests/test_can_fingerprint.py index 9f3f232980..406ccd07f0 100755 --- a/selfdrive/car/tests/test_can_fingerprint.py +++ b/selfdrive/car/tests/test_can_fingerprint.py @@ -3,8 +3,8 @@ from parameterized import parameterized import unittest from cereal import log, messaging -from selfdrive.car.car_helpers import can_fingerprint -from selfdrive.car.fingerprints import _FINGERPRINTS as FINGERPRINTS +from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, can_fingerprint +from openpilot.selfdrive.car.fingerprints import _FINGERPRINTS as FINGERPRINTS class TestCanFingerprint(unittest.TestCase): @@ -24,6 +24,43 @@ class TestCanFingerprint(unittest.TestCase): self.assertEqual(car_fingerprint, car_model) self.assertEqual(finger[0], fingerprint) self.assertEqual(finger[1], fingerprint) + self.assertEqual(finger[2], {}) + + def test_timing(self): + # just pick any CAN fingerprinting car + car_model = 'CHEVROLET BOLT EUV 2022' + fingerprint = FINGERPRINTS[car_model][0] + + cases = [] + + # case 1 - one match, make sure we keep going for 100 frames + can = messaging.new_message('can', 1) + can.can = [log.CanData(address=address, dat=b'\x00' * length, src=src) + for address, length in fingerprint.items() for src in (0, 1)] + cases.append((FRAME_FINGERPRINT, car_model, can)) + + # case 2 - no matches, make sure we keep going for 100 frames + can = messaging.new_message('can', 1) + can.can = [log.CanData(address=1, dat=b'\x00' * 1, src=src) for src in (0, 1)] # uncommon address + cases.append((FRAME_FINGERPRINT, None, can)) + + # case 3 - multiple matches, make sure we keep going for 200 frames to try to eliminate some + can = messaging.new_message('can', 1) + can.can = [log.CanData(address=2016, dat=b'\x00' * 8, src=src) for src in (0, 1)] # common address + cases.append((FRAME_FINGERPRINT * 2, None, can)) + + for expected_frames, car_model, can in cases: + with self.subTest(expected_frames=expected_frames, car_model=car_model): + frames = 0 + + def test(): + nonlocal frames + frames += 1 + return can # noqa: B023 + + car_fingerprint, _ = can_fingerprint(test) + self.assertEqual(car_fingerprint, car_model) + self.assertEqual(frames, expected_frames + 2) # TODO: fix extra frames if __name__ == "__main__": diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 3945fbb987..787b5d6316 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -1,18 +1,24 @@ #!/usr/bin/env python3 +import os import math import unittest import hypothesis.strategies as st -from hypothesis import given, settings +from hypothesis import Phase, given, settings import importlib from parameterized import parameterized -from cereal import car -from common.realtime import DT_CTRL -from selfdrive.car import gen_empty_fingerprint -from selfdrive.car.car_helpers import interfaces -from selfdrive.car.fingerprints import all_known_cars -from selfdrive.car.interfaces import get_interface_attr -from selfdrive.test.fuzzy_generation import DrawType, FuzzyGenerator +from cereal import car, messaging +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car import gen_empty_fingerprint +from openpilot.selfdrive.car.car_helpers import interfaces +from openpilot.selfdrive.car.fingerprints import all_known_cars +from openpilot.selfdrive.car.fw_versions import FW_VERSIONS +from openpilot.selfdrive.car.interfaces import get_interface_attr +from openpilot.selfdrive.test.fuzzy_generation import DrawType, FuzzyGenerator + +ALL_ECUS = list({ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()}) + +MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '5')) def get_fuzzy_car_interface_args(draw: DrawType) -> dict: @@ -21,12 +27,8 @@ def get_fuzzy_car_interface_args(draw: DrawType) -> dict: st.integers(min_value=0, max_value=64)) for key in gen_empty_fingerprint()}) - # just the most important fields - car_fw_strategy = st.lists(st.fixed_dictionaries({ - 'ecu': st.sampled_from(list(car.CarParams.Ecu.schema.enumerants.keys())), - # TODO: only use reasonable addrs for the paired ecu and brand/platform - 'address': st.integers(min_value=0, max_value=0x800), - })) + # only pick from possible ecus to reduce search space + car_fw_strategy = st.lists(st.sampled_from(ALL_ECUS)) params_strategy = st.fixed_dictionaries({ 'fingerprints': fingerprint_strategy, @@ -35,14 +37,21 @@ def get_fuzzy_car_interface_args(draw: DrawType) -> dict: }) params: dict = draw(params_strategy) - params['car_fw'] = [car.CarParams.CarFw(**fw) for fw in params['car_fw']] + params['car_fw'] = [car.CarParams.CarFw(ecu=fw[0], address=fw[1], subAddress=fw[2] or 0) for fw in params['car_fw']] return params class TestCarInterfaces(unittest.TestCase): + @classmethod + def setUpClass(cls): + os.environ['NO_RADAR_SLEEP'] = '1' + + # FIXME: Due to the lists used in carParams, Phase.target is very slow and will cause + # many generated examples to overrun when max_examples > ~20, don't use it @parameterized.expand([(car,) for car in sorted(all_known_cars())]) - @settings(max_examples=5) + @settings(max_examples=MAX_EXAMPLES, deadline=None, + phases=(Phase.reuse, Phase.generate, Phase.shrink)) @given(data=st.data()) def test_car_interfaces(self, car_name, data): CarInterface, CarController, CarState = interfaces[car_name] @@ -78,9 +87,6 @@ class TestCarInterfaces(unittest.TestCase): self.assertTrue(not math.isnan(tune.torque.kf) and tune.torque.kf > 0) self.assertTrue(not math.isnan(tune.torque.friction) and tune.torque.friction > 0) - elif tune.which() == 'indi': - self.assertTrue(len(tune.indi.outerLoopGainV)) - cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True) # Run car interface now_nanos = 0 @@ -110,6 +116,12 @@ class TestCarInterfaces(unittest.TestCase): hasattr(radar_interface, '_update') and hasattr(radar_interface, 'trigger_msg'): radar_interface._update([radar_interface.trigger_msg]) + # Test radar fault + if not car_params.radarUnavailable and radar_interface.rcp is not None: + cans = [messaging.new_message('can', 1).to_bytes() for _ in range(5)] + rr = radar_interface.update(cans) + self.assertTrue(rr is None or len(rr.errors) > 0) + def test_interface_attrs(self): """Asserts basic behavior of interface attribute getter""" num_brands = len(get_interface_attr('CAR')) diff --git a/selfdrive/car/tests/test_docs.py b/selfdrive/car/tests/test_docs.py index 6e4a58149c..d9d67fe87d 100755 --- a/selfdrive/car/tests/test_docs.py +++ b/selfdrive/car/tests/test_docs.py @@ -4,13 +4,13 @@ import os import re import unittest -from common.basedir import BASEDIR -from selfdrive.car.car_helpers import interfaces, get_interface_attr -from selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_all_car_info -from selfdrive.car.docs_definitions import Cable, Column, PartType, Star -from selfdrive.car.honda.values import CAR as HONDA -from selfdrive.debug.dump_car_info import dump_car_info -from selfdrive.debug.print_docs_diff import print_car_info_diff +from openpilot.common.basedir import BASEDIR +from openpilot.selfdrive.car.car_helpers import interfaces, get_interface_attr +from openpilot.selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_all_car_info +from openpilot.selfdrive.car.docs_definitions import Cable, Column, PartType, Star +from openpilot.selfdrive.car.honda.values import CAR as HONDA +from openpilot.selfdrive.debug.dump_car_info import dump_car_info +from openpilot.selfdrive.debug.print_docs_diff import print_car_info_diff class TestCarDocs(unittest.TestCase): diff --git a/selfdrive/car/tests/test_fingerprints.py b/selfdrive/car/tests/test_fingerprints.py index a0baec68d6..61e9a4d165 100755 --- a/selfdrive/car/tests/test_fingerprints.py +++ b/selfdrive/car/tests/test_fingerprints.py @@ -3,7 +3,7 @@ import os import sys from typing import Dict, List -from common.basedir import BASEDIR +from openpilot.common.basedir import BASEDIR # messages reserved for CAN based ignition (see can_ignition_hook function in panda/board/drivers/can) # (addr, len) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 3feb595219..378b68e43e 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import random import time import unittest @@ -7,11 +8,12 @@ from parameterized import parameterized import threading from cereal import car -from common.params import Params -from selfdrive.car.car_helpers import interfaces -from selfdrive.car.fingerprints import FW_VERSIONS -from selfdrive.car.fw_versions import FW_QUERY_CONFIGS, FUZZY_EXCLUDE_ECUS, VERSIONS, build_fw_dict, match_fw_to_car, get_fw_versions, get_present_ecus -from selfdrive.car.vin import get_vin +from openpilot.common.params import Params +from openpilot.selfdrive.car.car_helpers import interfaces +from openpilot.selfdrive.car.fingerprints import FW_VERSIONS +from openpilot.selfdrive.car.fw_versions import FW_QUERY_CONFIGS, FUZZY_EXCLUDE_ECUS, VERSIONS, build_fw_dict, \ + match_fw_to_car, get_fw_versions, get_present_ecus +from openpilot.selfdrive.car.vin import get_vin CarFw = car.CarParams.CarFw Ecu = car.CarParams.Ecu @@ -167,10 +169,14 @@ class TestFwFingerprint(unittest.TestCase): for request_obj in config.requests: self.assertEqual(len(request_obj.request), len(request_obj.response)) + # No request on the OBD port (bus 1, multiplexed) should be run on an aux panda + self.assertFalse(request_obj.auxiliary and request_obj.bus == 1 and request_obj.obd_multiplexing, + f"{brand.title()}: OBD multiplexed request is marked auxiliary: {request_obj}") + class TestFwFingerprintTiming(unittest.TestCase): N: int = 5 - TOL: float = 0.1 + TOL: float = 0.12 @staticmethod def _run_thread(thread: threading.Thread) -> float: @@ -192,7 +198,7 @@ class TestFwFingerprintTiming(unittest.TestCase): kwargs=dict(num_pandas=num_pandas)) brand_time += self._run_thread(thread) - return round(brand_time / self.N, 2) + return brand_time / self.N def _assert_timing(self, avg_time, ref_time): self.assertLess(avg_time, ref_time + self.TOL) @@ -219,25 +225,26 @@ class TestFwFingerprintTiming(unittest.TestCase): self._assert_timing(vin_time / self.N, vin_ref_time) print(f'get_vin, query time={vin_time / self.N} seconds') + @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 6.1 + total_ref_time = 6.07 brand_ref_times = { 1: { - 'body': 0.1, + 'body': 0.11, 'chrysler': 0.3, 'ford': 0.2, - 'honda': 0.5, - 'hyundai': 0.7, + 'honda': 0.52, + 'hyundai': 0.72, 'mazda': 0.2, - 'nissan': 0.3, + 'nissan': 0.4, 'subaru': 0.2, 'tesla': 0.2, 'toyota': 1.6, 'volkswagen': 0.2, }, 2: { - 'ford': 0.4, - 'hyundai': 1.1, + 'ford': 0.3, + 'hyundai': 1.12, } } @@ -251,10 +258,12 @@ class TestFwFingerprintTiming(unittest.TestCase): avg_time = self._benchmark_brand(brand, num_pandas) total_time += avg_time + avg_time = round(avg_time, 2) self._assert_timing(avg_time, brand_ref_times[num_pandas][brand]) print(f'{brand=}, {num_pandas=}, {len(config.requests)=}, avg FW query time={avg_time} seconds') with self.subTest(brand='all_brands'): + total_time = round(total_time, 2) self._assert_timing(total_time, total_ref_time) print(f'all brands, total FW query time={total_time} seconds') diff --git a/selfdrive/car/tests/test_lateral_limits.py b/selfdrive/car/tests/test_lateral_limits.py index 9e9043d236..1fc626972f 100755 --- a/selfdrive/car/tests/test_lateral_limits.py +++ b/selfdrive/car/tests/test_lateral_limits.py @@ -6,11 +6,11 @@ import sys from typing import DefaultDict, Dict import unittest -from common.realtime import DT_CTRL -from selfdrive.car.car_helpers import interfaces -from selfdrive.car.fingerprints import all_known_cars -from selfdrive.car.interfaces import get_torque_params -from selfdrive.car.subaru.values import CAR as SUBARU +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car.car_helpers import interfaces +from openpilot.selfdrive.car.fingerprints import all_known_cars +from openpilot.selfdrive.car.interfaces import get_torque_params +from openpilot.selfdrive.car.subaru.values import CAR as SUBARU CAR_MODELS = all_known_cars() @@ -32,7 +32,7 @@ ABOVE_LIMITS_CARS = [ car_model_jerks: DefaultDict[str, Dict[str, float]] = defaultdict(dict) -@parameterized_class('car_model', [(c,) for c in CAR_MODELS]) +@parameterized_class('car_model', [(c,) for c in sorted(CAR_MODELS)]) class TestLateralLimits(unittest.TestCase): car_model: str diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index c40d8bb505..e6460d491e 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# pylint: disable=E1101 import capnp import os import importlib @@ -9,21 +8,26 @@ from typing import List, Optional, Tuple from parameterized import parameterized_class from cereal import log, car -from common.basedir import BASEDIR -from common.realtime import DT_CTRL -from selfdrive.car.fingerprints import all_known_cars -from selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces -from selfdrive.car.gm.values import CAR as GM -from selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH -from selfdrive.car.hyundai.values import CAR as HYUNDAI -from selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute -from selfdrive.test.openpilotci import get_url -from tools.lib.logreader import LogReader -from tools.lib.route import Route, SegmentName, RouteName +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car.fingerprints import all_known_cars +from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces +from openpilot.selfdrive.car.gm.values import CAR as GM +from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH +from openpilot.selfdrive.car.hyundai.values import CAR as HYUNDAI +from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute +from openpilot.selfdrive.controls.controlsd import Controls +from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.route import Route, SegmentName, RouteName from panda.tests.libpanda import libpanda_py +from openpilot.selfdrive.test.helpers import SKIP_ENV_VAR +EventName = car.CarEvent.EventName PandaType = log.PandaState.PandaType +SafetyModel = car.CarParams.SafetyModel NUM_JOBS = int(os.environ.get("NUM_JOBS", "1")) JOB_ID = int(os.environ.get("JOB_ID", "0")) @@ -63,8 +67,6 @@ def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: return test_cases -SKIP_ENV_VAR = "SKIP_LONG_TESTS" - class TestCarModelBase(unittest.TestCase): car_model: Optional[str] = None @@ -72,6 +74,7 @@ class TestCarModelBase(unittest.TestCase): ci: bool = True can_msgs: List[capnp.lib.capnp._DynamicStructReader] + elm_frame: Optional[int] @unittest.skipIf(SKIP_ENV_VAR in os.environ, f"Long running test skipped. Unset {SKIP_ENV_VAR} to run") @classmethod @@ -107,6 +110,7 @@ class TestCarModelBase(unittest.TestCase): car_fw = [] can_msgs = [] + cls.elm_frame = None fingerprint = defaultdict(dict) experimental_long = False enabled_toggle = True @@ -132,6 +136,16 @@ class TestCarModelBase(unittest.TestCase): if param.key == 'OpenpilotEnabledToggle': enabled_toggle = param.value.strip(b'\x00') == b'1' + # Log which can frame the panda safety mode left ELM327, for CAN validity checks + if msg.which() == 'pandaStates': + for ps in msg.pandaStates: + if cls.elm_frame is None and ps.safetyModel != SafetyModel.elm327: + cls.elm_frame = len(can_msgs) + + elif msg.which() == 'pandaStateDEPRECATED': + if cls.elm_frame is None and msg.pandaStateDEPRECATED.safetyModel != SafetyModel.elm327: + cls.elm_frame = len(can_msgs) + if len(can_msgs) > int(50 / DT_CTRL): break else: @@ -152,9 +166,11 @@ class TestCarModelBase(unittest.TestCase): del cls.can_msgs def setUp(self): - self.CI = self.CarInterface(self.CP, self.CarController, self.CarState) + self.CI = self.CarInterface(self.CP.copy(), self.CarController, self.CarState) assert self.CI + Params().put_bool("OpenpilotEnabledToggle", self.openpilot_enabled) + # TODO: check safetyModel is in release panda build self.safety = libpanda_py.libpanda @@ -176,8 +192,6 @@ class TestCarModelBase(unittest.TestCase): self.assertTrue(len(self.CP.lateralTuning.pid.kpV)) elif tuning == 'torque': self.assertTrue(self.CP.lateralTuning.torque.kf > 0) - elif tuning == 'indi': - self.assertTrue(len(self.CP.lateralTuning.indi.outerLoopGainV)) else: raise Exception("unknown tuning") @@ -206,8 +220,10 @@ class TestCarModelBase(unittest.TestCase): RI = RadarInterface(self.CP) assert RI + # Since OBD port is multiplexed to bus 1 (commonly radar bus) while fingerprinting, + # start parsing CAN messages after we've left ELM mode and can expect CAN traffic error_cnt = 0 - for i, msg in enumerate(self.can_msgs): + for i, msg in enumerate(self.can_msgs[self.elm_frame:]): rr = RI.update((msg.as_builder().to_bytes(),)) if rr is not None and i > 50: error_cnt += car.RadarData.Error.canError in rr.errors @@ -240,9 +256,9 @@ class TestCarModelBase(unittest.TestCase): if t > 1e6: self.assertTrue(self.safety.addr_checks_valid()) - # No need to check relay malfunction on disabled routes (relay closed) or for reasonable fingerprinting time - # TODO: detect when relay has flipped to properly check relay malfunction - if self.openpilot_enabled and t > 5e6: + # No need to check relay malfunction on disabled routes (relay closed), + # or before fingerprinting is done (1s of tolerance to exit silent mode) + if self.openpilot_enabled and t / 1e4 > (self.elm_frame + 100): self.assertFalse(self.safety.get_relay_malfunction()) else: self.safety.set_relay_malfunction(False) @@ -304,6 +320,8 @@ class TestCarModelBase(unittest.TestCase): controls_allowed_prev = False CS_prev = car.CarState.new_message() checks = defaultdict(lambda: 0) + controlsd = Controls(CI=self.CI) + controlsd.initialized = True for idx, can in enumerate(self.can_msgs): CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) for msg in filter(lambda m: m.src in range(64), can.can): @@ -348,7 +366,10 @@ class TestCarModelBase(unittest.TestCase): checks['cruiseState'] += CS.cruiseState.enabled != self.safety.get_cruise_engaged_prev() else: # Check for enable events on rising edge of controls allowed - button_enable = any(evt.enable for evt in CS.events) + controlsd.update_events(CS) + controlsd.CS_prev = CS + button_enable = (any(evt.enable for evt in CS.events) and + not any(evt == EventName.pedalPressed for evt in controlsd.events.names)) mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev) checks['controlsAllowed'] += mismatch controls_allowed_prev = self.safety.get_controls_allowed() diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index fa4bb6a171..fc39cd76cb 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -10,6 +10,7 @@ NISSAN ROGUE 2019: [.nan, 1.5, .nan] # New subarus angle based controllers SUBARU FORESTER 2022: [.nan, 3.0, .nan] SUBARU OUTBACK 7TH GEN: [.nan, 3.0, .nan] +SUBARU ASCENT 2023: [.nan, 3.0, .nan] # Toyota LTA also has torque TOYOTA RAV4 2023: [.nan, 3.0, .nan] @@ -55,6 +56,11 @@ GENESIS GV80 2023: [2.5, 2.5, 0.1] KIA CARNIVAL 4TH GEN: [1.75, 1.75, 0.15] GMC ACADIA DENALI 2018: [1.6, 1.6, 0.2] LEXUS IS 2023: [2.0, 2.0, 0.1] +KIA SORENTO HYBRID 4TH GEN: [2.5, 2.5, 0.1] +HYUNDAI KONA ELECTRIC 2ND GEN: [2.5, 2.5, 0.1] +HYUNDAI IONIQ 6 2023: [2.5, 2.5, 0.1] +HYUNDAI AZERA 6TH GEN: [1.8, 1.8, 0.1] +KIA K8 HYBRID 1ST GEN: [2.5, 2.5, 0.1] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index d79dbe8573..786fa05aab 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -34,7 +34,6 @@ HYUNDAI KONA ELECTRIC 2022: HYUNDAI KONA ELECTRIC 2019 HYUNDAI IONIQ HYBRID 2017-2019: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ HYBRID 2020-2022: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019 -HYUNDAI IONIQ 6 2023: HYUNDAI IONIQ 5 2022 HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019 HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 @@ -76,6 +75,8 @@ VOLKSWAGEN POLO 6TH GEN: VOLKSWAGEN GOLF 7TH GEN SEAT LEON 3RD GEN: VOLKSWAGEN GOLF 7TH GEN SEAT ATECA 1ST GEN: VOLKSWAGEN GOLF 7TH GEN +SUBARU CROSSTREK HYBRID 2020: SUBARU IMPREZA SPORT 2020 +SUBARU FORESTER HYBRID 2020: SUBARU IMPREZA SPORT 2020 SUBARU LEGACY 7TH GEN: SUBARU OUTBACK 6TH GEN # Old subarus don't have much data guessing it's like low torque impreza diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index d2558b5b29..2a02a57d86 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -1,12 +1,10 @@ from cereal import car -from common.numpy_fast import clip, interp -from selfdrive.car import apply_meas_steer_torque_limits, apply_std_steer_angle_limits, common_fault_avoidance, \ +from openpilot.common.numpy_fast import clip, interp +from openpilot.selfdrive.car import apply_meas_steer_torque_limits, apply_std_steer_angle_limits, common_fault_avoidance, \ create_gas_interceptor_command, make_can_msg -from selfdrive.car.toyota.toyotacan import create_steer_command, create_ui_command, \ - create_accel_command, create_acc_cancel_command, \ - create_fcw_command, create_lta_steer_command -from selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, TSS2_CAR, \ - MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams, \ +from openpilot.selfdrive.car.toyota import toyotacan +from openpilot.selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, TSS2_CAR, \ + MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams, ToyotaFlags, \ UNSUPPORTED_DSU_CAR from opendbc.can.packer import CANPacker @@ -85,13 +83,13 @@ class CarController: # toyota can trace shows this message at 42Hz, with counter adding alternatively 1 and 2; # sending it at 100Hz seem to allow a higher rate limit, as the rate limit seems imposed # on consecutive messages - can_sends.append(create_steer_command(self.packer, apply_steer, apply_steer_req)) + can_sends.append(toyotacan.create_steer_command(self.packer, apply_steer, apply_steer_req)) if self.frame % 2 == 0 and self.CP.carFingerprint in TSS2_CAR: lta_active = lat_active and self.CP.steerControlType == SteerControlType.angle full_torque_condition = (abs(CS.out.steeringTorqueEps) < self.params.STEER_MAX and abs(CS.out.steeringTorque) < MAX_DRIVER_TORQUE_ALLOWANCE) setme_x64 = 100 if lta_active and full_torque_condition else 0 - can_sends.append(create_lta_steer_command(self.packer, self.last_angle, lta_active, self.frame // 2, setme_x64)) + can_sends.append(toyotacan.create_lta_steer_command(self.packer, self.last_angle, lta_active, self.frame // 2, setme_x64)) # *** gas and brake *** if self.CP.enableGasInterceptor and CC.longActive: @@ -125,18 +123,22 @@ class CarController: self.last_standstill = CS.out.standstill + # handle UI messages + fcw_alert = hud_control.visualAlert == VisualAlert.fcw + steer_alert = hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw) + # we can spam can to cancel the system even if we are using lat only control if (self.frame % 3 == 0 and self.CP.openpilotLongitudinalControl) or pcm_cancel_cmd: lead = hud_control.leadVisible or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged # Lexus IS uses a different cancellation message if pcm_cancel_cmd and self.CP.carFingerprint in UNSUPPORTED_DSU_CAR: - can_sends.append(create_acc_cancel_command(self.packer)) + can_sends.append(toyotacan.create_acc_cancel_command(self.packer)) elif self.CP.openpilotLongitudinalControl: - can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type)) + can_sends.append(toyotacan.create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type, fcw_alert)) self.accel = pcm_accel_cmd else: - can_sends.append(create_accel_command(self.packer, 0, pcm_cancel_cmd, False, lead, CS.acc_type)) + can_sends.append(toyotacan.create_accel_command(self.packer, 0, pcm_cancel_cmd, False, lead, CS.acc_type, False)) if self.frame % 2 == 0 and self.CP.enableGasInterceptor and self.CP.openpilotLongitudinalControl: # send exactly zero if gas cmd is zero. Interceptor will send the max between read value and gas cmd. @@ -149,9 +151,6 @@ class CarController: # ui mesg is at 1Hz but we send asap if: # - there is something to display # - there is something to stop displaying - fcw_alert = hud_control.visualAlert == VisualAlert.fcw - steer_alert = hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw) - send_ui = False if ((fcw_alert or steer_alert) and not self.alert_active) or \ (not (fcw_alert or steer_alert) and self.alert_active): @@ -162,18 +161,22 @@ class CarController: send_ui = True if self.frame % 20 == 0 or send_ui: - can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible, - hud_control.rightLaneVisible, hud_control.leftLaneDepart, - hud_control.rightLaneDepart, CC.enabled, CS.lkas_hud)) + can_sends.append(toyotacan.create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible, + hud_control.rightLaneVisible, hud_control.leftLaneDepart, + hud_control.rightLaneDepart, CC.enabled, CS.lkas_hud)) - if (self.frame % 100 == 0 or send_ui) and self.CP.enableDsu: - can_sends.append(create_fcw_command(self.packer, fcw_alert)) + if (self.frame % 100 == 0 or send_ui) and (self.CP.enableDsu or self.CP.flags & ToyotaFlags.DISABLE_RADAR.value): + can_sends.append(toyotacan.create_fcw_command(self.packer, fcw_alert)) # *** static msgs *** for addr, cars, bus, fr_step, vl in STATIC_DSU_MSGS: if self.frame % fr_step == 0 and self.CP.enableDsu and self.CP.carFingerprint in cars: can_sends.append(make_can_msg(addr, vl, bus)) + # keep radar disabled + if self.frame % 20 == 0 and self.CP.flags & ToyotaFlags.DISABLE_RADAR.value: + can_sends.append([0x750, 0, b"\x0F\x02\x3E\x00\x00\x00\x00\x00", 0]) + new_actuators = actuators.copy() new_actuators.steer = apply_steer / self.params.STEER_MAX new_actuators.steerOutputCan = apply_steer diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 8a75ab4277..076668fffb 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -1,14 +1,15 @@ import copy from cereal import car -from common.conversions import Conversions as CV -from common.numpy_fast import mean -from common.filter_simple import FirstOrderFilter -from common.realtime import DT_CTRL +from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import mean +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.realtime import DT_CTRL from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser -from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.toyota.values import ToyotaFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR +from openpilot.selfdrive.car.interfaces import CarStateBase +from openpilot.selfdrive.car.toyota.values import ToyotaFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, \ + TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR SteerControlType = car.CarParams.SteerControlType @@ -129,7 +130,7 @@ class CarState(CarStateBase): cp_acc = cp_cam if self.CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR) else cp - if self.CP.carFingerprint in (TSS2_CAR | RADAR_ACC_CAR): + if self.CP.carFingerprint in TSS2_CAR and not self.CP.flags & ToyotaFlags.DISABLE_RADAR.value: if not (self.CP.flags & ToyotaFlags.SMART_DSU.value): self.acc_type = cp_acc.vl["ACC_CONTROL"]["ACC_TYPE"] ret.stockFcw = bool(cp_acc.vl["PCS_HUD"]["FCW"]) @@ -152,7 +153,7 @@ class CarState(CarStateBase): ret.genericToggle = bool(cp.vl["LIGHT_STALK"]["AUTO_HIGH_BEAM"]) ret.espDisabled = cp.vl["ESP_CONTROL"]["TC_DISABLED"] != 0 - if not self.CP.enableDsu: + if not self.CP.enableDsu and not self.CP.flags & ToyotaFlags.DISABLE_RADAR.value: ret.stockAeb = bool(cp_acc.vl["PRE_COLLISION"]["PRECOLLISION_ACTIVE"] and cp_acc.vl["PRE_COLLISION"]["FORCE"] < -1e-5) if self.CP.enableBsm: @@ -200,7 +201,7 @@ class CarState(CarStateBase): if CP.enableBsm: messages.append(("BSM", 1)) - if CP.carFingerprint in RADAR_ACC_CAR: + if CP.carFingerprint in RADAR_ACC_CAR and not CP.flags & ToyotaFlags.DISABLE_RADAR.value: if not CP.flags & ToyotaFlags.SMART_DSU.value: messages += [ ("ACC_CONTROL", 33), @@ -209,7 +210,7 @@ class CarState(CarStateBase): ("PCS_HUD", 1), ] - if CP.carFingerprint not in (TSS2_CAR - RADAR_ACC_CAR) and not CP.enableDsu: + if CP.carFingerprint not in (TSS2_CAR - RADAR_ACC_CAR) and not CP.enableDsu and not CP.flags & ToyotaFlags.DISABLE_RADAR.value: messages += [ ("PRE_COLLISION", 33), ] diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 15ec919cd0..3dcd736991 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -1,11 +1,12 @@ -#!/usr/bin/env python3 from cereal import car -from common.conversions import Conversions as CV +from openpilot.common.conversions import Conversions as CV from panda import Panda -from selfdrive.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, \ +from panda.python import uds +from openpilot.selfdrive.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, \ MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, UNSUPPORTED_DSU_CAR, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR -from selfdrive.car import get_safety_config -from selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car.disable_ecu import disable_ecu +from openpilot.selfdrive.car.interfaces import CarInterfaceBase EventName = car.CarEvent.EventName SteerControlType = car.CarParams.SteerControlType @@ -197,10 +198,14 @@ class CarInterface(CarInterfaceBase): ret.mass = 4305. * CV.LB_TO_KG ret.centerToFront = ret.wheelbase * 0.44 + + # TODO: Some TSS-P platforms have BSM, but are flipped based on region or driving direction. + # Detect flipped signals and enable for C-HR and others ret.enableBsm = 0x3F6 in fingerprint[0] and candidate in TSS2_CAR # Detect smartDSU, which intercepts ACC_CMD from the DSU (or radar) allowing openpilot to send it - if 0x2FF in fingerprint[0]: + # 0x2AA is sent by a similar device which intercepts the radar instead of DSU on NO_DSU_CARs + if 0x2FF in fingerprint[0] or (0x2AA in fingerprint[0] and candidate in NO_DSU_CAR): ret.flags |= ToyotaFlags.SMART_DSU.value # No radar dbc for cars without DSU which are not TSS 2.0 @@ -214,11 +219,17 @@ class CarInterface(CarInterfaceBase): ret.enableGasInterceptor = 0x201 in fingerprint[0] # if the smartDSU is detected, openpilot can send ACC_CONTROL and the smartDSU will block it from the DSU or radar. - # since we don't yet parse radar on TSS2 radar-based ACC cars, gate longitudinal behind experimental toggle + # since we don't yet parse radar on TSS2/TSS-P radar-based ACC cars, gate longitudinal behind experimental toggle use_sdsu = bool(ret.flags & ToyotaFlags.SMART_DSU) - if candidate in RADAR_ACC_CAR: + if candidate in (RADAR_ACC_CAR | NO_DSU_CAR): ret.experimentalLongitudinalAvailable = use_sdsu - use_sdsu = use_sdsu and experimental_long + + if not use_sdsu: + # Disabling radar is only supported on TSS2 radar-ACC cars + if experimental_long and candidate in RADAR_ACC_CAR and False: # TODO: disabling radar isn't supported yet + ret.flags |= ToyotaFlags.DISABLE_RADAR.value + else: + use_sdsu = use_sdsu and experimental_long # openpilot longitudinal enabled by default: # - non-(TSS2 radar ACC cars) w/ smartDSU installed @@ -226,7 +237,9 @@ class CarInterface(CarInterfaceBase): # - TSS2 cars with camera sending ACC_CONTROL where we can block it # openpilot longitudinal behind experimental long toggle: # - TSS2 radar ACC cars w/ smartDSU installed - ret.openpilotLongitudinalControl = use_sdsu or ret.enableDsu or candidate in (TSS2_CAR - RADAR_ACC_CAR) + # - TSS2 radar ACC cars w/o smartDSU installed (disables radar) + # - TSS-P DSU-less cars w/ CAN filter installed (no radar parser yet) + ret.openpilotLongitudinalControl = use_sdsu or ret.enableDsu or candidate in (TSS2_CAR - RADAR_ACC_CAR) or bool(ret.flags & ToyotaFlags.DISABLE_RADAR.value) ret.autoResumeSng = ret.openpilotLongitudinalControl and candidate in NO_STOP_TIMER_CAR if not ret.openpilotLongitudinalControl: @@ -261,6 +274,13 @@ class CarInterface(CarInterfaceBase): return ret + @staticmethod + def init(CP, logcan, sendcan): + # disable radar if alpha longitudinal toggled on radar-ACC car without CAN filter/smartDSU + if CP.flags & ToyotaFlags.DISABLE_RADAR.value: + communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL]) + disable_ecu(logcan, sendcan, bus=0, addr=0x750, sub_addr=0xf, com_cont_req=communication_control) + # returns a car.CarState def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) diff --git a/selfdrive/car/toyota/radar_interface.py b/selfdrive/car/toyota/radar_interface.py index 378178f5c8..fae6eecaf6 100755 --- a/selfdrive/car/toyota/radar_interface.py +++ b/selfdrive/car/toyota/radar_interface.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 -from collections import defaultdict - from opendbc.can.parser import CANParser from cereal import car -from selfdrive.car.toyota.values import DBC, TSS2_CAR -from selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.toyota.values import DBC, TSS2_CAR +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase def _create_radar_can_parser(car_fingerprint): @@ -38,47 +36,34 @@ class RadarInterface(RadarInterfaceBase): self.rcp = None if CP.radarUnavailable else _create_radar_can_parser(CP.carFingerprint) self.trigger_msg = self.RADAR_B_MSGS[-1] - self.updated_values = defaultdict(lambda: defaultdict(list)) + self.updated_messages = set() def update(self, can_strings): if self.rcp is None: - return None + return super().update(None) - addresses = self.rcp.update_strings(can_strings) - for addr in addresses: - vals_dict = self.rcp.vl_all[addr] - for sig_name, vals in vals_dict.items(): - self.updated_values[addr][sig_name].extend(vals) + vls = self.rcp.update_strings(can_strings) + self.updated_messages.update(vls) - if self.trigger_msg not in self.updated_values: + if self.trigger_msg not in self.updated_messages: return None - radar_data = self._radar_msg_from_buffer(self.updated_values, self.rcp.can_valid) - self.updated_values.clear() + rr = self._update(self.updated_messages) + self.updated_messages.clear() - return radar_data + return rr - def _radar_msg_from_buffer(self, updated_values, can_valid): + def _update(self, updated_messages): ret = car.RadarData.new_message() errors = [] - if not can_valid: + if not self.rcp.can_valid: errors.append("canError") ret.errors = errors - for ii in sorted(updated_values): - if ii not in self.RADAR_A_MSGS: - continue - - radar_a_msgs = updated_values[ii] - radar_b_msgs = updated_values[ii+16] + for ii in sorted(updated_messages): + if ii in self.RADAR_A_MSGS: + cpt = self.rcp.vl[ii] - n_vals_per_addr = len(list(radar_a_msgs.values())[0]) - cpts = [ - {k: v[i] for k, v in radar_a_msgs.items()} - for i in range(n_vals_per_addr) - ] - - for index, cpt in enumerate(cpts): if cpt['LONG_DIST'] >= 255 or cpt['NEW_TRACK']: self.valid_cnt[ii] = 0 # reset counter if cpt['VALID'] and cpt['LONG_DIST'] < 255: @@ -86,15 +71,11 @@ class RadarInterface(RadarInterfaceBase): else: self.valid_cnt[ii] = max(self.valid_cnt[ii] - 1, 0) - n_b_scores = len(radar_b_msgs['SCORE']) - if n_b_scores > 0: - score_index = min(index, n_b_scores - 1) - score = radar_b_msgs['SCORE'][score_index] - else: - score = None + score = self.rcp.vl[ii+16]['SCORE'] + # print ii, self.valid_cnt[ii], score, cpt['VALID'], cpt['LONG_DIST'], cpt['LAT_DIST'] # radar point only valid if it's a valid measurement and score is above 50 - if cpt['VALID'] or (score and score > 50 and cpt['LONG_DIST'] < 255 and self.valid_cnt[ii] > 0): + if cpt['VALID'] or (score > 50 and cpt['LONG_DIST'] < 255 and self.valid_cnt[ii] > 0): if ii not in self.pts or cpt['NEW_TRACK']: self.pts[ii] = car.RadarData.RadarPoint.new_message() self.pts[ii].trackId = self.track_id diff --git a/selfdrive/car/toyota/tests/print_platform_codes.py b/selfdrive/car/toyota/tests/print_platform_codes.py new file mode 100755 index 0000000000..94badc5cde --- /dev/null +++ b/selfdrive/car/toyota/tests/print_platform_codes.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +from cereal import car +from openpilot.selfdrive.car.toyota.values import FW_VERSIONS, PLATFORM_CODE_ECUS, get_platform_codes + +Ecu = car.CarParams.Ecu +ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} + +if __name__ == "__main__": + for car_model, ecus in FW_VERSIONS.items(): + print() + print(car_model) + for ecu in sorted(ecus, key=lambda x: int(x[0])): + if ecu[0] not in PLATFORM_CODE_ECUS: + continue + + platform_codes = get_platform_codes(ecus[ecu]) + codes = {code for code, _ in platform_codes} + dates = {date for _, date in platform_codes if date is not None} + print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):') + print(f' Codes: {codes}') + print(f' Versions: {dates}') diff --git a/selfdrive/car/toyota/tests/test_toyota.py b/selfdrive/car/toyota/tests/test_toyota.py index 46e3fc2d27..0c503ebbf0 100755 --- a/selfdrive/car/toyota/tests/test_toyota.py +++ b/selfdrive/car/toyota/tests/test_toyota.py @@ -1,15 +1,19 @@ #!/usr/bin/env python3 -from cereal import car +from hypothesis import given, settings, strategies as st import unittest -from selfdrive.car.toyota.values import CAR, DBC, TSS2_CAR, ANGLE_CONTROL_CAR, FW_VERSIONS +from cereal import car +from openpilot.selfdrive.car.toyota.values import CAR, DBC, TSS2_CAR, ANGLE_CONTROL_CAR, RADAR_ACC_CAR, FW_VERSIONS, \ + get_platform_codes Ecu = car.CarParams.Ecu +ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} class TestToyotaInterfaces(unittest.TestCase): - def test_angle_car_set(self): + def test_car_sets(self): self.assertTrue(len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0) + self.assertTrue(len(RADAR_ACC_CAR - TSS2_CAR) == 0) def test_tss2_dbc(self): # We make some assumptions about TSS2 platforms, @@ -38,5 +42,22 @@ class TestToyotaInterfaces(unittest.TestCase): self.assertIn(Ecu.eps, present_ecus) +class TestToyotaFingerprint(unittest.TestCase): + @settings(max_examples=100) + @given(data=st.data()) + def test_platform_codes_fuzzy_fw(self, data): + fw_strategy = st.lists(st.binary()) + fws = data.draw(fw_strategy) + get_platform_codes(fws) + + def test_fw_pattern(self): + """Asserts all ECUs can be parsed""" + for ecus in FW_VERSIONS.values(): + for fws in ecus.values(): + for fw in fws: + ret = get_platform_codes([fw]) + self.assertTrue(len(ret)) + + if __name__ == "__main__": unittest.main() diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index 0c3330efa2..ed0237c1be 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -27,7 +27,7 @@ def create_lta_steer_command(packer, steer_angle, steer_req, frame, setme_x64): return packer.make_can_msg("STEERING_LTA", 0, values) -def create_accel_command(packer, accel, pcm_cancel, standstill_req, lead, acc_type): +def create_accel_command(packer, accel, pcm_cancel, standstill_req, lead, acc_type, fcw_alert): # TODO: find the exact canceling bit that does not create a chime values = { "ACCEL_CMD": accel, @@ -38,6 +38,7 @@ def create_accel_command(packer, accel, pcm_cancel, standstill_req, lead, acc_ty "RELEASE_STANDSTILL": not standstill_req, "CANCEL_REQ": pcm_cancel, "ALLOW_LONG_PRESS": 1, + "ACC_CUT_IN": fcw_alert, # only shown when ACC enabled } return packer.make_can_msg("ACC_CONTROL", 0, values) @@ -56,7 +57,7 @@ def create_acc_cancel_command(packer): def create_fcw_command(packer, fcw): values = { - "PCS_INDICATOR": 1, + "PCS_INDICATOR": 1, # PCS turned off "FCW": fcw, "SET_ME_X20": 0x20, "SET_ME_X10": 0x10, diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 21c9d7f17e..690b60af25 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -1,13 +1,14 @@ +import re from collections import defaultdict from dataclasses import dataclass, field from enum import Enum, IntFlag -from typing import Dict, List, Union +from typing import Dict, List, Set, Tuple, Union from cereal import car -from common.conversions import Conversions as CV -from selfdrive.car import AngleRateLimit, dbc_dict -from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, CarParts, CarHarness -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car import AngleRateLimit, dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, CarParts, CarHarness +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu MIN_ACC_SPEED = 19. * CV.MPH_TO_MS @@ -42,6 +43,7 @@ class CarControllerParams: class ToyotaFlags(IntFlag): HYBRID = 1 SMART_DSU = 2 + DISABLE_RADAR = 4 class CAR: @@ -233,6 +235,71 @@ STATIC_DSU_MSGS = [ CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_ESH, CAR.LEXUS_RX, CAR.PRIUS_V), 0, 100, b'\x0c\x00\x00\x00\x00\x00\x00\x00'), ] + +def get_platform_codes(fw_versions: List[bytes]) -> Set[Tuple[bytes, bytes]]: + codes = set() # (Optional[part]-platform-major_version, minor_version) + for fw in fw_versions: + # FW versions returned from UDS queries can return multiple fields/chunks of data (different ECU calibrations, different data?) + # and are prefixed with a byte that describes how many chunks of data there are. + # But FW returned from KWP requires querying of each sub-data id and does not have a length prefix. + + length_code = 1 + length_code_match = FW_LEN_CODE.search(fw) + if length_code_match is not None: + length_code = length_code_match.group()[0] + fw = fw[1:] + + # fw length should be multiple of 16 bytes (per chunk, even if no length code), skip parsing if unexpected length + if length_code * FW_CHUNK_LEN != len(fw): + continue + + chunks = [fw[FW_CHUNK_LEN * i:FW_CHUNK_LEN * i + FW_CHUNK_LEN].strip(b'\x00 ') for i in range(length_code)] + + # only first is considered for now since second is commonly shared (TODO: understand that) + first_chunk = chunks[0] + if len(first_chunk) == 8: + # TODO: no part number, but some short chunks have it in subsequent chunks + fw_match = SHORT_FW_PATTERN.search(first_chunk) + if fw_match is not None: + platform, major_version, sub_version = fw_match.groups() + # codes.add((platform + b'-' + major_version, sub_version)) + codes.add((b'-'.join((platform, major_version)), sub_version)) + + elif len(first_chunk) == 10: + fw_match = MEDIUM_FW_PATTERN.search(first_chunk) + if fw_match is not None: + part, platform, major_version, sub_version = fw_match.groups() + codes.add((b'-'.join((part, platform, major_version)), sub_version)) + + elif len(first_chunk) == 12: + fw_match = LONG_FW_PATTERN.search(first_chunk) + if fw_match is not None: + part, platform, major_version, sub_version = fw_match.groups() + codes.add((b'-'.join((part, platform, major_version)), sub_version)) + + return codes + + +# Regex patterns for parsing more general platform-specific identifiers from FW versions. +# - Part number: Toyota part number (usually last character needs to be ignored to find a match). +# - Platform: usually multiple codes per an openpilot platform, however this has the less variability and +# is usually shared across ECUs and model years signifying this describes something about the specific platform. +# - Major version: second least variable part of the FW version. Seen splitting cars by model year such as RAV4 2022/2023 and Prius. +# It is important to note that these aren't always consecutive, for example: +# Prius TSS-P has these major versions over 16 FW: 2, 3, 4, 6, 8 while Prius TSS2 has: 5 +# - Sub version: exclusive to major version, but shared with other cars. Should only be used for further filtering, +# more exploration is needed. +SHORT_FW_PATTERN = re.compile(b'(?P[A-Z0-9]{2})(?P[A-Z0-9]{2})(?P[A-Z0-9]{4})') +MEDIUM_FW_PATTERN = re.compile(b'(?P[A-Z0-9]{5})(?P[A-Z0-9]{2})(?P[A-Z0-9]{1})(?P[A-Z0-9]{2})') +LONG_FW_PATTERN = re.compile(b'(?P[A-Z0-9]{5})(?P[A-Z0-9]{2})(?P[A-Z0-9]{2})(?P[A-Z0-9]{3})') +FW_LEN_CODE = re.compile(b'^[\x01-\x05]') # 5 chunks max. highest seen is 3 chunks, 16 bytes each +FW_CHUNK_LEN = 16 + +# List of ECUs expected to have platform codes +# TODO: use hybrid ECU, splits many similar ICE and hybrid variants +PLATFORM_CODE_ECUS = [Ecu.abs, Ecu.engine, Ecu.eps, Ecu.dsu, Ecu.fwdCamera, Ecu.fwdRadar] + + # Some ECUs that use KWP2000 have their FW versions on non-standard data identifiers. # Toyota diagnostic software first gets the supported data ids, then queries them one by one. # For example, sends: 0x1a8800, receives: 0x1a8800010203, queries: 0x1a8801, 0x1a8802, 0x1a8803 @@ -640,12 +707,14 @@ FW_VERSIONS = { (Ecu.engine, 0x700, None): [ b'\x018966306Q6000\x00\x00\x00\x00', b'\x018966306Q7000\x00\x00\x00\x00', + b'\x018966306T0000\x00\x00\x00\x00', b'\x018966306V1000\x00\x00\x00\x00', b'\x01896633T20000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 15): [ b'\x018821F6201200\x00\x00\x00\x00', b'\x018821F6201300\x00\x00\x00\x00', + b'\x018821F6201400\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 109): [ b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index cf1c25e851..28edf157ae 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -3,9 +3,9 @@ import re import cereal.messaging as messaging from panda.python.uds import get_rx_addr_for_tx_addr, FUNCTIONAL_ADDRS -from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery -from selfdrive.car.fw_query_definitions import StdQueries -from system.swaglog import cloudlog +from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery +from openpilot.selfdrive.car.fw_query_definitions import StdQueries +from openpilot.system.swaglog import cloudlog VIN_UNKNOWN = "0" * 17 VIN_RE = "[A-HJ-NPR-Z0-9]{17}" diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index ddcf80f006..e7a7f2a998 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -1,11 +1,11 @@ from cereal import car from opendbc.can.packer import CANPacker -from common.numpy_fast import clip -from common.conversions import Conversions as CV -from common.realtime import DT_CTRL -from selfdrive.car import apply_driver_steer_torque_limits -from selfdrive.car.volkswagen import mqbcan, pqcan -from selfdrive.car.volkswagen.values import CANBUS, PQ_CARS, CarControllerParams +from openpilot.common.numpy_fast import clip +from openpilot.common.conversions import Conversions as CV +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car import apply_driver_steer_torque_limits +from openpilot.selfdrive.car.volkswagen import mqbcan, pqcan +from openpilot.selfdrive.car.volkswagen.values import CANBUS, PQ_CARS, CarControllerParams VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 35f7e8bf67..cbe8918d48 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -1,9 +1,9 @@ import numpy as np from cereal import car -from common.conversions import Conversions as CV -from selfdrive.car.interfaces import CarStateBase +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser -from selfdrive.car.volkswagen.values import DBC, CANBUS, PQ_CARS, NetworkLocation, TransmissionType, GearShifter, \ +from openpilot.selfdrive.car.volkswagen.values import DBC, CANBUS, PQ_CARS, NetworkLocation, TransmissionType, GearShifter, \ CarControllerParams diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 66b6239cb3..d5377453c1 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,9 +1,9 @@ from cereal import car from panda import Panda -from common.conversions import Conversions as CV -from selfdrive.car import get_safety_config -from selfdrive.car.interfaces import CarInterfaceBase -from selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName diff --git a/selfdrive/car/volkswagen/radar_interface.py b/selfdrive/car/volkswagen/radar_interface.py index b2f7651136..e654bd61fd 100644 --- a/selfdrive/car/volkswagen/radar_interface.py +++ b/selfdrive/car/volkswagen/radar_interface.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -from selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.interfaces import RadarInterfaceBase class RadarInterface(RadarInterfaceBase): pass diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py old mode 100755 new mode 100644 index bc129f79cc..a5ed3744a5 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -6,10 +6,10 @@ from typing import Dict, List, Union from cereal import car from panda.python import uds from opendbc.can.can_define import CANDefine -from selfdrive.car import dbc_dict -from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ +from openpilot.selfdrive.car import dbc_dict +from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ Device -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu NetworkLocation = car.CarParams.NetworkLocation @@ -265,7 +265,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]), CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-22"), CAR.SKODA_OCTAVIA_MK3: [ - VWCarInfo("Škoda Octavia 2015, 2018-19"), + VWCarInfo("Škoda Octavia 2015-19"), VWCarInfo("Škoda Octavia RS 2016"), ], } @@ -396,6 +396,7 @@ FW_VERSIONS = { }, CAR.CRAFTER_MK2: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704L906056BP\xf1\x894729', b'\xf1\x8704L906056EK\xf1\x896391', b'\xf1\x8705L906023BC\xf1\x892688', ], @@ -403,15 +404,18 @@ FW_VERSIONS = { #(Ecu.transmission, 0x7e1, None): [ #], (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655AL\xf1\x890505\xf1\x82\x0e1411001413001203151311031100', b'\xf1\x873Q0959655BG\xf1\x890703\xf1\x82\x0e16120016130012051G1313052900', b'\xf1\x875QF959655AS\xf1\x890755\xf1\x82\x1315140015150011111100050200--1311120749', ], (Ecu.eps, 0x712, None): [ + b'\xf1\x872N0909143D\x00\xf1\x897010\xf1\x82\x05183AZ306A2', b'\xf1\x872N0909143E \xf1\x897021\xf1\x82\x05163AZ306A2', b'\xf1\x872N0909144K \xf1\x897045\xf1\x82\x05233AZ810A2', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572J \xf1\x890156', b'\xf1\x872Q0907572M \xf1\x890233', ], }, @@ -461,6 +465,7 @@ FW_VERSIONS = { b'\xf1\x878V0906259K \xf1\x890003', b'\xf1\x878V0906259P \xf1\x890001', b'\xf1\x878V0906259Q \xf1\x890002', + b'\xf1\x878V0906259R \xf1\x890002', b'\xf1\x878V0906264F \xf1\x890003', b'\xf1\x878V0906264L \xf1\x890002', b'\xf1\x878V0906264M \xf1\x890001', @@ -502,6 +507,7 @@ FW_VERSIONS = { b'\xf1\x870GC300012A \xf1\x891401', b'\xf1\x870GC300012A \xf1\x891403', b'\xf1\x870GC300014B \xf1\x892401', + b'\xf1\x870GC300014B \xf1\x892403', b'\xf1\x870GC300014B \xf1\x892405', b'\xf1\x870GC300020G \xf1\x892401', b'\xf1\x870GC300020G \xf1\x892403', @@ -521,6 +527,7 @@ FW_VERSIONS = { b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2251229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2252229333463100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2251229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2252229333463100', b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112', b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\x111413001113120006110417121A101A9113', @@ -547,6 +554,7 @@ FW_VERSIONS = { b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571A0J714A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571A0JA15A1', b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A01A18A1', + b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A02A16A1', b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A0JA16A1', b'\xf1\x873QM909144 \xf1\x895072\xf1\x82\x0571A01714A1', b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820519A9040203', @@ -814,11 +822,13 @@ FW_VERSIONS = { b'\xf1\x875NA906259H \xf1\x890002', b'\xf1\x875NA907115E \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890005', + b'\xf1\x875NA907115J \xf1\x890002', b'\xf1\x8783A907115B \xf1\x890005', b'\xf1\x8783A907115F \xf1\x890002', b'\xf1\x8783A907115G \xf1\x890001', b'\xf1\x8783A907115K \xf1\x890001', b'\xf1\x8704E906024AP\xf1\x891461', + b'\xf1\x8783A907115 \xf1\x890007', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158DT\xf1\x893698', @@ -836,12 +846,14 @@ FW_VERSIONS = { b'\xf1\x870DL300013G \xf1\x892120', b'\xf1\x870DL300014C \xf1\x893703', b'\xf1\x870DD300046K \xf1\x892302', + b'\xf1\x870GC300013P \xf1\x892401', ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\02331310031333334313132573732379333313100', b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100', b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1331310031333334313132013730379333423100', b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02316143231313500314641011750179333423100', + b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1312110031333300314240013750379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02312110031333300314240583752379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02331310031333336313140013950399333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140013750379333423100', @@ -849,6 +861,7 @@ FW_VERSIONS = { b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1316143231313500314647021750179333613100', b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x1331310031333300314240024050409333613100', b'\xf1\x875Q0959655CD\xf1\x890421\xf1\x82\x13123112313333003145406F6154619333613100', + b'\xf1\x875Q0959655AG\xf1\x890338\xf1\x82\x1316143231313500314617011730179333423100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603', @@ -863,6 +876,7 @@ FW_VERSIONS = { b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A60804A1', b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002SA6092SOM', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6017A00', + b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60804A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572AA\xf1\x890396', @@ -1254,6 +1268,7 @@ FW_VERSIONS = { b'\xf1\x8704E906027HD\xf1\x893742', b'\xf1\x8704E906027MH\xf1\x894786', b'\xf1\x8704L906021DT\xf1\x898127', + b'\xf1\x8704L906026BP\xf1\x897608', b'\xf1\x8704L906026BS\xf1\x891541', b'\xf1\x875G0906259C \xf1\x890002', ], @@ -1263,11 +1278,13 @@ FW_VERSIONS = { b'\xf1\x870CW300043B \xf1\x891601', b'\xf1\x870CW300043P \xf1\x891605', b'\xf1\x870D9300041C \xf1\x894936', + b'\xf1\x870D9300041H \xf1\x895220', b'\xf1\x870D9300041J \xf1\x894902', b'\xf1\x870D9300041P \xf1\x894507', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AC\xf1\x890200\xf1\x82\r11120011100010022212110200', + b'\xf1\x873Q0959655AP\xf1\x890305\xf1\x82\r11110011110011213331312131', b'\xf1\x873Q0959655AQ\xf1\x890200\xf1\x82\r11120011100010312212113100', b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11120011100010022212110200', b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\0163221003221002105755331052100', @@ -1279,13 +1296,15 @@ FW_VERSIONS = { b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\00566A01513A1', b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\00521T00403A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521T00403A1', - b'\xf1\x875QD909144E \xf1\x891081\xf1\x82\x0521T00503A1', b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521T00601A1', + b'\xf1\x875QD909144E \xf1\x891081\xf1\x82\x0521T00503A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\00101', b'\xf1\x875Q0907572J \xf1\x890654', + b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', b'\xf1\x875Q0907572P \xf1\x890682', b'\xf1\x875Q0907572R \xf1\x890771', ], @@ -1315,8 +1334,10 @@ FW_VERSIONS = { }, CAR.SKODA_SUPERB_MK3: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906027BT\xf1\x899042', b'\xf1\x8704L906026ET\xf1\x891343', b'\xf1\x8704L906026FP\xf1\x891196', + b'\xf1\x8704L906026KA\xf1\x896014', b'\xf1\x8704L906026KB\xf1\x894071', b'\xf1\x8704L906026KD\xf1\x894798', b'\xf1\x8704L906026MT\xf1\x893076', @@ -1327,12 +1348,14 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870CW300042H \xf1\x891601', + b'\xf1\x870CW300049Q \xf1\x890906', b'\xf1\x870D9300011T \xf1\x894801', b'\xf1\x870D9300012 \xf1\x894940', b'\xf1\x870D9300013A \xf1\x894905', b'\xf1\x870D9300014K \xf1\x895006', b'\xf1\x870D9300041H \xf1\x894905', b'\xf1\x870D9300043F \xf1\x895202', + b'\xf1\x870GC300013K \xf1\x892403', b'\xf1\x870GC300014M \xf1\x892801', b'\xf1\x870GC300019G \xf1\x892803', b'\xf1\x870GC300043 \xf1\x892301', @@ -1345,11 +1368,13 @@ FW_VERSIONS = { b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\02331310031313100313131013141319331413100', b'\xf1\x875Q0959655BK\xf1\x890336\xf1\x82\x1331310031313100313131013141319331413100', b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1331310031313100313151013141319331423100', + b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1331310031313100313151823143319331423100', b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1333310031313100313152025350539331463100', b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1333310031313100313152855372539331463100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820514UZ070203', + b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ050303', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ070303', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526UZ070505', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\00563UZ060700', diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 8084b18ff6..1dc9dd39f7 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -1,32 +1,33 @@ #!/usr/bin/env python3 import os import math +import time from typing import SupportsFloat from cereal import car, log -from common.numpy_fast import clip -from common.realtime import sec_since_boot, config_realtime_process, Priority, Ratekeeper, DT_CTRL -from common.profiler import Profiler -from common.params import Params, put_nonblocking, put_bool_nonblocking +from openpilot.common.numpy_fast import clip +from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL +from openpilot.common.profiler import Profiler +from openpilot.common.params import Params, put_nonblocking, put_bool_nonblocking import cereal.messaging as messaging from cereal.visionipc import VisionIpcClient, VisionStreamType -from common.conversions import Conversions as CV +from openpilot.common.conversions import Conversions as CV from panda import ALTERNATIVE_EXPERIENCE -from system.swaglog import cloudlog -from system.version import is_release_branch, get_short_branch -from selfdrive.boardd.boardd import can_list_to_can_capnp -from selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can -from selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET -from selfdrive.controls.lib.drive_helpers import VCruiseHelper, get_lag_adjusted_curvature -from selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED -from selfdrive.controls.lib.longcontrol import LongControl -from selfdrive.controls.lib.latcontrol_pid import LatControlPID -from selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD -from selfdrive.controls.lib.latcontrol_torque import LatControlTorque -from selfdrive.controls.lib.events import Events, ET -from selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert -from selfdrive.controls.lib.vehicle_model import VehicleModel -from system.hardware import HARDWARE +from openpilot.system.swaglog import cloudlog +from openpilot.system.version import is_release_branch, get_short_branch +from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp +from openpilot.selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can +from openpilot.selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET +from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, get_lag_adjusted_curvature +from openpilot.selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED +from openpilot.selfdrive.controls.lib.longcontrol import LongControl +from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID +from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD +from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque +from openpilot.selfdrive.controls.lib.events import Events, ET +from openpilot.selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert +from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel +from openpilot.system.hardware import HARDWARE SOFT_DISABLE_TIME = 3 # seconds LDW_MIN_SPEED = 31 * CV.MPH_TO_MS @@ -373,17 +374,19 @@ class Controls: else: self.logged_comm_issue = None - if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY): - self.events.add(EventName.vehicleModelInvalid) if not self.sm['lateralPlan'].mpcSolutionValid: self.events.add(EventName.plannerError) - if not (self.sm['liveParameters'].sensorValid or self.sm['liveLocationKalman'].sensorsOK) and not NOSENSOR: - if self.sm.frame > 5 / DT_CTRL: # Give locationd some time to receive all the inputs - self.events.add(EventName.sensorDataInvalid) if not self.sm['liveLocationKalman'].posenetOK: self.events.add(EventName.posenetInvalid) if not self.sm['liveLocationKalman'].deviceStable: self.events.add(EventName.deviceFalling) + if not (self.sm['liveParameters'].sensorValid or self.sm['liveLocationKalman'].sensorsOK): + if self.sm.frame > 5 / DT_CTRL: # Give locationd some time to receive sensor inputs + self.events.add(EventName.sensorDataInvalid) + if not self.sm['liveLocationKalman'].inputsOK: + self.events.add(EventName.locationdTemporaryError) + if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY): + self.events.add(EventName.paramsdTemporaryError) if not REPLAY: # Check for mismatch between openpilot and car's PCM @@ -419,8 +422,6 @@ class Controls: if self.sm['modelV2'].frameDropPerc > 20: self.events.add(EventName.modeldLagging) - if self.sm['liveLocationKalman'].excessiveResets: - self.events.add(EventName.localizerMalfunction) def data_sample(self): """Receive data from sockets and update carState""" @@ -743,7 +744,7 @@ class Controls: if not self.read_only and self.initialized: # send car controls over can - now_nanos = self.can_log_mono_time if REPLAY else int(sec_since_boot() * 1e9) + now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9) self.last_actuators, can_sends = self.CI.apply(CC, now_nanos) self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid)) CC.actuatorsOutput = self.last_actuators @@ -806,8 +807,6 @@ class Controls: controlsState.lateralControlState.pidState = lac_log elif lat_tuning == 'torque': controlsState.lateralControlState.torqueState = lac_log - elif lat_tuning == 'indi': - controlsState.lateralControlState.indiState = lac_log self.pm.send('controlsState', dat) @@ -842,7 +841,7 @@ class Controls: self.CC = CC def step(self): - start_time = sec_since_boot() + start_time = time.monotonic() self.prof.checkpoint("Ratekeeper", ignore=True) self.is_metric = self.params.get_bool("IsMetric") diff --git a/selfdrive/controls/lib/alertmanager.py b/selfdrive/controls/lib/alertmanager.py index f32e838333..6abcf4cbba 100644 --- a/selfdrive/controls/lib/alertmanager.py +++ b/selfdrive/controls/lib/alertmanager.py @@ -5,9 +5,9 @@ from collections import defaultdict from dataclasses import dataclass from typing import List, Dict, Optional -from common.basedir import BASEDIR -from common.params import Params -from selfdrive.controls.lib.events import Alert +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.selfdrive.controls.lib.events import Alert with open(os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json")) as f: diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index 4652b41c1c..d538035070 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -1,6 +1,6 @@ from cereal import log -from common.conversions import Conversions as CV -from common.realtime import DT_MDL +from openpilot.common.conversions import Conversions as CV +from openpilot.common.realtime import DT_MDL LaneChangeState = log.LateralPlan.LaneChangeState LaneChangeDirection = log.LateralPlan.LaneChangeDirection diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index ab65da2e1d..00916ddf78 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -1,10 +1,10 @@ import math from cereal import car, log -from common.conversions import Conversions as CV -from common.numpy_fast import clip, interp -from common.realtime import DT_MDL -from selfdrive.modeld.constants import T_IDXS +from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import clip, interp +from openpilot.common.realtime import DT_MDL +from openpilot.selfdrive.modeld.constants import T_IDXS # WARNING: this value was determined based on the model's training distribution, # model predictions above this speed can be unpredictable diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index 0230a5775c..81e55e6bbc 100755 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import math import os from enum import IntEnum @@ -5,10 +6,10 @@ from typing import Dict, Union, Callable, List, Optional from cereal import log, car import cereal.messaging as messaging -from common.conversions import Conversions as CV -from common.realtime import DT_CTRL -from selfdrive.locationd.calibrationd import MIN_SPEED_FILTER -from system.version import get_short_branch +from openpilot.common.conversions import Conversions as CV +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER +from openpilot.system.version import get_short_branch AlertSize = log.ControlsState.AlertSize AlertStatus = log.ControlsState.AlertStatus @@ -423,19 +424,6 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { # ********** events only containing alerts that display while engaged ********** - # openpilot tries to learn certain parameters about your car by observing - # how the car behaves to steering inputs from both human and openpilot driving. - # This includes: - # - steer ratio: gear ratio of the steering rack. Steering angle divided by tire angle - # - tire stiffness: how much grip your tires have - # - angle offset: most steering angle sensors are offset and measure a non zero angle when driving straight - # This alert is thrown when any of these values exceed a sanity check. This can be caused by - # bad alignment or bad sensor data. If this happens consistently consider creating an issue on GitHub - EventName.vehicleModelInvalid: { - ET.NO_ENTRY: NoEntryAlert("Vehicle Parameter Identification Failed"), - ET.SOFT_DISABLE: soft_disable_alert("Vehicle Parameter Identification Failed"), - }, - EventName.steerTempUnavailableSilent: { ET.WARNING: Alert( "Steering Temporarily Unavailable", @@ -575,11 +563,34 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { ET.PERMANENT: NormalPermanentAlert("GPS Malfunction", "Likely Hardware Issue"), }, - # When the GPS position and localizer diverge the localizer is reset to the - # current GPS position. This alert is thrown when the localizer is reset - # more often than expected. - EventName.localizerMalfunction: { - # ET.PERMANENT: NormalPermanentAlert("Sensor Malfunction", "Hardware Malfunction"), + EventName.locationdTemporaryError: { + ET.NO_ENTRY: NoEntryAlert("locationd Temporary Error"), + ET.SOFT_DISABLE: soft_disable_alert("locationd Temporary Error"), + }, + + EventName.locationdPermanentError: { + ET.NO_ENTRY: NoEntryAlert("locationd Permanent Error"), + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("locationd Permanent Error"), + ET.PERMANENT: NormalPermanentAlert("locationd Permanent Error"), + }, + + # openpilot tries to learn certain parameters about your car by observing + # how the car behaves to steering inputs from both human and openpilot driving. + # This includes: + # - steer ratio: gear ratio of the steering rack. Steering angle divided by tire angle + # - tire stiffness: how much grip your tires have + # - angle offset: most steering angle sensors are offset and measure a non zero angle when driving straight + # This alert is thrown when any of these values exceed a sanity check. This can be caused by + # bad alignment or bad sensor data. If this happens consistently consider creating an issue on GitHub + EventName.paramsdTemporaryError: { + ET.NO_ENTRY: NoEntryAlert("paramsd Temporary Error"), + ET.SOFT_DISABLE: soft_disable_alert("paramsd Temporary Error"), + }, + + EventName.paramsdPermanentError: { + ET.NO_ENTRY: NoEntryAlert("paramsd Permanent Error"), + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("paramsd Permanent Error"), + ET.PERMANENT: NormalPermanentAlert("paramsd Permanent Error"), }, # ********** events that affect controls state transitions ********** diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index d38959c560..30e1918442 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -1,7 +1,7 @@ from abc import abstractmethod, ABC -from common.numpy_fast import clip -from common.realtime import DT_CTRL +from openpilot.common.numpy_fast import clip +from openpilot.common.realtime import DT_CTRL MIN_LATERAL_CONTROL_SPEED = 0.3 # m/s diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index 9ed140d38e..b13d41e51c 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -1,7 +1,7 @@ import math from cereal import log -from selfdrive.controls.lib.latcontrol import LatControl +from openpilot.selfdrive.controls.lib.latcontrol import LatControl STEER_ANGLE_SATURATION_THRESHOLD = 2.5 # Degrees diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index 6696d2e304..c41130af95 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -1,8 +1,8 @@ import math from cereal import log -from selfdrive.controls.lib.latcontrol import LatControl -from selfdrive.controls.lib.pid import PIDController +from openpilot.selfdrive.controls.lib.latcontrol import LatControl +from openpilot.selfdrive.controls.lib.pid import PIDController class LatControlPID(LatControl): diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 6550b19227..2c77630632 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -1,10 +1,10 @@ import math from cereal import log -from common.numpy_fast import interp -from selfdrive.controls.lib.latcontrol import LatControl -from selfdrive.controls.lib.pid import PIDController -from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY +from openpilot.common.numpy_fast import interp +from openpilot.selfdrive.controls.lib.latcontrol import LatControl +from openpilot.selfdrive.controls.lib.pid import PIDController +from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY # At higher speeds (25+mph) we can assume: # Lateral acceleration achieved by a specific car correlates to diff --git a/selfdrive/controls/lib/lateral_mpc_lib/SConscript b/selfdrive/controls/lib/lateral_mpc_lib/SConscript index 507964efe4..49666abe69 100644 --- a/selfdrive/controls/lib/lateral_mpc_lib/SConscript +++ b/selfdrive/controls/lib/lateral_mpc_lib/SConscript @@ -1,4 +1,4 @@ -Import('env', 'envCython', 'arch', 'common') +Import('env', 'envCython', 'arch') gen = "c_generated_code" @@ -32,22 +32,24 @@ generated_files = [ f'{gen}/Makefile', f'{gen}/main_lat.c', + f'{gen}/main_sim_lat.c', f'{gen}/acados_solver_lat.h', + f'{gen}/acados_sim_solver_lat.h', + f'{gen}/acados_sim_solver_lat.c', f'{gen}/acados_solver.pxd', f'{gen}/lat_model/lat_expl_vde_adj.c', f'{gen}/lat_model/lat_model.h', - f'{gen}/lat_cost/lat_cost_y_fun.h', - f'{gen}/lat_cost/lat_cost_y_e_fun.h', - f'{gen}/lat_cost/lat_cost_y_0_fun.h', + f'{gen}/lat_constraints/lat_constraints.h', + f'{gen}/lat_cost/lat_cost.h', ] + build_files acados_dir = '#third_party/acados' acados_templates_dir = '#third_party/acados/acados_template/c_templates_tera' source_list = ['lat_mpc.py', - '#/selfdrive/modeld/constants.py', + '#selfdrive/modeld/constants.py', f'{acados_dir}/include/acados_c/ocp_nlp_interface.h', f'{acados_templates_dir}/acados_solver.in.c', ] @@ -58,7 +60,6 @@ lenv.Clean(generated_files, Dir(gen)) generated_lat = lenv.Command(generated_files, source_list, f"cd {Dir('.').abspath} && python3 lat_mpc.py") -lenv.Depends(generated_lat, "#common") lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") diff --git a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py index 6afcb99dab..5e6f884df4 100755 --- a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py +++ b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 import os +import time import numpy as np from casadi import SX, vertcat, sin, cos -from common.realtime import sec_since_boot # WARNING: imports outside of constants will not trigger a rebuild -from selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.modeld.constants import T_IDXS if __name__ == '__main__': # generating code - from third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver + from openpilot.third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver else: - from selfdrive.controls.lib.lateral_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverCython + from openpilot.selfdrive.controls.lib.lateral_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverCython LAT_MPC_DIR = os.path.dirname(os.path.abspath(__file__)) EXPORT_DIR = os.path.join(LAT_MPC_DIR, "c_generated_code") @@ -182,9 +182,9 @@ class LateralMpc(): self.solver.set(N, "p", p_cp[N]) self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM]) - t = sec_since_boot() + t = time.monotonic() self.solution_status = self.solver.solve() - self.solve_time = sec_since_boot() - t + self.solve_time = time.monotonic() - t for i in range(N+1): self.x_sol[i] = self.solver.get(i, 'x') diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 38258b4b5d..92786f73d8 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -1,11 +1,12 @@ +import time import numpy as np -from common.realtime import sec_since_boot, DT_MDL -from common.numpy_fast import interp -from system.swaglog import cloudlog -from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import LateralMpc -from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import N as LAT_MPC_N -from selfdrive.controls.lib.drive_helpers import CONTROL_N, MIN_SPEED, get_speed_error -from selfdrive.controls.lib.desire_helper import DesireHelper +from openpilot.common.realtime import DT_MDL +from openpilot.common.numpy_fast import interp +from openpilot.system.swaglog import cloudlog +from openpilot.selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import LateralMpc +from openpilot.selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import N as LAT_MPC_N +from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, MIN_SPEED, get_speed_error +from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper import cereal.messaging as messaging from cereal import log @@ -108,7 +109,7 @@ class LateralPlanner: # Check for infeasible MPC solution mpc_nans = np.isnan(self.lat_mpc.x_sol[:, 3]).any() - t = sec_since_boot() + t = time.monotonic() if mpc_nans or self.lat_mpc.solution_status != 0: self.reset_mpc() self.x0[3] = measured_curvature * self.v_ego diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index e8095813f2..61c150aadc 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -1,9 +1,9 @@ from cereal import car -from common.numpy_fast import clip, interp -from common.realtime import DT_CTRL -from selfdrive.controls.lib.drive_helpers import CONTROL_N, apply_deadzone -from selfdrive.controls.lib.pid import PIDController -from selfdrive.modeld.constants import T_IDXS +from openpilot.common.numpy_fast import clip, interp +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, apply_deadzone +from openpilot.selfdrive.controls.lib.pid import PIDController +from openpilot.selfdrive.modeld.constants import T_IDXS LongCtrlState = car.CarControl.Actuators.LongControlState diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript index 8717bdf89b..79afa1d918 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript @@ -1,4 +1,4 @@ -Import('env', 'envCython', 'arch', 'common') +Import('env', 'envCython', 'arch', 'messaging_python', 'common_python') gen = "c_generated_code" @@ -38,23 +38,24 @@ generated_files = [ f'{gen}/Makefile', f'{gen}/main_long.c', + f'{gen}/main_sim_long.c', f'{gen}/acados_solver_long.h', + f'{gen}/acados_sim_solver_long.h', + f'{gen}/acados_sim_solver_long.c', f'{gen}/acados_solver.pxd', f'{gen}/long_model/long_expl_vde_adj.c', f'{gen}/long_model/long_model.h', - f'{gen}/long_constraints/long_h_constraint.h', - f'{gen}/long_cost/long_cost_y_fun.h', - f'{gen}/long_cost/long_cost_y_e_fun.h', - f'{gen}/long_cost/long_cost_y_0_fun.h', + f'{gen}/long_constraints/long_constraints.h', + f'{gen}/long_cost/long_cost.h', ] + build_files acados_dir = '#third_party/acados' acados_templates_dir = '#third_party/acados/acados_template/c_templates_tera' source_list = ['long_mpc.py', - '#/selfdrive/modeld/constants.py', + '#selfdrive/modeld/constants.py', f'{acados_dir}/include/acados_c/ocp_nlp_interface.h', f'{acados_templates_dir}/acados_solver.in.c', ] @@ -65,7 +66,7 @@ lenv.Clean(generated_files, Dir(gen)) generated_long = lenv.Command(generated_files, source_list, f"cd {Dir('.').abspath} && python3 long_mpc.py") -lenv.Depends(generated_long, "#cereal") +lenv.Depends(generated_long, [messaging_python, common_python]) lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py old mode 100644 new mode 100755 index dadf6cba28..a23ba1eaf6 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -1,19 +1,19 @@ #!/usr/bin/env python3 import os +import time import numpy as np from cereal import log -from common.realtime import sec_since_boot -from common.numpy_fast import clip -from system.swaglog import cloudlog +from openpilot.common.numpy_fast import clip +from openpilot.system.swaglog import cloudlog # WARNING: imports outside of constants will not trigger a rebuild -from selfdrive.modeld.constants import index_function -from selfdrive.car.interfaces import ACCEL_MIN -from selfdrive.controls.radard import _LEAD_ACCEL_TAU +from openpilot.selfdrive.modeld.constants import index_function +from openpilot.selfdrive.car.interfaces import ACCEL_MIN +from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU if __name__ == '__main__': # generating code - from third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver + from openpilot.third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver else: - from selfdrive.controls.lib.longitudinal_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverCython + from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverCython from casadi import SX, vertcat @@ -290,7 +290,7 @@ class LongitudinalMpc: self.x0[1] = v self.x0[2] = a if abs(v_prev - v) > 2.: # probably only helps if v < v_prev - for i in range(0, N+1): + for i in range(N+1): self.solver.set(i, 'x', self.x0) @staticmethod @@ -411,7 +411,7 @@ class LongitudinalMpc: self.source = 'lead1' def run(self): - # t0 = sec_since_boot() + # t0 = time.monotonic() # reset = 0 for i in range(N+1): self.solver.set(i, 'p', self.params[i]) @@ -442,14 +442,14 @@ class LongitudinalMpc: self.prev_a = np.interp(T_IDXS + 0.05, T_IDXS, self.a_solution) - t = sec_since_boot() + t = time.monotonic() if self.solution_status != 0: if t > self.last_cloudlog_t + 5.0: self.last_cloudlog_t = t cloudlog.warning(f"Long mpc reset, solution_status: {self.solution_status}") self.reset() # reset = 1 - # print(f"long_mpc timings: total internal {self.solve_time:.2e}, external: {(sec_since_boot() - t0):.2e} qp {self.time_qp_solution:.2e}, \ + # print(f"long_mpc timings: total internal {self.solve_time:.2e}, external: {(time.monotonic() - t0):.2e} qp {self.time_qp_solution:.2e}, \ # lin {self.time_linearization:.2e} qp_iter {qp_iter}, reset {reset}") diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 5765272928..c1e782fa5c 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -1,21 +1,21 @@ #!/usr/bin/env python3 import math import numpy as np -from common.numpy_fast import clip, interp -from common.params import Params +from openpilot.common.numpy_fast import clip, interp +from openpilot.common.params import Params from cereal import log import cereal.messaging as messaging -from common.conversions import Conversions as CV -from common.filter_simple import FirstOrderFilter -from common.realtime import DT_MDL -from selfdrive.modeld.constants import T_IDXS -from selfdrive.car.interfaces import ACCEL_MIN, ACCEL_MAX -from selfdrive.controls.lib.longcontrol import LongCtrlState -from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc -from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC -from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N, get_speed_error -from system.swaglog import cloudlog +from openpilot.common.conversions import Conversions as CV +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.realtime import DT_MDL +from openpilot.selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.car.interfaces import ACCEL_MIN, ACCEL_MAX +from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC +from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N, get_speed_error +from openpilot.system.swaglog import cloudlog LON_MPC_STEP = 0.2 # first step is 0.2s A_CRUISE_MIN = -1.2 diff --git a/selfdrive/controls/lib/pid.py b/selfdrive/controls/lib/pid.py index 965158131b..f4ec7e5996 100644 --- a/selfdrive/controls/lib/pid.py +++ b/selfdrive/controls/lib/pid.py @@ -1,7 +1,7 @@ import numpy as np from numbers import Number -from common.numpy_fast import clip, interp +from openpilot.common.numpy_fast import clip, interp class PIDController(): diff --git a/selfdrive/controls/lib/tests/__init__.py b/selfdrive/controls/lib/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/controls/lib/tests/test_alertmanager.py b/selfdrive/controls/lib/tests/test_alertmanager.py index 6c1b6fc4a2..dbd42858a0 100755 --- a/selfdrive/controls/lib/tests/test_alertmanager.py +++ b/selfdrive/controls/lib/tests/test_alertmanager.py @@ -2,8 +2,8 @@ import random import unittest -from selfdrive.controls.lib.events import Alert, EVENTS -from selfdrive.controls.lib.alertmanager import AlertManager +from openpilot.selfdrive.controls.lib.events import Alert, EVENTS +from openpilot.selfdrive.controls.lib.alertmanager import AlertManager class TestAlertManager(unittest.TestCase): diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py index b504b3d125..9580e604ff 100755 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -4,14 +4,15 @@ import unittest from parameterized import parameterized from cereal import car, log -from selfdrive.car.car_helpers import interfaces -from selfdrive.car.honda.values import CAR as HONDA -from selfdrive.car.toyota.values import CAR as TOYOTA -from selfdrive.car.nissan.values import CAR as NISSAN -from selfdrive.controls.lib.latcontrol_pid import LatControlPID -from selfdrive.controls.lib.latcontrol_torque import LatControlTorque -from selfdrive.controls.lib.latcontrol_angle import LatControlAngle -from selfdrive.controls.lib.vehicle_model import VehicleModel +from openpilot.selfdrive.car.car_helpers import interfaces +from openpilot.selfdrive.car.honda.values import CAR as HONDA +from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA +from openpilot.selfdrive.car.nissan.values import CAR as NISSAN +from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID +from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque +from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle +from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel +from openpilot.selfdrive.navd.tests.test_map_renderer import gen_llk class TestLatControl(unittest.TestCase): @@ -27,13 +28,15 @@ class TestLatControl(unittest.TestCase): CS = car.CarState.new_message() CS.vEgo = 30 + CS.steeringPressed = False last_actuators = car.CarControl.Actuators.new_message() params = log.LiveParametersData.new_message() + llk = gen_llk() for _ in range(1000): - _, _, lac_log = controller.update(True, CS, CP, VM, params, last_actuators, True, 1, 0) + _, _, lac_log = controller.update(True, CS, VM, params, last_actuators, False, 1, 0, llk) self.assertTrue(lac_log.saturated) diff --git a/selfdrive/controls/lib/tests/test_vehicle_model.py b/selfdrive/controls/lib/tests/test_vehicle_model.py index 03d97a7e3f..d016e87527 100755 --- a/selfdrive/controls/lib/tests/test_vehicle_model.py +++ b/selfdrive/controls/lib/tests/test_vehicle_model.py @@ -5,9 +5,9 @@ import unittest import numpy as np from control import StateSpace -from selfdrive.car.honda.interface import CarInterface -from selfdrive.car.honda.values import CAR -from selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices +from openpilot.selfdrive.car.honda.interface import CarInterface +from openpilot.selfdrive.car.honda.values import CAR +from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices class TestVehicleModel(unittest.TestCase): @@ -38,7 +38,7 @@ class TestVehicleModel(unittest.TestCase): # Compute yaw rate using direct computations yr2 = self.VM.yaw_rate(sa, u, roll) - self.assertAlmostEqual(float(yr1), yr2) + self.assertAlmostEqual(float(yr1[0]), yr2) def test_syn_ss_sol_simulate(self): """Verifies that dyn_ss_sol matches a simulation""" diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 7e61efcf45..2b23a0440e 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -2,12 +2,12 @@ import os import numpy as np from cereal import car -from common.params import Params -from common.realtime import Priority, config_realtime_process -from system.swaglog import cloudlog -from selfdrive.modeld.constants import T_IDXS -from selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner -from selfdrive.controls.lib.lateral_planner import LateralPlanner +from openpilot.common.params import Params +from openpilot.common.realtime import Priority, config_realtime_process +from openpilot.system.swaglog import cloudlog +from openpilot.selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner +from openpilot.selfdrive.controls.lib.lateral_planner import LateralPlanner import cereal.messaging as messaging def cumtrapz(x, t): diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index 303bbaaafe..ec42ae66f2 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -6,12 +6,12 @@ from typing import Optional, Dict, Any import capnp from cereal import messaging, log, car -from common.numpy_fast import interp -from common.params import Params -from common.realtime import Ratekeeper, Priority, config_realtime_process, DT_MDL -from system.swaglog import cloudlog +from openpilot.common.numpy_fast import interp +from openpilot.common.params import Params +from openpilot.common.realtime import Ratekeeper, Priority, config_realtime_process +from openpilot.system.swaglog import cloudlog -from common.kalman.simple_kalman import KF1D +from openpilot.common.kalman.simple_kalman import KF1D # Default lead acceleration decay set to 50% at 1s @@ -64,12 +64,8 @@ class Track: self.dRel = d_rel # LONG_DIST self.yRel = y_rel # -LAT_DIST self.vRel = v_rel # REL_SPEED - self.measured = measured # measured or estimate - - self.update_vlead(v_lead) - - def update_vlead(self, v_lead: float): self.vLead = v_lead + self.measured = measured # measured or estimate # computed velocity and accelerations if self.cnt > 0: @@ -173,8 +169,7 @@ def get_RadarState_from_vision(lead_msg: capnp._DynamicStructReader, v_ego: floa def get_lead(v_ego: float, ready: bool, tracks: Dict[int, Track], lead_msg: capnp._DynamicStructReader, model_v_ego: float, low_speed_override: bool = True) -> Dict[str, Any]: # Determine leads, this is where the essential logic happens - lead_msg_empty = any(len(c) == 0 for c in [lead_msg.x, lead_msg.y, lead_msg.v]) - if len(tracks) > 0 and ready and not lead_msg_empty and lead_msg.prob > .5: + if len(tracks) > 0 and ready and lead_msg.prob > .5: track = match_vision_to_track(v_ego, lead_msg, tracks) else: track = None @@ -182,7 +177,7 @@ def get_lead(v_ego: float, ready: bool, tracks: Dict[int, Track], lead_msg: capn lead_dict = {'status': False} if track is not None: lead_dict = track.get_RadarState(lead_msg.prob) - elif track is None and ready and not lead_msg_empty and lead_msg.prob > .5: + elif (track is None) and ready and (lead_msg.prob > .5): lead_dict = get_RadarState_from_vision(lead_msg, v_ego, model_v_ego) if low_speed_override: @@ -212,14 +207,14 @@ class RadarD: self.ready = False - def update(self, sm: messaging.SubMaster, radar_data: Optional[car.RadarData]): + def update(self, sm: messaging.SubMaster, rr: Optional[car.RadarData]): self.current_time = 1e-9*max(sm.logMonoTime.values()) radar_points = [] radar_errors = [] - if radar_data is not None: - radar_points = radar_data.points - radar_errors = radar_data.errors + if rr is not None: + radar_points = rr.points + radar_errors = rr.errors if sm.updated['carState']: self.v_ego = sm['carState'].vEgo @@ -227,32 +222,26 @@ class RadarD: if sm.updated['modelV2']: self.ready = True - if radar_data is not None: - ar_pts = {} - for pt in radar_points: - ar_pts[pt.trackId] = [pt.dRel, pt.yRel, pt.vRel, pt.measured] + ar_pts = {} + for pt in radar_points: + ar_pts[pt.trackId] = [pt.dRel, pt.yRel, pt.vRel, pt.measured] - # *** remove missing points from meta data *** - for ids in list(self.tracks.keys()): - if ids not in ar_pts: - self.tracks.pop(ids, None) + # *** remove missing points from meta data *** + for ids in list(self.tracks.keys()): + if ids not in ar_pts: + self.tracks.pop(ids, None) - # *** compute the tracks *** - for ids in ar_pts: - rpt = ar_pts[ids] + # *** compute the tracks *** + for ids in ar_pts: + rpt = ar_pts[ids] - # align v_ego by a fixed time to align it with the radar measurement - v_lead = rpt[2] + self.v_ego_hist[0] + # align v_ego by a fixed time to align it with the radar measurement + v_lead = rpt[2] + self.v_ego_hist[0] - # create the track if it doesn't exist or it's a new track - if ids not in self.tracks: - self.tracks[ids] = Track(ids, v_lead, self.kalman_params) - self.tracks[ids].update(rpt[0], rpt[1], rpt[2], v_lead, rpt[3]) - else: - # *** no radar points, keep existing tracks, update v_lead - for track in self.tracks.values(): - v_lead = track.vRel + self.v_ego_hist[0] - track.update_vlead(v_lead) + # create the track if it doesn't exist or it's a new track + if ids not in self.tracks: + self.tracks[ids] = Track(ids, v_lead, self.kalman_params) + self.tracks[ids].update(rpt[0], rpt[1], rpt[2], v_lead, rpt[3]) # *** publish radarState *** self.radar_state_valid = sm.all_checks() and len(radar_errors) == 0 @@ -305,33 +294,32 @@ def radard_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[messagi cloudlog.info("radard is importing %s", CP.carName) RadarInterface = importlib.import_module(f'selfdrive.car.{CP.carName}.radar_interface').RadarInterface - # setup messaging + # *** setup messaging if can_sock is None: can_sock = messaging.sub_sock('can') if sm is None: - sm = messaging.SubMaster(['modelV2', 'carState'], poll=["modelV2"]) + sm = messaging.SubMaster(['modelV2', 'carState'], ignore_avg_freq=['modelV2', 'carState']) # Can't check average frequency, since radar determines timing if pm is None: pm = messaging.PubMaster(['radarState', 'liveTracks']) - interface = RadarInterface(CP) + RI = RadarInterface(CP) - rk = Ratekeeper(1 / DT_MDL, print_delay_threshold=None) - radar = RadarD(DT_MDL, interface.delay) + rk = Ratekeeper(1.0 / CP.radarTimeStep, print_delay_threshold=None) + RD = RadarD(CP.radarTimeStep, RI.delay) - while True: - sm.update() + while 1: + can_strings = messaging.drain_sock_raw(can_sock, wait_for_one=True) + rr = RI.update(can_strings) - if sm.updated['modelV2']: - can_strings = messaging.drain_sock_raw(can_sock) - if len(can_strings) == 0: - radar_data = None - else: - radar_data = interface.update(can_strings) + if rr is None: + continue + + sm.update(0) - radar.update(sm, radar_data) - radar.publish(pm, -rk.remaining*1000.0) + RD.update(sm, rr) + RD.publish(pm, -rk.remaining*1000.0) - rk.monitor_time() + rk.monitor_time() def main(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None, can_sock: messaging.SubSocket = None): diff --git a/selfdrive/controls/tests/test_alerts.py b/selfdrive/controls/tests/test_alerts.py index c0d70b3b6f..7b4fba0dce 100755 --- a/selfdrive/controls/tests/test_alerts.py +++ b/selfdrive/controls/tests/test_alerts.py @@ -8,11 +8,11 @@ from PIL import Image, ImageDraw, ImageFont from cereal import log, car from cereal.messaging import SubMaster -from common.basedir import BASEDIR -from common.params import Params -from selfdrive.controls.lib.events import Alert, EVENTS, ET -from selfdrive.controls.lib.alertmanager import set_offroad_alert -from selfdrive.test.process_replay.process_replay import CONFIGS +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.selfdrive.controls.lib.events import Alert, EVENTS, ET +from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert +from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS AlertSize = log.ControlsState.AlertSize diff --git a/selfdrive/controls/tests/test_cruise_speed.py b/selfdrive/controls/tests/test_cruise_speed.py index 6d11c30ab2..76a2222e85 100755 --- a/selfdrive/controls/tests/test_cruise_speed.py +++ b/selfdrive/controls/tests/test_cruise_speed.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 +import itertools import numpy as np import unittest from parameterized import parameterized_class from cereal import log -from common.params import Params -from selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT +from openpilot.common.params import Params +from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT from cereal import car -from common.conversions import Conversions as CV -from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver ButtonEvent = car.CarState.ButtonEvent ButtonType = car.CarState.ButtonEvent.Type @@ -31,28 +32,26 @@ def run_cruise_simulation(cruise, e2e, t_end=20.): return output[-1, 3] +@parameterized_class(("e2e", "personality", "speed"), itertools.product( + [True, False], # e2e + log.LongitudinalPersonality.schema.enumerants, # personality + [5,35])) # speed class TestCruiseSpeed(unittest.TestCase): def test_cruise_speed(self): params = Params() - personalities = [log.LongitudinalPersonality.relaxed, - log.LongitudinalPersonality.standard, - log.LongitudinalPersonality.aggressive] - for personality in personalities: - params.put("LongitudinalPersonality", str(personality)) - for e2e in [False, True]: - for speed in [5,35]: - print(f'Testing {speed} m/s') - cruise_speed = float(speed) + params.put("LongitudinalPersonality", str(self.personality)) + print(f'Testing {self.speed} m/s') + cruise_speed = float(self.speed) - simulation_steady_state = run_cruise_simulation(cruise_speed, e2e) - self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {speed} m/s') + simulation_steady_state = run_cruise_simulation(cruise_speed, self.e2e) + self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {self.speed} m/s') # TODO: test pcmCruise @parameterized_class(('pcm_cruise',), [(False,)]) class TestVCruiseHelper(unittest.TestCase): def setUp(self): - self.CP = car.CarParams(pcmCruise=self.pcm_cruise) # pylint: disable=E1101 + self.CP = car.CarParams(pcmCruise=self.pcm_cruise) self.v_cruise_helper = VCruiseHelper(self.CP) self.reset_cruise_speed_state() diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py old mode 100644 new mode 100755 index 9ee7bdfb3b..3b31632721 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -1,10 +1,13 @@ #!/usr/bin/env python3 import unittest -from common.params import Params +import itertools +from parameterized import parameterized_class + +from openpilot.common.params import Params from cereal import log -from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance, get_T_FOLLOW -from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance, get_T_FOLLOW +from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): @@ -23,22 +26,21 @@ def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): return output[-1,2] - output[-1,1] +@parameterized_class(("e2e", "personality", "speed"), itertools.product( + [True, False], # e2e + [log.LongitudinalPersonality.relaxed, # personality + log.LongitudinalPersonality.standard, + log.LongitudinalPersonality.aggressive], + [0,10,35])) # speed class TestFollowingDistance(unittest.TestCase): def test_following_distance(self): params = Params() - personalities = [log.LongitudinalPersonality.relaxed, - log.LongitudinalPersonality.standard, - log.LongitudinalPersonality.aggressive] - for personality in personalities: - params.put("LongitudinalPersonality", str(personality)) - for e2e in [False, True]: - for speed in [0,10,35]: - print(f'Testing {speed} m/s') - v_lead = float(speed) - simulation_steady_state = run_following_distance_simulation(v_lead, e2e=e2e) - correct_steady_state = desired_follow_distance(v_lead, v_lead, get_T_FOLLOW(personality)) - err_ratio = 0.2 if e2e else 0.1 - self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(err_ratio * correct_steady_state + .5)) + params.put("LongitudinalPersonality", str(self.personality)) + v_lead = float(self.speed) + simulation_steady_state = run_following_distance_simulation(v_lead, e2e=self.e2e) + correct_steady_state = desired_follow_distance(v_lead, v_lead, get_T_FOLLOW(self.personality)) + err_ratio = 0.2 if self.e2e else 0.1 + self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(err_ratio * correct_steady_state + .5)) if __name__ == "__main__": diff --git a/selfdrive/controls/tests/test_lateral_mpc.py b/selfdrive/controls/tests/test_lateral_mpc.py index b569da09b4..8c09f46b60 100644 --- a/selfdrive/controls/tests/test_lateral_mpc.py +++ b/selfdrive/controls/tests/test_lateral_mpc.py @@ -1,8 +1,8 @@ import unittest import numpy as np -from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import LateralMpc -from selfdrive.controls.lib.drive_helpers import CAR_ROTATION_RADIUS -from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import N as LAT_MPC_N +from openpilot.selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import LateralMpc +from openpilot.selfdrive.controls.lib.drive_helpers import CAR_ROTATION_RADIUS +from openpilot.selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import N as LAT_MPC_N def run_mpc(lat_mpc=None, v_ref=30., x_init=0., y_init=0., psi_init=0., curvature_init=0., diff --git a/selfdrive/controls/tests/test_leads.py b/selfdrive/controls/tests/test_leads.py new file mode 100755 index 0000000000..268d9c47a7 --- /dev/null +++ b/selfdrive/controls/tests/test_leads.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +import unittest + +import cereal.messaging as messaging + +from openpilot.selfdrive.test.process_replay import replay_process_with_name +from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA + + +class TestLeads(unittest.TestCase): + def test_radar_fault(self): + # if there's no radar-related can traffic, radard should either not respond or respond with an error + # this is tightly coupled with underlying car radar_interface implementation, but it's a good sanity check + def single_iter_pkg(): + # single iter package, with meaningless cans and empty carState/modelV2 + msgs = [] + for _ in range(5): + can = messaging.new_message("can", 1) + cs = messaging.new_message("carState") + msgs.append(can.as_reader()) + msgs.append(cs.as_reader()) + model = messaging.new_message("modelV2") + msgs.append(model.as_reader()) + + return msgs + + msgs = [m for _ in range(3) for m in single_iter_pkg()] + out = replay_process_with_name("radard", msgs, fingerprint=TOYOTA.COROLLA_TSS2) + states = [m for m in out if m.which() == "radarState"] + failures = [not state.valid and len(state.radarState.radarErrors) for state in states] + + self.assertTrue(len(states) == 0 or all(failures)) + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/controls/tests/test_startup.py b/selfdrive/controls/tests/test_startup.py index 18c8e79026..6eb803e8aa 100755 --- a/selfdrive/controls/tests/test_startup.py +++ b/selfdrive/controls/tests/test_startup.py @@ -5,13 +5,13 @@ from parameterized import parameterized from cereal import log, car import cereal.messaging as messaging -from common.params import Params -from selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp # pylint: disable=no-name-in-module,import-error -from selfdrive.car.fingerprints import _FINGERPRINTS -from selfdrive.car.toyota.values import CAR as TOYOTA -from selfdrive.car.mazda.values import CAR as MAZDA -from selfdrive.controls.lib.events import EVENT_NAME -from selfdrive.test.helpers import with_processes +from openpilot.common.params import Params +from openpilot.selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp +from openpilot.selfdrive.car.fingerprints import _FINGERPRINTS +from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA +from openpilot.selfdrive.car.mazda.values import CAR as MAZDA +from openpilot.selfdrive.controls.lib.events import EVENT_NAME +from openpilot.selfdrive.test.helpers import with_processes EventName = car.CarEvent.EventName Ecu = car.CarParams.Ecu diff --git a/selfdrive/controls/tests/test_state_machine.py b/selfdrive/controls/tests/test_state_machine.py index d5f468f214..bdeed9fb7a 100755 --- a/selfdrive/controls/tests/test_state_machine.py +++ b/selfdrive/controls/tests/test_state_machine.py @@ -2,10 +2,10 @@ import unittest from cereal import car, log -from common.realtime import DT_CTRL -from selfdrive.car.car_helpers import interfaces -from selfdrive.controls.controlsd import Controls, SOFT_DISABLE_TIME -from selfdrive.controls.lib.events import Events, ET, Alert, Priority, AlertSize, AlertStatus, VisualAlert, \ +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car.car_helpers import interfaces +from openpilot.selfdrive.controls.controlsd import Controls, SOFT_DISABLE_TIME +from openpilot.selfdrive.controls.lib.events import Events, ET, Alert, Priority, AlertSize, AlertStatus, VisualAlert, \ AudibleAlert, EVENTS State = log.ControlsState.OpenpilotState diff --git a/selfdrive/debug/can_print_changes.py b/selfdrive/debug/can_print_changes.py index ff98c20e60..ea1160d60b 100755 --- a/selfdrive/debug/can_print_changes.py +++ b/selfdrive/debug/can_print_changes.py @@ -5,8 +5,8 @@ import time from collections import defaultdict import cereal.messaging as messaging -from selfdrive.debug.can_table import can_table -from tools.lib.logreader import logreader_from_route_or_segment +from openpilot.selfdrive.debug.can_table import can_table +from openpilot.tools.lib.logreader import logreader_from_route_or_segment RED = '\033[91m' CLEAR = '\033[0m' diff --git a/selfdrive/debug/can_printer.py b/selfdrive/debug/can_printer.py index 3f991d4e6c..220008979d 100755 --- a/selfdrive/debug/can_printer.py +++ b/selfdrive/debug/can_printer.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 import argparse import binascii +import time from collections import defaultdict import cereal.messaging as messaging -from common.realtime import sec_since_boot def can_printer(bus, max_msg, addr, ascii_decode): logcan = messaging.sub_sock('can', addr=addr) - start = sec_since_boot() - lp = sec_since_boot() + start = time.monotonic() + lp = time.monotonic() msgs = defaultdict(list) while 1: can_recv = messaging.drain_sock(logcan, wait_for_one=True) @@ -20,17 +20,17 @@ def can_printer(bus, max_msg, addr, ascii_decode): if y.src == bus: msgs[y.address].append(y.dat) - if sec_since_boot() - lp > 0.1: + if time.monotonic() - lp > 0.1: dd = chr(27) + "[2J" - dd += f"{sec_since_boot() - start:5.2f}\n" + dd += f"{time.monotonic() - start:5.2f}\n" for addr in sorted(msgs.keys()): a = f"\"{msgs[addr][-1].decode('ascii', 'backslashreplace')}\"" if ascii_decode else "" x = binascii.hexlify(msgs[addr][-1]).decode('ascii') - freq = len(msgs[addr]) / (sec_since_boot() - start) + freq = len(msgs[addr]) / (time.monotonic() - start) if max_msg is None or addr < max_msg: dd += "%04X(%4d)(%6d)(%3dHz) %s %s\n" % (addr, addr, len(msgs[addr]), freq, x.ljust(20), a) print(dd) - lp = sec_since_boot() + lp = time.monotonic() if __name__ == "__main__": parser = argparse.ArgumentParser(description="simple CAN data viewer", diff --git a/selfdrive/debug/check_can_parser_performance.py b/selfdrive/debug/check_can_parser_performance.py index 4b430e013f..c4b688ce29 100755 --- a/selfdrive/debug/check_can_parser_performance.py +++ b/selfdrive/debug/check_can_parser_performance.py @@ -4,9 +4,9 @@ import time from tqdm import tqdm from cereal import car -from selfdrive.car.tests.routes import CarTestRoute -from selfdrive.car.tests.test_models import TestCarModelBase -from tools.plotjuggler.juggle import DEMO_ROUTE +from openpilot.selfdrive.car.tests.routes import CarTestRoute +from openpilot.selfdrive.car.tests.test_models import TestCarModelBase +from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE N_RUNS = 10 diff --git a/selfdrive/debug/check_freq.py b/selfdrive/debug/check_freq.py index 6436abb4f1..7e7b05e950 100755 --- a/selfdrive/debug/check_freq.py +++ b/selfdrive/debug/check_freq.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 import argparse import numpy as np +import time from collections import defaultdict, deque from typing import DefaultDict, Deque, MutableSequence -from common.realtime import sec_since_boot import cereal.messaging as messaging @@ -22,7 +22,7 @@ if __name__ == "__main__": rcv_times: DefaultDict[str, MutableSequence[float]] = defaultdict(lambda: deque(maxlen=100)) valids: DefaultDict[str, Deque[bool]] = defaultdict(lambda: deque(maxlen=100)) - t = sec_since_boot() + t = time.monotonic() for name in socket_names: sock = messaging.sub_sock(name, poller=poller) sockets[sock] = name @@ -36,7 +36,7 @@ if __name__ == "__main__": name = msg.which() - t = sec_since_boot() + t = time.monotonic() rcv_times[name].append(msg.logMonoTime / 1e9) valids[name].append(msg.valid) diff --git a/selfdrive/debug/count_events.py b/selfdrive/debug/count_events.py index a81d797b89..a8af5e6fe9 100755 --- a/selfdrive/debug/count_events.py +++ b/selfdrive/debug/count_events.py @@ -8,8 +8,8 @@ from tqdm import tqdm from typing import List, Tuple, cast from cereal.services import service_list -from tools.lib.route import Route -from tools.lib.logreader import LogReader +from openpilot.tools.lib.route import Route +from openpilot.tools.lib.logreader import LogReader if __name__ == "__main__": r = Route(sys.argv[1]) diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py index b3294c8728..9050fbb064 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/selfdrive/debug/cpu_usage_stat.py @@ -24,7 +24,7 @@ import argparse import re from collections import defaultdict -from selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.manager.process_config import managed_processes # Do statistics every 5 seconds PRINT_INTERVAL = 5 diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index 71c7b34be5..5037208643 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -4,11 +4,11 @@ import random from cereal import car, log import cereal.messaging as messaging -from common.realtime import DT_CTRL -from selfdrive.car.honda.interface import CarInterface -from selfdrive.controls.lib.events import ET, Events -from selfdrive.controls.lib.alertmanager import AlertManager -from selfdrive.manager.process_config import managed_processes +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car.honda.interface import CarInterface +from openpilot.selfdrive.controls.lib.events import ET, Events +from openpilot.selfdrive.controls.lib.alertmanager import AlertManager +from openpilot.selfdrive.manager.process_config import managed_processes EventName = car.CarEvent.EventName diff --git a/selfdrive/debug/dump_car_info.py b/selfdrive/debug/dump_car_info.py index c9a21c2848..6af328926b 100755 --- a/selfdrive/debug/dump_car_info.py +++ b/selfdrive/debug/dump_car_info.py @@ -2,7 +2,7 @@ import argparse import pickle -from selfdrive.car.docs import get_all_car_info +from openpilot.selfdrive.car.docs import get_all_car_info def dump_car_info(path): diff --git a/selfdrive/debug/filter_log_message.py b/selfdrive/debug/filter_log_message.py index 8d9ce8e6a2..20cef0fcc0 100755 --- a/selfdrive/debug/filter_log_message.py +++ b/selfdrive/debug/filter_log_message.py @@ -4,8 +4,8 @@ import argparse import json import cereal.messaging as messaging -from tools.lib.logreader import LogReader -from tools.lib.route import Route +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.route import Route LEVELS = { "DEBUG": 10, diff --git a/selfdrive/debug/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index b3598b105c..c2bff7c638 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 import sys -from tools.lib.route import Route -from tools.lib.logreader import MultiLogIterator +from openpilot.tools.lib.route import Route +from openpilot.tools.lib.logreader import MultiLogIterator def get_fingerprint(lr): diff --git a/selfdrive/debug/internal/fuzz_fw_fingerprint.py b/selfdrive/debug/internal/fuzz_fw_fingerprint.py index 8209bbf4ce..aedb3ada1d 100755 --- a/selfdrive/debug/internal/fuzz_fw_fingerprint.py +++ b/selfdrive/debug/internal/fuzz_fw_fingerprint.py @@ -5,11 +5,11 @@ from collections import defaultdict from tqdm import tqdm -from selfdrive.car.fw_versions import match_fw_to_car_fuzzy -from selfdrive.car.toyota.values import FW_VERSIONS as TOYOTA_FW_VERSIONS -from selfdrive.car.honda.values import FW_VERSIONS as HONDA_FW_VERSIONS -from selfdrive.car.hyundai.values import FW_VERSIONS as HYUNDAI_FW_VERSIONS -from selfdrive.car.volkswagen.values import FW_VERSIONS as VW_FW_VERSIONS +from openpilot.selfdrive.car.fw_versions import match_fw_to_car_fuzzy +from openpilot.selfdrive.car.toyota.values import FW_VERSIONS as TOYOTA_FW_VERSIONS +from openpilot.selfdrive.car.honda.values import FW_VERSIONS as HONDA_FW_VERSIONS +from openpilot.selfdrive.car.hyundai.values import FW_VERSIONS as HYUNDAI_FW_VERSIONS +from openpilot.selfdrive.car.volkswagen.values import FW_VERSIONS as VW_FW_VERSIONS FWS = {} diff --git a/selfdrive/debug/internal/qlog_size.py b/selfdrive/debug/internal/qlog_size.py index be9ab5b17e..9b7f369525 100755 --- a/selfdrive/debug/internal/qlog_size.py +++ b/selfdrive/debug/internal/qlog_size.py @@ -6,8 +6,8 @@ from collections import defaultdict import matplotlib.pyplot as plt from cereal.services import service_list -from tools.lib.logreader import LogReader -from tools.lib.route import Route +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.route import Route MIN_SIZE = 0.5 # Percent size of total to show as separate entry diff --git a/selfdrive/debug/live_cpu_and_temp.py b/selfdrive/debug/live_cpu_and_temp.py index c35afc474b..06f1be0b00 100755 --- a/selfdrive/debug/live_cpu_and_temp.py +++ b/selfdrive/debug/live_cpu_and_temp.py @@ -4,7 +4,7 @@ import capnp from collections import defaultdict from cereal.messaging import SubMaster -from common.numpy_fast import mean +from openpilot.common.numpy_fast import mean from typing import Optional, Dict def cputime_total(ct): diff --git a/selfdrive/debug/print_docs_diff.py b/selfdrive/debug/print_docs_diff.py index 1cb6c3cdda..8fb6277d3b 100755 --- a/selfdrive/debug/print_docs_diff.py +++ b/selfdrive/debug/print_docs_diff.py @@ -4,8 +4,8 @@ from collections import defaultdict import difflib import pickle -from selfdrive.car.docs import get_all_car_info -from selfdrive.car.docs_definitions import Column +from openpilot.selfdrive.car.docs import get_all_car_info +from openpilot.selfdrive.car.docs_definitions import Column FOOTNOTE_TAG = "{}" STAR_ICON = '' diff --git a/selfdrive/debug/run_process_on_route.py b/selfdrive/debug/run_process_on_route.py index c7a1434975..c7b6250d3f 100755 --- a/selfdrive/debug/run_process_on_route.py +++ b/selfdrive/debug/run_process_on_route.py @@ -2,10 +2,10 @@ import argparse -from selfdrive.test.process_replay.process_replay import CONFIGS, replay_process -from tools.lib.logreader import MultiLogIterator -from tools.lib.route import Route -from tools.lib.helpers import save_log +from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, replay_process +from openpilot.tools.lib.logreader import MultiLogIterator +from openpilot.tools.lib.route import Route +from openpilot.tools.lib.helpers import save_log if __name__ == "__main__": parser = argparse.ArgumentParser(description="Run process on route and create new logs", diff --git a/selfdrive/debug/sensor_data_to_hist.py b/selfdrive/debug/sensor_data_to_hist.py index ceed4b0ec3..73f98b285c 100755 --- a/selfdrive/debug/sensor_data_to_hist.py +++ b/selfdrive/debug/sensor_data_to_hist.py @@ -10,8 +10,8 @@ import sys import numpy as np from collections import defaultdict -from tools.lib.logreader import LogReader -from tools.lib.route import Route +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.route import Route import matplotlib.pyplot as plt diff --git a/selfdrive/debug/set_car_params.py b/selfdrive/debug/set_car_params.py index 24258db9f2..6060dfbc36 100755 --- a/selfdrive/debug/set_car_params.py +++ b/selfdrive/debug/set_car_params.py @@ -2,9 +2,9 @@ import sys from cereal import car -from common.params import Params -from tools.lib.route import Route -from tools.lib.logreader import LogReader +from openpilot.common.params import Params +from openpilot.tools.lib.route import Route +from openpilot.tools.lib.logreader import LogReader if __name__ == "__main__": CP = None diff --git a/selfdrive/debug/show_matching_cars.py b/selfdrive/debug/show_matching_cars.py index a97c41f128..19144ead7e 100755 --- a/selfdrive/debug/show_matching_cars.py +++ b/selfdrive/debug/show_matching_cars.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars +from openpilot.selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars import cereal.messaging as messaging diff --git a/selfdrive/debug/test_car_model.py b/selfdrive/debug/test_car_model.py index 3f7a1a167b..66fe2ea65f 100755 --- a/selfdrive/debug/test_car_model.py +++ b/selfdrive/debug/test_car_model.py @@ -4,9 +4,9 @@ import sys from typing import List import unittest -from selfdrive.car.tests.routes import CarTestRoute -from selfdrive.car.tests.test_models import TestCarModel -from tools.lib.route import SegmentName +from openpilot.selfdrive.car.tests.routes import CarTestRoute +from openpilot.selfdrive.car.tests.test_models import TestCarModel +from openpilot.tools.lib.route import SegmentName def create_test_models_suite(routes: List[CarTestRoute], ci=False) -> unittest.TestSuite: diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index 51cd426315..83e173244e 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -6,10 +6,10 @@ import argparse import os import traceback from tqdm import tqdm -from tools.lib.logreader import LogReader -from tools.lib.route import Route -from selfdrive.car.car_helpers import interface_names -from selfdrive.car.fw_versions import VERSIONS, match_fw_to_car +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.route import Route +from openpilot.selfdrive.car.car_helpers import interface_names +from openpilot.selfdrive.car.fw_versions import VERSIONS, match_fw_to_car NO_API = "NO_API" in os.environ diff --git a/selfdrive/debug/toyota_eps_factor.py b/selfdrive/debug/toyota_eps_factor.py index 0a459bb719..d60d2d372b 100755 --- a/selfdrive/debug/toyota_eps_factor.py +++ b/selfdrive/debug/toyota_eps_factor.py @@ -2,11 +2,11 @@ import sys import numpy as np import matplotlib.pyplot as plt -from sklearn import linear_model # pylint: disable=import-error -from selfdrive.car.toyota.values import STEER_THRESHOLD +from sklearn import linear_model +from openpilot.selfdrive.car.toyota.values import STEER_THRESHOLD -from tools.lib.route import Route -from tools.lib.logreader import MultiLogIterator +from openpilot.tools.lib.route import Route +from openpilot.tools.lib.logreader import MultiLogIterator MIN_SAMPLES = 30 * 100 diff --git a/selfdrive/debug/uiview.py b/selfdrive/debug/uiview.py index 93d901f7c9..f4440a912c 100755 --- a/selfdrive/debug/uiview.py +++ b/selfdrive/debug/uiview.py @@ -2,8 +2,8 @@ import time from cereal import car, log, messaging -from common.params import Params -from selfdrive.manager.process_config import managed_processes +from openpilot.common.params import Params +from openpilot.selfdrive.manager.process_config import managed_processes if __name__ == "__main__": CP = car.CarParams(notCar=True) diff --git a/selfdrive/locationd/SConscript b/selfdrive/locationd/SConscript index 740f827a49..a6febe0170 100644 --- a/selfdrive/locationd/SConscript +++ b/selfdrive/locationd/SConscript @@ -7,8 +7,4 @@ locationd_sources = ["locationd.cc", "models/live_kf.cc", ekf_sym_cc] lenv = env.Clone() lenv["_LIBFLAGS"] += f' {libkf[0].get_labspath()}' locationd = lenv.Program("locationd", locationd_sources, LIBS=loc_libs + transformations) -lenv.Depends(locationd, libkf) - -if File("liblocationd.cc").exists(): - liblocationd = lenv.SharedLibrary("liblocationd", ["liblocationd.cc"] + locationd_sources, LIBS=loc_libs + transformations) - lenv.Depends(liblocationd, libkf) \ No newline at end of file +lenv.Depends(locationd, libkf) \ No newline at end of file diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 7f8fd03b98..6469ece402 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -14,11 +14,11 @@ from typing import List, NoReturn, Optional from cereal import log import cereal.messaging as messaging -from common.conversions import Conversions as CV -from common.params import Params, put_nonblocking -from common.realtime import set_realtime_priority -from common.transformations.orientation import rot_from_euler, euler_from_rot -from system.swaglog import cloudlog +from openpilot.common.conversions import Conversions as CV +from openpilot.common.params import Params, put_nonblocking +from openpilot.common.realtime import set_realtime_priority +from openpilot.common.transformations.orientation import rot_from_euler, euler_from_rot +from openpilot.system.swaglog import cloudlog MIN_SPEED_FILTER = 15 * CV.MPH_TO_MS MAX_VEL_ANGLE_STD = np.radians(0.25) diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index e4a408d130..f00595618e 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -11,23 +11,24 @@ from typing import List, Optional, Dict, Any import numpy as np from cereal import log, messaging -from common.params import Params, put_nonblocking +from openpilot.common.params import Params, put_nonblocking from laika import AstroDog from laika.constants import SECS_IN_HR, SECS_IN_MIN from laika.downloader import DownloadFailed from laika.ephemeris import EphemerisType, GPSEphemeris, GLONASSEphemeris, ephemeris_structs, parse_qcom_ephem from laika.gps_time import GPSTime from laika.helpers import ConstellationId, get_sv_id -from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox, read_raw_qcom +from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox +from laika.raw_gnss import gps_time_from_qcom_report, get_measurements_from_qcom_reports from laika.opt import calc_pos_fix, get_posfix_sympy_fun, calc_vel_fix, get_velfix_sympy_func -from selfdrive.locationd.models.constants import GENERATED_DIR, ObservationKind -from selfdrive.locationd.models.gnss_kf import GNSSKalman -from selfdrive.locationd.models.gnss_kf import States as GStates -from system.swaglog import cloudlog +from openpilot.selfdrive.locationd.models.constants import GENERATED_DIR, ObservationKind +from openpilot.selfdrive.locationd.models.gnss_kf import GNSSKalman +from openpilot.selfdrive.locationd.models.gnss_kf import States as GStates +from openpilot.system.hardware.hw import Paths +from openpilot.system.swaglog import cloudlog MAX_TIME_GAP = 10 EPHEMERIS_CACHE = 'LaikadEphemerisV3' -DOWNLOADS_CACHE_FOLDER = "/tmp/comma_download_cache/" CACHE_VERSION = 0.2 POS_FIX_RESIDUAL_THRESHOLD = 100.0 @@ -82,7 +83,7 @@ class Laikad: save_ephemeris: If true saves and loads nav and orbit ephemeris to cache. """ self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, - clear_old_ephemeris=True, cache_dir=DOWNLOADS_CACHE_FOLDER) + clear_old_ephemeris=True, cache_dir=Paths.download_cache_root()) self.gnss_kf = GNSSKalman(GENERATED_DIR, cython=True, erratic_clock=use_qcom) self.auto_fetch_navs = auto_fetch_navs @@ -102,9 +103,10 @@ class Laikad: self.use_qcom = use_qcom self.first_log_time = None self.ttff = -1 + self.measurement_lag = 0.630 if self.use_qcom else 0.095 # qcom specific stuff - self.qcom_reports_received = 1 + self.qcom_reports_received = 4 self.qcom_reports = [] def load_cache(self): @@ -161,47 +163,31 @@ class Laikad: def get_lsq_fix(self, t, measurements): if self.last_fix_t is None or abs(self.last_fix_t - t) > 0: min_measurements = 5 if any(p.constellation_id == ConstellationId.GLONASS for p in measurements) else 4 + position_solution, pr_residuals, pos_std = calc_pos_fix(measurements, self.posfix_functions, min_measurements=min_measurements) if len(position_solution) < 3: return None position_estimate = position_solution[:3] - - position_std_residual = np.median(np.abs(pr_residuals)) - position_std = np.median(np.abs(pos_std))/10 - position_std = max(position_std_residual, position_std) * np.ones(3) + position_std = pos_std[:3] velocity_solution, prr_residuals, vel_std = calc_vel_fix(measurements, position_estimate, self.velfix_function, min_measurements=min_measurements) if len(velocity_solution) < 3: return None velocity_estimate = velocity_solution[:3] - - velocity_std_residual = np.median(np.abs(prr_residuals)) - velocity_std = np.median(np.abs(vel_std))/10 - velocity_std = max(velocity_std, velocity_std_residual) * np.ones(3) + velocity_std = vel_std[:3] return position_estimate, position_std, velocity_estimate, velocity_std - def gps_time_from_qcom_report(self, gnss_msg): - report = gnss_msg.drMeasurementReport - if report.source == log.QcomGnss.MeasurementSource.gps: - report_time = GPSTime(report.gpsWeek, report.gpsMilliseconds / 1000.0) - elif report.source == log.QcomGnss.MeasurementSource.sbas: - report_time = GPSTime(report.gpsWeek, report.gpsMilliseconds / 1000.0) - elif report.source == log.QcomGnss.MeasurementSource.glonass: - report_time = GPSTime.from_glonass(report.glonassYear, - report.glonassDay, - report.glonassMilliseconds / 1000.0) - else: - raise NotImplementedError(f'Unknownconstellation {report.source}') - return report_time - def is_good_report(self, gnss_msg): - if gnss_msg.which() == 'drMeasurementReport' and self.use_qcom: + if gnss_msg.which() in ['drMeasurementReport', 'measurementReport'] and self.use_qcom: # TODO: Understand and use remaining unknown constellations try: - constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source) + if gnss_msg.which() == 'drMeasurementReport': + constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source) + else: + constellation_id = ConstellationId.from_qcom_source(gnss_msg.measurementReport.source) good_constellation = constellation_id in [ConstellationId.GPS, ConstellationId.SBAS, ConstellationId.GLONASS] - report_time = self.gps_time_from_qcom_report(gnss_msg) + report_time = gps_time_from_qcom_report(gnss_msg) except NotImplementedError: return False # Garbage timestamps with week > 32767 are sometimes sent by module. @@ -216,21 +202,24 @@ class Laikad: def read_report(self, gnss_msg): if self.use_qcom: # QCOM reports are per constellation, so we need to aggregate them - report = gnss_msg.drMeasurementReport - report_time = self.gps_time_from_qcom_report(gnss_msg) - - if report_time - self.last_report_time > 0: + # Additionally, the pseudoranges are broken in the measurementReports + # and the doppler filteredSpeed is broken in the drMeasurementReports + report_time = gps_time_from_qcom_report(gnss_msg) + if report_time - self.last_report_time == 0: + self.qcom_reports.append(gnss_msg) + self.last_report_time = report_time + elif report_time - self.last_report_time > 0: self.qcom_reports_received = max(1, len(self.qcom_reports)) - self.qcom_reports = [report] + self.qcom_reports = [gnss_msg] + self.last_report_time = report_time else: - self.qcom_reports.append(report) - self.last_report_time = report_time + # Sometimes DR reports get sent one iteration late (1second), they need to be ignored + cloudlog.warning(f"Received report with time {report_time} before last report time {self.last_report_time}") - new_meas = [] if len(self.qcom_reports) == self.qcom_reports_received: - for report in self.qcom_reports: - new_meas.extend(read_raw_qcom(report)) - + new_meas = get_measurements_from_qcom_reports(self.qcom_reports) + else: + new_meas = [] else: report = gnss_msg.measurementReport self.last_report_time = GPSTime(report.gpsWeek, report.rcvTow) @@ -284,6 +273,11 @@ class Laikad: est_pos = self.gnss_kf.x[GStates.ECEF_POS].tolist() correct_delay = False corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog, correct_delay=correct_delay) + # If many measurements weren't corrected, position may be garbage, so reset + if len(processed_measurements) >= 8 and len(corrected_measurements) < 5: + cloudlog.error("Didn't correct enough measurements, resetting estimate position") + self.last_fix_pos = None + self.last_fix_t = None return corrected_measurements def calc_fix(self, t, measurements): @@ -300,7 +294,7 @@ class Laikad: def process_gnss_msg(self, gnss_msg, gnss_mono_time: int, block=False): out_msg = messaging.new_message("gnssMeasurements") t = gnss_mono_time * 1e-9 - msg_dict: Dict[str, Any] = {"measTime": gnss_mono_time} + msg_dict: Dict[str, Any] = {"measTime": gnss_mono_time - int(1e9 * self.measurement_lag)} if self.first_log_time is None: self.first_log_time = 1e-9 * gnss_mono_time if self.is_ephemeris(gnss_msg): @@ -441,9 +435,9 @@ def kf_add_observations(gnss_kf: GNSSKalman, t: float, measurements: List[GNSSMe def clear_tmp_cache(): - if os.path.exists(DOWNLOADS_CACHE_FOLDER): - shutil.rmtree(DOWNLOADS_CACHE_FOLDER) - os.mkdir(DOWNLOADS_CACHE_FOLDER) + if os.path.exists(Paths.download_cache_root()): + shutil.rmtree(Paths.download_cache_root()) + os.mkdir(Paths.download_cache_root()) def main(sm=None, pm=None): diff --git a/selfdrive/locationd/liblocationd.cc b/selfdrive/locationd/liblocationd.cc deleted file mode 100755 index 6f298deab6..0000000000 --- a/selfdrive/locationd/liblocationd.cc +++ /dev/null @@ -1,41 +0,0 @@ -#include "locationd.h" - -extern "C" { - typedef Localizer* Localizer_t; - - Localizer *localizer_init(bool has_ublox) { - return new Localizer(has_ublox ? LocalizerGnssSource::UBLOX : LocalizerGnssSource::QCOM); - } - - void localizer_get_message_bytes(Localizer *localizer, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid, - char *buff, size_t buff_size) { - MessageBuilder msg_builder; - kj::ArrayPtr arr = localizer->get_message_bytes(msg_builder, inputsOK, sensorsOK, gpsOK, msgValid).asChars(); - assert(buff_size >= arr.size()); - memcpy(buff, arr.begin(), arr.size()); - } - - void localizer_handle_msg_bytes(Localizer *localizer, const char *data, size_t size) { - localizer->handle_msg_bytes(data, size); - } - - void get_filter_internals(Localizer *localizer, double *state_buff, double *std_buff){ - Eigen::VectorXd state = localizer->get_state(); - memcpy(state_buff, state.data(), sizeof(double) * state.size()); - Eigen::VectorXd stdev = localizer->get_stdev(); - memcpy(std_buff, stdev.data(), sizeof(double) * stdev.size()); - } - - bool is_gps_ok(Localizer *localizer){ - return localizer->is_gps_ok(); - } - - bool are_inputs_ok(Localizer *localizer){ - return localizer->are_inputs_ok(); - } - - void observation_timings_invalid_reset(Localizer *localizer){ - localizer->observation_timings_invalid_reset(); - } - -} diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc old mode 100755 new mode 100644 index 9b3e3b3b85..600ba46853 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -1,9 +1,11 @@ +#include "selfdrive/locationd/locationd.h" + #include #include +#include #include - -#include "locationd.h" +#include using namespace EKFS; using namespace Eigen; @@ -19,9 +21,11 @@ const double VALID_TIME_SINCE_RESET = 1.0; // s const double VALID_POS_STD = 50.0; // m const double MAX_RESET_TRACKER = 5.0; const double SANE_GPS_UNCERTAINTY = 1500.0; // m -const double INPUT_INVALID_THRESHOLD = 5.0; // same as reset tracker -const double DECAY = 0.99995; // same as reset tracker +const double INPUT_INVALID_THRESHOLD = 0.5; // same as reset tracker +const double RESET_TRACKER_DECAY = 0.99995; +const double DECAY = 0.9993; // ~10 secs to resume after a bad input const double MAX_FILTER_REWIND_TIME = 0.8; // s +const double YAWRATE_CROSS_ERR_CHECK_FACTOR = 30; // TODO: GPS sensor time offsets are empirically calculated // They should be replaced with synced time from a real clock @@ -240,8 +244,7 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData LOGE("Sensor reading ignored, sensor timestamp more than 100ms off from log time"); this->observation_timings_invalid = true; return; - } - else if (!this->is_timestamp_valid(sensor_time)) { + } else if (!this->is_timestamp_valid(sensor_time)) { this->observation_timings_invalid = true; return; } @@ -255,11 +258,16 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData if (log.getSensor() == SENSOR_GYRO_UNCALIBRATED && log.getType() == SENSOR_TYPE_GYROSCOPE_UNCALIBRATED) { auto v = log.getGyroUncalibrated().getV(); auto meas = Vector3d(-v[2], -v[1], -v[0]); - if (meas.norm() < ROTATION_SANITY_CHECK) { + + VectorXd gyro_bias = this->kf->get_x().segment(STATE_GYRO_BIAS_START); + float gyro_camodo_yawrate_err = std::abs((meas[2] - gyro_bias[2]) - this->camodo_yawrate_distribution[0]); + float gyro_camodo_yawrate_err_threshold = YAWRATE_CROSS_ERR_CHECK_FACTOR * this->camodo_yawrate_distribution[1]; + bool gyro_valid = gyro_camodo_yawrate_err < gyro_camodo_yawrate_err_threshold; + + if ((meas.norm() < ROTATION_SANITY_CHECK) && gyro_valid) { this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_GYRO, { meas }); this->observation_values_invalid["gyroscope"] *= DECAY; - } - else{ + } else { this->observation_values_invalid["gyroscope"] += 1.0; } } @@ -277,8 +285,7 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData if (meas.norm() < ACCEL_SANITY_CHECK) { this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_ACCEL, { meas }); this->observation_values_invalid["accelerometer"] *= DECAY; - } - else{ + } else { this->observation_values_invalid["accelerometer"] += 1.0; } } @@ -293,8 +300,8 @@ void Localizer::input_fake_gps_observations(double current_time) { VectorXd current_x = this->kf->get_x(); VectorXd ecef_pos = current_x.segment(STATE_ECEF_POS_START); VectorXd ecef_vel = current_x.segment(STATE_ECEF_VELOCITY_START); - MatrixXdr ecef_pos_R = this->kf->get_fake_gps_pos_cov(); - MatrixXdr ecef_vel_R = this->kf->get_fake_gps_vel_cov(); + const MatrixXdr &ecef_pos_R = this->kf->get_fake_gps_pos_cov(); + const MatrixXdr &ecef_vel_R = this->kf->get_fake_gps_vel_cov(); this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_POS, { ecef_pos }, { ecef_pos_R }); this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_VEL, { ecef_vel }, { ecef_vel_R }); @@ -360,7 +367,7 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R void Localizer::handle_gnss(double current_time, const cereal::GnssMeasurements::Reader& log) { - if(!log.getPositionECEF().getValid() || !log.getVelocityECEF().getValid()) { + if (!log.getPositionECEF().getValid() || !log.getVelocityECEF().getValid()) { this->determine_gps_mode(current_time); return; } @@ -414,8 +421,7 @@ void Localizer::handle_gnss(double current_time, const cereal::GnssMeasurements: orientation_reset &= !this->standstill; if (orientation_reset) { this->orientation_reset_count++; - } - else { + } else { this->orientation_reset_count = 0; } @@ -485,6 +491,7 @@ void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry this->kf->predict_and_observe(current_time, OBSERVATION_CAMERA_ODO_TRANSLATION, { trans_device }, { trans_device_cov }); this->observation_values_invalid["cameraOdometry"] *= DECAY; + this->camodo_yawrate_distribution = Vector2d(rot_device[2], rotate_std(this->device_from_calib, rot_calib_std)[2]); } void Localizer::handle_live_calib(double current_time, const cereal::LiveCalibrationData::Reader& log) { @@ -509,8 +516,8 @@ void Localizer::handle_live_calib(double current_time, const cereal::LiveCalibra } void Localizer::reset_kalman(double current_time) { - VectorXd init_x = this->kf->get_initial_x(); - MatrixXdr init_P = this->kf->get_initial_P(); + const VectorXd &init_x = this->kf->get_initial_x(); + const MatrixXdr &init_P = this->kf->get_initial_P(); this->reset_kalman(current_time, init_x, init_P); } @@ -540,18 +547,18 @@ void Localizer::time_check(double current_time) { void Localizer::update_reset_tracker() { // reset tracker is tuned to trigger when over 1reset/10s over 2min period if (this->is_gps_ok()) { - this->reset_tracker *= DECAY; + this->reset_tracker *= RESET_TRACKER_DECAY; } else { this->reset_tracker = 0.0; } } -void Localizer::reset_kalman(double current_time, VectorXd init_orient, VectorXd init_pos, VectorXd init_vel, MatrixXdr init_pos_R, MatrixXdr init_vel_R) { +void Localizer::reset_kalman(double current_time, const VectorXd &init_orient, const VectorXd &init_pos, const VectorXd &init_vel, const MatrixXdr &init_pos_R, const MatrixXdr &init_vel_R) { // too nonlinear to init on completely wrong VectorXd current_x = this->kf->get_x(); MatrixXdr current_P = this->kf->get_P(); MatrixXdr init_P = this->kf->get_initial_P(); - MatrixXdr reset_orientation_P = this->kf->get_reset_orientation_P(); + const MatrixXdr &reset_orientation_P = this->kf->get_reset_orientation_P(); int non_ecef_state_err_len = init_P.rows() - (STATE_ECEF_POS_ERR_LEN + STATE_ECEF_ORIENTATION_ERR_LEN + STATE_ECEF_VELOCITY_ERR_LEN); current_x.segment(STATE_ECEF_ORIENTATION_START) = init_orient; @@ -561,12 +568,13 @@ void Localizer::reset_kalman(double current_time, VectorXd init_orient, VectorXd init_P.block(STATE_ECEF_POS_ERR_START, STATE_ECEF_POS_ERR_START).diagonal() = init_pos_R.diagonal(); init_P.block(STATE_ECEF_ORIENTATION_ERR_START, STATE_ECEF_ORIENTATION_ERR_START).diagonal() = reset_orientation_P.diagonal(); init_P.block(STATE_ECEF_VELOCITY_ERR_START, STATE_ECEF_VELOCITY_ERR_START).diagonal() = init_vel_R.diagonal(); - init_P.block(STATE_ANGULAR_VELOCITY_ERR_START, STATE_ANGULAR_VELOCITY_ERR_START, non_ecef_state_err_len, non_ecef_state_err_len).diagonal() = current_P.block(STATE_ANGULAR_VELOCITY_ERR_START, STATE_ANGULAR_VELOCITY_ERR_START, non_ecef_state_err_len, non_ecef_state_err_len).diagonal(); + init_P.block(STATE_ANGULAR_VELOCITY_ERR_START, STATE_ANGULAR_VELOCITY_ERR_START, non_ecef_state_err_len, non_ecef_state_err_len).diagonal() = current_P.block(STATE_ANGULAR_VELOCITY_ERR_START, + STATE_ANGULAR_VELOCITY_ERR_START, non_ecef_state_err_len, non_ecef_state_err_len).diagonal(); this->reset_kalman(current_time, current_x, init_P); } -void Localizer::reset_kalman(double current_time, VectorXd init_x, MatrixXdr init_P) { +void Localizer::reset_kalman(double current_time, const VectorXd &init_x, const MatrixXdr &init_P) { this->kf->init_state(init_x, init_P, current_time); this->last_reset_time = current_time; this->reset_tracker += 1.0; @@ -621,7 +629,7 @@ bool Localizer::is_gps_ok() { return (this->kf->get_filter_time() - this->last_gps_msg) < 2.0; } -bool Localizer::critical_services_valid(std::map critical_services) { +bool Localizer::critical_services_valid(const std::map &critical_services) { for (auto &kv : critical_services){ if (kv.second >= INPUT_INVALID_THRESHOLD){ return false; @@ -648,14 +656,13 @@ void Localizer::determine_gps_mode(double current_time) { if (this->gps_mode){ this->gps_mode = false; this->reset_kalman(current_time); - } - else{ + } else { this->input_fake_gps_observations(current_time); } } } -void Localizer::configure_gnss_source(LocalizerGnssSource source) { +void Localizer::configure_gnss_source(const LocalizerGnssSource &source) { this->gnss_source = source; if (source == LocalizerGnssSource::UBLOX) { this->gps_std_factor = 10.0; diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h old mode 100755 new mode 100644 index e8f2f04a2c..47c8bf5627 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -32,13 +33,13 @@ public: int locationd_thread(); void reset_kalman(double current_time = NAN); - void reset_kalman(double current_time, Eigen::VectorXd init_orient, Eigen::VectorXd init_pos, Eigen::VectorXd init_vel, MatrixXdr init_pos_R, MatrixXdr init_vel_R); - void reset_kalman(double current_time, Eigen::VectorXd init_x, MatrixXdr init_P); + void reset_kalman(double current_time, const Eigen::VectorXd &init_orient, const Eigen::VectorXd &init_pos, const Eigen::VectorXd &init_vel, const MatrixXdr &init_pos_R, const MatrixXdr &init_vel_R); + void reset_kalman(double current_time, const Eigen::VectorXd &init_x, const MatrixXdr &init_P); void finite_check(double current_time = NAN); void time_check(double current_time = NAN); void update_reset_tracker(); bool is_gps_ok(); - bool critical_services_valid(std::map critical_services); + bool critical_services_valid(const std::map &critical_services); bool is_timestamp_valid(double current_time); void determine_gps_mode(double current_time); bool are_inputs_ok(); @@ -93,6 +94,7 @@ private: float gps_variance_factor; float gps_vertical_variance_factor; double gps_time_offset; + Eigen::VectorXd camodo_yawrate_distribution = Eigen::Vector2d(0.0, 10.0); // mean, std - void configure_gnss_source(LocalizerGnssSource source); + void configure_gnss_source(const LocalizerGnssSource &source); }; diff --git a/selfdrive/locationd/models/car_kf.py b/selfdrive/locationd/models/car_kf.py index a5abf8514a..b87c83cac2 100755 --- a/selfdrive/locationd/models/car_kf.py +++ b/selfdrive/locationd/models/car_kf.py @@ -5,9 +5,9 @@ from typing import Any, Dict import numpy as np -from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY -from selfdrive.locationd.models.constants import ObservationKind -from system.swaglog import cloudlog +from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY +from openpilot.selfdrive.locationd.models.constants import ObservationKind +from openpilot.system.swaglog import cloudlog from rednose.helpers.kalmanfilter import KalmanFilter @@ -15,7 +15,7 @@ if __name__ == '__main__': # Generating sympy import sympy as sp from rednose.helpers.ekf_sym import gen_code else: - from rednose.helpers.ekf_sym_pyx import EKF_sym_pyx # pylint: disable=no-name-in-module, import-error + from rednose.helpers.ekf_sym_pyx import EKF_sym_pyx i = 0 @@ -160,7 +160,7 @@ class CarKalman(KalmanFilter): gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state, global_vars=global_vars) - def __init__(self, generated_dir, steer_ratio=15, stiffness_factor=1, angle_offset=0, P_initial=None): # pylint: disable=super-init-not-called + def __init__(self, generated_dir, steer_ratio=15, stiffness_factor=1, angle_offset=0, P_initial=None): dim_state = self.initial_x.shape[0] dim_state_err = self.P_initial.shape[0] x_init = self.initial_x diff --git a/selfdrive/locationd/models/gnss_kf.py b/selfdrive/locationd/models/gnss_kf.py index 0d661dc321..c4f3b2e210 100755 --- a/selfdrive/locationd/models/gnss_kf.py +++ b/selfdrive/locationd/models/gnss_kf.py @@ -4,15 +4,15 @@ from typing import List import numpy as np -from selfdrive.locationd.models.constants import ObservationKind -from selfdrive.locationd.models.gnss_helpers import parse_pr, parse_prr +from openpilot.selfdrive.locationd.models.constants import ObservationKind +from openpilot.selfdrive.locationd.models.gnss_helpers import parse_pr, parse_prr if __name__ == '__main__': # Generating sympy import sympy as sp from rednose.helpers.ekf_sym import gen_code else: - from rednose.helpers.ekf_sym_pyx import EKF_sym_pyx # pylint: disable=no-name-in-module,import-error - from rednose.helpers.ekf_sym import EKF_sym # pylint: disable=no-name-in-module,import-error + from rednose.helpers.ekf_sym_pyx import EKF_sym_pyx + from rednose.helpers.ekf_sym import EKF_sym class States(): diff --git a/selfdrive/locationd/models/lane_kf.py b/selfdrive/locationd/models/lane_kf.py index 4d38fa8e09..e8fa999956 100755 --- a/selfdrive/locationd/models/lane_kf.py +++ b/selfdrive/locationd/models/lane_kf.py @@ -3,7 +3,7 @@ import sys import numpy as np import sympy as sp -from selfdrive.locationd.models.constants import ObservationKind +from openpilot.selfdrive.locationd.models.constants import ObservationKind from rednose.helpers.ekf_sym import gen_code, EKF_sym diff --git a/selfdrive/locationd/models/live_kf.cc b/selfdrive/locationd/models/live_kf.cc old mode 100755 new mode 100644 index f8c03365e1..fc3bfb7246 --- a/selfdrive/locationd/models/live_kf.cc +++ b/selfdrive/locationd/models/live_kf.cc @@ -1,27 +1,27 @@ -#include "live_kf.h" +#include "selfdrive/locationd/models/live_kf.h" using namespace EKFS; using namespace Eigen; -Eigen::Map get_mapvec(Eigen::VectorXd& vec) { - return Eigen::Map(vec.data(), vec.rows(), vec.cols()); +Eigen::Map get_mapvec(const Eigen::VectorXd &vec) { + return Eigen::Map((double*)vec.data(), vec.rows(), vec.cols()); } -Eigen::Map get_mapmat(MatrixXdr& mat) { - return Eigen::Map(mat.data(), mat.rows(), mat.cols()); +Eigen::Map get_mapmat(const MatrixXdr &mat) { + return Eigen::Map((double*)mat.data(), mat.rows(), mat.cols()); } -std::vector> get_vec_mapvec(std::vector& vec_vec) { +std::vector> get_vec_mapvec(const std::vector &vec_vec) { std::vector> res; - for (Eigen::VectorXd& vec : vec_vec) { + for (const Eigen::VectorXd &vec : vec_vec) { res.push_back(get_mapvec(vec)); } return res; } -std::vector> get_vec_mapmat(std::vector& mat_vec) { +std::vector> get_vec_mapmat(const std::vector &mat_vec) { std::vector> res; - for (MatrixXdr& mat : mat_vec) { + for (const MatrixXdr &mat : mat_vec) { res.push_back(get_mapmat(mat)); } return res; @@ -43,20 +43,20 @@ LiveKalman::LiveKalman() { // init filter this->filter = std::make_shared(this->name, get_mapmat(this->Q), get_mapvec(this->initial_x), - get_mapmat(initial_P), this->dim_state, this->dim_state_err, 0, 0, 0, std::vector(), + get_mapmat(initial_P), this->dim_state, this->dim_state_err, 0, 0, 0, std::vector(), std::vector{3}, std::vector(), 0.8); } -void LiveKalman::init_state(VectorXd& state, VectorXd& covs_diag, double filter_time) { +void LiveKalman::init_state(const VectorXd &state, const VectorXd &covs_diag, double filter_time) { MatrixXdr covs = covs_diag.asDiagonal(); this->filter->init_state(get_mapvec(state), get_mapmat(covs), filter_time); } -void LiveKalman::init_state(VectorXd& state, MatrixXdr& covs, double filter_time) { +void LiveKalman::init_state(const VectorXd &state, const MatrixXdr &covs, double filter_time) { this->filter->init_state(get_mapvec(state), get_mapmat(covs), filter_time); } -void LiveKalman::init_state(VectorXd& state, double filter_time) { +void LiveKalman::init_state(const VectorXd &state, double filter_time) { MatrixXdr covs = this->filter->covs(); this->filter->init_state(get_mapvec(state), get_mapmat(covs), filter_time); } @@ -81,7 +81,7 @@ std::vector LiveKalman::get_R(int kind, int n) { return R; } -std::optional LiveKalman::predict_and_observe(double t, int kind, std::vector meas, std::vector R) { +std::optional LiveKalman::predict_and_observe(double t, int kind, const std::vector &meas, std::vector R) { std::optional r; if (R.size() == 0) { R = this->get_R(kind, meas.size()); @@ -94,29 +94,29 @@ void LiveKalman::predict(double t) { this->filter->predict(t); } -Eigen::VectorXd LiveKalman::get_initial_x() { +const Eigen::VectorXd &LiveKalman::get_initial_x() { return this->initial_x; } -MatrixXdr LiveKalman::get_initial_P() { +const MatrixXdr &LiveKalman::get_initial_P() { return this->initial_P; } -MatrixXdr LiveKalman::get_fake_gps_pos_cov() { +const MatrixXdr &LiveKalman::get_fake_gps_pos_cov() { return this->fake_gps_pos_cov; } -MatrixXdr LiveKalman::get_fake_gps_vel_cov() { +const MatrixXdr &LiveKalman::get_fake_gps_vel_cov() { return this->fake_gps_vel_cov; } -MatrixXdr LiveKalman::get_reset_orientation_P() { +const MatrixXdr &LiveKalman::get_reset_orientation_P() { return this->reset_orientation_P; } -MatrixXdr LiveKalman::H(VectorXd in) { +MatrixXdr LiveKalman::H(const VectorXd &in) { assert(in.size() == 6); Matrix res; - this->filter->get_extra_routine("H")(in.data(), res.data()); + this->filter->get_extra_routine("H")((double*)in.data(), res.data()); return res; } diff --git a/selfdrive/locationd/models/live_kf.h b/selfdrive/locationd/models/live_kf.h old mode 100755 new mode 100644 index 06ec3854cb..e4b3e326b3 --- a/selfdrive/locationd/models/live_kf.h +++ b/selfdrive/locationd/models/live_kf.h @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -14,37 +16,37 @@ using namespace EKFS; -Eigen::Map get_mapvec(Eigen::VectorXd& vec); -Eigen::Map get_mapmat(MatrixXdr& mat); -std::vector> get_vec_mapvec(std::vector& vec_vec); -std::vector> get_vec_mapmat(std::vector& mat_vec); +Eigen::Map get_mapvec(const Eigen::VectorXd &vec); +Eigen::Map get_mapmat(const MatrixXdr &mat); +std::vector> get_vec_mapvec(const std::vector &vec_vec); +std::vector> get_vec_mapmat(const std::vector &mat_vec); class LiveKalman { public: LiveKalman(); - void init_state(Eigen::VectorXd& state, Eigen::VectorXd& covs_diag, double filter_time); - void init_state(Eigen::VectorXd& state, MatrixXdr& covs, double filter_time); - void init_state(Eigen::VectorXd& state, double filter_time); + void init_state(const Eigen::VectorXd &state, const Eigen::VectorXd &covs_diag, double filter_time); + void init_state(const Eigen::VectorXd &state, const MatrixXdr &covs, double filter_time); + void init_state(const Eigen::VectorXd &state, double filter_time); Eigen::VectorXd get_x(); MatrixXdr get_P(); double get_filter_time(); std::vector get_R(int kind, int n); - std::optional predict_and_observe(double t, int kind, std::vector meas, std::vector R = {}); + std::optional predict_and_observe(double t, int kind, const std::vector &meas, std::vector R = {}); std::optional predict_and_update_odo_speed(std::vector speed, double t, int kind); std::optional predict_and_update_odo_trans(std::vector trans, double t, int kind); std::optional predict_and_update_odo_rot(std::vector rot, double t, int kind); void predict(double t); - Eigen::VectorXd get_initial_x(); - MatrixXdr get_initial_P(); - MatrixXdr get_fake_gps_pos_cov(); - MatrixXdr get_fake_gps_vel_cov(); - MatrixXdr get_reset_orientation_P(); + const Eigen::VectorXd &get_initial_x(); + const MatrixXdr &get_initial_P(); + const MatrixXdr &get_fake_gps_pos_cov(); + const MatrixXdr &get_fake_gps_vel_cov(); + const MatrixXdr &get_reset_orientation_P(); - MatrixXdr H(Eigen::VectorXd in); + MatrixXdr H(const Eigen::VectorXd &in); private: std::string name = "live"; diff --git a/selfdrive/locationd/models/live_kf.py b/selfdrive/locationd/models/live_kf.py index dc439c23f6..c4933a6129 100755 --- a/selfdrive/locationd/models/live_kf.py +++ b/selfdrive/locationd/models/live_kf.py @@ -4,7 +4,7 @@ import sys import os import numpy as np -from selfdrive.locationd.models.constants import ObservationKind +from openpilot.selfdrive.locationd.models.constants import ObservationKind import sympy as sp import inspect diff --git a/selfdrive/locationd/models/loc_kf.py b/selfdrive/locationd/models/loc_kf.py index d865509e95..8bd317bd58 100755 --- a/selfdrive/locationd/models/loc_kf.py +++ b/selfdrive/locationd/models/loc_kf.py @@ -9,8 +9,8 @@ from rednose.helpers.ekf_sym import EKF_sym, gen_code from rednose.helpers.lst_sq_computer import LstSqComputer from rednose.helpers.sympy_helpers import euler_rotate, quat_matrix_r, quat_rotate -from selfdrive.locationd.models.constants import ObservationKind -from selfdrive.locationd.models.gnss_helpers import parse_pr, parse_prr +from openpilot.selfdrive.locationd.models.constants import ObservationKind +from openpilot.selfdrive.locationd.models.gnss_helpers import parse_pr, parse_prr EARTH_GM = 3.986005e14 # m^3/s^2 (gravitational constant * mass of earth) diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 1644ceaf92..55ad62145e 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -7,12 +7,12 @@ import numpy as np import cereal.messaging as messaging from cereal import car from cereal import log -from common.params import Params, put_nonblocking -from common.realtime import config_realtime_process, DT_MDL -from common.numpy_fast import clip -from selfdrive.locationd.models.car_kf import CarKalman, ObservationKind, States -from selfdrive.locationd.models.constants import GENERATED_DIR -from system.swaglog import cloudlog +from openpilot.common.params import Params, put_nonblocking +from openpilot.common.realtime import config_realtime_process, DT_MDL +from openpilot.common.numpy_fast import clip +from openpilot.selfdrive.locationd.models.car_kf import CarKalman, ObservationKind, States +from openpilot.selfdrive.locationd.models.constants import GENERATED_DIR +from openpilot.system.swaglog import cloudlog MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s diff --git a/selfdrive/locationd/test/_test_locationd_lib.py b/selfdrive/locationd/test/_test_locationd_lib.py deleted file mode 100755 index 97207908e7..0000000000 --- a/selfdrive/locationd/test/_test_locationd_lib.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 -"""This test can't be run together with other locationd tests. -cffi.dlopen breaks the list of registered filters.""" -import os -import random -import unittest - -from cffi import FFI - -import cereal.messaging as messaging -from cereal import log - -from common.ffi_wrapper import suffix - -SENSOR_DECIMATION = 1 -VISION_DECIMATION = 1 - -LIBLOCATIOND_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../liblocationd' + suffix())) - - -class TestLocationdLib(unittest.TestCase): - def setUp(self): - header = '''typedef ...* Localizer_t; -Localizer_t localizer_init(bool has_ublox); -void localizer_get_message_bytes(Localizer_t localizer, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid, char *buff, size_t buff_size); -void localizer_handle_msg_bytes(Localizer_t localizer, const char *data, size_t size);''' - - self.ffi = FFI() - self.ffi.cdef(header) - self.lib = self.ffi.dlopen(LIBLOCATIOND_PATH) - - self.localizer = self.lib.localizer_init(True) # default to ublox - - self.buff_size = 2048 - self.msg_buff = self.ffi.new(f'char[{self.buff_size}]') - - def localizer_handle_msg(self, msg_builder): - bytstr = msg_builder.to_bytes() - self.lib.localizer_handle_msg_bytes(self.localizer, self.ffi.from_buffer(bytstr), len(bytstr)) - - def localizer_get_msg(self, t=0, inputsOK=True, sensorsOK=True, gpsOK=True, msgValid=True): - self.lib.localizer_get_message_bytes(self.localizer, inputsOK, sensorsOK, gpsOK, msgValid, self.ffi.addressof(self.msg_buff, 0), self.buff_size) - with log.Event.from_bytes(self.ffi.buffer(self.msg_buff), nesting_limit=self.buff_size // 8) as log_evt: - return log_evt - - def test_liblocalizer(self): - msg = messaging.new_message('liveCalibration') - msg.liveCalibration.validBlocks = random.randint(1, 10) - msg.liveCalibration.rpyCalib = [random.random() / 10 for _ in range(3)] - - self.localizer_handle_msg(msg) - liveloc = self.localizer_get_msg() - self.assertTrue(liveloc is not None) - - @unittest.skip("temporarily disabled due to false positives") - def test_device_fell(self): - msg = messaging.new_message('accelerometer') - msg.accelerometer.sensor = 1 - msg.accelerometer.timestamp = msg.logMonoTime - msg.accelerometer.type = 1 - msg.accelerometer.init('acceleration') - msg.accelerometer.acceleration.v = [10.0, 0.0, 0.0] # zero with gravity - self.localizer_handle_msg(msg) - - ret = self.localizer_get_msg() - self.assertTrue(ret.liveLocationKalman.deviceStable) - - msg = messaging.new_message('accelerometer') - msg.accelerometer.sensor = 1 - msg.accelerometer.timestamp = msg.logMonoTime - msg.accelerometer.type = 1 - msg.accelerometer.init('acceleration') - msg.accelerometer.acceleration.v = [50.1, 0.0, 0.0] # more than 40 m/s**2 - self.localizer_handle_msg(msg) - - ret = self.localizer_get_msg() - self.assertFalse(ret.liveLocationKalman.deviceStable) - - def test_posenet_spike(self): - for _ in range(SENSOR_DECIMATION): - msg = messaging.new_message('carState') - msg.carState.vEgo = 6.0 # more than 5 m/s - self.localizer_handle_msg(msg) - - ret = self.localizer_get_msg() - self.assertTrue(ret.liveLocationKalman.posenetOK) - - for _ in range(20 * VISION_DECIMATION): # size of hist_old - msg = messaging.new_message('cameraOdometry') - msg.cameraOdometry.rot = [0.0, 0.0, 0.0] - msg.cameraOdometry.rotStd = [0.1, 0.1, 0.1] - msg.cameraOdometry.trans = [0.0, 0.0, 0.0] - msg.cameraOdometry.transStd = [2.0, 0.1, 0.1] - self.localizer_handle_msg(msg) - - for _ in range(20 * VISION_DECIMATION): # size of hist_new - msg = messaging.new_message('cameraOdometry') - msg.cameraOdometry.rot = [0.0, 0.0, 0.0] - msg.cameraOdometry.rotStd = [1.0, 1.0, 1.0] - msg.cameraOdometry.trans = [0.0, 0.0, 0.0] - msg.cameraOdometry.transStd = [10.1, 0.1, 0.1] # more than 4 times larger - self.localizer_handle_msg(msg) - - ret = self.localizer_get_msg() - self.assertFalse(ret.liveLocationKalman.posenetOK) - -if __name__ == "__main__": - unittest.main() - diff --git a/selfdrive/locationd/test/test_calibrationd.py b/selfdrive/locationd/test/test_calibrationd.py index e3e8c3ca7c..a5eedaf99a 100755 --- a/selfdrive/locationd/test/test_calibrationd.py +++ b/selfdrive/locationd/test/test_calibrationd.py @@ -6,8 +6,8 @@ import numpy as np import cereal.messaging as messaging from cereal import log -from common.params import Params -from selfdrive.locationd.calibrationd import Calibrator, INPUTS_NEEDED, INPUTS_WANTED, BLOCK_SIZE, MIN_SPEED_FILTER, \ +from openpilot.common.params import Params +from openpilot.selfdrive.locationd.calibrationd import Calibrator, INPUTS_NEEDED, INPUTS_WANTED, BLOCK_SIZE, MIN_SPEED_FILTER, \ MAX_YAW_RATE_FILTER, SMOOTH_CYCLES, HEIGHT_INIT diff --git a/selfdrive/locationd/test/test_laikad.py b/selfdrive/locationd/test/test_laikad.py index c90095cdca..2c4a8a406d 100755 --- a/selfdrive/locationd/test/test_laikad.py +++ b/selfdrive/locationd/test/test_laikad.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 +import os import time import unittest from cereal import log -from common.params import Params +from openpilot.common.params import Params from datetime import datetime from unittest import mock @@ -13,11 +14,12 @@ from laika.ephemeris import EphemerisType from laika.gps_time import GPSTime from laika.helpers import ConstellationId from laika.raw_gnss import GNSSMeasurement, read_raw_ublox, read_raw_qcom -from selfdrive.locationd.laikad import EPHEMERIS_CACHE, Laikad -from selfdrive.test.openpilotci import get_url -from tools.lib.logreader import LogReader +from openpilot.selfdrive.locationd.laikad import EPHEMERIS_CACHE, Laikad +from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.tools.lib.logreader import LogReader -from selfdrive.test.process_replay.process_replay import get_process_config, replay_process +from openpilot.selfdrive.test.process_replay.process_replay import get_process_config, replay_process +from openpilot.selfdrive.test.helpers import SKIP_ENV_VAR GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC = GPSTime.from_datetime(datetime(2022, month=1, day=29, hour=12)) UBLOX_TEST_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" @@ -92,6 +94,7 @@ def get_measurement_mock(gpstime, sat_ephemeris): return meas +@unittest.skipIf(SKIP_ENV_VAR in os.environ, f"Laika test skipped since it's long and not currently used. Unset {SKIP_ENV_VAR} to run") class TestLaikad(unittest.TestCase): @classmethod @@ -177,8 +180,8 @@ class TestLaikad(unittest.TestCase): laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.NAV, use_qcom=use_qcom) # Disable fetch_orbits to test NAV only correct_msgs = verify_messages(logs, laikad) - correct_msgs_expected = 56 if use_qcom else 560 - valid_fix_expected = 56 if use_qcom else 560 + correct_msgs_expected = 55 if use_qcom else 560 + valid_fix_expected = 55 if use_qcom else 560 self.assertEqual(correct_msgs_expected, len(correct_msgs)) self.assertEqual(valid_fix_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) diff --git a/selfdrive/locationd/test/test_locationd.py b/selfdrive/locationd/test/test_locationd.py index 96233f5320..99047c37f3 100755 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -7,10 +7,10 @@ import capnp import cereal.messaging as messaging from cereal.services import service_list -from common.params import Params -from common.transformations.coordinates import ecef2geodetic +from openpilot.common.params import Params +from openpilot.common.transformations.coordinates import ecef2geodetic -from selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.manager.process_config import managed_processes class TestLocationdProc(unittest.TestCase): @@ -80,7 +80,7 @@ class TestLocationdProc(unittest.TestCase): for msg in sorted(msgs, key=lambda x: x.logMonoTime): self.pm.send(msg.which(), msg) if msg.which() == "cameraOdometry": - self.pm.wait_for_readers_to_update(msg.which(), 0.1) + self.pm.wait_for_readers_to_update(msg.which(), 0.1, dt=0.005) time.sleep(1) # wait for async params write lastGPS = json.loads(self.params.get('LastGPSPosition')) diff --git a/selfdrive/locationd/test/test_locationd_scenarios.py b/selfdrive/locationd/test/test_locationd_scenarios.py new file mode 100755 index 0000000000..d2455ef9e0 --- /dev/null +++ b/selfdrive/locationd/test/test_locationd_scenarios.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +import unittest +import numpy as np +from collections import defaultdict +from enum import Enum + + +from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.tools.lib.logreader import LogReader +from openpilot.selfdrive.test.process_replay.process_replay import replay_process_with_name + +TEST_ROUTE, TEST_SEG_NUM = "ff2bd20623fcaeaa|2023-09-05--10-14-54", 4 +GPS_MESSAGES = ['gpsLocationExternal', 'gpsLocation'] +SELECT_COMPARE_FIELDS = { + 'yaw_rate': ['angularVelocityCalibrated', 'value', 2], + 'roll': ['orientationNED', 'value', 0], + 'gps_flag': ['gpsOK'], + 'inputs_flag': ['inputsOK'], + 'sensors_flag': ['sensorsOK'], +} +JUNK_IDX = 100 + + +class Scenario(Enum): + BASE = 'base' + GPS_OFF = 'gps_off' + GPS_OFF_MIDWAY = 'gps_off_midway' + GPS_ON_MIDWAY = 'gps_on_midway' + GPS_TUNNEL = 'gps_tunnel' + GYRO_OFF = 'gyro_off' + GYRO_SPIKE_MIDWAY = 'gyro_spike_midway' + ACCEL_OFF = 'accel_off' + ACCEL_SPIKE_MIDWAY = 'accel_spike_midway' + + +def get_select_fields_data(logs): + def get_nested_keys(msg, keys): + val = None + for key in keys: + val = getattr(msg if val is None else val, key) if isinstance(key, str) else val[key] + return val + llk = [x.liveLocationKalman for x in logs if x.which() == 'liveLocationKalman'] + data = defaultdict(list) + for msg in llk: + for key, fields in SELECT_COMPARE_FIELDS.items(): + data[key].append(get_nested_keys(msg, fields)) + for key in data: + data[key] = np.array(data[key][JUNK_IDX:], dtype=float) + return data + + +def run_scenarios(scenario, logs): + if scenario == Scenario.BASE: + pass + + elif scenario == Scenario.GPS_OFF: + logs = sorted([x for x in logs if x.which() not in GPS_MESSAGES], key=lambda x: x.logMonoTime) + + elif scenario == Scenario.GPS_OFF_MIDWAY: + non_gps = [x for x in logs if x.which() not in GPS_MESSAGES] + gps = [x for x in logs if x.which() in GPS_MESSAGES] + logs = sorted(non_gps + gps[: len(gps) // 2], key=lambda x: x.logMonoTime) + + elif scenario == Scenario.GPS_ON_MIDWAY: + non_gps = [x for x in logs if x.which() not in GPS_MESSAGES] + gps = [x for x in logs if x.which() in GPS_MESSAGES] + logs = sorted(non_gps + gps[len(gps) // 2:], key=lambda x: x.logMonoTime) + + elif scenario == Scenario.GPS_TUNNEL: + non_gps = [x for x in logs if x.which() not in GPS_MESSAGES] + gps = [x for x in logs if x.which() in GPS_MESSAGES] + logs = sorted(non_gps + gps[:len(gps) // 4] + gps[-len(gps) // 4:], key=lambda x: x.logMonoTime) + + elif scenario == Scenario.GYRO_OFF: + logs = sorted([x for x in logs if x.which() != 'gyroscope'], key=lambda x: x.logMonoTime) + + elif scenario == Scenario.GYRO_SPIKE_MIDWAY: + non_gyro = [x for x in logs if x.which() not in 'gyroscope'] + gyro = [x for x in logs if x.which() in 'gyroscope'] + temp = gyro[len(gyro) // 2].as_builder() + temp.gyroscope.gyroUncalibrated.v[0] += 3.0 + gyro[len(gyro) // 2] = temp.as_reader() + logs = sorted(non_gyro + gyro, key=lambda x: x.logMonoTime) + + elif scenario == Scenario.ACCEL_OFF: + logs = sorted([x for x in logs if x.which() != 'accelerometer'], key=lambda x: x.logMonoTime) + + elif scenario == Scenario.ACCEL_SPIKE_MIDWAY: + non_accel = [x for x in logs if x.which() not in 'accelerometer'] + accel = [x for x in logs if x.which() in 'accelerometer'] + temp = accel[len(accel) // 2].as_builder() + temp.accelerometer.acceleration.v[0] += 10.0 + accel[len(accel) // 2] = temp.as_reader() + logs = sorted(non_accel + accel, key=lambda x: x.logMonoTime) + + replayed_logs = replay_process_with_name(name='locationd', lr=logs) + return get_select_fields_data(logs), get_select_fields_data(replayed_logs) + + +class TestLocationdScenarios(unittest.TestCase): + """ + Test locationd with different scenarios. In all these scenarios, we expect the following: + - locationd kalman filter should never go unstable (we care mostly about yaw_rate, roll, gpsOK, inputsOK, sensorsOK) + - faulty values should be ignored, with appropriate flags set + """ + + @classmethod + def setUpClass(cls): + cls.logs = list(LogReader(get_url(TEST_ROUTE, TEST_SEG_NUM))) + + def test_base(self): + """ + Test: unchanged log + Expected Result: + - yaw_rate: unchanged + - roll: unchanged + """ + orig_data, replayed_data = run_scenarios(Scenario.BASE, self.logs) + self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) + self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) + + def test_gps_off(self): + """ + Test: no GPS message for the entire segment + Expected Result: + - yaw_rate: unchanged + - roll: + - gpsOK: False + """ + orig_data, replayed_data = run_scenarios(Scenario.GPS_OFF, self.logs) + self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) + self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) + self.assertTrue(np.all(replayed_data['gps_flag'] == 0.0)) + + def test_gps_off_midway(self): + """ + Test: no GPS message for the second half of the segment + Expected Result: + - yaw_rate: unchanged + - roll: + - gpsOK: True for the first half, False for the second half + """ + orig_data, replayed_data = run_scenarios(Scenario.GPS_OFF_MIDWAY, self.logs) + self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) + self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) + self.assertTrue(np.diff(replayed_data['gps_flag'])[512] == -1.0) + + def test_gps_on_midway(self): + """ + Test: no GPS message for the first half of the segment + Expected Result: + - yaw_rate: unchanged + - roll: + - gpsOK: False for the first half, True for the second half + """ + orig_data, replayed_data = run_scenarios(Scenario.GPS_ON_MIDWAY, self.logs) + self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) + self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(1.5))) + self.assertTrue(np.diff(replayed_data['gps_flag'])[505] == 1.0) + + def test_gps_tunnel(self): + """ + Test: no GPS message for the middle section of the segment + Expected Result: + - yaw_rate: unchanged + - roll: + - gpsOK: False for the middle section, True for the rest + """ + orig_data, replayed_data = run_scenarios(Scenario.GPS_TUNNEL, self.logs) + self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) + self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) + self.assertTrue(np.diff(replayed_data['gps_flag'])[213] == -1.0) + self.assertTrue(np.diff(replayed_data['gps_flag'])[805] == 1.0) + + def test_gyro_off(self): + """ + Test: no gyroscope message for the entire segment + Expected Result: + - yaw_rate: 0 + - roll: 0 + - sensorsOK: False + """ + _, replayed_data = run_scenarios(Scenario.GYRO_OFF, self.logs) + self.assertTrue(np.allclose(replayed_data['yaw_rate'], 0.0)) + self.assertTrue(np.allclose(replayed_data['roll'], 0.0)) + self.assertTrue(np.all(replayed_data['sensors_flag'] == 0.0)) + + def test_gyro_spikes(self): + """ + Test: a gyroscope spike in the middle of the segment + Expected Result: + - yaw_rate: unchanged + - roll: unchanged + - inputsOK: False for some time after the spike, True for the rest + """ + orig_data, replayed_data = run_scenarios(Scenario.GYRO_SPIKE_MIDWAY, self.logs) + self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) + self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) + self.assertTrue(np.diff(replayed_data['inputs_flag'])[500] == -1.0) + self.assertTrue(np.diff(replayed_data['inputs_flag'])[694] == 1.0) + + def test_accel_off(self): + """ + Test: no accelerometer message for the entire segment + Expected Result: + - yaw_rate: 0 + - roll: 0 + - sensorsOK: False + """ + _, replayed_data = run_scenarios(Scenario.ACCEL_OFF, self.logs) + self.assertTrue(np.allclose(replayed_data['yaw_rate'], 0.0)) + self.assertTrue(np.allclose(replayed_data['roll'], 0.0)) + self.assertTrue(np.all(replayed_data['sensors_flag'] == 0.0)) + + def test_accel_spikes(self): + """ + ToDo: + Test: an accelerometer spike in the middle of the segment + Expected Result: Right now, the kalman filter is not robust to small spikes like it is to gyroscope spikes. + """ + orig_data, replayed_data = run_scenarios(Scenario.ACCEL_SPIKE_MIDWAY, self.logs) + self.assertTrue(np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.2))) + self.assertTrue(np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.5))) + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 2bac2a8988..ddb95e944c 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -7,11 +7,11 @@ from collections import deque, defaultdict import cereal.messaging as messaging from cereal import car, log -from common.params import Params -from common.realtime import config_realtime_process, DT_MDL -from common.filter_simple import FirstOrderFilter -from system.swaglog import cloudlog -from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY +from openpilot.common.params import Params +from openpilot.common.realtime import config_realtime_process, DT_MDL +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.system.swaglog import cloudlog +from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY HISTORY = 5 # secs POINTS_PER_BUCKET = 1500 diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index 26003cd1de..be37b3e7e4 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 import os import subprocess -import textwrap from pathlib import Path +from typing import List # NOTE: Do NOT import anything here that needs be built (e.g. params) -from common.basedir import BASEDIR -from common.spinner import Spinner -from common.text_window import TextWindow -from system.hardware import AGNOS -from system.swaglog import cloudlog, add_file_handler -from system.version import is_dirty +from openpilot.common.basedir import BASEDIR +from openpilot.common.spinner import Spinner +from openpilot.common.text_window import TextWindow +from openpilot.system.hardware import AGNOS +from openpilot.system.swaglog import cloudlog, add_file_handler +from openpilot.system.version import is_dirty MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 CACHE_DIR = Path("/data/scons_cache" if AGNOS else "/tmp/scons_cache") @@ -19,57 +19,59 @@ TOTAL_SCONS_NODES = 2560 MAX_BUILD_PROGRESS = 100 PREBUILT = os.path.exists(os.path.join(BASEDIR, 'prebuilt')) - def build(spinner: Spinner, dirty: bool = False) -> None: env = os.environ.copy() env['SCONS_PROGRESS'] = "1" nproc = os.cpu_count() - j_flag = "" if nproc is None else f"-j{nproc - 1}" - - scons: subprocess.Popen = subprocess.Popen(["scons", j_flag, "--cache-populate"], cwd=BASEDIR, env=env, stderr=subprocess.PIPE) - assert scons.stderr is not None - - compile_output = [] - - # Read progress from stderr and update spinner - while scons.poll() is None: - try: - line = scons.stderr.readline() - if line is None: - continue - line = line.rstrip() - - prefix = b'progress: ' - if line.startswith(prefix): - i = int(line[len(prefix):]) - spinner.update_progress(MAX_BUILD_PROGRESS * min(1., i / TOTAL_SCONS_NODES), 100.) - elif len(line): - compile_output.append(line) - print(line.decode('utf8', 'replace')) - except Exception: - pass + if nproc is None: + nproc = 2 + + # building with all cores can result in using too + # much memory, so retry with less parallelism + compile_output: List[bytes] = [] + for n in (nproc, nproc/2, 1): + compile_output.clear() + scons: subprocess.Popen = subprocess.Popen(["scons", f"-j{int(n)}", "--cache-populate", "--minimal"], cwd=BASEDIR, env=env, stderr=subprocess.PIPE) + assert scons.stderr is not None + + # Read progress from stderr and update spinner + while scons.poll() is None: + try: + line = scons.stderr.readline() + if line is None: + continue + line = line.rstrip() + + prefix = b'progress: ' + if line.startswith(prefix): + i = int(line[len(prefix):]) + spinner.update_progress(MAX_BUILD_PROGRESS * min(1., i / TOTAL_SCONS_NODES), 100.) + elif len(line): + compile_output.append(line) + print(line.decode('utf8', 'replace')) + except Exception: + pass + + if scons.returncode == 0: + break if scons.returncode != 0: # Read remaining output - r = scons.stderr.read().split(b'\n') - compile_output += r + if scons.stderr is not None: + compile_output += scons.stderr.read().split(b'\n') # Build failed log errors - errors = [line.decode('utf8', 'replace') for line in compile_output - if any(err in line for err in [b'error: ', b'not found, needed by target'])] - error_s = "\n".join(errors) + error_s = b"\n".join(compile_output).decode('utf8', 'replace') add_file_handler(cloudlog) cloudlog.error("scons build failed\n" + error_s) # Show TextWindow spinner.close() if not os.getenv("CI"): - error_s = "\n \n".join("\n".join(textwrap.wrap(e, 65)) for e in errors) with TextWindow("openpilot failed to build\n \n" + error_s) as t: t.wait_for_exit() exit(1) - # enforce max cache size cache_files = [f for f in CACHE_DIR.rglob('*') if f.is_file()] cache_files.sort(key=lambda f: f.stat().st_mtime) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index e9a1b2cb5b..a739437de7 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -9,18 +9,18 @@ from typing import List, Tuple, Union from cereal import log import cereal.messaging as messaging -import selfdrive.sentry as sentry -from common.basedir import BASEDIR -from common.params import Params, ParamKeyType -from common.text_window import TextWindow -from selfdrive.boardd.set_time import set_time -from system.hardware import HARDWARE, PC -from selfdrive.manager.helpers import unblock_stdout, write_onroad_params -from selfdrive.manager.process import ensure_running -from selfdrive.manager.process_config import managed_processes -from selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID -from system.swaglog import cloudlog, add_file_handler -from system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ +import openpilot.selfdrive.sentry as sentry +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params, ParamKeyType +from openpilot.common.text_window import TextWindow +from openpilot.selfdrive.boardd.set_time import set_time +from openpilot.system.hardware import HARDWARE, PC +from openpilot.selfdrive.manager.helpers import unblock_stdout, write_onroad_params +from openpilot.selfdrive.manager.process import ensure_running +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID +from openpilot.system.swaglog import cloudlog, add_file_handler +from openpilot.system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ get_normalized_origin, terms_version, training_version, \ is_tested_branch, is_release_branch diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index 0acb23857a..82cc8d74a3 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -8,16 +8,14 @@ from typing import Optional, Callable, List, ValuesView from abc import ABC, abstractmethod from multiprocessing import Process -from setproctitle import setproctitle # pylint: disable=no-name-in-module +from setproctitle import setproctitle +from cereal import car, log import cereal.messaging as messaging -import selfdrive.sentry as sentry -from cereal import car -from common.basedir import BASEDIR -from common.params import Params -from common.realtime import sec_since_boot -from system.swaglog import cloudlog -from cereal import log +import openpilot.selfdrive.sentry as sentry +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.system.swaglog import cloudlog WATCHDOG_FN = "/dev/shm/wd_" ENABLE_WATCHDOG = os.getenv("NO_WATCHDOG") is None @@ -68,9 +66,7 @@ def join_process(process: Process, timeout: float) -> None: class ManagerProcess(ABC): daemon = False sigkill = False - onroad = True - offroad = False - callback: Optional[Callable[[bool, Params, car.CarParams], bool]] = None + should_run: Callable[[bool, Params, car.CarParams], bool] proc: Optional[Process] = None enabled = True name = "" @@ -100,11 +96,11 @@ class ManagerProcess(ABC): fn = WATCHDOG_FN + str(self.proc.pid) with open(fn, "rb") as f: # TODO: why can't pylint find struct.unpack? - self.last_watchdog_time = struct.unpack('Q', f.read())[0] # pylint: disable=no-member + self.last_watchdog_time = struct.unpack('Q', f.read())[0] except Exception: pass - dt = sec_since_boot() - self.last_watchdog_time / 1e9 + dt = time.monotonic() - self.last_watchdog_time / 1e9 if dt > self.watchdog_max_dt: if self.watchdog_seen and ENABLE_WATCHDOG: @@ -172,15 +168,12 @@ class ManagerProcess(ABC): class NativeProcess(ManagerProcess): - def __init__(self, name, cwd, cmdline, enabled=True, onroad=True, offroad=False, callback=None, unkillable=False, sigkill=False, watchdog_max_dt=None): + def __init__(self, name, cwd, cmdline, should_run, enabled=True, sigkill=False, watchdog_max_dt=None): self.name = name self.cwd = cwd self.cmdline = cmdline + self.should_run = should_run self.enabled = enabled - self.onroad = onroad - self.offroad = offroad - self.callback = callback - self.unkillable = unkillable self.sigkill = sigkill self.watchdog_max_dt = watchdog_max_dt self.launcher = nativelauncher @@ -205,14 +198,11 @@ class NativeProcess(ManagerProcess): class PythonProcess(ManagerProcess): - def __init__(self, name, module, enabled=True, onroad=True, offroad=False, callback=None, unkillable=False, sigkill=False, watchdog_max_dt=None): + def __init__(self, name, module, should_run, enabled=True, sigkill=False, watchdog_max_dt=None): self.name = name self.module = module + self.should_run = should_run self.enabled = enabled - self.onroad = onroad - self.offroad = offroad - self.callback = callback - self.unkillable = unkillable self.sigkill = sigkill self.watchdog_max_dt = watchdog_max_dt self.launcher = launcher @@ -245,10 +235,12 @@ class DaemonProcess(ManagerProcess): self.module = module self.param_name = param_name self.enabled = enabled - self.onroad = True - self.offroad = True self.params = None + @staticmethod + def should_run(started, params, CP): + return True + def prepare(self) -> None: pass @@ -269,7 +261,7 @@ class DaemonProcess(ManagerProcess): pass cloudlog.info(f"starting daemon {self.name}") - proc = subprocess.Popen(['python', '-m', self.module], # pylint: disable=subprocess-popen-preexec-fn + proc = subprocess.Popen(['python', '-m', self.module], stdin=open('/dev/null'), stdout=open('/dev/null', 'w'), stderr=open('/dev/null', 'w'), @@ -288,21 +280,7 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None running = [] for p in procs: - # Conditions that make a process run - run = any(( - p.offroad and not started, - p.onroad and started, - )) - if p.callback is not None and None not in (params, CP): - run = run or p.callback(started, params, CP) - - # Conditions that block a process from starting - run = run and not any(( - not p.enabled, - p.name in not_run, - )) - - if run: + if p.enabled and p.name not in not_run and p.should_run(started, params, CP): p.start() running.append(p) else: diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 7cd1ed9489..aee4748a26 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -1,20 +1,20 @@ import os from cereal import car -from common.params import Params -from system.hardware import PC, TICI -from selfdrive.manager.process import PythonProcess, NativeProcess, DaemonProcess +from openpilot.common.params import Params +from openpilot.system.hardware import PC, TICI +from openpilot.selfdrive.manager.process import PythonProcess, NativeProcess, DaemonProcess WEBCAM = os.getenv("USE_WEBCAM") is not None def driverview(started: bool, params: Params, CP: car.CarParams) -> bool: - return params.get_bool("IsDriverViewEnabled") # type: ignore + return started or params.get_bool("IsDriverViewEnabled") def notcar(started: bool, params: Params, CP: car.CarParams) -> bool: - return CP.notCar # type: ignore + return started and CP.notCar def iscar(started: bool, params: Params, CP: car.CarParams) -> bool: - return not CP.notCar + return started and not CP.notCar def logging(started, params, CP: car.CarParams) -> bool: run = (not CP.notCar) or not params.get_bool("DisableLogging") @@ -32,51 +32,61 @@ def ublox(started, params, CP: car.CarParams) -> bool: def qcomgps(started, params, CP: car.CarParams) -> bool: return started and not ublox_available() -procs = [ - NativeProcess("camerad", "system/camerad", ["./camerad"], callback=driverview), - NativeProcess("clocksd", "system/clocksd", ["./clocksd"]), - NativeProcess("logcatd", "system/logcatd", ["./logcatd"]), - NativeProcess("proclogd", "system/proclogd", ["./proclogd"]), - PythonProcess("logmessaged", "system.logmessaged", offroad=True), - PythonProcess("micd", "system.micd", callback=iscar), - PythonProcess("timezoned", "system.timezoned", enabled=not PC, offroad=True), +def always_run(started, params, CP: car.CarParams) -> bool: + return True + +def only_onroad(started: bool, params, CP: car.CarParams) -> bool: + return started + +def only_offroad(started, params, CP: car.CarParams) -> bool: + return not started +procs = [ DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), - NativeProcess("dmonitoringmodeld", "selfdrive/modeld", ["./dmonitoringmodeld"], enabled=(not PC or WEBCAM), callback=driverview), - NativeProcess("encoderd", "system/loggerd", ["./encoderd"]), - NativeProcess("stream_encoderd", "system/loggerd", ["./encoderd", "--stream"], onroad=False, callback=notcar), - NativeProcess("loggerd", "system/loggerd", ["./loggerd"], onroad=False, callback=logging), - NativeProcess("modeld", "selfdrive/modeld", ["./modeld"]), - NativeProcess("mapsd", "selfdrive/navd", ["./mapsd"]), - NativeProcess("navmodeld", "selfdrive/modeld", ["./navmodeld"]), - NativeProcess("sensord", "system/sensord", ["./sensord"], enabled=not PC), - NativeProcess("ui", "selfdrive/ui", ["./ui"], offroad=True, watchdog_max_dt=(5 if not PC else None)), - NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"]), - NativeProcess("locationd", "selfdrive/locationd", ["./locationd"]), - NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], enabled=False), - PythonProcess("calibrationd", "selfdrive.locationd.calibrationd"), - PythonProcess("torqued", "selfdrive.locationd.torqued"), - PythonProcess("controlsd", "selfdrive.controls.controlsd"), - PythonProcess("deleter", "system.loggerd.deleter", offroad=True), - PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", enabled=(not PC or WEBCAM), callback=driverview), - PythonProcess("laikad", "selfdrive.locationd.laikad"), - PythonProcess("rawgpsd", "system.sensord.rawgps.rawgpsd", enabled=TICI, onroad=False, callback=qcomgps), - PythonProcess("navd", "selfdrive.navd.navd"), - PythonProcess("pandad", "selfdrive.boardd.pandad", offroad=True), - PythonProcess("paramsd", "selfdrive.locationd.paramsd"), - NativeProcess("ubloxd", "system/ubloxd", ["./ubloxd"], enabled=TICI, onroad=False, callback=ublox), - PythonProcess("pigeond", "system.sensord.pigeond", enabled=TICI, onroad=False, callback=ublox), - PythonProcess("plannerd", "selfdrive.controls.plannerd"), - PythonProcess("radard", "selfdrive.controls.radard"), - PythonProcess("thermald", "selfdrive.thermald.thermald", offroad=True), - PythonProcess("tombstoned", "selfdrive.tombstoned", enabled=not PC, offroad=True), - PythonProcess("updated", "selfdrive.updated", enabled=not PC, onroad=False, offroad=True), - PythonProcess("uploader", "system.loggerd.uploader", offroad=True), - PythonProcess("statsd", "selfdrive.statsd", offroad=True), + + NativeProcess("camerad", "system/camerad", ["./camerad"], driverview), + NativeProcess("clocksd", "system/clocksd", ["./clocksd"], only_onroad), + NativeProcess("logcatd", "system/logcatd", ["./logcatd"], only_onroad), + NativeProcess("proclogd", "system/proclogd", ["./proclogd"], only_onroad), + PythonProcess("logmessaged", "system.logmessaged", always_run), + PythonProcess("micd", "system.micd", iscar), + PythonProcess("timezoned", "system.timezoned", always_run, enabled=not PC), + + PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(not PC or WEBCAM)), + NativeProcess("encoderd", "system/loggerd", ["./encoderd"], only_onroad), + NativeProcess("stream_encoderd", "system/loggerd", ["./encoderd", "--stream"], notcar), + NativeProcess("loggerd", "system/loggerd", ["./loggerd"], logging), + NativeProcess("modeld", "selfdrive/modeld", ["./modeld"], only_onroad), + NativeProcess("mapsd", "selfdrive/navd", ["./mapsd"], only_onroad), + PythonProcess("navmodeld", "selfdrive.modeld.navmodeld", only_onroad), + NativeProcess("sensord", "system/sensord", ["./sensord"], only_onroad, enabled=not PC), + NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), + NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"], only_onroad), + NativeProcess("locationd", "selfdrive/locationd", ["./locationd"], only_onroad), + NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], always_run, enabled=False), + PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad), + PythonProcess("torqued", "selfdrive.locationd.torqued", only_onroad), + PythonProcess("controlsd", "selfdrive.controls.controlsd", only_onroad), + PythonProcess("deleter", "system.loggerd.deleter", always_run), + PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(not PC or WEBCAM)), + PythonProcess("laikad", "selfdrive.locationd.laikad", only_onroad), + PythonProcess("rawgpsd", "system.sensord.rawgps.rawgpsd", qcomgps, enabled=TICI), + PythonProcess("navd", "selfdrive.navd.navd", only_onroad), + PythonProcess("pandad", "selfdrive.boardd.pandad", always_run), + PythonProcess("paramsd", "selfdrive.locationd.paramsd", only_onroad), + NativeProcess("ubloxd", "system/ubloxd", ["./ubloxd"], ublox, enabled=TICI), + PythonProcess("pigeond", "system.sensord.pigeond", ublox, enabled=TICI), + PythonProcess("plannerd", "selfdrive.controls.plannerd", only_onroad), + PythonProcess("radard", "selfdrive.controls.radard", only_onroad), + PythonProcess("thermald", "selfdrive.thermald.thermald", always_run), + PythonProcess("tombstoned", "selfdrive.tombstoned", always_run, enabled=not PC), + PythonProcess("updated", "selfdrive.updated", only_offroad, enabled=not PC), + PythonProcess("uploader", "system.loggerd.uploader", always_run), + PythonProcess("statsd", "selfdrive.statsd", always_run), # debug procs - NativeProcess("bridge", "cereal/messaging", ["./bridge"], onroad=False, callback=notcar), - PythonProcess("webjoystick", "tools.bodyteleop.web", onroad=False, callback=notcar), + NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar), + PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), ] managed_processes = {p.name: p for p in procs} diff --git a/selfdrive/manager/test/test_manager.py b/selfdrive/manager/test/test_manager.py index 39bda1e156..89ad483744 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -5,17 +5,18 @@ import time import unittest from cereal import car -from common.params import Params -import selfdrive.manager.manager as manager -from selfdrive.manager.process import ensure_running -from selfdrive.manager.process_config import managed_processes -from system.hardware import HARDWARE +from openpilot.common.params import Params +import openpilot.selfdrive.manager.manager as manager +from openpilot.selfdrive.manager.process import ensure_running +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.hardware import HARDWARE os.environ['FAKEUPLOAD'] = "1" MAX_STARTUP_TIME = 3 BLACKLIST_PROCS = ['manage_athenad', 'pandad', 'pigeond'] + class TestManager(unittest.TestCase): def setUp(self): os.environ['PASSIVE'] = '0' @@ -32,6 +33,10 @@ class TestManager(unittest.TestCase): os.environ['PREPAREONLY'] = '1' manager.main() + def test_blacklisted_procs(self): + # TODO: ensure there are blacklisted procs until we have a dedicated test + self.assertTrue(len(BLACKLIST_PROCS), "No blacklisted procs to test not_run") + def test_startup_time(self): for _ in range(10): start = time.monotonic() @@ -59,6 +64,8 @@ class TestManager(unittest.TestCase): self.assertTrue(state.running, f"{p.name} not running") exit_code = p.stop(retry=False) + self.assertNotIn(p.name, BLACKLIST_PROCS, f"{p.name} was started") + # TODO: mapsd should exit cleanly if p.name == "mapsd": continue diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 7be4446e67..63f3b2b97a 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,74 +1,59 @@ -import os - -Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'transformations') +Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'transformations') lenv = env.Clone() +lenvCython = envCython.Clone() -libs = [cereal, messaging, common, visionipc, gpucommon, - 'OpenCL', 'SNPE', 'capnp', 'zmq', 'kj', 'yuv'] - -def get_dlsym_offset(): - """Returns the offset between dlopen and dlsym in libdl.so""" - import ctypes - libdl = ctypes.PyDLL('libdl.so') - dlopen = ctypes.cast(libdl.dlopen, ctypes.c_void_p).value - dlsym = ctypes.cast(libdl.dlsym, ctypes.c_void_p).value - return dlsym - dlopen - +libs = [cereal, messaging, visionipc, gpucommon, common, 'capnp', 'zmq', 'kj', 'pthread'] +frameworks = [] common_src = [ "models/commonmodel.cc", - "runners/snpemodel.cc", "transforms/loadyuv.cc", - "transforms/transform.cc" + "transforms/transform.cc", ] -thneed_src = [ +thneed_src_common = [ "thneed/thneed_common.cc", - "thneed/thneed_qcom2.cc", "thneed/serialize.cc", - "runners/thneedmodel.cc", ] -use_thneed = not GetOption('no_thneed') +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 -if arch == "larch64": - libs += ['gsl', 'CB', 'pthread', 'dl'] +# SNPE except on Mac and ARM Linux +snpe_lib = [] +if arch != "Darwin" and arch != "aarch64": + common_src += ['runners/snpemodel.cc'] + snpe_lib += ['SNPE'] - if use_thneed: - common_src += thneed_src - dlsym_offset = get_dlsym_offset() - lenv['CXXFLAGS'].append("-DUSE_THNEED") - lenv['CXXFLAGS'].append(f"-DDLSYM_OFFSET={dlsym_offset}") +# OpenCL is a framework on Mac +if arch == "Darwin": + frameworks += ['OpenCL'] else: - libs += ['pthread'] - - if not GetOption('snpe'): - # for onnx support - common_src += ['runners/onnxmodel.cc'] - - # tell runners to use onnx - lenv['CFLAGS'].append("-DUSE_ONNX_MODEL") - lenv['CXXFLAGS'].append("-DUSE_ONNX_MODEL") - - if arch == "Darwin": - # fix OpenCL - del libs[libs.index('OpenCL')] - lenv['FRAMEWORKS'] = ['OpenCL'] - - if arch == "Darwin" or arch == "aarch64": - # no SNPE on Mac and ARM Linux - del libs[libs.index('SNPE')] - del common_src[common_src.index('runners/snpemodel.cc')] - -common_model = lenv.Object(common_src) - -lenv.Program('_dmonitoringmodeld', [ - "dmonitoringmodeld.cc", - "models/dmonitoring.cc", - ]+common_model, LIBS=libs) - -# build thneed model -if use_thneed and arch == "larch64" or GetOption('pc_thneed'): + 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 +snpe_rpath_qcom = "/data/pythonpath/third_party/snpe/larch64" +snpe_rpath_pc = f"{Dir('#').abspath}/third_party/snpe/x86_64-linux-clang" +snpe_rpath = lenvCython['RPATH'] + [snpe_rpath_qcom if arch == "larch64" else snpe_rpath_pc] + +cython_libs = envCython["LIBS"] + libs +snpemodel_lib = lenv.Library('snpemodel', ['runners/snpemodel.cc']) +commonmodel_lib = lenv.Library('commonmodel', common_src) +driving_lib = lenv.Library('driving', ['models/driving.cc']) + +lenvCython.Program('runners/runmodel_pyx.so', 'runners/runmodel_pyx.pyx', LIBS=cython_libs, FRAMEWORKS=frameworks) +lenvCython.Program('runners/snpemodel_pyx.so', 'runners/snpemodel_pyx.pyx', LIBS=[snpemodel_lib, snpe_lib, *cython_libs], FRAMEWORKS=frameworks, RPATH=snpe_rpath) +lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) +lenvCython.Program('models/driving_pyx.so', 'models/driving_pyx.pyx', LIBS=[driving_lib, commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) + +# Build thneed model +if arch == "larch64" or GetOption('pc_thneed'): fn = File("models/supercombo").abspath tinygrad_opts = ["NATIVE_EXPLOG=1", "VALIDHACKS=1", "OPTLOCAL=1", "IMAGE=2", "GPU=1", "ENABLE_METHOD_CACHE=1"] @@ -80,25 +65,6 @@ if use_thneed and arch == "larch64" or GetOption('pc_thneed'): tinygrad_files = sum([lenv.Glob("#"+x) for x in open(File("#release/files_common").abspath).read().split("\n") if x.startswith("tinygrad_repo/")], []) lenv.Command(fn + ".thneed", [fn + ".onnx"] + tinygrad_files, cmd) -llenv = lenv.Clone() -if GetOption('pc_thneed'): - pc_thneed_src = [ - "thneed/thneed_common.cc", - "thneed/thneed_pc.cc", - "thneed/serialize.cc", - "runners/thneedmodel.cc", - ] - llenv['CFLAGS'].append("-DUSE_THNEED") - llenv['CXXFLAGS'].append("-DUSE_THNEED") - common_model += llenv.Object(pc_thneed_src) - libs += ['dl'] - -llenv.Program('_modeld', [ - "modeld.cc", - "models/driving.cc", - ]+common_model, LIBS=libs + transformations) - -lenv.Program('_navmodeld', [ - "navmodeld.cc", - "models/nav.cc", - ]+common_model, LIBS=libs + transformations) + thneed_lib = env.SharedLibrary('thneed', thneed_src, LIBS=[gpucommon, common, 'zmq', '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, gpucommon, common, 'dl', 'zmq', 'OpenCL']) diff --git a/selfdrive/modeld/dmonitoringmodeld b/selfdrive/modeld/dmonitoringmodeld deleted file mode 100755 index f292fe4c0b..0000000000 --- a/selfdrive/modeld/dmonitoringmodeld +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -cd $DIR - -if [ -f /TICI ]; then - export LD_LIBRARY_PATH="/usr/lib/aarch64-linux-gnu:/data/pythonpath/third_party/snpe/larch64:$LD_LIBRARY_PATH" - export ADSP_LIBRARY_PATH="/data/pythonpath/third_party/snpe/dsp/" -else - export LD_LIBRARY_PATH="$DIR/../../third_party/snpe/x86_64-linux-clang:$DIR/../../openpilot/third_party/snpe/x86_64:$LD_LIBRARY_PATH" -fi -exec ./_dmonitoringmodeld diff --git a/selfdrive/modeld/dmonitoringmodeld.cc b/selfdrive/modeld/dmonitoringmodeld.cc deleted file mode 100644 index 2890b44fe0..0000000000 --- a/selfdrive/modeld/dmonitoringmodeld.cc +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include - -#include -#include - -#include "cereal/visionipc/visionipc_client.h" -#include "common/params.h" -#include "common/swaglog.h" -#include "common/util.h" -#include "selfdrive/modeld/models/dmonitoring.h" - -ExitHandler do_exit; - -void run_model(DMonitoringModelState &model, VisionIpcClient &vipc_client) { - PubMaster pm({"driverStateV2"}); - SubMaster sm({"liveCalibration"}); - float calib[CALIB_LEN] = {0}; - // double last = 0; - - while (!do_exit) { - VisionIpcBufExtra extra = {}; - VisionBuf *buf = vipc_client.recv(&extra); - if (buf == nullptr) continue; - - sm.update(0); - if (sm.updated("liveCalibration")) { - auto calib_msg = sm["liveCalibration"].getLiveCalibration().getRpyCalib(); - for (int i = 0; i < CALIB_LEN; i++) { - calib[i] = calib_msg[i]; - } - } - - double t1 = millis_since_boot(); - DMonitoringModelResult model_res = dmonitoring_eval_frame(&model, buf->addr, buf->width, buf->height, buf->stride, buf->uv_offset, calib); - double t2 = millis_since_boot(); - - // send dm packet - dmonitoring_publish(pm, extra.frame_id, model_res, (t2 - t1) / 1000.0, model.output); - - // printf("dmonitoring process: %.2fms, from last %.2fms\n", t2 - t1, t1 - last); - // last = t1; - } -} - -int main(int argc, char **argv) { - setpriority(PRIO_PROCESS, 0, -15); - - // init the models - DMonitoringModelState model; - dmonitoring_init(&model); - - Params().putBool("DmModelInitialized", true); - - LOGW("connecting to driver stream"); - VisionIpcClient vipc_client = VisionIpcClient("camerad", VISION_STREAM_DRIVER, true); - while (!do_exit && !vipc_client.connect(false)) { - util::sleep_for(100); - } - - // run the models - if (vipc_client.connected) { - LOGW("connected with buffer size: %zu", vipc_client.buffers[0].len); - run_model(model, vipc_client); - } - - dmonitoring_free(&model); - return 0; -} diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py new file mode 100755 index 0000000000..53c0af0ff3 --- /dev/null +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +import os +import gc +import math +import time +import ctypes +import numpy as np +from pathlib import Path +from typing import Tuple, Dict + +from cereal import messaging +from cereal.messaging import PubMaster, SubMaster +from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from openpilot.system.swaglog import cloudlog +from openpilot.common.params import Params +from openpilot.common.realtime import set_realtime_priority +from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime +from openpilot.selfdrive.modeld.models.commonmodel_pyx import sigmoid + +CALIB_LEN = 3 +REG_SCALE = 0.25 +MODEL_WIDTH = 1440 +MODEL_HEIGHT = 960 +OUTPUT_SIZE = 84 +SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') +MODEL_PATHS = { + ModelRunner.SNPE: Path(__file__).parent / 'models/dmonitoring_model_q.dlc', + ModelRunner.ONNX: Path(__file__).parent / 'models/dmonitoring_model.onnx'} + +class DriverStateResult(ctypes.Structure): + _fields_ = [ + ("face_orientation", ctypes.c_float*3), + ("face_position", ctypes.c_float*3), + ("face_orientation_std", ctypes.c_float*3), + ("face_position_std", ctypes.c_float*3), + ("face_prob", ctypes.c_float), + ("_unused_a", ctypes.c_float*8), + ("left_eye_prob", ctypes.c_float), + ("_unused_b", ctypes.c_float*8), + ("right_eye_prob", ctypes.c_float), + ("left_blink_prob", ctypes.c_float), + ("right_blink_prob", ctypes.c_float), + ("sunglasses_prob", ctypes.c_float), + ("occluded_prob", ctypes.c_float), + ("ready_prob", ctypes.c_float*4), + ("not_ready_prob", ctypes.c_float*2)] + +class DMonitoringModelResult(ctypes.Structure): + _fields_ = [ + ("driver_state_lhd", DriverStateResult), + ("driver_state_rhd", DriverStateResult), + ("poor_vision_prob", ctypes.c_float), + ("wheel_on_right_prob", ctypes.c_float)] + +class ModelState: + inputs: Dict[str, np.ndarray] + output: np.ndarray + model: ModelRunner + + def __init__(self): + assert ctypes.sizeof(DMonitoringModelResult) == OUTPUT_SIZE * ctypes.sizeof(ctypes.c_float) + self.output = np.zeros(OUTPUT_SIZE, dtype=np.float32) + self.inputs = { + 'input_img': np.zeros(MODEL_HEIGHT * MODEL_WIDTH, dtype=np.uint8), + 'calib': np.zeros(CALIB_LEN, dtype=np.float32)} + + self.model = ModelRunner(MODEL_PATHS, self.output, Runtime.DSP, True, None) + self.model.addInput("input_img", None) + self.model.addInput("calib", self.inputs['calib']) + + def run(self, buf:VisionBuf, calib:np.ndarray) -> Tuple[np.ndarray, float]: + self.inputs['calib'][:] = calib + + v_offset = buf.height - MODEL_HEIGHT + h_offset = (buf.width - MODEL_WIDTH) // 2 + buf_data = buf.data.reshape(-1, buf.stride) + input_data = self.inputs['input_img'].reshape(MODEL_HEIGHT, MODEL_WIDTH) + input_data[:] = buf_data[v_offset:v_offset+MODEL_HEIGHT, h_offset:h_offset+MODEL_WIDTH] + + t1 = time.perf_counter() + self.model.setInputBuffer("input_img", self.inputs['input_img'].view(np.float32)) + self.model.execute() + t2 = time.perf_counter() + return self.output, t2 - t1 + + +def fill_driver_state(msg, ds_result: DriverStateResult): + msg.faceOrientation = [x * REG_SCALE for x in ds_result.face_orientation] + msg.faceOrientationStd = [math.exp(x) for x in ds_result.face_orientation_std] + msg.facePosition = [x * REG_SCALE for x in ds_result.face_position[:2]] + msg.facePositionStd = [math.exp(x) for x in ds_result.face_position_std[:2]] + msg.faceProb = sigmoid(ds_result.face_prob) + msg.leftEyeProb = sigmoid(ds_result.left_eye_prob) + msg.rightEyeProb = sigmoid(ds_result.right_eye_prob) + msg.leftBlinkProb = sigmoid(ds_result.left_blink_prob) + msg.rightBlinkProb = sigmoid(ds_result.right_blink_prob) + msg.sunglassesProb = sigmoid(ds_result.sunglasses_prob) + msg.occludedProb = sigmoid(ds_result.occluded_prob) + msg.readyProb = [sigmoid(x) for x in ds_result.ready_prob] + msg.notReadyProb = [sigmoid(x) for x in ds_result.not_ready_prob] + +def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts: int, execution_time: float, dsp_execution_time: float): + model_result = ctypes.cast(model_output.ctypes.data, ctypes.POINTER(DMonitoringModelResult)).contents + msg = messaging.new_message('driverStateV2') + ds = msg.driverStateV2 + ds.frameId = frame_id + ds.modelExecutionTime = execution_time + ds.dspExecutionTime = dsp_execution_time + ds.poorVisionProb = sigmoid(model_result.poor_vision_prob) + ds.wheelOnRightProb = sigmoid(model_result.wheel_on_right_prob) + ds.rawPredictions = model_output.tobytes() if SEND_RAW_PRED else b'' + fill_driver_state(ds.leftDriverData, model_result.driver_state_lhd) + fill_driver_state(ds.rightDriverData, model_result.driver_state_rhd) + return msg + + +def main(): + gc.disable() + set_realtime_priority(1) + + model = ModelState() + cloudlog.warning("models loaded, dmonitoringmodeld starting") + Params().put_bool("DmModelInitialized", True) + + cloudlog.warning("connecting to driver stream") + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) + while not vipc_client.connect(False): + time.sleep(0.1) + assert vipc_client.is_connected() + cloudlog.warning(f"connected with buffer size: {vipc_client.buffer_len}") + + sm = SubMaster(["liveCalibration"]) + pm = PubMaster(["driverStateV2"]) + + calib = np.zeros(CALIB_LEN, dtype=np.float32) + # last = 0 + + while True: + buf = vipc_client.recv() + if buf is None: + continue + + sm.update(0) + if sm.updated["liveCalibration"]: + calib[:] = np.array(sm["liveCalibration"].rpyCalib) + + t1 = time.perf_counter() + model_output, dsp_execution_time = model.run(buf, calib) + t2 = time.perf_counter() + + pm.send("driverStateV2", get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, dsp_execution_time)) + # print("dmonitoring process: %.2fms, from last %.2fms\n" % (t2 - t1, t1 - last)) + # last = t1 + + +if __name__ == "__main__": + main() diff --git a/selfdrive/modeld/modeld b/selfdrive/modeld/modeld index c067cf3d62..14048ec9fd 100755 --- a/selfdrive/modeld/modeld +++ b/selfdrive/modeld/modeld @@ -1,11 +1,10 @@ -#!/bin/sh +#!/usr/bin/env bash DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -cd $DIR +cd "$DIR/../../" -if [ -f /TICI ]; then - export LD_LIBRARY_PATH="/usr/lib/aarch64-linux-gnu:/data/pythonpath/third_party/snpe/larch64:$LD_LIBRARY_PATH" -else - export LD_LIBRARY_PATH="$DIR/../../third_party/snpe/x86_64-linux-clang:$DIR/../../openpilot/third_party/snpe/x86_64:$LD_LIBRARY_PATH" +if [ -f "$DIR/libthneed.so" ]; then + export LD_PRELOAD="$DIR/libthneed.so" fi -exec ./_modeld + +exec "$DIR/modeld.py" diff --git a/selfdrive/modeld/modeld.cc b/selfdrive/modeld/modeld.cc deleted file mode 100644 index f19138b179..0000000000 --- a/selfdrive/modeld/modeld.cc +++ /dev/null @@ -1,271 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "cereal/messaging/messaging.h" -#include "common/transformations/orientation.hpp" - -#include "cereal/visionipc/visionipc_client.h" -#include "common/clutil.h" -#include "common/params.h" -#include "common/swaglog.h" -#include "common/util.h" -#include "system/hardware/hw.h" -#include "selfdrive/modeld/models/driving.h" -#include "selfdrive/modeld/models/nav.h" - - -ExitHandler do_exit; - -mat3 update_calibration(Eigen::Vector3d device_from_calib_euler, bool wide_camera, bool bigmodel_frame) { - /* - import numpy as np - from common.transformations.model import medmodel_frame_from_calib_frame - medmodel_frame_from_calib_frame = medmodel_frame_from_calib_frame[:, :3] - calib_from_smedmodel_frame = np.linalg.inv(medmodel_frame_from_calib_frame) - */ - static const auto calib_from_medmodel = (Eigen::Matrix() << - 0.00000000e+00, 0.00000000e+00, 1.00000000e+00, - 1.09890110e-03, 0.00000000e+00, -2.81318681e-01, - -2.25466395e-20, 1.09890110e-03,-5.23076923e-02).finished(); - - static const auto calib_from_sbigmodel = (Eigen::Matrix() << - 0.00000000e+00, 7.31372216e-19, 1.00000000e+00, - 2.19780220e-03, 4.11497335e-19, -5.62637363e-01, - -6.66298828e-20, 2.19780220e-03, -3.33626374e-01).finished(); - - static const auto view_from_device = (Eigen::Matrix() << - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0, - 1.0, 0.0, 0.0).finished(); - - - const auto cam_intrinsics = Eigen::Matrix(wide_camera ? ECAM_INTRINSIC_MATRIX.v : FCAM_INTRINSIC_MATRIX.v); - Eigen::Matrix device_from_calib = euler2rot(device_from_calib_euler).cast (); - auto calib_from_model = bigmodel_frame ? calib_from_sbigmodel : calib_from_medmodel; - auto camera_from_calib = cam_intrinsics * view_from_device * device_from_calib; - auto warp_matrix = camera_from_calib * calib_from_model; - - mat3 transform = {}; - for (int i=0; i<3*3; i++) { - transform.v[i] = warp_matrix(i / 3, i % 3); - } - return transform; -} - - -void run_model(ModelState &model, VisionIpcClient &vipc_client_main, VisionIpcClient &vipc_client_extra, bool main_wide_camera, bool use_extra_client) { - // messaging - PubMaster pm({"modelV2", "cameraOdometry"}); - SubMaster sm({"lateralPlan", "roadCameraState", "liveCalibration", "driverMonitoringState", "navModel", "navInstruction"}); - - Params params; - PublishState ps = {}; - - // setup filter to track dropped frames - FirstOrderFilter frame_dropped_filter(0., 10., 1. / MODEL_FREQ); - - uint32_t frame_id = 0, last_vipc_frame_id = 0; - // double last = 0; - uint32_t run_count = 0; - - mat3 model_transform_main = {}; - mat3 model_transform_extra = {}; - bool nav_enabled = false; - bool live_calib_seen = false; - float driving_style[DRIVING_STYLE_LEN] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}; - float nav_features[NAV_FEATURE_LEN] = {0}; - float nav_instructions[NAV_INSTRUCTION_LEN] = {0}; - - VisionBuf *buf_main = nullptr; - VisionBuf *buf_extra = nullptr; - - VisionIpcBufExtra meta_main = {0}; - VisionIpcBufExtra meta_extra = {0}; - - while (!do_exit) { - // Keep receiving frames until we are at least 1 frame ahead of previous extra frame - while (meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000ULL) { - buf_main = vipc_client_main.recv(&meta_main); - if (buf_main == nullptr) break; - } - - if (buf_main == nullptr) { - LOGE("vipc_client_main no frame"); - continue; - } - - if (use_extra_client) { - // Keep receiving extra frames until frame id matches main camera - do { - buf_extra = vipc_client_extra.recv(&meta_extra); - } while (buf_extra != nullptr && meta_main.timestamp_sof > meta_extra.timestamp_sof + 25000000ULL); - - if (buf_extra == nullptr) { - LOGE("vipc_client_extra no frame"); - continue; - } - - if (std::abs((int64_t)meta_main.timestamp_sof - (int64_t)meta_extra.timestamp_sof) > 10000000ULL) { - LOGE("frames out of sync! main: %d (%.5f), extra: %d (%.5f)", - meta_main.frame_id, double(meta_main.timestamp_sof) / 1e9, - meta_extra.frame_id, double(meta_extra.timestamp_sof) / 1e9); - } - } else { - // Use single camera - buf_extra = buf_main; - meta_extra = meta_main; - } - - // TODO: path planner timeout? - sm.update(0); - int desire = ((int)sm["lateralPlan"].getLateralPlan().getDesire()); - bool is_rhd = ((bool)sm["driverMonitoringState"].getDriverMonitoringState().getIsRHD()); - frame_id = sm["roadCameraState"].getRoadCameraState().getFrameId(); - if (sm.updated("liveCalibration")) { - auto rpy_calib = sm["liveCalibration"].getLiveCalibration().getRpyCalib(); - Eigen::Vector3d device_from_calib_euler; - for (int i=0; i<3; i++) { - device_from_calib_euler(i) = rpy_calib[i]; - } - model_transform_main = update_calibration(device_from_calib_euler, main_wide_camera, false); - model_transform_extra = update_calibration(device_from_calib_euler, true, true); - live_calib_seen = true; - } - - float vec_desire[DESIRE_LEN] = {0}; - if (desire >= 0 && desire < DESIRE_LEN) { - vec_desire[desire] = 1.0; - } - - // Enable/disable nav features - uint64_t timestamp_llk = sm["navModel"].getNavModel().getLocationMonoTime(); - bool nav_valid = sm["navModel"].getValid() && (nanos_since_boot() - timestamp_llk < 1e9); - bool use_nav = nav_valid && params.getBool("ExperimentalMode"); - if (!nav_enabled && use_nav) { - nav_enabled = true; - } else if (nav_enabled && !use_nav) { - memset(nav_features, 0, sizeof(float)*NAV_FEATURE_LEN); - memset(nav_instructions, 0, sizeof(float)*NAV_INSTRUCTION_LEN); - nav_enabled = false; - } - - if (nav_enabled && sm.updated("navModel")) { - auto nav_model_features = sm["navModel"].getNavModel().getFeatures(); - for (int i=0; i= 0 && distance_idx < 50) { - nav_instructions[distance_idx*3 + direction_idx] = 1; - } - } - } - - // tracked dropped frames - uint32_t vipc_dropped_frames = meta_main.frame_id - last_vipc_frame_id - 1; - float frames_dropped = frame_dropped_filter.update((float)std::min(vipc_dropped_frames, 10U)); - if (run_count < 10) { // let frame drops warm up - frame_dropped_filter.reset(0); - frames_dropped = 0.; - } - run_count++; - - float frame_drop_ratio = frames_dropped / (1 + frames_dropped); - bool prepare_only = vipc_dropped_frames > 0; - - if (prepare_only) { - LOGE("skipping model eval. Dropped %d frames", vipc_dropped_frames); - } - - double mt1 = millis_since_boot(); - ModelOutput *model_output = model_eval_frame(&model, buf_main, buf_extra, model_transform_main, model_transform_extra, vec_desire, is_rhd, driving_style, nav_features, nav_instructions, prepare_only); - double mt2 = millis_since_boot(); - float model_execution_time = (mt2 - mt1) / 1000.0; - - if (model_output != nullptr) { - model_publish(pm, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio, *model_output, model, ps, meta_main.timestamp_eof, timestamp_llk, model_execution_time, - nav_enabled, live_calib_seen); - posenet_publish(pm, meta_main.frame_id, vipc_dropped_frames, *model_output, meta_main.timestamp_eof, live_calib_seen); - } - - // printf("model process: %.2fms, from last %.2fms, vipc_frame_id %u, frame_id, %u, frame_drop %.3f\n", mt2 - mt1, mt1 - last, extra.frame_id, frame_id, frame_drop_ratio); - // last = mt1; - last_vipc_frame_id = meta_main.frame_id; - } -} - -int main(int argc, char **argv) { - if (!Hardware::PC()) { - int ret; - ret = util::set_realtime_priority(54); - assert(ret == 0); - util::set_core_affinity({7}); - assert(ret == 0); - } - - // cl init - cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); - cl_context context = CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); - - // init the models - ModelState model; - model_init(&model, device_id, context); - LOGW("models loaded, modeld starting"); - - bool main_wide_camera = false; - bool use_extra_client = true; // set to false to use single camera - while (!do_exit) { - auto streams = VisionIpcClient::getAvailableStreams("camerad", false); - if (!streams.empty()) { - use_extra_client = streams.count(VISION_STREAM_WIDE_ROAD) > 0 && streams.count(VISION_STREAM_ROAD) > 0; - main_wide_camera = streams.count(VISION_STREAM_ROAD) == 0; - break; - } - - util::sleep_for(100); - } - - VisionIpcClient vipc_client_main = VisionIpcClient("camerad", main_wide_camera ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD, true, device_id, context); - VisionIpcClient vipc_client_extra = VisionIpcClient("camerad", VISION_STREAM_WIDE_ROAD, false, device_id, context); - LOGW("vision stream set up, main_wide_camera: %d, use_extra_client: %d", main_wide_camera, use_extra_client); - - while (!do_exit && !vipc_client_main.connect(false)) { - util::sleep_for(100); - } - - while (!do_exit && use_extra_client && !vipc_client_extra.connect(false)) { - util::sleep_for(100); - } - - // run the models - // vipc_client.connected is false only when do_exit is true - if (!do_exit) { - const VisionBuf *b = &vipc_client_main.buffers[0]; - LOGW("connected main cam with buffer size: %zu (%zu x %zu)", b->len, b->width, b->height); - - if (use_extra_client) { - const VisionBuf *wb = &vipc_client_extra.buffers[0]; - LOGW("connected extra cam with buffer size: %zu (%zu x %zu)", wb->len, wb->width, wb->height); - } - - run_model(model, vipc_client_main, vipc_client_extra, main_wide_camera, use_extra_client); - } - - model_free(&model); - CL_CHECK(clReleaseContext(context)); - return 0; -} diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py new file mode 100755 index 0000000000..3c416ce939 --- /dev/null +++ b/selfdrive/modeld/modeld.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +import sys +import time +import numpy as np +from pathlib import Path +from typing import Dict, Optional +from setproctitle import setproctitle +from cereal.messaging import PubMaster, SubMaster +from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from openpilot.system.swaglog import cloudlog +from openpilot.common.params import Params +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.realtime import config_realtime_process +from openpilot.common.transformations.model import get_warp_matrix +from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime +from openpilot.selfdrive.modeld.models.commonmodel_pyx import ModelFrame, CLContext +from openpilot.selfdrive.modeld.models.driving_pyx import ( + PublishState, create_model_msg, create_pose_msg, + FEATURE_LEN, HISTORY_BUFFER_LEN, DESIRE_LEN, TRAFFIC_CONVENTION_LEN, NAV_FEATURE_LEN, NAV_INSTRUCTION_LEN, + OUTPUT_SIZE, NET_OUTPUT_SIZE, MODEL_FREQ) + +MODEL_PATHS = { + ModelRunner.THNEED: Path(__file__).parent / 'models/supercombo.thneed', + ModelRunner.ONNX: Path(__file__).parent / 'models/supercombo.onnx'} + +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: + 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): + self.frame = ModelFrame(context) + self.wide_frame = ModelFrame(context) + self.prev_desire = np.zeros(DESIRE_LEN, dtype=np.float32) + self.output = np.zeros(NET_OUTPUT_SIZE, dtype=np.float32) + self.inputs = { + 'desire': np.zeros(DESIRE_LEN * (HISTORY_BUFFER_LEN+1), dtype=np.float32), + 'traffic_convention': np.zeros(TRAFFIC_CONVENTION_LEN, dtype=np.float32), + 'nav_features': np.zeros(NAV_FEATURE_LEN, dtype=np.float32), + 'nav_instructions': np.zeros(NAV_INSTRUCTION_LEN, dtype=np.float32), + 'features_buffer': np.zeros(HISTORY_BUFFER_LEN * FEATURE_LEN, dtype=np.float32), + } + + 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 run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_wide: np.ndarray, + inputs: Dict[str, np.ndarray], prepare_only: bool) -> Optional[np.ndarray]: + # Model decides when action is completed, so desire input is just a pulse triggered on rising edge + inputs['desire'][0] = 0 + self.inputs['desire'][:-DESIRE_LEN] = self.inputs['desire'][DESIRE_LEN:] + self.inputs['desire'][-DESIRE_LEN:] = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0) + self.prev_desire[:] = inputs['desire'] + + self.inputs['traffic_convention'][:] = inputs['traffic_convention'] + self.inputs['nav_features'][:] = inputs['nav_features'] + self.inputs['nav_instructions'][:] = inputs['nav_instructions'] + # self.inputs['driving_style'][:] = inputs['driving_style'] + + # 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() + self.inputs['features_buffer'][:-FEATURE_LEN] = self.inputs['features_buffer'][FEATURE_LEN:] + self.inputs['features_buffer'][-FEATURE_LEN:] = self.output[OUTPUT_SIZE:OUTPUT_SIZE+FEATURE_LEN] + return self.output + + +def main(): + cloudlog.bind(daemon="selfdrive.modeld.modeld") + setproctitle("selfdrive.modeld.modeld") + config_realtime_process(7, 54) + + cl_context = CLContext() + 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 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", "cameraOdometry"]) + sm = SubMaster(["lateralPlan", "roadCameraState", "liveCalibration", "driverMonitoringState", "navModel", "navInstruction"]) + + state = PublishState() + params = Params() + + # setup filter to track dropped frames + frame_dropped_filter = FirstOrderFilter(0., 10., 1. / MODEL_FREQ) + frame_id = 0 + last_vipc_frame_id = 0 + run_count = 0 + # last = 0.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 + driving_style = np.array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], dtype=np.float32) + nav_features = np.zeros(NAV_FEATURE_LEN, dtype=np.float32) + nav_instructions = np.zeros(NAV_INSTRUCTION_LEN, dtype=np.float32) + buf_main, buf_extra = None, None + meta_main = FrameMeta() + meta_extra = FrameMeta() + + 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.error("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.error("vipc_client_extra no frame") + continue + + if abs(meta_main.timestamp_sof - meta_extra.timestamp_sof) > 10000000: + cloudlog.error("frames out of sync! main: {} ({:.5f}), extra: {} ({:.5f})".format( + meta_main.frame_id, meta_main.timestamp_sof / 1e9, + meta_extra.frame_id, meta_extra.timestamp_sof / 1e9)) + + else: + # Use single camera + buf_extra = buf_main + meta_extra = meta_main + + # TODO: path planner timeout? + sm.update(0) + desire = sm["lateralPlan"].desire.raw + is_rhd = sm["driverMonitoringState"].isRHD + frame_id = sm["roadCameraState"].frameId + if sm.updated["liveCalibration"]: + device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) + model_transform_main = get_warp_matrix(device_from_calib_euler, main_wide_camera, False).astype(np.float32) + model_transform_extra = get_warp_matrix(device_from_calib_euler, True, True).astype(np.float32) + live_calib_seen = True + + traffic_convention = np.zeros(2) + traffic_convention[int(is_rhd)] = 1 + + vec_desire = np.zeros(DESIRE_LEN, dtype=np.float32) + if desire >= 0 and desire < DESIRE_LEN: + vec_desire[desire] = 1 + + # Enable/disable nav features + timestamp_llk = sm["navModel"].locationMonoTime + nav_valid = sm.valid["navModel"] # and (nanos_since_boot() - timestamp_llk < 1e9) + nav_enabled = nav_valid and params.get_bool("ExperimentalMode") + + if not nav_enabled: + nav_features[:] = 0 + nav_instructions[:] = 0 + + if nav_enabled and sm.updated["navModel"]: + nav_features = np.array(sm["navModel"].features) + + if nav_enabled and sm.updated["navInstruction"]: + nav_instructions[:] = 0 + for maneuver in sm["navInstruction"].allManeuvers: + distance_idx = 25 + int(maneuver.distance / 20) + direction_idx = 0 + if maneuver.modifier in ("left", "slight left", "sharp left"): + direction_idx = 1 + if maneuver.modifier in ("right", "slight right", "sharp right"): + direction_idx = 2 + if 0 <= distance_idx < 50: + nav_instructions[distance_idx*3 + direction_idx] = 1 + + # tracked dropped frames + vipc_dropped_frames = max(0, meta_main.frame_id - last_vipc_frame_id - 1) + frames_dropped = frame_dropped_filter.update(min(vipc_dropped_frames, 10)) + 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, + 'driving_style': driving_style, + 'nav_features': nav_features, + 'nav_instructions': nav_instructions} + + mt1 = time.perf_counter() + model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only) + mt2 = time.perf_counter() + model_execution_time = mt2 - mt1 + + if model_output is not None: + pm.send("modelV2", create_model_msg(model_output, state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio, + meta_main.timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, live_calib_seen)) + pm.send("cameraOdometry", create_pose_msg(model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen)) + + # print("model process: %.2fms, from last %.2fms, vipc_frame_id %u, frame_id, %u, frame_drop %.3f" % + # ((mt2 - mt1)*1000, (mt1 - last)*1000, meta_extra.frame_id, frame_id, frame_drop_ratio)) + # last = mt1 + last_vipc_frame_id = meta_main.frame_id + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + sys.exit() diff --git a/selfdrive/modeld/models/__init__.py b/selfdrive/modeld/models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc index b7c9051c6e..5e28e9b95d 100644 --- a/selfdrive/modeld/models/commonmodel.cc +++ b/selfdrive/modeld/models/commonmodel.cc @@ -55,14 +55,14 @@ ModelFrame::~ModelFrame() { void softmax(const float* input, float* output, size_t len) { const float max_val = *std::max_element(input, input + len); float denominator = 0; - for(int i = 0; i < len; i++) { + for (int i = 0; i < len; i++) { float const v_exp = expf(input[i] - max_val); denominator += v_exp; output[i] = v_exp; } const float inv_denominator = 1. / denominator; - for(int i = 0; i < len; i++) { + for (int i = 0; i < len; i++) { output[i] *= inv_denominator; } } diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd new file mode 100644 index 0000000000..57b79aeafb --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.pxd @@ -0,0 +1,20 @@ +# distutils: language = c++ + +from cereal.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 "selfdrive/modeld/models/commonmodel.h": + float sigmoid(float) + + 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/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd new file mode 100644 index 0000000000..21c0716de4 --- /dev/null +++ b/selfdrive/modeld/models/commonmodel_pyx.pxd @@ -0,0 +1,13 @@ +# distutils: language = c++ + +from cereal.visionipc.visionipc cimport cl_mem +from cereal.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 new file mode 100644 index 0000000000..e33d301aff --- /dev/null +++ b/selfdrive/modeld/models/commonmodel_pyx.pyx @@ -0,0 +1,43 @@ +# distutils: language = c++ +# cython: c_string_encoding=ascii + +import numpy as np +cimport numpy as cnp +from libc.string cimport memcpy + +from cereal.visionipc.visionipc cimport cl_mem +from cereal.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext +from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context +from .commonmodel cimport mat3, sigmoid as cppSigmoid, ModelFrame as cppModelFrame + +def sigmoid(x): + return cppSigmoid(x) + +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 = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection, output.mem) + if not data: + return None + return np.asarray( data) diff --git a/selfdrive/modeld/models/dmonitoring.cc b/selfdrive/modeld/models/dmonitoring.cc deleted file mode 100644 index eb5239bef0..0000000000 --- a/selfdrive/modeld/models/dmonitoring.cc +++ /dev/null @@ -1,133 +0,0 @@ -#include - -#include "common/mat.h" -#include "common/modeldata.h" -#include "common/params.h" -#include "common/timing.h" -#include "system/hardware/hw.h" - -#include "selfdrive/modeld/models/dmonitoring.h" - -constexpr int MODEL_WIDTH = 1440; -constexpr int MODEL_HEIGHT = 960; - -template -static inline T *get_buffer(std::vector &buf, const size_t size) { - if (buf.size() < size) buf.resize(size); - return buf.data(); -} - -void dmonitoring_init(DMonitoringModelState* s) { - -#ifdef USE_ONNX_MODEL - s->m = new ONNXModel("models/dmonitoring_model.onnx", &s->output[0], OUTPUT_SIZE, USE_DSP_RUNTIME, true); -#else - s->m = new SNPEModel("models/dmonitoring_model_q.dlc", &s->output[0], OUTPUT_SIZE, USE_DSP_RUNTIME, true); -#endif - - s->m->addInput("input_imgs", NULL, 0); - s->m->addInput("calib", s->calib, CALIB_LEN); -} - -void parse_driver_data(DriverStateResult &ds_res, const DMonitoringModelState* s, int out_idx_offset) { - for (int i = 0; i < 3; ++i) { - ds_res.face_orientation[i] = s->output[out_idx_offset+i] * REG_SCALE; - ds_res.face_orientation_std[i] = exp(s->output[out_idx_offset+6+i]); - } - for (int i = 0; i < 2; ++i) { - ds_res.face_position[i] = s->output[out_idx_offset+3+i] * REG_SCALE; - ds_res.face_position_std[i] = exp(s->output[out_idx_offset+9+i]); - } - for (int i = 0; i < 4; ++i) { - ds_res.ready_prob[i] = sigmoid(s->output[out_idx_offset+35+i]); - } - for (int i = 0; i < 2; ++i) { - ds_res.not_ready_prob[i] = sigmoid(s->output[out_idx_offset+39+i]); - } - ds_res.face_prob = sigmoid(s->output[out_idx_offset+12]); - ds_res.left_eye_prob = sigmoid(s->output[out_idx_offset+21]); - ds_res.right_eye_prob = sigmoid(s->output[out_idx_offset+30]); - ds_res.left_blink_prob = sigmoid(s->output[out_idx_offset+31]); - ds_res.right_blink_prob = sigmoid(s->output[out_idx_offset+32]); - ds_res.sunglasses_prob = sigmoid(s->output[out_idx_offset+33]); - ds_res.occluded_prob = sigmoid(s->output[out_idx_offset+34]); -} - -void fill_driver_data(cereal::DriverStateV2::DriverData::Builder ddata, const DriverStateResult &ds_res) { - ddata.setFaceOrientation(ds_res.face_orientation); - ddata.setFaceOrientationStd(ds_res.face_orientation_std); - ddata.setFacePosition(ds_res.face_position); - ddata.setFacePositionStd(ds_res.face_position_std); - ddata.setFaceProb(ds_res.face_prob); - ddata.setLeftEyeProb(ds_res.left_eye_prob); - ddata.setRightEyeProb(ds_res.right_eye_prob); - ddata.setLeftBlinkProb(ds_res.left_blink_prob); - ddata.setRightBlinkProb(ds_res.right_blink_prob); - ddata.setSunglassesProb(ds_res.sunglasses_prob); - ddata.setOccludedProb(ds_res.occluded_prob); - ddata.setReadyProb(ds_res.ready_prob); - ddata.setNotReadyProb(ds_res.not_ready_prob); -} - -DMonitoringModelResult dmonitoring_eval_frame(DMonitoringModelState* s, void* stream_buf, int width, int height, int stride, int uv_offset, float *calib) { - int v_off = height - MODEL_HEIGHT; - int h_off = (width - MODEL_WIDTH) / 2; - int yuv_buf_len = MODEL_WIDTH * MODEL_HEIGHT; - - uint8_t *raw_buf = (uint8_t *) stream_buf; - // vertical crop free - uint8_t *raw_y_start = raw_buf + stride * v_off; - - uint8_t *net_input_buf = get_buffer(s->net_input_buf, yuv_buf_len); - - // here makes a uint8 copy - for (int r = 0; r < MODEL_HEIGHT; ++r) { - memcpy(net_input_buf + r * MODEL_WIDTH, raw_y_start + r * stride + h_off, MODEL_WIDTH); - } - - // printf("preprocess completed. %d \n", yuv_buf_len); - // FILE *dump_yuv_file = fopen("/tmp/rawdump.yuv", "wb"); - // fwrite(net_input_buf, yuv_buf_len, sizeof(uint8_t), dump_yuv_file); - // fclose(dump_yuv_file); - - double t1 = millis_since_boot(); - s->m->setInputBuffer("input_imgs", (float*)net_input_buf, yuv_buf_len / sizeof(float)); - for (int i = 0; i < CALIB_LEN; i++) { - s->calib[i] = calib[i]; - } - s->m->execute(); - double t2 = millis_since_boot(); - - DMonitoringModelResult model_res = {0}; - parse_driver_data(model_res.driver_state_lhd, s, 0); - parse_driver_data(model_res.driver_state_rhd, s, 41); - model_res.poor_vision_prob = sigmoid(s->output[82]); - model_res.wheel_on_right_prob = sigmoid(s->output[83]); - model_res.dsp_execution_time = (t2 - t1) / 1000.; - - return model_res; -} - -void dmonitoring_publish(PubMaster &pm, uint32_t frame_id, const DMonitoringModelResult &model_res, float execution_time, kj::ArrayPtr raw_pred) { - // make msg - MessageBuilder msg; - auto framed = msg.initEvent().initDriverStateV2(); - framed.setFrameId(frame_id); - framed.setModelExecutionTime(execution_time); - framed.setDspExecutionTime(model_res.dsp_execution_time); - - framed.setPoorVisionProb(model_res.poor_vision_prob); - framed.setWheelOnRightProb(model_res.wheel_on_right_prob); - fill_driver_data(framed.initLeftDriverData(), model_res.driver_state_lhd); - fill_driver_data(framed.initRightDriverData(), model_res.driver_state_rhd); - - if (send_raw_pred) { - framed.setRawPredictions(raw_pred.asBytes()); - } - - pm.send("driverStateV2", msg); -} - -void dmonitoring_free(DMonitoringModelState* s) { - delete s->m; -} diff --git a/selfdrive/modeld/models/dmonitoring.h b/selfdrive/modeld/models/dmonitoring.h deleted file mode 100644 index ae2bf05394..0000000000 --- a/selfdrive/modeld/models/dmonitoring.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include - -#include "cereal/messaging/messaging.h" -#include "common/util.h" -#include "selfdrive/modeld/models/commonmodel.h" -#include "selfdrive/modeld/runners/run.h" - -#define CALIB_LEN 3 - -#define OUTPUT_SIZE 84 -#define REG_SCALE 0.25f - -typedef struct DriverStateResult { - float face_orientation[3]; - float face_orientation_std[3]; - float face_position[2]; - float face_position_std[2]; - float face_prob; - float left_eye_prob; - float right_eye_prob; - float left_blink_prob; - float right_blink_prob; - float sunglasses_prob; - float occluded_prob; - float ready_prob[4]; - float not_ready_prob[2]; -} DriverStateResult; - -typedef struct DMonitoringModelResult { - DriverStateResult driver_state_lhd; - DriverStateResult driver_state_rhd; - float poor_vision_prob; - float wheel_on_right_prob; - float dsp_execution_time; -} DMonitoringModelResult; - -typedef struct DMonitoringModelState { - RunModel *m; - float output[OUTPUT_SIZE]; - std::vector net_input_buf; - float calib[CALIB_LEN]; -} DMonitoringModelState; - -void dmonitoring_init(DMonitoringModelState* s); -DMonitoringModelResult dmonitoring_eval_frame(DMonitoringModelState* s, void* stream_buf, int width, int height, int stride, int uv_offset, float *calib); -void dmonitoring_publish(PubMaster &pm, uint32_t frame_id, const DMonitoringModelResult &model_res, float execution_time, kj::ArrayPtr raw_pred); -void dmonitoring_free(DMonitoringModelState* s); - diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc index fb42fcd4f9..0a7f0c949d 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -1,124 +1,7 @@ #include "selfdrive/modeld/models/driving.h" -#include -#include - -#include #include -#include - -#include "common/clutil.h" -#include "common/params.h" -#include "common/timing.h" -#include "common/swaglog.h" - - -// #define DUMP_YUV - -void model_init(ModelState* s, cl_device_id device_id, cl_context context) { - s->frame = new ModelFrame(device_id, context); - s->wide_frame = new ModelFrame(device_id, context); - -#ifdef USE_THNEED - s->m = std::make_unique("models/supercombo.thneed", -#elif USE_ONNX_MODEL - s->m = std::make_unique("models/supercombo.onnx", -#else - s->m = std::make_unique("models/supercombo.dlc", -#endif - &s->output[0], NET_OUTPUT_SIZE, USE_GPU_RUNTIME, false, context); - - s->m->addInput("input_imgs", NULL, 0); - s->m->addInput("big_input_imgs", NULL, 0); - - // TODO: the input is important here, still need to fix this -#ifdef DESIRE - s->m->addInput("desire_pulse", s->pulse_desire, DESIRE_LEN*(HISTORY_BUFFER_LEN+1)); -#endif - -#ifdef TRAFFIC_CONVENTION - s->m->addInput("traffic_convention", s->traffic_convention, TRAFFIC_CONVENTION_LEN); -#endif - -#ifdef DRIVING_STYLE - s->m->addInput("driving_style", s->driving_style, DRIVING_STYLE_LEN); -#endif - -#ifdef NAV - s->m->addInput("nav_features", s->nav_features, NAV_FEATURE_LEN); - s->m->addInput("nav_instructions", s->nav_instructions, NAV_INSTRUCTION_LEN); -#endif - -#ifdef TEMPORAL - s->m->addInput("feature_buffer", &s->feature_buffer[0], TEMPORAL_SIZE); -#endif - -} - -ModelOutput* model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* wbuf, const mat3 &transform, const mat3 &transform_wide, - float *desire_in, bool is_rhd, float *driving_style, float *nav_features, float *nav_instructions, bool prepare_only) { -#ifdef DESIRE - std::memmove(&s->pulse_desire[0], &s->pulse_desire[DESIRE_LEN], sizeof(float) * DESIRE_LEN*HISTORY_BUFFER_LEN); - if (desire_in != NULL) { - for (int i = 1; i < DESIRE_LEN; i++) { - // Model decides when action is completed - // so desire input is just a pulse triggered on rising edge - if (desire_in[i] - s->prev_desire[i] > .99) { - s->pulse_desire[DESIRE_LEN*HISTORY_BUFFER_LEN+i] = desire_in[i]; - } else { - s->pulse_desire[DESIRE_LEN*HISTORY_BUFFER_LEN+i] = 0.0; - } - s->prev_desire[i] = desire_in[i]; - } - } -LOGT("Desire enqueued"); -#endif - -#ifdef NAV - std::memcpy(s->nav_features, nav_features, sizeof(float)*NAV_FEATURE_LEN); - std::memcpy(s->nav_instructions, nav_instructions, sizeof(float)*NAV_INSTRUCTION_LEN); -#endif - -#ifdef DRIVING_STYLE - std::memcpy(s->driving_style, driving_style, sizeof(float)*DRIVING_STYLE_LEN); -#endif - - int rhd_idx = is_rhd; - s->traffic_convention[rhd_idx] = 1.0; - s->traffic_convention[1-rhd_idx] = 0.0; - - // if getInputBuf is not NULL, net_input_buf will be - auto net_input_buf = s->frame->prepare(buf->buf_cl, buf->width, buf->height, buf->stride, buf->uv_offset, transform, static_cast(s->m->getCLBuffer("input_imgs"))); - s->m->setInputBuffer("input_imgs", net_input_buf, s->frame->buf_size); - LOGT("Image added"); - - if (wbuf != nullptr) { - auto net_extra_buf = s->wide_frame->prepare(wbuf->buf_cl, wbuf->width, wbuf->height, wbuf->stride, wbuf->uv_offset, transform_wide, static_cast(s->m->getCLBuffer("big_input_imgs"))); - s->m->setInputBuffer("big_input_imgs", net_extra_buf, s->wide_frame->buf_size); - LOGT("Extra image added"); - } - - if (prepare_only) { - return nullptr; - } - - s->m->execute(); - LOGT("Execution finished"); - - #ifdef TEMPORAL - std::memmove(&s->feature_buffer[0], &s->feature_buffer[FEATURE_LEN], sizeof(float) * FEATURE_LEN*(HISTORY_BUFFER_LEN-1)); - std::memcpy(&s->feature_buffer[FEATURE_LEN*(HISTORY_BUFFER_LEN-1)], &s->output[OUTPUT_SIZE], sizeof(float) * FEATURE_LEN); - LOGT("Features enqueued"); - #endif - - return (ModelOutput*)&s->output; -} - -void model_free(ModelState* s) { - delete s->frame; - delete s->wide_frame; -} void fill_lead(cereal::ModelDataV2::LeadDataV3::Builder lead, const ModelOutputLeads &leads, int t_idx, float prob_t) { std::array lead_t = {0.0, 2.0, 4.0, 6.0, 8.0, 10.0}; @@ -157,7 +40,7 @@ void fill_meta(cereal::ModelDataV2::MetaData::Builder meta, const ModelOutputMet softmax(meta_data.desire_pred_prob[i].array.data(), desire_pred_softmax.data() + (i * DESIRE_LEN), DESIRE_LEN); } - std::array lat_long_t = {2,4,6,8,10}; + std::array lat_long_t = {2, 4, 6, 8, 10}; std::array gas_disengage_sigmoid, brake_disengage_sigmoid, steer_override_sigmoid, brake_3ms2_sigmoid, brake_4ms2_sigmoid, brake_5ms2_sigmoid; for (int i=0; i acc_x, acc_y, acc_z; std::array rot_rate_x, rot_rate_y, rot_rate_z; - for(int i=0; i vipc_frame_id) ? (frame_id - vipc_frame_id) : 0; - MessageBuilder msg; auto framed = msg.initEvent(valid).initModelV2(); framed.setFrameId(vipc_frame_id); framed.setFrameIdExtra(vipc_frame_id_extra); @@ -418,36 +299,32 @@ void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_frame_id framed.setModelExecutionTime(model_execution_time); framed.setNavEnabled(nav_enabled); if (send_raw_pred) { - framed.setRawPredictions((kj::ArrayPtr(s.output.data(), s.output.size())).asBytes()); + framed.setRawPredictions(kj::ArrayPtr(net_output_data, NET_OUTPUT_SIZE).asBytes()); } - fill_model(framed, net_outputs, ps); - pm.send("modelV2", msg); + fill_model(framed, *((ModelOutput*) net_output_data), ps); } -void posenet_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, - const ModelOutput &net_outputs, uint64_t timestamp_eof, const bool valid) { - MessageBuilder msg; - const auto &v_mean = net_outputs.pose.velocity_mean; - const auto &r_mean = net_outputs.pose.rotation_mean; - const auto &t_mean = net_outputs.wide_from_device_euler.mean; - const auto &v_std = net_outputs.pose.velocity_std; - const auto &r_std = net_outputs.pose.rotation_std; - const auto &t_std = net_outputs.wide_from_device_euler.std; - const auto &road_transform_trans_mean = net_outputs.road_transform.position_mean; - const auto &road_transform_trans_std = net_outputs.road_transform.position_std; - - auto posenetd = msg.initEvent(valid && (vipc_dropped_frames < 1)).initCameraOdometry(); - posenetd.setTrans({v_mean.x, v_mean.y, v_mean.z}); - posenetd.setRot({r_mean.x, r_mean.y, r_mean.z}); - posenetd.setWideFromDeviceEuler({t_mean.x, t_mean.y, t_mean.z}); - posenetd.setRoadTransformTrans({road_transform_trans_mean.x, road_transform_trans_mean.y, road_transform_trans_mean.z}); - posenetd.setTransStd({exp(v_std.x), exp(v_std.y), exp(v_std.z)}); - posenetd.setRotStd({exp(r_std.x), exp(r_std.y), exp(r_std.z)}); - posenetd.setWideFromDeviceEulerStd({exp(t_std.x), exp(t_std.y), exp(t_std.z)}); - posenetd.setRoadTransformTransStd({exp(road_transform_trans_std.x), exp(road_transform_trans_std.y), exp(road_transform_trans_std.z)}); - - posenetd.setTimestampEof(timestamp_eof); - posenetd.setFrameId(vipc_frame_id); - - pm.send("cameraOdometry", msg); +void fill_pose_msg(MessageBuilder &msg, float *net_output_data, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, uint64_t timestamp_eof, const bool valid) { + const ModelOutput &net_outputs = *((ModelOutput*) net_output_data); + const auto &v_mean = net_outputs.pose.velocity_mean; + const auto &r_mean = net_outputs.pose.rotation_mean; + const auto &t_mean = net_outputs.wide_from_device_euler.mean; + const auto &v_std = net_outputs.pose.velocity_std; + const auto &r_std = net_outputs.pose.rotation_std; + const auto &t_std = net_outputs.wide_from_device_euler.std; + const auto &road_transform_trans_mean = net_outputs.road_transform.position_mean; + const auto &road_transform_trans_std = net_outputs.road_transform.position_std; + + auto posenetd = msg.initEvent(valid && (vipc_dropped_frames < 1)).initCameraOdometry(); + posenetd.setTrans({v_mean.x, v_mean.y, v_mean.z}); + posenetd.setRot({r_mean.x, r_mean.y, r_mean.z}); + posenetd.setWideFromDeviceEuler({t_mean.x, t_mean.y, t_mean.z}); + posenetd.setRoadTransformTrans({road_transform_trans_mean.x, road_transform_trans_mean.y, road_transform_trans_mean.z}); + posenetd.setTransStd({exp(v_std.x), exp(v_std.y), exp(v_std.z)}); + posenetd.setRotStd({exp(r_std.x), exp(r_std.y), exp(r_std.z)}); + posenetd.setWideFromDeviceEulerStd({exp(t_std.x), exp(t_std.y), exp(t_std.z)}); + posenetd.setRoadTransformTransStd({exp(road_transform_trans_std.x), exp(road_transform_trans_std.y), exp(road_transform_trans_std.z)}); + + posenetd.setTimestampEof(timestamp_eof); + posenetd.setFrameId(vipc_frame_id); } diff --git a/selfdrive/modeld/models/driving.h b/selfdrive/modeld/models/driving.h index 53f81ccde1..de0250d212 100644 --- a/selfdrive/modeld/models/driving.h +++ b/selfdrive/modeld/models/driving.h @@ -4,25 +4,18 @@ #include #include "cereal/messaging/messaging.h" -#include "cereal/visionipc/visionipc_client.h" -#include "common/mat.h" #include "common/modeldata.h" #include "common/util.h" #include "selfdrive/modeld/models/commonmodel.h" -#include "selfdrive/modeld/models/nav.h" #include "selfdrive/modeld/runners/run.h" -// gate this here -#define TEMPORAL -#define DESIRE -#define TRAFFIC_CONVENTION -#define NAV - constexpr int FEATURE_LEN = 128; constexpr int HISTORY_BUFFER_LEN = 99; constexpr int DESIRE_LEN = 8; constexpr int DESIRE_PRED_LEN = 4; constexpr int TRAFFIC_CONVENTION_LEN = 2; +constexpr int NAV_FEATURE_LEN = 256; +constexpr int NAV_INSTRUCTION_LEN = 150; constexpr int DRIVING_STYLE_LEN = 12; constexpr int MODEL_FREQ = 20; @@ -31,10 +24,8 @@ constexpr int BLINKER_LEN = 6; constexpr int META_STRIDE = 7; constexpr int PLAN_MHP_N = 5; - constexpr int LEAD_MHP_N = 2; constexpr int LEAD_TRAJ_LEN = 6; -constexpr int LEAD_PRED_DIM = 4; constexpr int LEAD_MHP_SELECTION = 3; // Padding to get output shape as multiple of 4 constexpr int PAD_SIZE = 2; @@ -253,49 +244,14 @@ struct ModelOutput { }; constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float); - -#ifdef TEMPORAL - constexpr int TEMPORAL_SIZE = HISTORY_BUFFER_LEN * FEATURE_LEN; -#else - constexpr int TEMPORAL_SIZE = 0; -#endif constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + FEATURE_LEN + PAD_SIZE; -// TODO: convert remaining arrays to std::array and update model runners -struct ModelState { - ModelFrame *frame = nullptr; - ModelFrame *wide_frame = nullptr; - std::array feature_buffer = {}; - std::array output = {}; - std::unique_ptr m; -#ifdef DESIRE - float prev_desire[DESIRE_LEN] = {}; - float pulse_desire[DESIRE_LEN*(HISTORY_BUFFER_LEN+1)] = {}; -#endif -#ifdef TRAFFIC_CONVENTION - float traffic_convention[TRAFFIC_CONVENTION_LEN] = {}; -#endif -#ifdef DRIVING_STYLE - float driving_style[DRIVING_STYLE_LEN] = {}; -#endif -#ifdef NAV - float nav_features[NAV_FEATURE_LEN] = {}; - float nav_instructions[NAV_INSTRUCTION_LEN] = {}; -#endif -}; - struct PublishState { std::array disengage_buffer = {}; std::array prev_brake_5ms2_probs = {}; std::array prev_brake_3ms2_probs = {}; }; -void model_init(ModelState* s, cl_device_id device_id, cl_context context); -ModelOutput *model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* buf_wide, const mat3 &transform, const mat3 &transform_wide, - float *desire_in, bool is_rhd, float *driving_style, float *nav_features, float *nav_instructions, bool prepare_only); -void model_free(ModelState* s); -void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, - const ModelOutput &net_outputs, ModelState &s, PublishState &ps, uint64_t timestamp_eof, uint64_t timestamp_llk, - float model_execution_time, const bool nav_enabled, const bool valid); -void posenet_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, - const ModelOutput &net_outputs, uint64_t timestamp_eof, const bool valid); +void fill_model_msg(MessageBuilder &msg, float *net_output_data, PublishState &ps, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, + uint64_t timestamp_eof, uint64_t timestamp_llk, float model_execution_time, const bool nav_enabled, const bool valid); +void fill_pose_msg(MessageBuilder &msg, float *net_outputs, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, uint64_t timestamp_eof, const bool valid); diff --git a/selfdrive/modeld/models/driving.pxd b/selfdrive/modeld/models/driving.pxd new file mode 100644 index 0000000000..8d6b8d755e --- /dev/null +++ b/selfdrive/modeld/models/driving.pxd @@ -0,0 +1,25 @@ +# distutils: language = c++ + +from libcpp cimport bool +from libc.stdint cimport uint32_t, uint64_t + +cdef extern from "cereal/messaging/messaging.h": + cdef cppclass MessageBuilder: + size_t getSerializedSize() + int serializeToBuffer(unsigned char *, size_t) + +cdef extern from "selfdrive/modeld/models/driving.h": + cdef int FEATURE_LEN + cdef int HISTORY_BUFFER_LEN + cdef int DESIRE_LEN + cdef int TRAFFIC_CONVENTION_LEN + cdef int DRIVING_STYLE_LEN + cdef int NAV_FEATURE_LEN + cdef int NAV_INSTRUCTION_LEN + cdef int OUTPUT_SIZE + cdef int NET_OUTPUT_SIZE + cdef int MODEL_FREQ + cdef struct PublishState: pass + + void fill_model_msg(MessageBuilder, float *, PublishState, uint32_t, uint32_t, uint32_t, float, uint64_t, uint64_t, float, bool, bool) + void fill_pose_msg(MessageBuilder, float *, uint32_t, uint32_t, uint64_t, bool) diff --git a/selfdrive/modeld/models/driving_pyx.pyx b/selfdrive/modeld/models/driving_pyx.pyx new file mode 100644 index 0000000000..b98a8f3ff1 --- /dev/null +++ b/selfdrive/modeld/models/driving_pyx.pyx @@ -0,0 +1,52 @@ +# distutils: language = c++ +# cython: c_string_encoding=ascii + +import numpy as np +cimport numpy as cnp +from libcpp cimport bool +from libc.string cimport memcpy +from libc.stdint cimport uint32_t, uint64_t + +from .commonmodel cimport mat3 +from .driving cimport FEATURE_LEN as CPP_FEATURE_LEN, HISTORY_BUFFER_LEN as CPP_HISTORY_BUFFER_LEN, DESIRE_LEN as CPP_DESIRE_LEN, \ + TRAFFIC_CONVENTION_LEN as CPP_TRAFFIC_CONVENTION_LEN, DRIVING_STYLE_LEN as CPP_DRIVING_STYLE_LEN, \ + NAV_FEATURE_LEN as CPP_NAV_FEATURE_LEN, NAV_INSTRUCTION_LEN as CPP_NAV_INSTRUCTION_LEN, \ + OUTPUT_SIZE as CPP_OUTPUT_SIZE, NET_OUTPUT_SIZE as CPP_NET_OUTPUT_SIZE, MODEL_FREQ as CPP_MODEL_FREQ +from .driving cimport MessageBuilder, PublishState as cppPublishState +from .driving cimport fill_model_msg, fill_pose_msg + +FEATURE_LEN = CPP_FEATURE_LEN +HISTORY_BUFFER_LEN = CPP_HISTORY_BUFFER_LEN +DESIRE_LEN = CPP_DESIRE_LEN +TRAFFIC_CONVENTION_LEN = CPP_TRAFFIC_CONVENTION_LEN +DRIVING_STYLE_LEN = CPP_DRIVING_STYLE_LEN +NAV_FEATURE_LEN = CPP_NAV_FEATURE_LEN +NAV_INSTRUCTION_LEN = CPP_NAV_INSTRUCTION_LEN +OUTPUT_SIZE = CPP_OUTPUT_SIZE +NET_OUTPUT_SIZE = CPP_NET_OUTPUT_SIZE +MODEL_FREQ = CPP_MODEL_FREQ + +cdef class PublishState: + cdef cppPublishState state + +def create_model_msg(float[:] model_outputs, PublishState ps, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, + uint64_t timestamp_eof, uint64_t timestamp_llk, float model_execution_time, bool nav_enabled, bool valid): + cdef MessageBuilder msg + fill_model_msg(msg, &model_outputs[0], ps.state, vipc_frame_id, vipc_frame_id_extra, frame_id, frame_drop, + timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, valid) + + output_size = msg.getSerializedSize() + output_data = bytearray(output_size) + cdef unsigned char * output_ptr = output_data + assert msg.serializeToBuffer(output_ptr, output_size) > 0, "output buffer is too small to serialize" + return bytes(output_data) + +def create_pose_msg(float[:] model_outputs, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, uint64_t timestamp_eof, bool valid): + cdef MessageBuilder msg + fill_pose_msg(msg, &model_outputs[0], vipc_frame_id, vipc_dropped_frames, timestamp_eof, valid) + + output_size = msg.getSerializedSize() + output_data = bytearray(output_size) + cdef unsigned char * output_ptr = output_data + assert msg.serializeToBuffer(output_ptr, output_size) > 0, "output buffer is too small to serialize" + return bytes(output_data) diff --git a/selfdrive/modeld/models/nav.cc b/selfdrive/modeld/models/nav.cc deleted file mode 100644 index 8208b0bfef..0000000000 --- a/selfdrive/modeld/models/nav.cc +++ /dev/null @@ -1,71 +0,0 @@ -#include "selfdrive/modeld/models/nav.h" - -#include -#include - -#include "common/mat.h" -#include "common/modeldata.h" -#include "common/timing.h" - - -void navmodel_init(NavModelState* s) { - #ifdef USE_ONNX_MODEL - s->m = new ONNXModel("models/navmodel.onnx", &s->output[0], NAV_NET_OUTPUT_SIZE, USE_DSP_RUNTIME, true); - #else - s->m = new SNPEModel("models/navmodel_q.dlc", &s->output[0], NAV_NET_OUTPUT_SIZE, USE_DSP_RUNTIME, true); - #endif - - s->m->addInput("map", NULL, 0); -} - -NavModelResult* navmodel_eval_frame(NavModelState* s, VisionBuf* buf) { - memcpy(s->net_input_buf, buf->addr, NAV_INPUT_SIZE); - - double t1 = millis_since_boot(); - s->m->setInputBuffer("map", (float*)s->net_input_buf, NAV_INPUT_SIZE/sizeof(float)); - s->m->execute(); - double t2 = millis_since_boot(); - - NavModelResult *model_res = (NavModelResult*)&s->output; - model_res->dsp_execution_time = (t2 - t1) / 1000.; - return model_res; -} - -void fill_plan(cereal::NavModelData::Builder &framed, const NavModelOutputPlan &plan) { - std::array pos_x, pos_y; - std::array pos_x_std, pos_y_std; - - for (int i=0; im; -} diff --git a/selfdrive/modeld/models/nav.h b/selfdrive/modeld/models/nav.h deleted file mode 100644 index 800abec252..0000000000 --- a/selfdrive/modeld/models/nav.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include "cereal/messaging/messaging.h" -#include "cereal/visionipc/visionipc_client.h" -#include "common/util.h" -#include "common/modeldata.h" -#include "selfdrive/modeld/models/commonmodel.h" -#include "selfdrive/modeld/runners/run.h" - -constexpr int NAV_INPUT_SIZE = 256*256; -constexpr int NAV_FEATURE_LEN = 256; -constexpr int NAV_INSTRUCTION_LEN = 150; -constexpr int NAV_DESIRE_LEN = 32; - -struct NavModelOutputXY { - float x; - float y; -}; -static_assert(sizeof(NavModelOutputXY) == sizeof(float)*2); - -struct NavModelOutputPlan { - std::array mean; - std::array std; -}; -static_assert(sizeof(NavModelOutputPlan) == sizeof(NavModelOutputXY)*TRAJECTORY_SIZE*2); - -struct NavModelOutputDesirePrediction { - std::array values; -}; -static_assert(sizeof(NavModelOutputDesirePrediction) == sizeof(float)*NAV_DESIRE_LEN); - -struct NavModelOutputFeatures { - std::array values; -}; -static_assert(sizeof(NavModelOutputFeatures) == sizeof(float)*NAV_FEATURE_LEN); - -struct NavModelResult { - const NavModelOutputPlan plan; - const NavModelOutputDesirePrediction desire_pred; - const NavModelOutputFeatures features; - float dsp_execution_time; -}; -static_assert(sizeof(NavModelResult) == sizeof(NavModelOutputPlan) + sizeof(NavModelOutputDesirePrediction) + sizeof(NavModelOutputFeatures) + sizeof(float)); - -constexpr int NAV_OUTPUT_SIZE = sizeof(NavModelResult) / sizeof(float); -constexpr int NAV_NET_OUTPUT_SIZE = NAV_OUTPUT_SIZE - 1; - -struct NavModelState { - RunModel *m; - uint8_t net_input_buf[NAV_INPUT_SIZE]; - float output[NAV_OUTPUT_SIZE]; -}; - -void navmodel_init(NavModelState* s); -NavModelResult* navmodel_eval_frame(NavModelState* s, VisionBuf* buf); -void navmodel_publish(PubMaster &pm, VisionIpcBufExtra &extra, const NavModelResult &model_res, float execution_time, bool route_valid); -void navmodel_free(NavModelState* s); diff --git a/selfdrive/modeld/navmodeld b/selfdrive/modeld/navmodeld deleted file mode 100755 index 079afd9677..0000000000 --- a/selfdrive/modeld/navmodeld +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -cd $DIR - -if [ -f /TICI ]; then - export LD_LIBRARY_PATH="/usr/lib/aarch64-linux-gnu:/data/pythonpath/third_party/snpe/larch64:$LD_LIBRARY_PATH" - export ADSP_LIBRARY_PATH="/data/pythonpath/third_party/snpe/dsp/" -else - export LD_LIBRARY_PATH="$DIR/../../third_party/snpe/x86_64-linux-clang:$DIR/../../openpilot/third_party/snpe/x86_64:$LD_LIBRARY_PATH" -fi -exec ./_navmodeld diff --git a/selfdrive/modeld/navmodeld.cc b/selfdrive/modeld/navmodeld.cc deleted file mode 100644 index 4610f503d1..0000000000 --- a/selfdrive/modeld/navmodeld.cc +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include - -#include -#include - -#include "cereal/visionipc/visionipc_client.h" -#include "common/params.h" -#include "common/swaglog.h" -#include "common/util.h" -#include "selfdrive/modeld/models/nav.h" - -ExitHandler do_exit; - -void run_model(NavModelState &model, VisionIpcClient &vipc_client) { - SubMaster sm({"navInstruction"}); - PubMaster pm({"navModel"}); - - //double last_ts = 0; - //uint32_t last_frame_id = 0; - VisionIpcBufExtra extra = {}; - - while (!do_exit) { - VisionBuf *buf = vipc_client.recv(&extra); - if (buf == nullptr) continue; - - sm.update(0); - - double t1 = millis_since_boot(); - NavModelResult *model_res = navmodel_eval_frame(&model, buf); - double t2 = millis_since_boot(); - - // send navmodel packet - navmodel_publish(pm, extra, *model_res, (t2 - t1) / 1000.0, sm["navInstruction"].getValid()); - - //printf("navmodel process: %.2fms, from last %.2fms\n", t2 - t1, t1 - last_ts); - //last_ts = t1; - //last_frame_id = extra.frame_id; - } -} - -int main(int argc, char **argv) { - setpriority(PRIO_PROCESS, 0, -15); - - // there exists a race condition when two processes try to create a - // SNPE model runner at the same time, wait for dmonitoringmodeld to finish - LOGW("waiting for dmonitoringmodeld to initialize"); - if (!Params().getBool("DmModelInitialized", true)) { - return 0; - } - - // init the models - NavModelState model; - navmodel_init(&model); - LOGW("models loaded, navmodeld starting"); - - VisionIpcClient vipc_client = VisionIpcClient("navd", VISION_STREAM_MAP, true); - while (!do_exit && !vipc_client.connect(false)) { - util::sleep_for(100); - } - - // run the models - if (vipc_client.connected) { - LOGW("connected with buffer size: %zu", vipc_client.buffers[0].len); - run_model(model, vipc_client); - } - - navmodel_free(&model); - return 0; -} diff --git a/selfdrive/modeld/navmodeld.py b/selfdrive/modeld/navmodeld.py new file mode 100755 index 0000000000..f5dc51ed20 --- /dev/null +++ b/selfdrive/modeld/navmodeld.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +import gc +import math +import time +import ctypes +import numpy as np +from pathlib import Path +from typing import Tuple, Dict + +from cereal import messaging +from cereal.messaging import PubMaster, SubMaster +from cereal.visionipc import VisionIpcClient, VisionStreamType +from openpilot.system.swaglog import cloudlog +from openpilot.common.params import Params +from openpilot.common.realtime import set_realtime_priority +from openpilot.selfdrive.modeld.constants import IDX_N +from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime + +NAV_INPUT_SIZE = 256*256 +NAV_FEATURE_LEN = 256 +NAV_DESIRE_LEN = 32 +NAV_OUTPUT_SIZE = 2*2*IDX_N + NAV_DESIRE_LEN + NAV_FEATURE_LEN +MODEL_PATHS = { + ModelRunner.SNPE: Path(__file__).parent / 'models/navmodel_q.dlc', + ModelRunner.ONNX: Path(__file__).parent / 'models/navmodel.onnx'} + +class NavModelOutputXY(ctypes.Structure): + _fields_ = [ + ("x", ctypes.c_float), + ("y", ctypes.c_float)] + +class NavModelOutputPlan(ctypes.Structure): + _fields_ = [ + ("mean", NavModelOutputXY*IDX_N), + ("std", NavModelOutputXY*IDX_N)] + +class NavModelResult(ctypes.Structure): + _fields_ = [ + ("plan", NavModelOutputPlan), + ("desire_pred", ctypes.c_float*NAV_DESIRE_LEN), + ("features", ctypes.c_float*NAV_FEATURE_LEN)] + +class ModelState: + inputs: Dict[str, np.ndarray] + output: np.ndarray + model: ModelRunner + + def __init__(self): + assert ctypes.sizeof(NavModelResult) == NAV_OUTPUT_SIZE * ctypes.sizeof(ctypes.c_float) + self.output = np.zeros(NAV_OUTPUT_SIZE, dtype=np.float32) + self.inputs = {'input_img': np.zeros(NAV_INPUT_SIZE, dtype=np.uint8)} + self.model = ModelRunner(MODEL_PATHS, self.output, Runtime.DSP, True, None) + self.model.addInput("input_img", None) + + def run(self, buf:np.ndarray) -> Tuple[np.ndarray, float]: + self.inputs['input_img'][:] = buf + + t1 = time.perf_counter() + self.model.setInputBuffer("input_img", self.inputs['input_img'].view(np.float32)) + self.model.execute() + t2 = time.perf_counter() + return self.output, t2 - t1 + +def get_navmodel_packet(model_output: np.ndarray, valid: bool, frame_id: int, location_ts: int, execution_time: float, dsp_execution_time: float): + model_result = ctypes.cast(model_output.ctypes.data, ctypes.POINTER(NavModelResult)).contents + msg = messaging.new_message('navModel') + msg.valid = valid + msg.navModel.frameId = frame_id + msg.navModel.locationMonoTime = location_ts + msg.navModel.modelExecutionTime = execution_time + msg.navModel.dspExecutionTime = dsp_execution_time + msg.navModel.features = model_result.features[:] + msg.navModel.desirePrediction = model_result.desire_pred[:] + msg.navModel.position.x = [p.x for p in model_result.plan.mean] + msg.navModel.position.y = [p.y for p in model_result.plan.mean] + msg.navModel.position.xStd = [math.exp(p.x) for p in model_result.plan.std] + msg.navModel.position.yStd = [math.exp(p.y) for p in model_result.plan.std] + return msg + + +def main(): + gc.disable() + set_realtime_priority(1) + + # there exists a race condition when two processes try to create a + # SNPE model runner at the same time, wait for dmonitoringmodeld to finish + cloudlog.warning("waiting for dmonitoringmodeld to initialize") + if not Params().get_bool("DmModelInitialized", True): + return + + model = ModelState() + cloudlog.warning("models loaded, navmodeld starting") + + vipc_client = VisionIpcClient("navd", VisionStreamType.VISION_STREAM_MAP, True) + while not vipc_client.connect(False): + time.sleep(0.1) + assert vipc_client.is_connected() + cloudlog.warning(f"connected with buffer size: {vipc_client.buffer_len}") + + sm = SubMaster(["navInstruction"]) + pm = PubMaster(["navModel"]) + + while True: + buf = vipc_client.recv() + if buf is None: + continue + + sm.update(0) + t1 = time.perf_counter() + model_output, dsp_execution_time = model.run(buf.data[:buf.uv_offset]) + t2 = time.perf_counter() + + valid = vipc_client.valid and sm.valid["navInstruction"] + pm.send("navModel", get_navmodel_packet(model_output, valid, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, dsp_execution_time)) + + +if __name__ == "__main__": + main() diff --git a/selfdrive/modeld/runners/__init__.py b/selfdrive/modeld/runners/__init__.py new file mode 100644 index 0000000000..4c29bf3f1c --- /dev/null +++ b/selfdrive/modeld/runners/__init__.py @@ -0,0 +1,27 @@ +import os +from openpilot.system.hardware import TICI +from openpilot.selfdrive.modeld.runners.runmodel_pyx import RunModel, Runtime +assert Runtime + +USE_THNEED = int(os.getenv('USE_THNEED', str(int(TICI)))) +USE_SNPE = int(os.getenv('USE_SNPE', str(int(TICI)))) + +class ModelRunner(RunModel): + THNEED = 'THNEED' + SNPE = 'SNPE' + ONNX = 'ONNX' + + def __new__(cls, paths, *args, **kwargs): + if ModelRunner.THNEED in paths and USE_THNEED: + from openpilot.selfdrive.modeld.runners.thneedmodel_pyx import ThneedModel as Runner + runner_type = ModelRunner.THNEED + elif ModelRunner.SNPE in paths and USE_SNPE: + from openpilot.selfdrive.modeld.runners.snpemodel_pyx import SNPEModel as Runner + runner_type = ModelRunner.SNPE + elif ModelRunner.ONNX in paths: + from openpilot.selfdrive.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/selfdrive/modeld/runners/onnx_runner.py b/selfdrive/modeld/runners/onnx_runner.py deleted file mode 100755 index 9e53874578..0000000000 --- a/selfdrive/modeld/runners/onnx_runner.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -import numpy as np -from typing import Tuple, Dict, Union, Any - -os.environ["OMP_NUM_THREADS"] = "4" -os.environ["OMP_WAIT_POLICY"] = "PASSIVE" - -import onnxruntime as ort - -ORT_TYPES_TO_NP_TYPES = {'tensor(float16)': np.float16, 'tensor(float)': np.float32, 'tensor(uint8)': np.uint8} - -def read(sz, tf8=False): - dd = [] - gt = 0 - szof = 1 if tf8 else 4 - while gt < sz * szof: - st = os.read(0, sz * szof - gt) - assert(len(st) > 0) - dd.append(st) - gt += len(st) - r = np.frombuffer(b''.join(dd), dtype=np.uint8 if tf8 else np.float32) - if tf8: - r = r / 255. - return r - -def write(d): - os.write(1, d.tobytes()) - -def run_loop(m, tf8_input=False): - ishapes = [[1]+ii.shape[1:] for ii in m.get_inputs()] - keys = [x.name for x in m.get_inputs()] - itypes = [ORT_TYPES_TO_NP_TYPES[x.type] for x in m.get_inputs()] - - # run once to initialize CUDA provider - if "CUDAExecutionProvider" in m.get_providers(): - m.run(None, dict(zip(keys, [np.zeros(shp, dtype=itp) for shp, itp in zip(ishapes, itypes, strict=True)], strict=True))) - - print("ready to run onnx model", keys, ishapes, file=sys.stderr) - while 1: - inputs = [] - for k, shp, itp in zip(keys, ishapes, itypes, strict=True): - ts = np.product(shp) - #print("reshaping %s with offset %d" % (str(shp), offset), file=sys.stderr) - inputs.append(read(ts, (k=='input_img' and tf8_input)).reshape(shp).astype(itp)) - ret = m.run(None, dict(zip(keys, inputs, strict=True))) - #print(ret, file=sys.stderr) - for r in ret: - write(r.astype(np.float32)) - - -if __name__ == "__main__": - print(sys.argv, file=sys.stderr) - print("Onnx available providers: ", ort.get_available_providers(), file=sys.stderr) - options = ort.SessionOptions() - options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_DISABLE_ALL - - provider: Union[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' - - try: - print("Onnx selected provider: ", [provider], file=sys.stderr) - ort_session = ort.InferenceSession(sys.argv[1], options, providers=[provider]) - print("Onnx using ", ort_session.get_providers(), file=sys.stderr) - run_loop(ort_session, tf8_input=("--use_tf8" in sys.argv)) - except KeyboardInterrupt: - pass diff --git a/selfdrive/modeld/runners/onnxmodel.cc b/selfdrive/modeld/runners/onnxmodel.cc deleted file mode 100644 index 48c16457fd..0000000000 --- a/selfdrive/modeld/runners/onnxmodel.cc +++ /dev/null @@ -1,88 +0,0 @@ -#include "selfdrive/modeld/runners/onnxmodel.h" - -#include -#include -#include -#include -#include - -#include "common/util.h" - -ONNXModel::ONNXModel(const std::string path, float *_output, size_t _output_size, int runtime, bool _use_tf8, cl_context context) { - LOGD("loading model %s", path.c_str()); - - output = _output; - output_size = _output_size; - use_tf8 = _use_tf8; - - int err = pipe(pipein); - assert(err == 0); - err = pipe(pipeout); - assert(err == 0); - - std::string exe_dir = util::dir_name(util::readlink("/proc/self/exe")); - std::string onnx_runner = exe_dir + "/runners/onnx_runner.py"; - std::string tf8_arg = use_tf8 ? "--use_tf8" : ""; - - proc_pid = fork(); - if (proc_pid == 0) { - LOGD("spawning onnx process %s", onnx_runner.c_str()); - char *argv[] = {(char*)onnx_runner.c_str(), (char*)path.c_str(), (char*)tf8_arg.c_str(), nullptr}; - dup2(pipein[0], 0); - dup2(pipeout[1], 1); - close(pipein[0]); - close(pipein[1]); - close(pipeout[0]); - close(pipeout[1]); - execvp(onnx_runner.c_str(), argv); - } - - // parent - close(pipein[0]); - close(pipeout[1]); -} - -ONNXModel::~ONNXModel() { - close(pipein[1]); - close(pipeout[0]); - kill(proc_pid, SIGTERM); -} - -void ONNXModel::pwrite(float *buf, int size) { - char *cbuf = (char *)buf; - int tw = size*sizeof(float); - while (tw > 0) { - int err = write(pipein[1], cbuf, tw); - //printf("host write %d\n", err); - assert(err >= 0); - cbuf += err; - tw -= err; - } - LOGD("host write of size %d done", size); -} - -void ONNXModel::pread(float *buf, int size) { - char *cbuf = (char *)buf; - int tr = size*sizeof(float); - struct pollfd fds[1]; - fds[0].fd = pipeout[0]; - fds[0].events = POLLIN; - while (tr > 0) { - int err; - err = poll(fds, 1, 10000); // 10 second timeout - assert(err == 1 || (err == -1 && errno == EINTR)); - LOGD("host read remaining %d/%lu poll %d", tr, size*sizeof(float), err); - err = read(pipeout[0], cbuf, tr); - assert(err > 0 || (err == 0 && errno == EINTR)); - cbuf += err; - tr -= err; - } - LOGD("host read done"); -} - -void ONNXModel::execute() { - for (auto &input : inputs) { - pwrite(input->buffer, input->size); - } - pread(output, output_size); -} diff --git a/selfdrive/modeld/runners/onnxmodel.h b/selfdrive/modeld/runners/onnxmodel.h deleted file mode 100644 index 6c325f644e..0000000000 --- a/selfdrive/modeld/runners/onnxmodel.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "selfdrive/modeld/runners/runmodel.h" - -class ONNXModel : public RunModel { -public: - ONNXModel(const std::string path, float *output, size_t output_size, int runtime, bool _use_tf8 = false, cl_context context = NULL); - ~ONNXModel(); - void execute(); -private: - int proc_pid; - float *output; - size_t output_size; - bool use_tf8; - - // pipe to communicate to onnx_runner subprocess - void pread(float *buf, int size); - void pwrite(float *buf, int size); - int pipein[2]; - int pipeout[2]; -}; diff --git a/selfdrive/modeld/runners/onnxmodel.py b/selfdrive/modeld/runners/onnxmodel.py new file mode 100644 index 0000000000..3c20a39760 --- /dev/null +++ b/selfdrive/modeld/runners/onnxmodel.py @@ -0,0 +1,70 @@ +import os +import sys +import numpy as np +from typing import Tuple, Dict, Union, Any + +from openpilot.selfdrive.modeld.runners.runmodel_pyx import RunModel + +ORT_TYPES_TO_NP_TYPES = {'tensor(float16)': np.float16, 'tensor(float)': np.float32, 'tensor(uint8)': np.uint8} + +def create_ort_session(path): + 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: Union[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' + + print("Onnx selected provider: ", [provider], file=sys.stderr) + ort_session = ort.InferenceSession(path, 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.use_tf8 = use_tf8 + + self.session = create_ort_session(path) + 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(np.uint8) / 255. if self.use_tf8 and k == 'input_img' else v) 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/selfdrive/modeld/runners/run.h b/selfdrive/modeld/runners/run.h index c64f300fe2..36ad262a5b 100644 --- a/selfdrive/modeld/runners/run.h +++ b/selfdrive/modeld/runners/run.h @@ -1,10 +1,4 @@ #pragma once -#include "runmodel.h" -#include "snpemodel.h" - -#if defined(USE_THNEED) -#include "thneedmodel.h" -#elif defined(USE_ONNX_MODEL) -#include "onnxmodel.h" -#endif +#include "selfdrive/modeld/runners/runmodel.h" +#include "selfdrive/modeld/runners/snpemodel.h" diff --git a/selfdrive/modeld/runners/runmodel.h b/selfdrive/modeld/runners/runmodel.h index 00c88131bf..18cc180cb7 100644 --- a/selfdrive/modeld/runners/runmodel.h +++ b/selfdrive/modeld/runners/runmodel.h @@ -8,6 +8,10 @@ #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; diff --git a/selfdrive/modeld/runners/runmodel.pxd b/selfdrive/modeld/runners/runmodel.pxd new file mode 100644 index 0000000000..01b2a9cf2c --- /dev/null +++ b/selfdrive/modeld/runners/runmodel.pxd @@ -0,0 +1,14 @@ +# distutils: language = c++ + +from libcpp.string cimport string + +cdef extern from "selfdrive/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/selfdrive/modeld/runners/runmodel_pyx.pxd b/selfdrive/modeld/runners/runmodel_pyx.pxd new file mode 100644 index 0000000000..b6ede7cf37 --- /dev/null +++ b/selfdrive/modeld/runners/runmodel_pyx.pxd @@ -0,0 +1,6 @@ +# distutils: language = c++ + +from .runmodel cimport RunModel as cppRunModel + +cdef class RunModel: + cdef cppRunModel * model diff --git a/selfdrive/modeld/runners/runmodel_pyx.pyx b/selfdrive/modeld/runners/runmodel_pyx.pyx new file mode 100644 index 0000000000..cdc62a79be --- /dev/null +++ b/selfdrive/modeld/runners/runmodel_pyx.pyx @@ -0,0 +1,38 @@ +# distutils: language = c++ +# cython: c_string_encoding=ascii + +from libcpp.string cimport string +from libc.string cimport memcpy + +from .runmodel cimport USE_CPU_RUNTIME, USE_GPU_RUNTIME, USE_DSP_RUNTIME +from selfdrive.modeld.models.commonmodel_pyx cimport CLMem + +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/selfdrive/modeld/runners/snpemodel.cc b/selfdrive/modeld/runners/snpemodel.cc index 441122c522..15c1db0086 100644 --- a/selfdrive/modeld/runners/snpemodel.cc +++ b/selfdrive/modeld/runners/snpemodel.cc @@ -3,6 +3,10 @@ #include "selfdrive/modeld/runners/snpemodel.h" #include +#include +#include +#include +#include #include "common/util.h" #include "common/timing.h" @@ -71,12 +75,6 @@ SNPEModel::SNPEModel(const std::string path, float *_output, size_t _output_size std::vector output_strides = {output_size * sizeof(float), sizeof(float)}; output_buffer = ub_factory.createUserBuffer(output, output_size * sizeof(float), output_strides, &ub_encoding_float); output_map.add(output_tensor_name, output_buffer.get()); - -#ifdef USE_THNEED - if (snpe_runtime == zdl::DlSystem::Runtime_t::GPU) { - thneed.reset(new Thneed()); - } -#endif } void SNPEModel::addInput(const std::string name, float *buffer, int size) { diff --git a/selfdrive/modeld/runners/snpemodel.h b/selfdrive/modeld/runners/snpemodel.h index e646e5225b..86b2c86084 100644 --- a/selfdrive/modeld/runners/snpemodel.h +++ b/selfdrive/modeld/runners/snpemodel.h @@ -1,6 +1,10 @@ #pragma once #pragma clang diagnostic ignored "-Wdeprecated-declarations" +#include +#include +#include + #include #include #include @@ -13,14 +17,6 @@ #include "selfdrive/modeld/runners/runmodel.h" -#define USE_CPU_RUNTIME 0 -#define USE_GPU_RUNTIME 1 -#define USE_DSP_RUNTIME 2 - -#ifdef USE_THNEED -#include "selfdrive/modeld/thneed/thneed.h" -#endif - struct SNPEModelInput : public ModelInput { std::unique_ptr snpe_buffer; @@ -37,11 +33,6 @@ public: void addInput(const std::string name, float *buffer, int size); void execute(); -#ifdef USE_THNEED - std::unique_ptr thneed; - bool thneed_recorded = false; -#endif - private: std::string model_data; diff --git a/selfdrive/modeld/runners/snpemodel.pxd b/selfdrive/modeld/runners/snpemodel.pxd new file mode 100644 index 0000000000..1f928da332 --- /dev/null +++ b/selfdrive/modeld/runners/snpemodel.pxd @@ -0,0 +1,9 @@ +# distutils: language = c++ + +from libcpp.string cimport string + +from cereal.visionipc.visionipc cimport cl_context + +cdef extern from "selfdrive/modeld/runners/snpemodel.h": + cdef cppclass SNPEModel: + SNPEModel(string, float*, size_t, int, bool, cl_context) diff --git a/selfdrive/modeld/runners/snpemodel_pyx.pyx b/selfdrive/modeld/runners/snpemodel_pyx.pyx new file mode 100644 index 0000000000..c3b2b7e9bd --- /dev/null +++ b/selfdrive/modeld/runners/snpemodel_pyx.pyx @@ -0,0 +1,17 @@ +# distutils: language = c++ +# cython: c_string_encoding=ascii + +import os +from libcpp cimport bool +from libcpp.string cimport string + +from .snpemodel cimport SNPEModel as cppSNPEModel +from selfdrive.modeld.models.commonmodel_pyx cimport CLContext +from selfdrive.modeld.runners.runmodel_pyx cimport RunModel +from selfdrive.modeld.runners.runmodel cimport RunModel as cppRunModel + +os.environ['ADSP_LIBRARY_PATH'] = "/data/pythonpath/third_party/snpe/dsp/" + +cdef class SNPEModel(RunModel): + def __cinit__(self, string path, float[:] output, int runtime, bool use_tf8, CLContext context): + self.model = new cppSNPEModel(path, &output[0], len(output), runtime, use_tf8, context.context) diff --git a/selfdrive/modeld/runners/thneedmodel.cc b/selfdrive/modeld/runners/thneedmodel.cc index 0f35c94800..a16d8b42aa 100644 --- a/selfdrive/modeld/runners/thneedmodel.cc +++ b/selfdrive/modeld/runners/thneedmodel.cc @@ -1,5 +1,7 @@ #include "selfdrive/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) { diff --git a/selfdrive/modeld/runners/thneedmodel.h b/selfdrive/modeld/runners/thneedmodel.h index 90c40239bf..6ed479c081 100644 --- a/selfdrive/modeld/runners/thneedmodel.h +++ b/selfdrive/modeld/runners/thneedmodel.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "selfdrive/modeld/runners/runmodel.h" #include "selfdrive/modeld/thneed/thneed.h" diff --git a/selfdrive/modeld/runners/thneedmodel.pxd b/selfdrive/modeld/runners/thneedmodel.pxd new file mode 100644 index 0000000000..90af972865 --- /dev/null +++ b/selfdrive/modeld/runners/thneedmodel.pxd @@ -0,0 +1,9 @@ +# distutils: language = c++ + +from libcpp.string cimport string + +from cereal.visionipc.visionipc cimport cl_context + +cdef extern from "selfdrive/modeld/runners/thneedmodel.h": + cdef cppclass ThneedModel: + ThneedModel(string, float*, size_t, int, bool, cl_context) diff --git a/selfdrive/modeld/runners/thneedmodel_pyx.pyx b/selfdrive/modeld/runners/thneedmodel_pyx.pyx new file mode 100644 index 0000000000..53487afa1b --- /dev/null +++ b/selfdrive/modeld/runners/thneedmodel_pyx.pyx @@ -0,0 +1,14 @@ +# distutils: language = c++ +# cython: c_string_encoding=ascii + +from libcpp cimport bool +from libcpp.string cimport string + +from .thneedmodel cimport ThneedModel as cppThneedModel +from selfdrive.modeld.models.commonmodel_pyx cimport CLContext +from selfdrive.modeld.runners.runmodel_pyx cimport RunModel +from selfdrive.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/selfdrive/modeld/tests/snpe_benchmark/benchmark.cc b/selfdrive/modeld/tests/snpe_benchmark/benchmark.cc index 021e065d81..f72f7fb1a7 100644 --- a/selfdrive/modeld/tests/snpe_benchmark/benchmark.cc +++ b/selfdrive/modeld/tests/snpe_benchmark/benchmark.cc @@ -96,7 +96,7 @@ void test(char *filename) { void get_testframe(int index, std::unique_ptr &input) { FILE * pFile; string filepath="/data/ipt/quantize_samples/sample_input_"+std::to_string(index); - pFile = fopen(filepath.c_str(),"rb"); + pFile = fopen(filepath.c_str(), "rb"); int length = 1*6*160*320*4; float * frame_buffer = new float[length/4]; // 32/8 fread(frame_buffer, length, 1, pFile); @@ -156,18 +156,18 @@ void testrun(char* modelfile) { static zdl::DlSystem::TensorMap inputTensorMap; static zdl::DlSystem::TensorMap outputTensorMap; - assert (strList.size() == 1); + assert(strList.size() == 1); const auto &inputDims_opt = snpe->getInputDimensions(strList.at(0)); const auto &inputShape = *inputDims_opt; std::cout << "winkwink" << std::endl; - for (int i=0;i<10000;i++) { + for (int i=0; i<10000; i++) { std::unique_ptr input; input = zdl::SNPE::SNPEFactory::getTensorFactory().createTensor(inputShape); - get_testframe(i,input); + get_testframe(i, input); snpe->execute(input.get(), outputTensorMap); zdl::DlSystem::StringList tensorNames = outputTensorMap.getTensorNames(); - std::for_each( tensorNames.begin(), tensorNames.end(), [&](const char* name) { + std::for_each(tensorNames.begin(), tensorNames.end(), [&](const char* name) { std::ostringstream path; path << "/data/opt/Result_" << std::to_string(i) << ".raw"; auto tensorPtr = outputTensorMap.getTensor(name); diff --git a/selfdrive/modeld/tests/test_modeld.py b/selfdrive/modeld/tests/test_modeld.py index 758948811e..7ae6d5308f 100755 --- a/selfdrive/modeld/tests/test_modeld.py +++ b/selfdrive/modeld/tests/test_modeld.py @@ -1,15 +1,14 @@ #!/usr/bin/env python3 -import time import unittest import numpy as np import random import cereal.messaging as messaging from cereal.visionipc import VisionIpcServer, VisionStreamType -from common.transformations.camera import tici_f_frame_size -from common.realtime import DT_MDL -from selfdrive.manager.process_config import managed_processes -from selfdrive.test.process_replay.vision_meta import meta_from_camera_state +from openpilot.common.transformations.camera import tici_f_frame_size +from openpilot.common.realtime import DT_MDL +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state IMG = np.zeros(int(tici_f_frame_size[0]*tici_f_frame_size[1]*(3/2)), dtype=np.uint8) IMG_BYTES = IMG.flatten().tobytes() @@ -27,8 +26,7 @@ class TestModeld(unittest.TestCase): self.pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'liveCalibration', 'lateralPlan']) managed_processes['modeld'].start() - time.sleep(0.2) - self.sm.update(1000) + self.pm.wait_for_readers_to_update("roadCameraState", 10) def tearDown(self): managed_processes['modeld'].stop() diff --git a/selfdrive/modeld/tests/tf_test/main.cc b/selfdrive/modeld/tests/tf_test/main.cc index db1ef3d67e..b00f7f95e8 100644 --- a/selfdrive/modeld/tests/tf_test/main.cc +++ b/selfdrive/modeld/tests/tf_test/main.cc @@ -36,9 +36,9 @@ static void DeallocateBuffer(void* data, size_t) { int main(int argc, char* argv[]) { TF_Buffer* buf; - TF_Graph* graph; - TF_Status* status; - char *path = argv[1]; + TF_Graph* graph; + TF_Status* status; + char *path = argv[1]; // load model { diff --git a/selfdrive/modeld/tests/tf_test/pb_loader.py b/selfdrive/modeld/tests/tf_test/pb_loader.py index 78fd33aef2..3e476628eb 100755 --- a/selfdrive/modeld/tests/tf_test/pb_loader.py +++ b/selfdrive/modeld/tests/tf_test/pb_loader.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import sys -import tensorflow as tf # pylint: disable=import-error +import tensorflow as tf with open(sys.argv[1], "rb") as f: graph_def = tf.compat.v1.GraphDef() diff --git a/selfdrive/modeld/tests/timing/benchmark.py b/selfdrive/modeld/tests/timing/benchmark.py index f4fddaab40..4ab0afacbc 100755 --- a/selfdrive/modeld/tests/timing/benchmark.py +++ b/selfdrive/modeld/tests/timing/benchmark.py @@ -1,13 +1,12 @@ #!/usr/bin/env python3 # type: ignore -# pylint: skip-file import os import time import numpy as np import cereal.messaging as messaging -from selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.manager.process_config import managed_processes N = int(os.getenv("N", "5")) diff --git a/selfdrive/modeld/thneed/serialize.cc b/selfdrive/modeld/thneed/serialize.cc index f789e5bf57..6ed5c08e81 100644 --- a/selfdrive/modeld/thneed/serialize.cc +++ b/selfdrive/modeld/thneed/serialize.cc @@ -1,7 +1,7 @@ #include #include -#include "json11.hpp" +#include "third_party/json11/json11.hpp" #include "common/util.h" #include "common/clutil.h" #include "selfdrive/modeld/thneed/thneed.h" @@ -75,8 +75,7 @@ void Thneed::load(const char *filename) { #endif if (clbuf == NULL) { printf("clError: %s create image %zux%zu rp %zu with buffer %p\n", cl_get_error_string(errcode), - desc.image_width, desc.image_height, desc.image_row_pitch, desc.buffer - ); + desc.image_width, desc.image_height, desc.image_row_pitch, desc.buffer); } assert(clbuf != NULL); } diff --git a/selfdrive/modeld/thneed/thneed.h b/selfdrive/modeld/thneed/thneed.h index 6475577734..47e18e0be3 100644 --- a/selfdrive/modeld/thneed/thneed.h +++ b/selfdrive/modeld/thneed/thneed.h @@ -12,7 +12,7 @@ #include -#include "msm_kgsl.h" +#include "third_party/linux/include/msm_kgsl.h" using namespace std; diff --git a/selfdrive/modeld/thneed/thneed_qcom2.cc b/selfdrive/modeld/thneed/thneed_qcom2.cc index a3bfb8a8c2..21de15d17c 100644 --- a/selfdrive/modeld/thneed/thneed_qcom2.cc +++ b/selfdrive/modeld/thneed/thneed_qcom2.cc @@ -107,7 +107,8 @@ int ioctl(int filedes, unsigned long request, void *argp) { } int ret = my_ioctl(filedes, request, argp); - if (ret != 0) printf("ioctl returned %d with errno %d\n", ret, errno); + // 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; } diff --git a/selfdrive/modeld/transforms/loadyuv.cc b/selfdrive/modeld/transforms/loadyuv.cc index 39f404a897..c7ce7b0830 100644 --- a/selfdrive/modeld/transforms/loadyuv.cc +++ b/selfdrive/modeld/transforms/loadyuv.cc @@ -15,7 +15,7 @@ void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int w "-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, "transforms/loadyuv.cl", args); + 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)); diff --git a/selfdrive/modeld/transforms/transform.cc b/selfdrive/modeld/transforms/transform.cc index f341314144..305643cf42 100644 --- a/selfdrive/modeld/transforms/transform.cc +++ b/selfdrive/modeld/transforms/transform.cc @@ -8,7 +8,7 @@ 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, "transforms/transform.cl", ""); + 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)); diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 836ed9cc4f..c7cff88f3e 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -4,10 +4,10 @@ import gc import cereal.messaging as messaging from cereal import car from cereal import log -from common.params import Params, put_bool_nonblocking -from common.realtime import set_realtime_priority -from selfdrive.controls.lib.events import Events -from selfdrive.monitoring.driver_monitor import DriverStatus +from openpilot.common.params import Params, put_bool_nonblocking +from openpilot.common.realtime import set_realtime_priority +from openpilot.selfdrive.controls.lib.events import Events +from openpilot.selfdrive.monitoring.driver_monitor import DriverStatus def dmonitoringd_thread(sm=None, pm=None): diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/driver_monitor.py index 97407e25eb..ab82da301f 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/driver_monitor.py @@ -1,11 +1,11 @@ from math import atan2 from cereal import car -from common.numpy_fast import interp -from common.realtime import DT_DMON -from common.filter_simple import FirstOrderFilter -from common.stat_live import RunningStatFilter -from common.transformations.camera import tici_d_frame_size +from openpilot.common.numpy_fast import interp +from openpilot.common.realtime import DT_DMON +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.stat_live import RunningStatFilter +from openpilot.common.transformations.camera import tici_d_frame_size EventName = car.CarEvent.EventName diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 98d3f43f12..c02d44849f 100755 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -3,9 +3,9 @@ import unittest import numpy as np from cereal import car, log -from common.realtime import DT_DMON -from selfdrive.controls.lib.events import Events -from selfdrive.monitoring.driver_monitor import DriverStatus, DRIVER_MONITOR_SETTINGS +from openpilot.common.realtime import DT_DMON +from openpilot.selfdrive.controls.lib.events import Events +from openpilot.selfdrive.monitoring.driver_monitor import DriverStatus, DRIVER_MONITOR_SETTINGS EventName = car.CarEvent.EventName dm_settings = DRIVER_MONITOR_SETTINGS() @@ -54,7 +54,6 @@ always_false = [False] * int(TEST_TIMESPAN / DT_DMON) # TODO: this only tests DriverStatus class TestMonitoring(unittest.TestCase): - # pylint: disable=no-member def _run_seq(self, msgs, interaction, engaged, standstill): DS = DriverStatus() events = [] diff --git a/selfdrive/navd/helpers.py b/selfdrive/navd/helpers.py index 5957816334..55c3f88a9a 100644 --- a/selfdrive/navd/helpers.py +++ b/selfdrive/navd/helpers.py @@ -4,9 +4,9 @@ import json import math from typing import Any, Dict, List, Optional, Tuple, Union, cast -from common.conversions import Conversions -from common.numpy_fast import clip -from common.params import Params +from openpilot.common.conversions import Conversions +from openpilot.common.numpy_fast import clip +from openpilot.common.params import Params DIRECTIONS = ('left', 'right', 'straight') MODIFIABLE_DIRECTIONS = ('left', 'right') diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index 44acc67b94..543515c631 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -64,6 +64,7 @@ MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_set m_map->setCoordinateZoom(QMapbox::Coordinate(0, 0), DEFAULT_ZOOM); m_map->setStyleJson(style.c_str()); m_map->createRenderer(); + ever_loaded = false; m_map->resize(fbo->size()); m_map->setFramebufferObject(fbo->handle(), fbo->size()); @@ -72,11 +73,13 @@ MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_set QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) { // Ignore expected signals // https://github.com/mapbox/mapbox-gl-native/blob/cf734a2fec960025350d8de0d01ad38aeae155a0/platform/qt/include/qmapboxgl.hpp#L116 - if (change != QMapboxGL::MapChange::MapChangeRegionWillChange && - change != QMapboxGL::MapChange::MapChangeRegionDidChange && - change != QMapboxGL::MapChange::MapChangeWillStartRenderingFrame && - change != QMapboxGL::MapChange::MapChangeDidFinishRenderingFrameFullyRendered) { - LOGD("New map state: %d", change); + if (ever_loaded) { + if (change != QMapboxGL::MapChange::MapChangeRegionWillChange && + change != QMapboxGL::MapChange::MapChangeRegionDidChange && + change != QMapboxGL::MapChange::MapChangeWillStartRenderingFrame && + change != QMapboxGL::MapChange::MapChangeDidFinishRenderingFrameFullyRendered) { + LOGD("New map state: %d", change); + } } }); @@ -188,6 +191,7 @@ void MapRenderer::publish(const double render_time, const bool loaded) { auto location = (*sm)["liveLocationKalman"].getLiveLocationKalman(); bool valid = loaded && (location.getStatus() == cereal::LiveLocationKalman::Status::VALID) && location.getPositionGeodetic().getValid(); + ever_loaded = ever_loaded || loaded; uint64_t ts = nanos_since_boot(); VisionBuf* buf = vipc_server->get_buffer(VisionStreamType::VISION_STREAM_MAP); VisionIpcBufExtra extra = { diff --git a/selfdrive/navd/map_renderer.h b/selfdrive/navd/map_renderer.h index 5739ba88f2..dc92c70b0f 100644 --- a/selfdrive/navd/map_renderer.h +++ b/selfdrive/navd/map_renderer.h @@ -49,6 +49,7 @@ private: } QTimer* timer; + bool ever_loaded = false; public slots: void updatePosition(QMapbox::Coordinate position, float bearing); diff --git a/selfdrive/navd/map_renderer.py b/selfdrive/navd/map_renderer.py index 57d5e0593c..8d525ac73e 100755 --- a/selfdrive/navd/map_renderer.py +++ b/selfdrive/navd/map_renderer.py @@ -7,8 +7,8 @@ import numpy as np import polyline from cffi import FFI -from common.ffi_wrapper import suffix -from common.basedir import BASEDIR +from openpilot.common.ffi_wrapper import suffix +from openpilot.common.basedir import BASEDIR HEIGHT = WIDTH = SIZE = 256 METERS_PER_PIXEL = 2 diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index 3a8897e8e2..0ccd1f144b 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -5,23 +5,20 @@ import os import threading import requests -import numpy as np import cereal.messaging as messaging from cereal import log -from common.api import Api -from common.params import Params -from common.realtime import Ratekeeper -from common.transformations.coordinates import ecef2geodetic -from selfdrive.navd.helpers import (Coordinate, coordinate_from_param, +from openpilot.common.api import Api +from openpilot.common.params import Params +from openpilot.common.realtime import Ratekeeper +from openpilot.selfdrive.navd.helpers import (Coordinate, coordinate_from_param, distance_along_geometry, maxspeed_to_ms, minimum_distance, parse_banner_instructions) -from system.swaglog import cloudlog +from openpilot.system.swaglog import cloudlog REROUTE_DISTANCE = 25 MANEUVER_TRANSITION_THRESHOLD = 10 -VALID_POS_STD = 50.0 REROUTE_COUNTER_MIN = 3 @@ -79,21 +76,13 @@ class RouteEngine: def update_location(self): location = self.sm['liveLocationKalman'] - laikad = self.sm['gnssMeasurements'] + self.gps_ok = location.gpsOK - locationd_valid = (location.status == log.LiveLocationKalman.Status.valid) and location.positionGeodetic.valid - laikad_valid = laikad.positionECEF.valid and np.linalg.norm(laikad.positionECEF.std) < VALID_POS_STD + self.localizer_valid = (location.status == log.LiveLocationKalman.Status.valid) and location.positionGeodetic.valid - self.localizer_valid = locationd_valid or laikad_valid - self.gps_ok = location.gpsOK or laikad_valid - - if locationd_valid: + if self.localizer_valid: self.last_bearing = math.degrees(location.calibratedOrientationNED.value[2]) self.last_position = Coordinate(location.positionGeodetic.value[0], location.positionGeodetic.value[1]) - elif laikad_valid: - geodetic = ecef2geodetic(laikad.positionECEF.value) - self.last_position = Coordinate(geodetic[0], geodetic[1]) - self.last_bearing = None def recompute_route(self): if self.last_position is None: @@ -357,7 +346,7 @@ class RouteEngine: def main(sm=None, pm=None): if sm is None: - sm = messaging.SubMaster(['liveLocationKalman', 'gnssMeasurements', 'managerState']) + sm = messaging.SubMaster(['liveLocationKalman', 'managerState']) if pm is None: pm = messaging.PubMaster(['navInstruction', 'navRoute']) diff --git a/selfdrive/navd/set_destination.py b/selfdrive/navd/set_destination.py index e6158dbdee..811aa576d1 100755 --- a/selfdrive/navd/set_destination.py +++ b/selfdrive/navd/set_destination.py @@ -2,7 +2,7 @@ import json import sys -from common.params import Params +from openpilot.common.params import Params if __name__ == "__main__": params = Params() diff --git a/selfdrive/navd/tests/test_map_renderer.py b/selfdrive/navd/tests/test_map_renderer.py index e3eda8bdef..1c3d8acd7f 100755 --- a/selfdrive/navd/tests/test_map_renderer.py +++ b/selfdrive/navd/tests/test_map_renderer.py @@ -8,7 +8,7 @@ import cereal.messaging as messaging from typing import Any from cereal.visionipc import VisionIpcClient, VisionStreamType -from selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.manager.process_config import managed_processes LLK_DECIMATION = 10 CACHE_PATH = "/data/mbgl-cache-navd.db" diff --git a/selfdrive/rtshield.py b/selfdrive/rtshield.py index 45571fe2db..68dc4989cd 100755 --- a/selfdrive/rtshield.py +++ b/selfdrive/rtshield.py @@ -3,7 +3,7 @@ import os import time from typing import NoReturn -from common.realtime import set_core_affinity, set_realtime_priority +from openpilot.common.realtime import set_core_affinity, set_realtime_priority # RT shield - ensure CPU 3 always remains available for RT processes # runs as SCHED_FIFO with minimum priority to ensure kthreads don't diff --git a/selfdrive/sentry.py b/selfdrive/sentry.py index aa409ea394..02d9d17550 100644 --- a/selfdrive/sentry.py +++ b/selfdrive/sentry.py @@ -3,11 +3,11 @@ import sentry_sdk from enum import Enum from sentry_sdk.integrations.threading import ThreadingIntegration -from common.params import Params -from selfdrive.athena.registration import is_registered_device -from system.hardware import HARDWARE, PC -from system.swaglog import cloudlog -from system.version import get_branch, get_commit, get_origin, get_version, \ +from openpilot.common.params import Params +from openpilot.selfdrive.athena.registration import is_registered_device +from openpilot.system.hardware import HARDWARE, PC +from openpilot.system.swaglog import cloudlog +from openpilot.system.version import get_branch, get_commit, get_origin, get_version, \ is_comma_remote, is_dirty, is_tested_branch diff --git a/selfdrive/statsd.py b/selfdrive/statsd.py index a444a62312..8acf406515 100755 --- a/selfdrive/statsd.py +++ b/selfdrive/statsd.py @@ -7,13 +7,13 @@ from collections import defaultdict from datetime import datetime, timezone from typing import NoReturn, Union, List, Dict -from common.params import Params +from openpilot.common.params import Params from cereal.messaging import SubMaster -from system.swaglog import cloudlog -from system.hardware import HARDWARE -from common.file_helpers import atomic_write_in_dir -from system.version import get_normalized_origin, get_short_branch, get_short_version, is_dirty -from system.loggerd.config import STATS_DIR, STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S +from openpilot.system.swaglog import cloudlog +from openpilot.system.hardware import HARDWARE +from openpilot.common.file_helpers import atomic_write_in_dir +from openpilot.system.version import get_normalized_origin, get_short_branch, get_short_version, is_dirty +from openpilot.system.loggerd.config import STATS_DIR, STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S class METRIC_TYPE: diff --git a/selfdrive/test/ciui.py b/selfdrive/test/ciui.py index 291f9f6e44..3f33847b29 100755 --- a/selfdrive/test/ciui.py +++ b/selfdrive/test/ciui.py @@ -5,9 +5,9 @@ import subprocess signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGTERM, signal.SIG_DFL) -from PyQt5.QtCore import QTimer # pylint: disable=no-name-in-module, import-error -from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel # pylint: disable=no-name-in-module, import-error -from selfdrive.ui.qt.python_helpers import set_main_window +from PyQt5.QtCore import QTimer +from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel +from openpilot.selfdrive.ui.qt.python_helpers import set_main_window class Window(QWidget): def __init__(self, parent=None): diff --git a/selfdrive/test/docker_build.sh b/selfdrive/test/docker_build.sh new file mode 100755 index 0000000000..e0ba54f058 --- /dev/null +++ b/selfdrive/test/docker_build.sh @@ -0,0 +1,26 @@ +#!/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/../../ +if [ -n "$TARGET_ARCHITECTURE" ]; then + PLATFORM="linux/$TARGET_ARCHITECTURE" + TAG_SUFFIX="-$TARGET_ARCHITECTURE" +else + PLATFORM="linux/$(uname -m)" + TAG_SUFFIX="" +fi + +source $SCRIPT_DIR/docker_common.sh $1 "$TAG_SUFFIX" + +DOCKER_BUILDKIT=1 docker buildx build --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR + +if [ -n "$PUSH_IMAGE" ]; then + docker push $REMOTE_TAG + docker tag $REMOTE_TAG $REMOTE_SHA_TAG + docker push $REMOTE_SHA_TAG +fi diff --git a/selfdrive/test/docker_common.sh b/selfdrive/test/docker_common.sh new file mode 100644 index 0000000000..68ea472d26 --- /dev/null +++ b/selfdrive/test/docker_common.sh @@ -0,0 +1,27 @@ +if [ $1 = "base" ]; then + export DOCKER_IMAGE=openpilot-base + export DOCKER_FILE=Dockerfile.openpilot_base +elif [ $1 = "docs" ]; then + export DOCKER_IMAGE=openpilot-docs + export DOCKER_FILE=docs/docker/Dockerfile +elif [ $1 = "sim" ]; then + export DOCKER_IMAGE=openpilot-sim + export DOCKER_FILE=tools/sim/Dockerfile.sim +elif [ $1 = "prebuilt" ]; then + export DOCKER_IMAGE=openpilot-prebuilt + export DOCKER_FILE=Dockerfile.openpilot +elif [ $1 = "cl" ]; then + export DOCKER_IMAGE=openpilot-base-cl + export DOCKER_FILE=Dockerfile.openpilot_base_cl +else + echo "Invalid docker build image $1" + exit 1 +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/docker_tag_multiarch.sh b/selfdrive/test/docker_tag_multiarch.sh new file mode 100755 index 0000000000..c1761802c7 --- /dev/null +++ b/selfdrive/test/docker_tag_multiarch.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -e + +if [ $# -lt 2 ]; then + echo "Usage: $0 ..." + exit 1 +fi + +SCRIPT_DIR=$(dirname "$0") +ARCHS=("${@:2}") + +source $SCRIPT_DIR/docker_common.sh $1 + +MANIFEST_AMENDS="" +for ARCH in ${ARCHS[@]}; do + MANIFEST_AMENDS="$MANIFEST_AMENDS --amend $REMOTE_TAG-$ARCH:$COMMIT_SHA" +done + +docker manifest create $REMOTE_TAG $MANIFEST_AMENDS +docker manifest create $REMOTE_SHA_TAG $MANIFEST_AMENDS + +if [[ -n "$PUSH_IMAGE" ]]; then + docker manifest push $REMOTE_TAG + docker manifest push $REMOTE_SHA_TAG +fi diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index f7dab576f3..eb07432c40 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -1,13 +1,15 @@ import os import time + from functools import wraps import cereal.messaging as messaging -from common.params import Params -from selfdrive.manager.process_config import managed_processes -from system.hardware import PC -from system.version import training_version, terms_version +from openpilot.common.params import Params +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.hardware import PC +from openpilot.system.version import training_version, terms_version +SKIP_ENV_VAR = "SKIP_LONG_TESTS" def set_params_enabled(): os.environ['PASSIVE'] = "0" diff --git a/selfdrive/test/longitudinal_maneuvers/maneuver.py b/selfdrive/test/longitudinal_maneuvers/maneuver.py index 00ddfe627e..000225ab77 100644 --- a/selfdrive/test/longitudinal_maneuvers/maneuver.py +++ b/selfdrive/test/longitudinal_maneuvers/maneuver.py @@ -1,5 +1,5 @@ import numpy as np -from selfdrive.test.longitudinal_maneuvers.plant import Plant +from openpilot.selfdrive.test.longitudinal_maneuvers.plant import Plant class Maneuver: diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index 541f7d8747..0dce1a0f9b 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -4,11 +4,11 @@ import numpy as np from cereal import log import cereal.messaging as messaging -from common.realtime import Ratekeeper, DT_MDL -from selfdrive.controls.lib.longcontrol import LongCtrlState -from selfdrive.modeld.constants import T_IDXS -from selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner -from selfdrive.controls.radard import _LEAD_ACCEL_TAU +from openpilot.common.realtime import Ratekeeper, DT_MDL +from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState +from openpilot.selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner +from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU class Plant: @@ -43,11 +43,11 @@ class Plant: self.rk = Ratekeeper(self.rate, print_delay_threshold=100.0) self.ts = 1. / self.rate - time.sleep(1) + time.sleep(0.1) self.sm = messaging.SubMaster(['longitudinalPlan']) - from selfdrive.car.honda.values import CAR - from selfdrive.car.honda.interface import CarInterface + from openpilot.selfdrive.car.honda.values import CAR + from openpilot.selfdrive.car.honda.interface import CarInterface self.planner = LongitudinalPlanner(CarInterface.get_non_essential_params(CAR.CIVIC), init_v=self.speed) @@ -144,9 +144,9 @@ class Plant: v_rel = 0. # print at 5hz - if (self.rk.frame % (self.rate // 5)) == 0: - print("%2.2f sec %6.2f m %6.2f m/s %6.2f m/s2 lead_rel: %6.2f m %6.2f m/s" - % (self.current_time, self.distance, self.speed, self.acceleration, d_rel, v_rel)) + # if (self.rk.frame % (self.rate // 5)) == 0: + # print("%2.2f sec %6.2f m %6.2f m/s %6.2f m/s2 lead_rel: %6.2f m %6.2f m/s" + # % (self.current_time, self.distance, self.speed, self.acceleration, d_rel, v_rel)) # ******** update prevs ******** diff --git a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index bc477ca9fe..a3b307ccba 100755 --- a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -4,9 +4,9 @@ import os from parameterized import parameterized_class import unittest -from common.params import Params -from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE -from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver +from openpilot.common.params import Params +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE +from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver # TODO: make new FCW tests diff --git a/selfdrive/test/openpilotci.py b/selfdrive/test/openpilotci.py index 3a7868aaa6..f590b66e54 100755 --- a/selfdrive/test/openpilotci.py +++ b/selfdrive/test/openpilotci.py @@ -1,43 +1,64 @@ #!/usr/bin/env python3 import os -import sys -import subprocess +from datetime import datetime, timedelta +from functools import lru_cache +from pathlib import Path +from typing import IO, Union -BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" -TOKEN_PATH = "/data/azure_token" +DATA_CI_ACCOUNT = "commadataci" +DATA_CI_ACCOUNT_URL = f"https://{DATA_CI_ACCOUNT}.blob.core.windows.net" +DATA_CI_CONTAINER = "openpilotci" +BASE_URL = f"{DATA_CI_ACCOUNT_URL}/{DATA_CI_CONTAINER}/" +TOKEN_PATH = Path("/data/azure_token") -def get_url(route_name, segment_num, log_type="rlog"): + +def get_url(route_name: str, segment_num, log_type="rlog") -> str: ext = "hevc" if log_type.endswith('camera') else "bz2" return BASE_URL + f"{route_name.replace('|', '/')}/{segment_num}/{log_type}.{ext}" -def get_sas_token(): - sas_token = os.environ.get("AZURE_TOKEN", None) - if os.path.isfile(TOKEN_PATH): - sas_token = open(TOKEN_PATH).read().strip() - if sas_token is None: - sas_token = subprocess.check_output("az storage container generate-sas --account-name commadataci --name openpilotci \ - --https-only --permissions lrw --expiry $(date -u '+%Y-%m-%dT%H:%M:%SZ' -d '+1 hour') \ - --auth-mode login --as-user --output tsv", shell=True).decode().strip("\n") +@lru_cache +def get_azure_credential(): + if "AZURE_TOKEN" in os.environ: + return os.environ["AZURE_TOKEN"] + elif TOKEN_PATH.is_file(): + return TOKEN_PATH.read_text().strip() + else: + from azure.identity import AzureCliCredential + return AzureCliCredential() + - return sas_token +@lru_cache +def get_container_sas(account_name: str, container_name: str): + from azure.storage.blob import BlobServiceClient, ContainerSasPermissions, generate_container_sas + start_time = datetime.utcnow() + expiry_time = start_time + timedelta(hours=1) + blob_service = BlobServiceClient( + account_url=f"https://{account_name}.blob.core.windows.net", + credential=get_azure_credential(), + ) + return generate_container_sas( + account_name, + container_name, + user_delegation_key=blob_service.get_user_delegation_key(start_time, expiry_time), + permission=ContainerSasPermissions(read=True, write=True, list=True), + expiry=expiry_time, + ) -def upload_bytes(data, name): - from azure.storage.blob import BlockBlobService # pylint: disable=import-error - service = BlockBlobService(account_name="commadataci", sas_token=get_sas_token()) - service.create_blob_from_bytes("openpilotci", name, data) - return BASE_URL + name -def upload_file(path, name): - from azure.storage.blob import BlockBlobService # pylint: disable=import-error - service = BlockBlobService(account_name="commadataci", sas_token=get_sas_token()) - service.create_blob_from_path("openpilotci", name, path) - return BASE_URL + name +def upload_bytes(data: Union[bytes, IO], blob_name: str) -> str: + from azure.storage.blob import BlobClient + blob = BlobClient( + account_url=DATA_CI_ACCOUNT_URL, + container_name=DATA_CI_CONTAINER, + blob_name=blob_name, + credential=get_azure_credential(), + ) + blob.upload_blob(data) + return BASE_URL + blob_name -if __name__ == "__main__": - for f in sys.argv[1:]: - name = os.path.basename(f) - url = upload_file(f, name) - print(url) +def upload_file(path: Union[str, os.PathLike], blob_name: str) -> str: + with open(path, "rb") as f: + return upload_bytes(f, blob_name) diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md index d1743ac539..1174dbab83 100644 --- a/selfdrive/test/process_replay/README.md +++ b/selfdrive/test/process_replay/README.md @@ -62,8 +62,8 @@ def replay_process( Example usage: ```py -from selfdrive.test.process_replay import replay_process_with_name -from tools.lib.logreader import LogReader +from openpilot.selfdrive.test.process_replay import replay_process_with_name +from openpilot.tools.lib.logreader import LogReader lr = LogReader(...) @@ -91,7 +91,7 @@ Supported processes: Certain processes may require an initial state, which is usually supplied within `Params` and persisting from segment to segment (e.g CalibrationParams, LiveParameters). The `custom_params` is dictionary used to prepopulate `Params` with arbitrary values. The `get_custom_params_from_lr` helper is provided to fetch meaningful values from log files. ```py -from selfdrive.test.process_replay import get_custom_params_from_lr +from openpilot.selfdrive.test.process_replay import get_custom_params_from_lr previous_segment_lr = LogReader(...) current_segment_lr = LogReader(...) @@ -104,7 +104,7 @@ output_logs = replay_process_with_name('calibrationd', lr, custom_params=custom_ Replaying processes that use VisionIPC (e.g. modeld, dmonitoringmodeld) require additional `frs` dictionary with camera states as keys and `FrameReader` objects as values. ```py -from tools.lib.framereader import FrameReader +from openpilot.tools.lib.framereader import FrameReader frs = { 'roadCameraState': FrameReader(...), @@ -125,4 +125,4 @@ output_logs = replay_process_with_name(['radard', 'plannerd'], lr, captured_outp # entries with captured output in format { 'out': '...', 'err': '...' } will be added to provided dictionary for each replayed process print(output_store['radard']['out']) # radard stdout print(output_store['radard']['err']) # radard stderr -``` \ No newline at end of file +``` diff --git a/selfdrive/test/process_replay/__init__.py b/selfdrive/test/process_replay/__init__.py index a872c310d7..b994277186 100644 --- a/selfdrive/test/process_replay/__init__.py +++ b/selfdrive/test/process_replay/__init__.py @@ -1,2 +1,2 @@ -from selfdrive.test.process_replay.process_replay import CONFIGS, get_process_config, get_custom_params_from_lr, \ +from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, get_process_config, get_custom_params_from_lr, \ replay_process, replay_process_with_name # noqa: F401 diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index a929fae6ed..7de0a25761 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -6,7 +6,7 @@ import numbers import dictdiffer from collections import Counter -from tools.lib.logreader import LogReader +from openpilot.tools.lib.logreader import LogReader EPSILON = sys.float_info.epsilon @@ -39,25 +39,12 @@ def remove_ignored_fields(msg, ignore): return msg -def get_field_tolerance(diff_field, field_tolerances): - diff_field_str = diff_field[0] - for s in diff_field[1:]: - # loop until number in field - if not isinstance(s, str): - break - diff_field_str += '.'+s - if diff_field_str in field_tolerances: - return field_tolerances[diff_field_str] - - -def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=None, field_tolerances=None): +def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=None,): if ignore_fields is None: ignore_fields = [] if ignore_msgs is None: ignore_msgs = [] - if field_tolerances is None: - field_tolerances = {} - default_tolerance = EPSILON if tolerance is None else tolerance + tolerance = EPSILON if tolerance is None else tolerance log1, log2 = ( [m for m in log if m.which() not in ignore_msgs] @@ -88,13 +75,10 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non def outside_tolerance(diff): try: if diff[0] == "change": - field_tolerance = default_tolerance - if (tol := get_field_tolerance(diff[1], field_tolerances)) is not None: - field_tolerance = tol 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(field_tolerance, field_tolerance * max(abs(a), abs(b))) + return abs(a - b) > max(tolerance, tolerance * max(abs(a), abs(b))) except TypeError: pass return True diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index eb2aba8799..4560a66b97 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -1,7 +1,7 @@ from collections import defaultdict from cereal import messaging -from selfdrive.test.process_replay.vision_meta import meta_from_encode_index +from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_encode_index def migrate_all(lr, old_logtime=False, camera_states=False): diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 56bb3e1b3e..f576e07a40 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -6,18 +6,18 @@ from collections import defaultdict from typing import Any import cereal.messaging as messaging -from common.params import Params -from common.spinner import Spinner -from system.hardware import PC -from selfdrive.manager.process_config import managed_processes -from selfdrive.test.openpilotci import BASE_URL, get_url -from selfdrive.test.process_replay.compare_logs import compare_logs -from selfdrive.test.process_replay.test_processes import format_diff -from selfdrive.test.process_replay.process_replay import get_process_config, replay_process -from system.version import get_commit -from tools.lib.framereader import FrameReader -from tools.lib.logreader import LogReader -from tools.lib.helpers import save_log +from openpilot.common.params import Params +from openpilot.common.spinner import Spinner +from openpilot.system.hardware import PC +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.test.openpilotci import BASE_URL, get_url +from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs +from openpilot.selfdrive.test.process_replay.test_processes import format_diff +from openpilot.selfdrive.test.process_replay.process_replay import get_process_config, replay_process +from openpilot.system.version import get_commit +from openpilot.tools.lib.framereader import FrameReader +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.helpers import save_log TEST_ROUTE = "2f4452b03ccb98f0|2022-12-03--13-45-30" SEGMENT = 6 @@ -161,7 +161,7 @@ if __name__ == "__main__": import requests import threading import http.server - from selfdrive.test.openpilotci import upload_bytes + from openpilot.selfdrive.test.openpilotci import upload_bytes os.environ['MAPS_HOST'] = 'http://localhost:5000' class HTTPRequestHandler(http.server.BaseHTTPRequestHandler): @@ -244,7 +244,7 @@ if __name__ == "__main__": # upload new refs if (update or failed) and not PC: - from selfdrive.test.openpilotci import upload_file + from openpilot.selfdrive.test.openpilotci import upload_file print("Uploading new refs") diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index e101afb227..e80315133f 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -377ed362faa9410a8b81d50978d74ee60b8e6f6a +4f59f5945633f7a6e54e6f0fcd8ecf0baafa28cd diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 08e497e72d..eb49d0dedb 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -16,23 +16,35 @@ import cereal.messaging as messaging from cereal import car from cereal.services import service_list from cereal.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name -from common.params import Params -from common.timeout import Timeout -from common.realtime import DT_CTRL +from openpilot.common.params import Params +from openpilot.common.prefix import OpenpilotPrefix +from openpilot.common.timeout import Timeout +from openpilot.common.realtime import DT_CTRL from panda.python import ALTERNATIVE_EXPERIENCE -from selfdrive.car.car_helpers import get_car, interfaces -from selfdrive.manager.process_config import managed_processes -from selfdrive.test.process_replay.helpers import OpenpilotPrefix, DummySocket -from selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams -from selfdrive.test.process_replay.migration import migrate_all -from selfdrive.test.process_replay.capture import ProcessOutputCapture -from tools.lib.logreader import LogReader +from openpilot.selfdrive.car.car_helpers import get_car, interfaces +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams +from openpilot.selfdrive.test.process_replay.migration import migrate_all +from openpilot.selfdrive.test.process_replay.capture import ProcessOutputCapture +from openpilot.tools.lib.logreader import LogReader # Numpy gives different results based on CPU features after version 19 NUMPY_TOLERANCE = 1e-7 PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__)) FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/") +class DummySocket: + def __init__(self): + self.data: List[bytes] = [] + + def receive(self, non_blocking: bool = False) -> Optional[bytes]: + if non_blocking: + return None + + return self.data.pop() + + def send(self, data: bytes): + self.data.append(data) class LauncherWithCapture: def __init__(self, capture: ProcessOutputCapture, launcher: Callable): @@ -123,7 +135,6 @@ class ProcessConfig: should_recv_callback: Optional[Callable] = None tolerance: Optional[float] = None processing_time: float = 0.001 - field_tolerances: Dict[str, float] = field(default_factory=dict) timeout: int = 30 simulation: bool = True main_pub: Optional[str] = None @@ -144,6 +155,7 @@ class ProcessContainer: self.sockets: Optional[List[messaging.SubSocket]] = None self.rc: Optional[ReplayContext] = None self.vipc_server: Optional[VisionIpcServer] = None + self.environ_config: Optional[Dict[str, Any]] = None self.capture: Optional[ProcessOutputCapture] = None @property @@ -158,6 +170,15 @@ class ProcessContainer: def subs(self) -> List[str]: return self.cfg.subs + def _clean_env(self): + for k in self.environ_config.keys(): + if k in os.environ: + del os.environ[k] + + for k in ["PROC_NAME", "SIMULATION"]: + if k in os.environ: + del os.environ[k] + def _setup_env(self, params_config: Dict[str, Any], environ_config: Dict[str, Any]): for k, v in environ_config.items(): if len(v) != 0: @@ -178,6 +199,8 @@ class ProcessContainer: else: params.put(k, v) + self.environ_config = environ_config + def _setup_vision_ipc(self, all_msgs): assert len(self.cfg.vision_pubs) != 0 @@ -239,6 +262,7 @@ class ProcessContainer: self.process.stop() self.rc.close_context() self.prefix.clean_dirs() + self._clean_env() def run_step(self, msg: capnp._DynamicStructReader, frs: Optional[Dict[str, Any]]) -> List[capnp._DynamicStructReader]: assert self.rc and self.pm and self.sockets and self.process.proc @@ -465,8 +489,8 @@ CONFIGS = [ subs=["radarState", "liveTracks"], ignore=["logMonoTime", "valid", "radarState.cumLagMs"], init_callback=get_car_params_callback, - should_recv_callback=MessageBasedRcvCallback("modelV2"), - unlocked_pubs=["can"], + should_recv_callback=MessageBasedRcvCallback("can"), + main_pub="can", ), ProcessConfig( proc_name="plannerd", diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index ce6c513c10..c6a2d58fec 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -ac3f87bad519df408a295d2e007e0de58b11690c \ No newline at end of file +b421ff389ce720b70a36dd2b3510af54eb484b5f \ No newline at end of file diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index 07d3c04e37..81f27b7d0e 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -6,13 +6,13 @@ import capnp from typing import Union, Iterable, Optional, List, Any, Dict, Tuple -from selfdrive.test.process_replay.process_replay import CONFIGS, FAKEDATA, replay_process, get_process_config, \ +from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, FAKEDATA, replay_process, get_process_config, \ check_openpilot_enabled, get_custom_params_from_lr -from selfdrive.test.update_ci_routes import upload_route -from tools.lib.route import Route -from tools.lib.framereader import FrameReader -from tools.lib.logreader import LogReader -from tools.lib.helpers import save_log +from openpilot.selfdrive.test.update_ci_routes import upload_route +from openpilot.tools.lib.route import Route +from openpilot.tools.lib.framereader import FrameReader +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.helpers import save_log def regen_segment( diff --git a/selfdrive/test/process_replay/regen_all.py b/selfdrive/test/process_replay/regen_all.py index f69d07eb69..df7c76a14d 100755 --- a/selfdrive/test/process_replay/regen_all.py +++ b/selfdrive/test/process_replay/regen_all.py @@ -6,10 +6,10 @@ import random import traceback from tqdm import tqdm -from selfdrive.test.process_replay.helpers import OpenpilotPrefix -from selfdrive.test.process_replay.regen import regen_and_save -from selfdrive.test.process_replay.test_processes import FAKEDATA, source_segments as segments -from tools.lib.route import SegmentName +from openpilot.common.prefix import OpenpilotPrefix +from openpilot.selfdrive.test.process_replay.regen import regen_and_save +from openpilot.selfdrive.test.process_replay.test_processes import FAKEDATA, source_segments as segments +from openpilot.tools.lib.route import SegmentName def regen_job(segment, upload, disable_tqdm): diff --git a/selfdrive/test/process_replay/test_debayer.py b/selfdrive/test/process_replay/test_debayer.py index 2ed2be61ac..a6e6955dbf 100755 --- a/selfdrive/test/process_replay/test_debayer.py +++ b/selfdrive/test/process_replay/test_debayer.py @@ -6,13 +6,13 @@ import numpy as np import pyopencl as cl # install with `PYOPENCL_CL_PRETEND_VERSION=2.0 pip install pyopencl` -from system.hardware import PC, TICI -from common.basedir import BASEDIR -from selfdrive.test.openpilotci import BASE_URL, get_url -from system.version import get_commit -from system.camerad.snapshot.snapshot import yuv_to_rgb -from tools.lib.logreader import LogReader -from tools.lib.filereader import FileReader +from openpilot.system.hardware import PC, TICI +from openpilot.common.basedir import BASEDIR +from openpilot.selfdrive.test.openpilotci import BASE_URL, get_url +from openpilot.system.version import get_commit +from openpilot.system.camerad.snapshot.snapshot import yuv_to_rgb +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.filereader import FileReader TEST_ROUTE = "8345e3b82948d454|2022-05-04--13-45-33" SEGMENT = 0 @@ -172,7 +172,7 @@ if __name__ == "__main__": # upload new refs if update or (failed and TICI): - from selfdrive.test.openpilotci import upload_file + from openpilot.selfdrive.test.openpilotci import upload_file print("Uploading new refs") diff --git a/selfdrive/test/process_replay/test_fuzzy.py b/selfdrive/test/process_replay/test_fuzzy.py index c58599caee..f3f48d0159 100755 --- a/selfdrive/test/process_replay/test_fuzzy.py +++ b/selfdrive/test/process_replay/test_fuzzy.py @@ -6,9 +6,9 @@ from parameterized import parameterized import unittest from cereal import log -from selfdrive.car.toyota.values import CAR as TOYOTA -from selfdrive.test.fuzzy_generation import FuzzyGenerator -import selfdrive.test.process_replay.process_replay as pr +from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA +from openpilot.selfdrive.test.fuzzy_generation import FuzzyGenerator +import openpilot.selfdrive.test.process_replay.process_replay as pr # These processes currently fail because of unrealistic data breaking assumptions # that openpilot makes causing error with NaN, inf, int size, array indexing ... diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 0e099d83e2..973c16971e 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -7,21 +7,21 @@ from collections import defaultdict from tqdm import tqdm from typing import Any, DefaultDict, Dict -from selfdrive.car.car_helpers import interface_names -from selfdrive.test.openpilotci import get_url, upload_file -from selfdrive.test.process_replay.compare_logs import compare_logs -from selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, check_openpilot_enabled, replay_process -from system.version import get_commit -from tools.lib.filereader import FileReader -from tools.lib.logreader import LogReader -from tools.lib.helpers import save_log +from openpilot.selfdrive.car.car_helpers import interface_names +from openpilot.selfdrive.test.openpilotci import get_url, upload_file +from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs +from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, check_openpilot_enabled, replay_process +from openpilot.system.version import get_commit +from openpilot.tools.lib.filereader import FileReader +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.helpers import save_log source_segments = [ ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA ("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.KIA_EV6 (+ QCOM GPS) - ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI) - ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR) + ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS + ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 ("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.CIVIC (NIDEC) ("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.ACCORD (BOSCH) @@ -110,7 +110,7 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non return f"Route did not enable at all or for long enough: {new_log_path}", log_msgs try: - return compare_logs(ref_log_msgs, log_msgs, ignore_fields + cfg.ignore, ignore_msgs, cfg.tolerance, cfg.field_tolerances), log_msgs + return compare_logs(ref_log_msgs, log_msgs, ignore_fields + cfg.ignore, ignore_msgs, cfg.tolerance), log_msgs except Exception as e: return str(e), log_msgs @@ -158,6 +158,8 @@ if __name__ == "__main__": all_cars = {car for car, _ in segments} all_procs = {cfg.proc_name for cfg in CONFIGS if cfg.proc_name not in EXCLUDED_PROCS} + cpu_count = os.cpu_count() or 1 + parser = argparse.ArgumentParser(description="Regression test to identify changes in a process's output") parser.add_argument("--whitelist-procs", type=str, nargs="*", default=all_procs, help="Whitelist given processes from the test (e.g. controlsd)") @@ -175,7 +177,8 @@ if __name__ == "__main__": help="Updates reference logs using current commit") parser.add_argument("--upload-only", action="store_true", help="Skips testing processes and uploads logs from previous test run") - parser.add_argument("-j", "--jobs", type=int, default=1) + parser.add_argument("-j", "--jobs", type=int, default=max(cpu_count - 2, 1), + help="Max amount of parallel jobs") args = parser.parse_args() tested_procs = set(args.whitelist_procs) - set(args.blacklist_procs) diff --git a/selfdrive/test/process_replay/vision_meta.py b/selfdrive/test/process_replay/vision_meta.py index 77c6b0345d..2994b77452 100644 --- a/selfdrive/test/process_replay/vision_meta.py +++ b/selfdrive/test/process_replay/vision_meta.py @@ -1,7 +1,7 @@ from collections import namedtuple from cereal.visionipc import VisionStreamType -from common.realtime import DT_MDL, DT_DMON -from common.transformations.camera import tici_f_frame_size, tici_d_frame_size, tici_e_frame_size, eon_f_frame_size, eon_d_frame_size +from openpilot.common.realtime import DT_MDL, DT_DMON +from openpilot.common.transformations.camera import tici_f_frame_size, tici_d_frame_size, tici_e_frame_size, eon_f_frame_size, eon_d_frame_size VideoStreamMeta = namedtuple("VideoStreamMeta", ["camera_state", "encode_index", "stream", "dt", "frame_sizes"]) ROAD_CAMERA_FRAME_SIZES = {"tici": tici_f_frame_size, "tizi": tici_f_frame_size, "eon": eon_f_frame_size} diff --git a/selfdrive/test/profiling/lib.py b/selfdrive/test/profiling/lib.py index f28346f3f0..843bf52211 100644 --- a/selfdrive/test/profiling/lib.py +++ b/selfdrive/test/profiling/lib.py @@ -34,7 +34,7 @@ class PubSocket(): class SubMaster(messaging.SubMaster): - def __init__(self, msgs, trigger, services, check_averag_freq=False): # pylint: disable=super-init-not-called + def __init__(self, msgs, trigger, services, check_averag_freq=False): self.frame = 0 self.data = {} self.ignore_alive = [] @@ -87,5 +87,5 @@ class SubMaster(messaging.SubMaster): class PubMaster(messaging.PubMaster): - def __init__(self): # pylint: disable=super-init-not-called + def __init__(self): self.sock = defaultdict(PubSocket) diff --git a/selfdrive/test/profiling/profiler.py b/selfdrive/test/profiling/profiler.py index 28e3b34e40..1d380ba5eb 100755 --- a/selfdrive/test/profiling/profiler.py +++ b/selfdrive/test/profiling/profiler.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 import os import sys -import cProfile # pylint: disable=import-error +import cProfile import pprofile import pyprof2calltree -from common.params import Params -from tools.lib.logreader import LogReader -from selfdrive.test.profiling.lib import SubMaster, PubMaster, SubSocket, ReplayDone -from selfdrive.test.process_replay.process_replay import CONFIGS -from selfdrive.car.toyota.values import CAR as TOYOTA -from selfdrive.car.honda.values import CAR as HONDA -from selfdrive.car.volkswagen.values import CAR as VW +from openpilot.common.params import Params +from openpilot.tools.lib.logreader import LogReader +from openpilot.selfdrive.test.profiling.lib import SubMaster, PubMaster, SubSocket, ReplayDone +from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS +from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA +from openpilot.selfdrive.car.honda.values import CAR as HONDA +from openpilot.selfdrive.car.volkswagen.values import CAR as VW BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" @@ -79,11 +79,11 @@ def profile(proc, func, car='toyota'): if __name__ == '__main__': - from selfdrive.controls.controlsd import main as controlsd_thread - from selfdrive.controls.radard import radard_thread - from selfdrive.locationd.paramsd import main as paramsd_thread - from selfdrive.controls.plannerd import main as plannerd_thread - from selfdrive.locationd.laikad import main as laikad_thread + from openpilot.selfdrive.controls.controlsd import main as controlsd_thread + from openpilot.selfdrive.controls.radard import radard_thread + from openpilot.selfdrive.locationd.paramsd import main as paramsd_thread + from openpilot.selfdrive.controls.plannerd import main as plannerd_thread + from openpilot.selfdrive.locationd.laikad import main as laikad_thread procs = { 'radard': radard_thread, diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index 1b99c31038..ca458f2a79 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -56,31 +56,68 @@ sleep infinity EOF chmod +x $CONTINUE_PATH -# set up environment -if [ ! -d "$SOURCE_DIR" ]; then - git clone https://github.com/commaai/openpilot.git $SOURCE_DIR -fi -cd $SOURCE_DIR +safe_checkout() { + # completely clean TEST_DIR + + cd $SOURCE_DIR -# cleanup orphaned locks -find .git -type f -name "*.lock" -exec rm {} + + # cleanup orphaned locks + find .git -type f -name "*.lock" -exec rm {} + + + git reset --hard + git fetch --no-tags --no-recurse-submodules -j4 --verbose --depth 1 origin $GIT_COMMIT + find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \; + git reset --hard $GIT_COMMIT + git checkout $GIT_COMMIT + git clean -xdff + git submodule sync + git submodule update --init --recursive + git submodule foreach --recursive "git reset --hard && git clean -xdff" + + git lfs pull + (ulimit -n 65535 && git lfs prune) + + echo "git checkout done, t=$SECONDS" + du -hs $SOURCE_DIR $SOURCE_DIR/.git + + if [ -z "SKIP_COPY" ]; then + rsync -a --delete $SOURCE_DIR $TEST_DIR + fi +} -git reset --hard -git fetch --no-tags --no-recurse-submodules -j4 --verbose --depth 1 origin $GIT_COMMIT -find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \; -git reset --hard $GIT_COMMIT -git checkout $GIT_COMMIT -git clean -xdff -git submodule sync -git submodule update --init --recursive -git submodule foreach --recursive "git reset --hard && git clean -xdff" +unsafe_checkout() { + # checkout directly in test dir, leave old build products -git lfs pull -(ulimit -n 65535 && git lfs prune) + cd $TEST_DIR -echo "git checkout done, t=$SECONDS" -du -hs $SOURCE_DIR $SOURCE_DIR/.git + # cleanup orphaned locks + find .git -type f -name "*.lock" -exec rm {} + -rsync -a --delete $SOURCE_DIR $TEST_DIR + git fetch --no-tags --no-recurse-submodules -j8 --verbose --depth 1 origin $GIT_COMMIT + git checkout --force --no-recurse-submodules $GIT_COMMIT + git reset --hard $GIT_COMMIT + git clean -df + git submodule sync + git submodule update --init --recursive + git submodule foreach --recursive "git reset --hard && git clean -df" + + git lfs pull + (ulimit -n 65535 && git lfs prune) +} + +export GIT_PACK_THREADS=8 + +# set up environment +if [ ! -d "$SOURCE_DIR" ]; then + git clone https://github.com/commaai/openpilot.git $SOURCE_DIR +fi + +if [ ! -z "$UNSAFE" ]; then + echo "doing unsafe checkout" + unsafe_checkout +else + echo "doing safe checkout" + safe_checkout +fi echo "$TEST_DIR synced with $GIT_COMMIT, t=$SECONDS" diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index d71f49dc3a..553ef1afa6 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -14,14 +14,14 @@ from pathlib import Path from cereal import car import cereal.messaging as messaging from cereal.services import service_list -from common.basedir import BASEDIR -from common.timeout import Timeout -from common.params import Params -from selfdrive.controls.lib.events import EVENTS, ET -from system.hardware import HARDWARE -from system.loggerd.config import ROOT -from selfdrive.test.helpers import set_params_enabled, release_only -from tools.lib.logreader import LogReader +from openpilot.common.basedir import BASEDIR +from openpilot.common.timeout import Timeout +from openpilot.common.params import Params +from openpilot.selfdrive.controls.lib.events import EVENTS, ET +from openpilot.system.hardware import HARDWARE +from openpilot.selfdrive.test.helpers import set_params_enabled, release_only +from openpilot.system.hardware.hw import Paths +from openpilot.tools.lib.logreader import LogReader # Baseline CPU usage by process PROCS = { @@ -34,15 +34,15 @@ PROCS = { "selfdrive.controls.plannerd": 16.5, "./_ui": 18.0, "selfdrive.locationd.paramsd": 9.0, - "./_sensord": 12.0, + "./_sensord": 7.0, "selfdrive.controls.radard": 4.5, - "./_modeld": 4.48, - "./_dmonitoringmodeld": 5.0, - "./_navmodeld": 1.0, + "selfdrive.modeld.modeld": 8.0, + "selfdrive.modeld.dmonitoringmodeld": 8.0, + "selfdrive.modeld.navmodeld": 1.0, "selfdrive.thermald.thermald": 3.87, "selfdrive.locationd.calibrationd": 2.0, "selfdrive.locationd.torqued": 5.0, - "./_soundd": (15.0, 65.0), + "./_soundd": (1.0, 65.0), "selfdrive.monitoring.dmonitoringd": 4.0, "./proclogd": 1.54, "system.logmessaged": 0.2, @@ -102,7 +102,7 @@ class TestOnroad(unittest.TestCase): @classmethod def setUpClass(cls): if "DEBUG" in os.environ: - segs = filter(lambda x: os.path.exists(os.path.join(x, "rlog")), Path(ROOT).iterdir()) + segs = filter(lambda x: os.path.exists(os.path.join(x, "rlog")), Path(Paths.log_root()).iterdir()) segs = sorted(segs, key=lambda x: x.stat().st_mtime) print(segs[-3]) cls.lr = list(LogReader(os.path.join(segs[-3], "rlog"))) @@ -115,8 +115,8 @@ class TestOnroad(unittest.TestCase): params.remove("CurrentRoute") set_params_enabled() os.environ['TESTING_CLOSET'] = '1' - if os.path.exists(ROOT): - shutil.rmtree(ROOT) + if os.path.exists(Paths.log_root()): + shutil.rmtree(Paths.log_root()) os.system("rm /dev/shm/*") # Make sure athena isn't running @@ -143,8 +143,8 @@ class TestOnroad(unittest.TestCase): while len(cls.segments) < 3: segs = set() - if Path(ROOT).exists(): - segs = set(Path(ROOT).glob(f"{route}--*")) + if Path(Paths.log_root()).exists(): + segs = set(Path(Paths.log_root()).glob(f"{route}--*")) cls.segments = sorted(segs, key=lambda s: int(str(s).rsplit('--')[-1])) time.sleep(2) diff --git a/selfdrive/test/test_time_to_onroad.py b/selfdrive/test/test_time_to_onroad.py index 5991250945..429feca344 100755 --- a/selfdrive/test/test_time_to_onroad.py +++ b/selfdrive/test/test_time_to_onroad.py @@ -4,9 +4,9 @@ import time import subprocess import cereal.messaging as messaging -from common.basedir import BASEDIR -from common.timeout import Timeout -from selfdrive.test.helpers import set_params_enabled +from openpilot.common.basedir import BASEDIR +from openpilot.common.timeout import Timeout +from openpilot.selfdrive.test.helpers import set_params_enabled def test_time_to_onroad(): diff --git a/selfdrive/test/test_updated.py b/selfdrive/test/test_updated.py index aab8b256ac..e679cd2c3b 100755 --- a/selfdrive/test/test_updated.py +++ b/selfdrive/test/test_updated.py @@ -9,8 +9,8 @@ import signal import subprocess import random -from common.basedir import BASEDIR -from common.params import Params +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params class TestUpdated(unittest.TestCase): diff --git a/selfdrive/test/test_valgrind_replay.py b/selfdrive/test/test_valgrind_replay.py index a831ad6e2e..a8a3463104 100755 --- a/selfdrive/test/test_valgrind_replay.py +++ b/selfdrive/test/test_valgrind_replay.py @@ -14,9 +14,9 @@ else: import cereal.messaging as messaging from collections import namedtuple -from tools.lib.logreader import LogReader -from selfdrive.test.openpilotci import get_url -from common.basedir import BASEDIR +from openpilot.tools.lib.logreader import LogReader +from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.common.basedir import BASEDIR ProcessConfig = namedtuple('ProcessConfig', ['proc_name', 'pub_sub', 'ignore', 'command', 'path', 'segment', 'wait_for_response']) @@ -53,7 +53,7 @@ class TestValgrind(unittest.TestCase): os.chdir(os.path.join(BASEDIR, cwd)) # Run valgrind on a process command = "valgrind --leak-check=full " + arg - p = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True, preexec_fn=os.setsid) # pylint: disable=W1509 + p = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True, preexec_fn=os.setsid) while not self.replay_done: time.sleep(0.1) diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index cee2f49e56..8157066334 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -1,32 +1,38 @@ #!/usr/bin/env python3 -from functools import lru_cache -import sys import subprocess +import sys +from functools import lru_cache +from typing import Iterable, Optional + +from azure.storage.blob import ContainerClient from tqdm import tqdm -from azure.storage.blob import BlockBlobService # pylint: disable=import-error -from selfdrive.car.tests.routes import routes as test_car_models_routes -from selfdrive.locationd.test.test_laikad import UBLOX_TEST_ROUTE, QCOM_TEST_ROUTE -from selfdrive.test.process_replay.test_processes import source_segments as replay_segments -from xx.chffr.lib import azureutil # pylint: disable=import-error -from xx.chffr.lib.storage import _DATA_ACCOUNT_PRODUCTION, _DATA_ACCOUNT_CI, _DATA_BUCKET_PRODUCTION # pylint: disable=import-error +from openpilot.selfdrive.car.tests.routes import routes as test_car_models_routes +from openpilot.selfdrive.locationd.test.test_laikad import UBLOX_TEST_ROUTE, QCOM_TEST_ROUTE +from openpilot.selfdrive.test.process_replay.test_processes import source_segments as replay_segments +from openpilot.selfdrive.test.openpilotci import (DATA_CI_ACCOUNT, DATA_CI_ACCOUNT_URL, DATA_CI_CONTAINER, + get_azure_credential, get_container_sas) + +DATA_PROD_ACCOUNT = "commadata2" +DATA_PROD_CONTAINER = "commadata2" SOURCES = [ - (_DATA_ACCOUNT_PRODUCTION, _DATA_BUCKET_PRODUCTION), - (_DATA_ACCOUNT_CI, "commadataci"), + (DATA_PROD_ACCOUNT, DATA_PROD_CONTAINER), + (DATA_CI_ACCOUNT, DATA_CI_CONTAINER), ] @lru_cache def get_azure_keys(): - dest_key = azureutil.get_user_token(_DATA_ACCOUNT_CI, "openpilotci") - source_keys = [azureutil.get_user_token(account, bucket) for account, bucket in SOURCES] - service = BlockBlobService(_DATA_ACCOUNT_CI, sas_token=dest_key) - return dest_key, source_keys, service + dest_container = ContainerClient(DATA_CI_ACCOUNT_URL, DATA_CI_CONTAINER, credential=get_azure_credential()) + dest_key = get_container_sas(DATA_CI_ACCOUNT, DATA_CI_CONTAINER) + source_keys = [get_container_sas(*s) for s in SOURCES] + return dest_container, dest_key, source_keys -def upload_route(path, exclude_patterns=None): - dest_key, _, _ = get_azure_keys() +def upload_route(path: str, exclude_patterns: Optional[Iterable[str]] = None) -> None: + # TODO: use azure-storage-blob instead of azcopy, simplifies auth + dest_key = get_container_sas(DATA_CI_ACCOUNT, DATA_CI_CONTAINER) if exclude_patterns is None: exclude_patterns = ['*/dcamera.hevc'] @@ -37,28 +43,30 @@ def upload_route(path, exclude_patterns=None): "azcopy", "copy", f"{path}/*", - f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{destpath}?{dest_key}", + f"https://{DATA_CI_ACCOUNT}.blob.core.windows.net/{DATA_CI_CONTAINER}/{destpath}?{dest_key}", "--recursive=false", "--overwrite=false", ] + [f"--exclude-pattern={p}" for p in exclude_patterns] subprocess.check_call(cmd) -def sync_to_ci_public(route): - dest_key, source_keys, service = get_azure_keys() + +def sync_to_ci_public(route: str) -> bool: + dest_container, dest_key, source_keys = get_azure_keys() key_prefix = route.replace('|', '/') dongle_id = key_prefix.split('/')[0] - if next(azureutil.list_all_blobs(service, "openpilotci", prefix=key_prefix), None) is not None: + if next(dest_container.list_blob_names(name_starts_with=key_prefix), None) is not None: return True print(f"Uploading {route}") for (source_account, source_bucket), source_key in zip(SOURCES, source_keys, strict=True): + # assumes az login has been run print(f"Trying {source_account}/{source_bucket}") cmd = [ "azcopy", "copy", f"https://{source_account}.blob.core.windows.net/{source_bucket}/{key_prefix}?{source_key}", - f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{dongle_id}?{dest_key}", + f"https://{DATA_CI_ACCOUNT}.blob.core.windows.net/{DATA_CI_CONTAINER}/{dongle_id}?{dest_key}", "--recursive=true", "--overwrite=false", "--exclude-pattern=*/dcamera.hevc", diff --git a/selfdrive/thermald/fan_controller.py b/selfdrive/thermald/fan_controller.py old mode 100644 new mode 100755 index f3e822da51..64cd4c78ee --- a/selfdrive/thermald/fan_controller.py +++ b/selfdrive/thermald/fan_controller.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 from abc import ABC, abstractmethod -from common.realtime import DT_TRML -from common.numpy_fast import interp -from system.swaglog import cloudlog -from selfdrive.controls.lib.pid import PIDController +from openpilot.common.realtime import DT_TRML +from openpilot.common.numpy_fast import interp +from openpilot.system.swaglog import cloudlog +from openpilot.selfdrive.controls.lib.pid import PIDController class BaseFanController(ABC): @abstractmethod diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 06e2b5e8f9..0b3c73a1c0 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -1,11 +1,11 @@ +import time import threading from typing import Optional -from common.params import Params, put_nonblocking -from common.realtime import sec_since_boot -from system.hardware import HARDWARE -from system.swaglog import cloudlog -from selfdrive.statsd import statlog +from openpilot.common.params import Params, put_nonblocking +from openpilot.system.hardware import HARDWARE +from openpilot.system.swaglog import cloudlog +from openpilot.selfdrive.statsd import statlog CAR_VOLTAGE_LOW_PASS_K = 0.011 # LPF gain for 45s tau (dt/tau / (dt/tau + 1)) @@ -41,7 +41,7 @@ class PowerMonitoring: # Calculation tick def calculate(self, voltage: Optional[int], ignition: bool): try: - now = sec_since_boot() + now = time.monotonic() # If peripheralState is None, we're probably not in a car, so we don't care if voltage is None: @@ -113,7 +113,7 @@ class PowerMonitoring: if offroad_timestamp is None: return False - now = sec_since_boot() + now = time.monotonic() should_shutdown = False offroad_time = (now - offroad_timestamp) low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and diff --git a/selfdrive/thermald/tests/test_fan_controller.py b/selfdrive/thermald/tests/test_fan_controller.py index 22d618485c..7081e1353e 100755 --- a/selfdrive/thermald/tests/test_fan_controller.py +++ b/selfdrive/thermald/tests/test_fan_controller.py @@ -3,7 +3,7 @@ import unittest from unittest.mock import Mock, patch from parameterized import parameterized -from selfdrive.thermald.fan_controller import TiciFanController +from openpilot.selfdrive.thermald.fan_controller import TiciFanController ALL_CONTROLLERS = [(TiciFanController,)] diff --git a/selfdrive/thermald/tests/test_power_monitoring.py b/selfdrive/thermald/tests/test_power_monitoring.py index 57b16accff..c3a890f068 100755 --- a/selfdrive/thermald/tests/test_power_monitoring.py +++ b/selfdrive/thermald/tests/test_power_monitoring.py @@ -2,35 +2,32 @@ import unittest from unittest.mock import patch -from common.params import Params -params = Params() +from openpilot.common.params import Params +from openpilot.selfdrive.thermald.power_monitoring import PowerMonitoring, CAR_BATTERY_CAPACITY_uWh, \ + CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S + # Create fake time -ssb = 0 -def mock_sec_since_boot(): +ssb = 0. +def mock_time_monotonic(): global ssb - ssb += 1 + ssb += 1. return ssb -with patch("common.realtime.sec_since_boot", new=mock_sec_since_boot): - with patch("common.params.put_nonblocking", new=params.put): - from selfdrive.thermald.power_monitoring import PowerMonitoring, CAR_BATTERY_CAPACITY_uWh, \ - CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S - TEST_DURATION_S = 50 GOOD_VOLTAGE = 12 * 1e3 VOLTAGE_BELOW_PAUSE_CHARGING = (VBATT_PAUSE_CHARGING - 1) * 1e3 def pm_patch(name, value, constant=False): if constant: - return patch(f"selfdrive.thermald.power_monitoring.{name}", value) - return patch(f"selfdrive.thermald.power_monitoring.{name}", return_value=value) + return patch(f"openpilot.selfdrive.thermald.power_monitoring.{name}", value) + return patch(f"openpilot.selfdrive.thermald.power_monitoring.{name}", return_value=value) + +@patch("time.monotonic", new=mock_time_monotonic) class TestPowerMonitoring(unittest.TestCase): def setUp(self): - # Clear stored capacity before each test - params.remove("CarBatteryCapacity") - params.remove("DisablePowerDown") + self.params = Params() # Test to see that it doesn't do anything when pandaState is None def test_pandaState_present(self): @@ -139,7 +136,7 @@ class TestPowerMonitoring(unittest.TestCase): def test_disable_power_down(self): POWER_DRAW = 0 # To stop shutting down for other reasons TEST_TIME = 100 - params.put_bool("DisablePowerDown", True) + self.params.put_bool("DisablePowerDown", True) with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 931f9be88f..32cfe11c39 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -13,19 +13,19 @@ import psutil import cereal.messaging as messaging from cereal import log -from common.dict_helpers import strip_deprecated_keys -from common.time import MIN_DATE -from common.filter_simple import FirstOrderFilter -from common.params import Params -from common.realtime import DT_TRML, sec_since_boot -from selfdrive.controls.lib.alertmanager import set_offroad_alert -from system.hardware import HARDWARE, TICI, AGNOS -from system.loggerd.config import get_available_percent -from selfdrive.statsd import statlog -from system.swaglog import cloudlog -from selfdrive.thermald.power_monitoring import PowerMonitoring -from selfdrive.thermald.fan_controller import TiciFanController -from system.version import terms_version, training_version +from openpilot.common.dict_helpers import strip_deprecated_keys +from openpilot.common.time import MIN_DATE +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.params import Params +from openpilot.common.realtime import DT_TRML +from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert +from openpilot.system.hardware import HARDWARE, TICI, AGNOS +from openpilot.system.loggerd.config import get_available_percent +from openpilot.selfdrive.statsd import statlog +from openpilot.system.swaglog import cloudlog +from openpilot.selfdrive.thermald.power_monitoring import PowerMonitoring +from openpilot.selfdrive.thermald.fan_controller import TiciFanController +from openpilot.system.version import terms_version, training_version ThermalStatus = log.DeviceState.ThermalStatus NetworkType = log.DeviceState.NetworkType @@ -118,8 +118,8 @@ def hw_state_thread(end_event, hw_queue): # Log modem version once if AGNOS and ((modem_version is None) or (modem_nv is None)): - modem_version = HARDWARE.get_modem_version() # pylint: disable=assignment-from-none - modem_nv = HARDWARE.get_modem_nv() # pylint: disable=assignment-from-none + modem_version = HARDWARE.get_modem_version() + modem_nv = HARDWARE.get_modem_nv() if (modem_version is not None) and (modem_nv is not None): cloudlog.event("modem version", version=modem_version, nv=modem_nv) @@ -165,7 +165,7 @@ def hw_state_thread(end_event, hw_queue): time.sleep(DT_TRML) -def thermald_thread(end_event, hw_queue): +def thermald_thread(end_event, hw_queue) -> None: pm = messaging.PubMaster(['deviceState']) sm = messaging.SubMaster(["peripheralState", "gpsLocationExternal", "controlsState", "pandaStates"], poll=["pandaStates"]) @@ -177,10 +177,10 @@ def thermald_thread(end_event, hw_queue): startup_conditions: Dict[str, bool] = {} startup_conditions_prev: Dict[str, bool] = {} - off_ts = None - started_ts = None + off_ts: Optional[float] = None + started_ts: Optional[float] = None started_seen = False - startup_blocked_ts = None + startup_blocked_ts: Optional[float] = None thermal_status = ThermalStatus.yellow last_hw_state = HardwareState( @@ -230,7 +230,7 @@ def thermald_thread(end_event, hw_queue): if TICI: fan_controller = TiciFanController() - elif (sec_since_boot() - sm.rcv_time['pandaStates']) > DISCONNECT_TIMEOUT: + elif (time.monotonic() - sm.rcv_time['pandaStates']) > DISCONNECT_TIMEOUT: if onroad_conditions["ignition"]: onroad_conditions["ignition"] = False cloudlog.error("panda timed out onroad") @@ -273,7 +273,7 @@ def thermald_thread(end_event, hw_queue): if fan_controller is not None: msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"]) - is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (sec_since_boot() - off_ts > 60 * 5)) + is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (time.monotonic() - off_ts > 60 * 5)) if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP: # if device is offroad and already hot without the extra onroad load, # we want to cool down first before increasing load @@ -354,10 +354,10 @@ def thermald_thread(end_event, hw_queue): if should_start: off_ts = None if started_ts is None: - started_ts = sec_since_boot() + started_ts = time.monotonic() started_seen = True if startup_blocked_ts is not None: - cloudlog.event("Startup after block", block_duration=(sec_since_boot() - startup_blocked_ts), + cloudlog.event("Startup after block", block_duration=(time.monotonic() - startup_blocked_ts), startup_conditions=startup_conditions, onroad_conditions=onroad_conditions, startup_conditions_prev=startup_conditions_prev, error=True) startup_blocked_ts = None @@ -365,11 +365,11 @@ def thermald_thread(end_event, hw_queue): if onroad_conditions["ignition"] and (startup_conditions != startup_conditions_prev): cloudlog.event("Startup blocked", startup_conditions=startup_conditions, onroad_conditions=onroad_conditions, error=True) startup_conditions_prev = startup_conditions.copy() - startup_blocked_ts = sec_since_boot() + startup_blocked_ts = time.monotonic() started_ts = None if off_ts is None: - off_ts = sec_since_boot() + off_ts = time.monotonic() # Offroad power monitoring voltage = None if peripheralState.pandaType == log.PandaState.PandaType.unknown else peripheralState.voltage diff --git a/selfdrive/tombstoned.py b/selfdrive/tombstoned.py index b1086305b7..a1479003ee 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -9,11 +9,11 @@ import time import glob from typing import NoReturn -from common.file_helpers import mkdirs_exists_ok -from system.loggerd.config import ROOT -import selfdrive.sentry as sentry -from system.swaglog import cloudlog -from system.version import get_commit +from openpilot.common.file_helpers import mkdirs_exists_ok +import openpilot.selfdrive.sentry as sentry +from openpilot.system.hardware.hw import Paths +from openpilot.system.swaglog import cloudlog +from openpilot.system.version import get_commit MAX_SIZE = 1_000_000 * 100 # allow up to 100M MAX_TOMBSTONE_FN_LEN = 62 # 85 - 23 ("/crash/") @@ -38,7 +38,7 @@ def clear_apport_folder(): def get_apport_stacktrace(fn): try: cmd = f'apport-retrace -s <(cat <(echo "Package: openpilot") "{fn}")' - return subprocess.check_output(cmd, shell=True, encoding='utf8', timeout=30, executable='/bin/bash') # pylint: disable=unexpected-keyword-arg + return subprocess.check_output(cmd, shell=True, encoding='utf8', timeout=30, executable='/bin/bash') except subprocess.CalledProcessError: return "Error getting stacktrace" except subprocess.TimeoutExpired: @@ -95,7 +95,7 @@ def report_tombstone_apport(fn): try: sig_num = int(line.strip().split(': ')[-1]) - message += " (" + signal.Signals(sig_num).name + ")" # pylint: disable=no-member + message += " (" + signal.Signals(sig_num).name + ")" except ValueError: pass @@ -130,7 +130,7 @@ def report_tombstone_apport(fn): new_fn = f"{date}_{get_commit(default='nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] - crashlog_dir = os.path.join(ROOT, "crash") + crashlog_dir = os.path.join(Paths.log_root(), "crash") mkdirs_exists_ok(crashlog_dir) # Files could be on different filesystems, copy, then delete diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 7e90423fc2..017ce66793 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -24,7 +24,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/ "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", "qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", - "qt/request_repeater.cc", "qt/qt_window.cc", "qt/offroad/networking.cc", "qt/offroad/wifiManager.cc"] + "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] qt_env['CPPDEFINES'] = [] if maps: @@ -37,16 +37,42 @@ widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) Export('widgets') qt_libs = [widgets, qt_util] + base_libs +qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc", + "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", + "qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc", + "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc"] + +# build translation files +with open(File("translations/languages.json").abspath) as f: + languages = json.loads(f.read()) +translation_sources = [f"#selfdrive/ui/translations/{l}.ts" for l in languages.values()] +translation_targets = [src.replace(".ts", ".qm") for src in translation_sources] +lrelease_bin = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease' + +lupdate = qt_env.Command(translation_sources, qt_src + widgets_src, "selfdrive/ui/update_translations.py") +lrelease = qt_env.Command(translation_targets, translation_sources, f"{lrelease_bin} $SOURCES") +qt_env.Depends(lrelease, lupdate) +qt_env.NoClean(translation_sources) +qt_env.Precious(translation_sources) +qt_env.NoCache(lupdate) + +# create qrc file for compiled translations to include with assets +translations_assets_src = "#selfdrive/assets/translations_assets.qrc" +with open(File(translations_assets_src).abspath, 'w') as f: + f.write('\n\n') + f.write('\n'.join([f'../ui/translations/{l}.qm' for l in languages.values()])) + f.write('\n\n') + # build assets assets = "#selfdrive/assets/assets.cc" assets_src = "#selfdrive/assets/assets.qrc" -qt_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") -qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, "#selfdrive/assets/assets.o"])) +qt_env.Command(assets, [assets_src, translations_assets_src], f"rcc $SOURCES -o $TARGET") +qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, translations_assets_src, "#selfdrive/assets/assets.o"]) + [lrelease]) asset_obj = qt_env.Object("assets", assets) # build soundd qt_env.Program("soundd/_soundd", ["soundd/main.cc", "soundd/sound.cc"], LIBS=qt_libs) -if GetOption('test'): +if GetOption('extras'): qt_env.Program("tests/playsound", "tests/playsound.cc", LIBS=base_libs) qt_env.Program('tests/test_sound', ['tests/test_runner.cc', 'soundd/sound.cc', 'tests/test_sound.cc'], LIBS=qt_libs) @@ -57,39 +83,19 @@ qt_env.Program("qt/text", ["qt/text.cc"], LIBS=qt_libs) qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs) # build main UI -qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc", - "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", - "qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc", - "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc"] qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs) -if GetOption('test'): +if GetOption('extras'): qt_src.remove("main.cc") # replaced by test_runner qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs) qt_env.Program('tests/ui_snapshot', [asset_obj, "tests/ui_snapshot.cc"] + qt_src, LIBS=qt_libs) -# build translation files -with open(File("translations/languages.json").abspath) as f: - languages = json.loads(f.read()) -translation_sources = [f"#selfdrive/ui/translations/{l}.ts" for l in languages.values()] -translation_targets = [src.replace(".ts", ".qm") for src in translation_sources] -lrelease_bin = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease' - -lupdate = qt_env.Command(translation_sources, qt_src, "selfdrive/ui/update_translations.py") -lrelease = qt_env.Command(translation_targets, translation_sources, f"{lrelease_bin} $SOURCES") -qt_env.Depends(lrelease, lupdate) -qt_env.NoClean(translation_sources) -qt_env.Precious(translation_sources) -qt_env.NoCache(lupdate) - -# setup and factory resetter -if GetOption('extras'): +if GetOption('extras') and arch != "Darwin": + # setup and factory resetter qt_env.Program("qt/setup/reset", ["qt/setup/reset.cc"], LIBS=qt_libs) qt_env.Program("qt/setup/setup", ["qt/setup/setup.cc", asset_obj], LIBS=qt_libs + ['curl', 'common', 'json11']) - -if GetOption('extras'): # build updater UI qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs) diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 1af72c04df..179ce60c63 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include diff --git a/selfdrive/ui/main.cc b/selfdrive/ui/main.cc index ed54d5aa19..4903a3db3d 100644 --- a/selfdrive/ui/main.cc +++ b/selfdrive/ui/main.cc @@ -16,7 +16,7 @@ int main(int argc, char *argv[]) { QTranslator translator; QString translation_file = QString::fromStdString(Params().get("LanguageSetting")); - if (!translator.load(translation_file, "translations") && translation_file.length()) { + if (!translator.load(QString(":/%1").arg(translation_file)) && translation_file.length()) { qCritical() << "Failed to load translation file:" << translation_file; } diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc index 84e1a4032e..0e321d4e10 100644 --- a/selfdrive/ui/qt/api.cc +++ b/selfdrive/ui/qt/api.cc @@ -12,6 +12,8 @@ #include #include +#include + #include "common/params.h" #include "common/util.h" #include "system/hardware/hw.h" @@ -83,7 +85,7 @@ void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Meth return; } QString token; - if(create_jwt) { + if (create_jwt) { token = CommaApi::create_jwt(); } else { QString token_json = QString::fromStdString(util::read_file(util::getenv("HOME") + "/.comma/auth.json")); diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index b674d39fbd..f93b590007 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -157,9 +157,9 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { left_widget->addWidget(new PrimeAdWidget); left_widget->setStyleSheet("border-radius: 10px;"); - left_widget->setCurrentIndex(uiState()->primeType() ? 0 : 1); - connect(uiState(), &UIState::primeTypeChanged, [=](int prime_type) { - left_widget->setCurrentIndex(prime_type ? 0 : 1); + left_widget->setCurrentIndex(uiState()->hasPrime() ? 0 : 1); + connect(uiState(), &UIState::primeChanged, [=](bool prime) { + left_widget->setCurrentIndex(prime ? 0 : 1); }); home_layout->addWidget(left_widget, 1); diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index b596b11260..db56fcdcfc 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -1,16 +1,16 @@ #include "selfdrive/ui/qt/maps/map.h" +#include #include #include -#include "common/transformations/coordinates.hpp" #include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/ui.h" -const int PAN_TIMEOUT = 100; +const int INTERACTION_TIMEOUT = 100; const float MAX_ZOOM = 17; const float MIN_ZOOM = 14; @@ -18,7 +18,7 @@ const float MAX_PITCH = 50; const float MIN_PITCH = 0; const float MAP_SCALE = 2; -MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05) { +MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05, false) { QObject::connect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState); map_overlay = new QWidget (this); @@ -151,7 +151,7 @@ void MapWindow::updateState(const UIState &s) { if (locationd_valid) { last_position = QMapbox::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]); last_bearing = RAD2DEG(locationd_orientation.getValue()[2]); - velocity_filter.update(locationd_velocity.getValue()[0]); + velocity_filter.update(std::max(10.0, locationd_velocity.getValue()[0])); } } @@ -191,19 +191,19 @@ void MapWindow::updateState(const UIState &s) { carPosSource["type"] = "geojson"; carPosSource["data"] = QVariant::fromValue(feature1); m_map->updateSource("carPosSource", carPosSource); + + // Map bearing isn't updated when interacting, keep location marker up to date + if (last_bearing) { + m_map->setLayoutProperty("carPosLayer", "icon-rotate", *last_bearing - m_map->bearing()); + } } - if (pan_counter == 0) { + if (interaction_counter == 0) { if (last_position) m_map->setCoordinate(*last_position); if (last_bearing) m_map->setBearing(*last_bearing); - } else { - pan_counter--; - } - - if (zoom_counter == 0) { m_map->setZoom(util::map_val(velocity_filter.x(), 0, 30, MAX_ZOOM, MIN_ZOOM)); } else { - zoom_counter--; + interaction_counter--; } if (sm.updated("navInstruction")) { @@ -307,15 +307,14 @@ void MapWindow::mouseDoubleClickEvent(QMouseEvent *ev) { m_map->setZoom(util::map_val(velocity_filter.x(), 0, 30, MAX_ZOOM, MIN_ZOOM)); update(); - pan_counter = 0; - zoom_counter = 0; + interaction_counter = 0; } void MapWindow::mouseMoveEvent(QMouseEvent *ev) { QPointF delta = ev->localPos() - m_lastPos; if (!delta.isNull()) { - pan_counter = PAN_TIMEOUT; + interaction_counter = INTERACTION_TIMEOUT; m_map->moveBy(delta / MAP_SCALE); update(); } @@ -337,7 +336,7 @@ void MapWindow::wheelEvent(QWheelEvent *ev) { m_map->scaleBy(1 + factor, ev->pos() / MAP_SCALE); update(); - zoom_counter = PAN_TIMEOUT; + interaction_counter = INTERACTION_TIMEOUT; ev->accept(); } @@ -362,7 +361,7 @@ void MapWindow::pinchTriggered(QPinchGesture *gesture) { // TODO: figure out why gesture centerPoint doesn't work m_map->scaleBy(gesture->scaleFactor(), {width() / 2.0 / MAP_SCALE, height() / 2.0 / MAP_SCALE}); update(); - zoom_counter = PAN_TIMEOUT; + interaction_counter = INTERACTION_TIMEOUT; } } diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 66d10a00a0..5fe79f8b15 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -53,8 +53,7 @@ private: // Panning QPointF m_lastPos; - int pan_counter = 0; - int zoom_counter = 0; + int interaction_counter = 0; // Position std::optional last_valid_nav_dest; diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc index 0f96638820..ed843610a8 100644 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -1,5 +1,9 @@ #include "selfdrive/ui/qt/maps/map_helpers.h" +#include +#include +#include + #include #include @@ -57,7 +61,7 @@ QMapbox::CoordinatesCollections coordinate_to_collection(const QMapbox::Coordina QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader& coordinate_list) { QMapbox::Coordinates coordinates; - for (auto const &c: coordinate_list) { + for (auto const &c : coordinate_list) { coordinates.push_back({c.getLatitude(), c.getLongitude()}); } return {QMapbox::CoordinatesCollection{coordinates}}; @@ -145,8 +149,3 @@ std::pair map_format_distance(float d, bool is_metric) { : std::pair{QString::number(50 * std::nearbyint(d / 50)), QObject::tr("ft")}; } } - -double angle_difference(double angle1, double angle2) { - double diff = fmod(angle2 - angle1 + 180.0, 360.0) - 180.0; - return diff < -180.0 ? diff + 360.0 : diff; -} diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h index 0090753a40..4d6e9b5382 100644 --- a/selfdrive/ui/qt/maps/map_helpers.h +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include #include @@ -27,4 +29,3 @@ QMapbox::CoordinatesCollections coordinate_list_to_collection(const QList polyline_to_coordinate_list(const QString &polylineString); std::optional coordinate_from_param(const std::string ¶m); std::pair map_format_distance(float d, bool is_metric); -double angle_difference(double angle1, double angle2); diff --git a/selfdrive/ui/qt/maps/map_instructions.cc b/selfdrive/ui/qt/maps/map_instructions.cc index 0901213c76..ba8cb356bd 100644 --- a/selfdrive/ui/qt/maps/map_instructions.cc +++ b/selfdrive/ui/qt/maps/map_instructions.cc @@ -10,27 +10,31 @@ const QString ICON_SUFFIX = ".png"; MapInstructions::MapInstructions(QWidget *parent) : QWidget(parent) { is_rhd = Params().getBool("IsRhdDetected"); - QHBoxLayout *main_layout = new QHBoxLayout(this); - main_layout->setContentsMargins(11, UI_BORDER_SIZE, 11, 11); - main_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop); + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(11, UI_BORDER_SIZE, 11, 20); - QWidget *right_container = new QWidget(this); - right_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - QVBoxLayout *layout = new QVBoxLayout(right_container); + QHBoxLayout *top_layout = new QHBoxLayout; + top_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop); - layout->addWidget(distance = new QLabel); + QVBoxLayout *right_layout = new QVBoxLayout; + right_layout->setContentsMargins(9, 9, 9, 0); + right_layout->addWidget(distance = new QLabel); distance->setStyleSheet(R"(font-size: 90px;)"); - layout->addWidget(primary = new QLabel); + right_layout->addWidget(primary = new QLabel); primary->setStyleSheet(R"(font-size: 60px;)"); primary->setWordWrap(true); - layout->addWidget(secondary = new QLabel); + right_layout->addWidget(secondary = new QLabel); secondary->setStyleSheet(R"(font-size: 50px;)"); secondary->setWordWrap(true); - layout->addLayout(lane_layout = new QHBoxLayout); - main_layout->addWidget(right_container); + top_layout->addLayout(right_layout); + + main_layout->addLayout(top_layout); + main_layout->addLayout(lane_layout = new QHBoxLayout); + lane_layout->setAlignment(Qt::AlignHCenter); + lane_layout->setSpacing(10); setStyleSheet("color:white"); QPalette pal = palette(); diff --git a/selfdrive/ui/qt/maps/map_instructions.h b/selfdrive/ui/qt/maps/map_instructions.h index 6c08cb9b9e..06a943d27f 100644 --- a/selfdrive/ui/qt/maps/map_instructions.h +++ b/selfdrive/ui/qt/maps/map_instructions.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include #include #include diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc index f1b8f812fa..0a2286ff6f 100644 --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -14,21 +14,21 @@ MapPanel::MapPanel(const QMapboxGLSettings &mapboxSettings, QWidget *parent) : Q auto map = new MapWindow(mapboxSettings); QObject::connect(uiState(), &UIState::offroadTransition, map, &MapWindow::offroadTransition); - QObject::connect(device(), &Device::interactiveTimeout, [=]() { + QObject::connect(device(), &Device::interactiveTimeout, this, [=]() { content_stack->setCurrentIndex(0); }); - QObject::connect(map, &MapWindow::requestVisible, [=](bool visible) { + QObject::connect(map, &MapWindow::requestVisible, this, [=](bool visible) { // when we show the map for a new route, signal HomeWindow to hide the sidebar if (visible) { emit mapPanelRequested(); } setVisible(visible); }); - QObject::connect(map, &MapWindow::requestSettings, [=](bool settings) { + QObject::connect(map, &MapWindow::requestSettings, this, [=](bool settings) { content_stack->setCurrentIndex(settings ? 1 : 0); }); content_stack->addWidget(map); auto settings = new MapSettings(true, parent); - QObject::connect(settings, &MapSettings::closeSettings, [=]() { + QObject::connect(settings, &MapSettings::closeSettings, this, [=]() { content_stack->setCurrentIndex(0); }); content_stack->addWidget(settings); diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc index 018d0df984..4d655be36c 100644 --- a/selfdrive/ui/qt/maps/map_settings.cc +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -1,4 +1,6 @@ -#include "map_settings.h" +#include "selfdrive/ui/qt/maps/map_settings.h" + +#include #include #include @@ -7,6 +9,17 @@ #include "selfdrive/ui/qt/request_repeater.h" #include "selfdrive/ui/qt/widgets/scrollview.h" +static void swap(QJsonValueRef v1, QJsonValueRef v2) { std::swap(v1, v2); } + +static bool locationEqual(const QJsonValue &v1, const QJsonValue &v2) { + return v1["latitude"] == v2["latitude"] && v1["longitude"] == v2["longitude"]; +} + +static qint64 convertTimestampToEpoch(const QString ×tamp) { + QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate); + return dt.isValid() ? dt.toSecsSinceEpoch() : 0; +} + MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) { setContentsMargins(0, 0, 0, 0); setAttribute(Qt::WA_NoMousePropagation); @@ -59,11 +72,8 @@ MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) { frame->addSpacing(32); current_widget = new DestinationWidget(this); - QObject::connect(current_widget, &DestinationWidget::actionClicked, [=]() { - if (current_destination.empty()) return; - params.remove("NavDestination"); - updateCurrentRoute(); - }); + QObject::connect(current_widget, &DestinationWidget::actionClicked, + []() { NavManager::instance()->setCurrentDestination({}); }); frame->addWidget(current_widget); frame->addSpacing(32); @@ -82,40 +92,16 @@ MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) { frame->addWidget(destinations_scroller); setStyleSheet("MapSettings { background-color: #333333; }"); - - QObject::connect(NavigationRequest::instance(), &NavigationRequest::locationsUpdated, this, &MapSettings::updateLocations); - QObject::connect(NavigationRequest::instance(), &NavigationRequest::nextDestinationUpdated, this, &MapSettings::updateCurrentRoute); - - current_locations = NavigationRequest::instance()->currentLocations(); + QObject::connect(NavManager::instance(), &NavManager::updated, this, &MapSettings::refresh); } void MapSettings::showEvent(QShowEvent *event) { - updateCurrentRoute(); -} - -void MapSettings::updateCurrentRoute() { - auto dest = QString::fromStdString(params.get("NavDestination")); - if (dest.size()) { - QJsonDocument doc = QJsonDocument::fromJson(dest.trimmed().toUtf8()); - if (doc.isNull()) { - qWarning() << "JSON Parse failed on NavDestination" << dest; - return; - } - current_destination = doc.object(); - current_widget->set(current_destination, true); - } else { - current_destination = {}; - current_widget->unset("", true); - } - if (isVisible()) refresh(); -} - -void MapSettings::updateLocations(const QJsonArray &locations) { - current_locations = locations; refresh(); } void MapSettings::refresh() { + if (!isVisible()) return; + setUpdatesEnabled(false); auto get_w = [this](int i) { @@ -127,11 +113,17 @@ void MapSettings::refresh() { return w; }; + const auto current_dest = NavManager::instance()->currentDestination(); + if (!current_dest.isEmpty()) { + current_widget->set(current_dest, true); + } else { + current_widget->unset("", true); + } home_widget->unset(NAV_FAVORITE_LABEL_HOME); work_widget->unset(NAV_FAVORITE_LABEL_WORK); int n = 0; - for (auto location : current_locations) { + for (auto location : NavManager::instance()->currentLocations()) { DestinationWidget *w = nullptr; auto dest = location.toObject(); if (dest["save_type"].toString() == NAV_TYPE_FAVORITE) { @@ -141,7 +133,7 @@ void MapSettings::refresh() { } w = w ? w : get_w(n++); w->set(dest, false); - w->setVisible(dest != current_destination); + w->setVisible(!locationEqual(dest, current_dest)); } for (; n < widgets.size(); ++n) widgets[n]->setVisible(false); @@ -149,9 +141,7 @@ void MapSettings::refresh() { } void MapSettings::navigateTo(const QJsonObject &place) { - QJsonDocument doc(place); - params.put("NavDestination", doc.toJson().toStdString()); - updateCurrentRoute(); + NavManager::instance()->setCurrentDestination(place); emit closeSettings(); } @@ -277,24 +267,26 @@ void DestinationWidget::unset(const QString &label, bool current) { setVisible(true); } -// singleton NavigationRequest +// singleton NavManager -NavigationRequest *NavigationRequest::instance() { - static NavigationRequest *request = new NavigationRequest(qApp); +NavManager *NavManager::instance() { + static NavManager *request = new NavManager(qApp); return request; } -NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) { +NavManager::NavManager(QObject *parent) : QObject(parent) { + locations = QJsonDocument::fromJson(params.get("NavPastDestinations").c_str()).array(); + current_dest = QJsonDocument::fromJson(params.get("NavDestination").c_str()).object(); if (auto dongle_id = getDongleId()) { { // Fetch favorite and recent locations QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations"; RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true); - QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavigationRequest::parseLocationsResponse); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavManager::parseLocationsResponse); } { auto param_watcher = new ParamWatcher(this); - QObject::connect(param_watcher, &ParamWatcher::paramChanged, this, &NavigationRequest::nextDestinationUpdated); + QObject::connect(param_watcher, &ParamWatcher::paramChanged, this, &NavManager::updated); // Destination set while offline QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/next"; @@ -314,14 +306,14 @@ NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) { // athena can set destination at any time param_watcher->addParam("NavDestination"); + current_dest = QJsonDocument::fromJson(params.get("NavDestination").c_str()).object(); + emit updated(); }); } } } -static void swap(QJsonValueRef v1, QJsonValueRef v2) { std::swap(v1, v2); } - -void NavigationRequest::parseLocationsResponse(const QString &response, bool success) { +void NavManager::parseLocationsResponse(const QString &response, bool success) { if (!success || response == prev_response) return; prev_response = response; @@ -331,13 +323,63 @@ void NavigationRequest::parseLocationsResponse(const QString &response, bool suc return; } - // Sort: alphabetical FAVORITES, and then most recent (as returned by API). + // set last activity time. + auto remote_locations = doc.array(); + for (QJsonValueRef loc : remote_locations) { + auto obj = loc.toObject(); + auto serverTime = convertTimestampToEpoch(obj["modified"].toString()); + obj.insert("time", qMax(serverTime, getLastActivity(obj))); + loc = obj; + } + + locations = remote_locations; + sortLocations(); + emit updated(); +} + +void NavManager::sortLocations() { + // Sort: alphabetical FAVORITES, and then most recent. // We don't need to care about the ordering of HOME and WORK. DestinationWidget always displays them at the top. - locations = doc.array(); std::stable_sort(locations.begin(), locations.end(), [](const QJsonValue &a, const QJsonValue &b) { - bool has_favorite = a["save_type"] == NAV_TYPE_FAVORITE || b["save_type"] == NAV_TYPE_FAVORITE; - return has_favorite && (std::tuple(a["save_type"].toString(), a["place_name"].toString()) < - std::tuple(b["save_type"].toString(), b["place_name"].toString())); + if (a["save_type"] == NAV_TYPE_FAVORITE || b["save_type"] == NAV_TYPE_FAVORITE) { + return (std::tuple(a["save_type"].toString(), a["place_name"].toString()) < + std::tuple(b["save_type"].toString(), b["place_name"].toString())); + } else { + return a["time"].toVariant().toLongLong() > b["time"].toVariant().toLongLong(); + } + }); + + write_param_future = std::async(std::launch::async, [destinations = QJsonArray(locations)]() { + Params().put("NavPastDestinations", QJsonDocument(destinations).toJson().toStdString()); }); - emit locationsUpdated(locations); +} + +qint64 NavManager::getLastActivity(const QJsonObject &loc) const { + qint64 last_activity = 0; + auto it = std::find_if(locations.begin(), locations.end(), + [&loc](const QJsonValue &l) { return locationEqual(loc, l); }); + if (it != locations.end()) { + auto tm = it->toObject().value("time"); + if (!tm.isUndefined() && !tm.isNull()) { + last_activity = tm.toVariant().toLongLong(); + } + } + return last_activity; +} + +void NavManager::setCurrentDestination(const QJsonObject &loc) { + current_dest = loc; + if (!current_dest.isEmpty()) { + current_dest["time"] = QDateTime::currentSecsSinceEpoch(); + auto it = std::find_if(locations.begin(), locations.end(), + [&loc](const QJsonValue &l) { return locationEqual(loc, l); }); + if (it != locations.end()) { + *it = current_dest; + sortLocations(); + } + params.put("NavDestination", QJsonDocument(current_dest).toJson().toStdString()); + } else { + params.remove("NavDestination"); + } + emit updated(); } diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h index bfffd9206d..0e151df4ad 100644 --- a/selfdrive/ui/qt/maps/map_settings.h +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include #include #include @@ -20,42 +23,41 @@ const QString NAV_FAVORITE_LABEL_WORK = "work"; class DestinationWidget; -class NavigationRequest : public QObject { +class NavManager : public QObject { Q_OBJECT public: - static NavigationRequest *instance(); - QJsonArray currentLocations() const { return locations; }; + static NavManager *instance(); + QJsonArray currentLocations() const { return locations; } + QJsonObject currentDestination() const { return current_dest; } + void setCurrentDestination(const QJsonObject &loc); + qint64 getLastActivity(const QJsonObject &loc) const; signals: - void locationsUpdated(const QJsonArray &locations); - void nextDestinationUpdated(); + void updated(); private: - NavigationRequest(QObject *parent); + NavManager(QObject *parent); void parseLocationsResponse(const QString &response, bool success); + void sortLocations(); Params params; QString prev_response; QJsonArray locations; + QJsonObject current_dest; + std::future write_param_future; }; class MapSettings : public QFrame { Q_OBJECT public: explicit MapSettings(bool closeable = false, QWidget *parent = nullptr); - void navigateTo(const QJsonObject &place); - void updateLocations(const QJsonArray &locations); - void updateCurrentRoute(); private: void showEvent(QShowEvent *event) override; void refresh(); - Params params; - QJsonArray current_locations; - QJsonObject current_destination; QVBoxLayout *destinations_layout; DestinationWidget *current_widget; DestinationWidget *home_widget; diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/network/networking.cc similarity index 97% rename from selfdrive/ui/qt/offroad/networking.cc rename to selfdrive/ui/qt/network/networking.cc index 628a92fb58..98ecc90fe3 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/networking.h" +#include "selfdrive/ui/qt/network/networking.h" #include @@ -10,9 +10,9 @@ #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/widgets/prime.h" #include "selfdrive/ui/qt/widgets/scrollview.h" +static const int ICON_WIDTH = 49; // Networking functions @@ -80,7 +80,6 @@ void Networking::refresh() { void Networking::connectToNetwork(const Network n) { if (wifi->isKnownConnection(n.ssid)) { wifi->activateWifiConnection(n.ssid); - wifiWidget->refresh(); } else if (n.security_type == SecurityType::OPEN) { wifi->connect(n); } else if (n.security_type == SecurityType::WPA) { @@ -184,7 +183,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid // Set initial config wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered); - connect(uiState(), &UIState::primeTypeChanged, this, [=](int prime_type) { + connect(uiState(), &UIState::primeTypeChanged, this, [=](PrimeType prime_type) { bool gsmVisible = prime_type == PrimeType::NONE || prime_type == PrimeType::LITE; roamingToggle->setVisible(gsmVisible); editApnButton->setVisible(gsmVisible); @@ -218,9 +217,9 @@ WifiUI::WifiUI(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi) QPixmap pix(ASSET_PATH + "/offroad/icon_wifi_strength_" + s + ".svg"); strengths.push_back(pix.scaledToHeight(68, Qt::SmoothTransformation)); } - lock = QPixmap(ASSET_PATH + "offroad/icon_lock_closed.svg").scaledToWidth(49, Qt::SmoothTransformation); - checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(49, Qt::SmoothTransformation); - circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(49, Qt::SmoothTransformation); + lock = QPixmap(ASSET_PATH + "offroad/icon_lock_closed.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); + checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); + circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); scanningLabel = new QLabel(tr("Scanning for networks...")); scanningLabel->setStyleSheet("font-size: 65px;"); @@ -336,6 +335,7 @@ WifiItem::WifiItem(const QString &connecting_text, const QString &forget_text, Q hlayout->addWidget(iconLabel = new QLabel(), 0, Qt::AlignRight); hlayout->addWidget(strengthLabel = new QLabel(), 0, Qt::AlignRight); + iconLabel->setFixedWidth(ICON_WIDTH); QObject::connect(forgetBtn, &QPushButton::clicked, [this]() { emit forgotNetwork(network); }); QObject::connect(ssidLabel, &ElidedLabel::clicked, [this]() { if (network.connected == ConnectedType::DISCONNECTED) emit connectToNetwork(network); diff --git a/selfdrive/ui/qt/offroad/networking.h b/selfdrive/ui/qt/network/networking.h similarity index 96% rename from selfdrive/ui/qt/offroad/networking.h rename to selfdrive/ui/qt/network/networking.h index cdcdf067ab..4ff7380f42 100644 --- a/selfdrive/ui/qt/offroad/networking.h +++ b/selfdrive/ui/qt/network/networking.h @@ -1,6 +1,8 @@ #pragma once -#include "selfdrive/ui/qt/offroad/wifiManager.h" +#include + +#include "selfdrive/ui/qt/network/wifi_manager.h" #include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/ssh_keys.h" #include "selfdrive/ui/qt/widgets/toggle.h" diff --git a/selfdrive/ui/qt/offroad/networkmanager.h b/selfdrive/ui/qt/network/networkmanager.h similarity index 99% rename from selfdrive/ui/qt/offroad/networkmanager.h rename to selfdrive/ui/qt/network/networkmanager.h index 31b33fc9f5..2896b0fff7 100644 --- a/selfdrive/ui/qt/offroad/networkmanager.h +++ b/selfdrive/ui/qt/network/networkmanager.h @@ -1,3 +1,5 @@ +#pragma once + /** * We are using a NetworkManager DBUS API : https://developer.gnome.org/NetworkManager/1.26/spec.html * */ diff --git a/selfdrive/ui/qt/offroad/wifiManager.cc b/selfdrive/ui/qt/network/wifi_manager.cc similarity index 96% rename from selfdrive/ui/qt/offroad/wifiManager.cc rename to selfdrive/ui/qt/network/wifi_manager.cc index 391cc9fb50..03c6896f7a 100644 --- a/selfdrive/ui/qt/offroad/wifiManager.cc +++ b/selfdrive/ui/qt/network/wifi_manager.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/wifiManager.h" +#include "selfdrive/ui/qt/network/wifi_manager.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/prime.h" @@ -37,6 +37,10 @@ QDBusPendingCall asyncCall(const QString &path, const QString &interface, const return nm.asyncCall(method, args...); } +bool emptyPath(const QString &path) { + return path == "" || path == "/"; +} + WifiManager::WifiManager(QObject *parent) : QObject(parent) { qDBusRegisterMetaType(); qDBusRegisterMetaType(); @@ -162,8 +166,7 @@ SecurityType WifiManager::getSecurityType(const QVariantMap &properties) { } void WifiManager::connect(const Network &n, const QString &password, const QString &username) { - connecting_to_network = n.ssid; - seenNetworks[n.ssid].connected = ConnectedType::CONNECTING; + setCurrentConnecting(n.ssid); forgetConnection(n.ssid); // Clear all connections that may already exist to the network we are connecting Connection connection; connection["connection"]["type"] = "802-11-wireless"; @@ -190,7 +193,7 @@ void WifiManager::connect(const Network &n, const QString &password, const QStri void WifiManager::deactivateConnectionBySsid(const QString &ssid) { for (QDBusObjectPath active_connection : getActiveConnections()) { auto pth = call(active_connection.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "SpecificObject"); - if (pth.path() != "" && pth.path() != "/") { + if (!emptyPath(pth.path())) { QString Ssid = get_property(pth.path(), "Ssid"); if (Ssid == ssid) { deactivateConnection(active_connection); @@ -228,6 +231,14 @@ void WifiManager::forgetConnection(const QString &ssid) { } } +void WifiManager::setCurrentConnecting(const QString &ssid) { + connecting_to_network = ssid; + for (auto &network : seenNetworks) { + network.connected = (network.ssid == ssid) ? ConnectedType::CONNECTING : ConnectedType::DISCONNECTED; + } + emit refreshSignal(); +} + uint WifiManager::getAdapterType(const QDBusObjectPath &path) { return call(path.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_DEVICE, "DeviceType"); } @@ -273,7 +284,7 @@ void WifiManager::propertyChange(const QString &interface, const QVariantMap &pr } void WifiManager::deviceAdded(const QDBusObjectPath &path) { - if (getAdapterType(path) == NM_DEVICE_TYPE_WIFI && (adapter.isEmpty() || adapter == "/")) { + if (getAdapterType(path) == NM_DEVICE_TYPE_WIFI && emptyPath(adapter)) { adapter = path.path(); setup(); } @@ -316,7 +327,7 @@ void WifiManager::initConnections() { std::optional WifiManager::activateWifiConnection(const QString &ssid) { const QDBusObjectPath &path = getConnectionPath(ssid); if (!path.path().isEmpty()) { - connecting_to_network = ssid; + setCurrentConnecting(ssid); return asyncCall(NM_DBUS_PATH, NM_DBUS_INTERFACE, "ActivateConnection", QVariant::fromValue(path), QVariant::fromValue(QDBusObjectPath(adapter)), QVariant::fromValue(QDBusObjectPath("/"))); } return std::nullopt; @@ -450,7 +461,7 @@ void WifiManager::setTetheringEnabled(bool enabled) { } bool WifiManager::isTetheringEnabled() { - if (activeAp != "" && activeAp != "/") { + if (!emptyPath(activeAp)) { return get_property(activeAp, "Ssid") == tethering_ssid; } return false; diff --git a/selfdrive/ui/qt/offroad/wifiManager.h b/selfdrive/ui/qt/network/wifi_manager.h similarity index 96% rename from selfdrive/ui/qt/offroad/wifiManager.h rename to selfdrive/ui/qt/network/wifi_manager.h index 244a3cec4a..7debffa452 100644 --- a/selfdrive/ui/qt/offroad/wifiManager.h +++ b/selfdrive/ui/qt/network/wifi_manager.h @@ -4,7 +4,7 @@ #include #include -#include "selfdrive/ui/qt/offroad/networkmanager.h" +#include "selfdrive/ui/qt/network/networkmanager.h" enum class SecurityType { OPEN, @@ -85,6 +85,7 @@ private: void refreshNetworks(); void activateModemConnection(const QDBusObjectPath &path); void addTetheringConnection(); + void setCurrentConnecting(const QString &ssid); signals: void wrongPassword(const QString &ssid); diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index cc715c6755..693a0253b4 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -1,5 +1,6 @@ #include "selfdrive/ui/qt/offroad/driverview.h" +#include #include #include "selfdrive/ui/qt/qt_window.h" @@ -19,14 +20,22 @@ DriverViewWindow::DriverViewWindow(QWidget* parent) : QWidget(parent) { connect(cameraView, &CameraWidget::vipcThreadFrameReceived, scene, &DriverViewScene::frameUpdated); layout->addWidget(scene); layout->setCurrentWidget(scene); + + QObject::connect(device(), &Device::interactiveTimeout, this, &DriverViewWindow::closeView); +} + +void DriverViewWindow::closeView() { + if (isVisible()) { + cameraView->stopVipcThread(); + emit done(); + } } void DriverViewWindow::mouseReleaseEvent(QMouseEvent* e) { - cameraView->stopVipcThread(); - emit done(); + closeView(); } -DriverViewScene::DriverViewScene(QWidget* parent) : sm({"driverStateV2"}), QWidget(parent) { +DriverViewScene::DriverViewScene(QWidget* parent) : QWidget(parent) { face_img = loadPixmap("../assets/img_driver_face_static.png", {FACE_IMG_SIZE, FACE_IMG_SIZE}); } @@ -38,12 +47,10 @@ void DriverViewScene::showEvent(QShowEvent* event) { void DriverViewScene::hideEvent(QHideEvent* event) { params.putBool("IsDriverViewEnabled", false); - device()->resetInteractiveTimeout(); } void DriverViewScene::frameUpdated() { frame_updated = true; - sm.update(0); update(); } @@ -59,6 +66,7 @@ void DriverViewScene::paintEvent(QPaintEvent* event) { return; } + const auto &sm = *(uiState()->sm); cereal::DriverStateV2::Reader driver_state = sm["driverStateV2"].getDriverStateV2(); cereal::DriverStateV2::DriverData::Reader driver_data; diff --git a/selfdrive/ui/qt/offroad/driverview.h b/selfdrive/ui/qt/offroad/driverview.h index 255857970d..8bfc7a4b7b 100644 --- a/selfdrive/ui/qt/offroad/driverview.h +++ b/selfdrive/ui/qt/offroad/driverview.h @@ -1,10 +1,7 @@ #pragma once -#include - #include -#include "common/util.h" #include "selfdrive/ui/qt/widgets/cameraview.h" class DriverViewScene : public QWidget { @@ -23,7 +20,6 @@ protected: private: Params params; - SubMaster sm; QPixmap face_img; bool is_rhd = false; bool frame_updated = false; @@ -40,8 +36,8 @@ signals: protected: void mouseReleaseEvent(QMouseEvent* e) override; + void closeView(); -private: CameraWidget *cameraView; DriverViewScene *scene; QStackedLayout *layout; diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index 5bf1b6fc43..c7c22f0ea3 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -1,5 +1,7 @@ #include "selfdrive/ui/qt/offroad/onboarding.h" +#include + #include #include #include diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index 2fdae35de0..a1b6895ba0 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -63,7 +63,7 @@ class TermsPage : public QFrame { Q_OBJECT public: - explicit TermsPage(QWidget *parent = 0) : QFrame(parent) {}; + explicit TermsPage(QWidget *parent = 0) : QFrame(parent) {} public slots: void enableAccept(); @@ -82,7 +82,7 @@ class DeclinePage : public QFrame { Q_OBJECT public: - explicit DeclinePage(QWidget *parent = 0) : QFrame(parent) {}; + explicit DeclinePage(QWidget *parent = 0) : QFrame(parent) {} private: void showEvent(QShowEvent *event) override; diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 94a673dd71..f74e671d29 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -3,10 +3,12 @@ #include #include #include +#include +#include #include -#include "selfdrive/ui/qt/offroad/networking.h" +#include "selfdrive/ui/qt/network/networking.h" #include "common/params.h" #include "common/watchdog.h" @@ -20,7 +22,6 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/widgets/input.h" TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // param, title, desc, icon @@ -36,7 +37,8 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("openpilot Longitudinal Control (Alpha)"), QString("%1

%2") .arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) - .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")), + .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " + "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")), "../assets/offroad/icon_speed_limit.png", }, { @@ -88,7 +90,8 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { std::vector longi_button_texts{tr("Aggressive"), tr("Standard"), tr("Relaxed")}; long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), - tr("Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars."), + tr("Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " + "In relaxed mode openpilot will stay further away from lead cars."), "../assets/offroad/icon_speed_limit.png", longi_button_texts); for (auto &[param, title, desc, icon] : toggle_defs) { @@ -135,13 +138,14 @@ void TogglesPanel::updateToggles() { "

%6


" "%7") .arg(tr("openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. Experimental features are listed below:")) - .arg(tr("End-to-End Longitudinal Control" )) + .arg(tr("End-to-End Longitudinal Control")) .arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " - "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected.")) + "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " + "mistakes should be expected.")) .arg(tr("Navigate on openpilot")) - .arg(tr("When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. " - "Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. " - "These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc.")) + .arg(tr("When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right " + "appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around " + "exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc.")) .arg(tr("New Driving Visualization")) .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. " "When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green.")); @@ -345,10 +349,6 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget); sidebar_layout->setMargin(0); panel_widget = new QStackedWidget(); - panel_widget->setStyleSheet(R"( - border-radius: 30px; - background-color: #292929; - )"); // close button QPushButton *close_btn = new QPushButton(tr("×")); @@ -437,5 +437,9 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { SettingsWindow { background-color: black; } + QStackedWidget, ScrollView { + background-color: #292929; + border-radius: 30px; + } )"); } diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index edba5be800..a5dd25b14f 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include #include #include diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 7c070d2adf..bb97ebd5e8 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -1,6 +1,9 @@ #include "selfdrive/ui/qt/onroad.h" +#include #include +#include +#include #include #include @@ -60,6 +63,7 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition); + QObject::connect(uiState(), &UIState::primeChanged, this, &OnroadWindow::primeChanged); } void OnroadWindow::updateState(const UIState &s) { @@ -102,7 +106,7 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) { void OnroadWindow::offroadTransition(bool offroad) { #ifdef ENABLE_MAPS if (!offroad) { - if (map == nullptr && (uiState()->primeType() || !MAPBOX_TOKEN.isEmpty())) { + if (map == nullptr && (uiState()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) { auto m = new MapPanel(get_mapbox_settings()); map = m; @@ -122,6 +126,17 @@ void OnroadWindow::offroadTransition(bool offroad) { alerts->updateAlert({}); } +void OnroadWindow::primeChanged(bool prime) { +#ifdef ENABLE_MAPS + if (map && (!prime && MAPBOX_TOKEN.isEmpty())) { + nvg->map_settings_btn->setEnabled(false); + nvg->map_settings_btn->setVisible(false); + map->deleteLater(); + map = nullptr; + } +#endif +} + void OnroadWindow::paintEvent(QPaintEvent *event) { QPainter p(this); p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255)); @@ -198,7 +213,6 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) { setFixedSize(btn_size, btn_size); - params = Params(); engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size, img_size}); QObject::connect(this, &QPushButton::clicked, this, &ExperimentalButton::changeMode); @@ -268,53 +282,42 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { const bool cs_alive = sm.alive("controlsState"); const bool nav_alive = sm.alive("navInstruction") && sm["navInstruction"].getValid(); - const auto cs = sm["controlsState"].getControlsState(); const auto car_state = sm["carState"].getCarState(); const auto nav_instruction = sm["navInstruction"].getNavInstruction(); // Handle older routes where vCruiseCluster is not set float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster(); - float set_speed = cs_alive ? v_cruise : SET_SPEED_NA; - bool cruise_set = set_speed > 0 && (int)set_speed != SET_SPEED_NA; - if (cruise_set && !s.scene.is_metric) { - set_speed *= KM_TO_MILE; + setSpeed = cs_alive ? v_cruise : SET_SPEED_NA; + is_cruise_set = setSpeed > 0 && (int)setSpeed != SET_SPEED_NA; + if (is_cruise_set && !s.scene.is_metric) { + setSpeed *= KM_TO_MILE; } // Handle older routes where vEgoCluster is not set - float v_ego; - if (car_state.getVEgoCluster() == 0.0 && !v_ego_cluster_seen) { - v_ego = car_state.getVEgo(); - } else { - v_ego = car_state.getVEgoCluster(); - v_ego_cluster_seen = true; - } - float cur_speed = cs_alive ? std::max(0.0, v_ego) : 0.0; - cur_speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; + v_ego_cluster_seen = v_ego_cluster_seen || car_state.getVEgoCluster() != 0.0; + float v_ego = v_ego_cluster_seen ? car_state.getVEgoCluster() : car_state.getVEgo(); + speed = cs_alive ? std::max(0.0, v_ego) : 0.0; + speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; auto speed_limit_sign = nav_instruction.getSpeedLimitSign(); - float speed_limit = nav_alive ? nav_instruction.getSpeedLimit() : 0.0; - speed_limit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - - setProperty("speedLimit", speed_limit); - setProperty("has_us_speed_limit", nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD); - setProperty("has_eu_speed_limit", nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA); + speedLimit = nav_alive ? nav_instruction.getSpeedLimit() : 0.0; + speedLimit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - setProperty("is_cruise_set", cruise_set); - setProperty("is_metric", s.scene.is_metric); - setProperty("speed", cur_speed); - setProperty("setSpeed", set_speed); - setProperty("speedUnit", s.scene.is_metric ? tr("km/h") : tr("mph")); - setProperty("hideBottomIcons", (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE)); - setProperty("status", s.status); + has_us_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD); + has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA); + is_metric = s.scene.is_metric; + speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph"); + hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); + status = s.status; // update engageability/experimental mode button experimental_btn->updateState(s); // update DM icon auto dm_state = sm["driverMonitoringState"].getDriverMonitoringState(); - setProperty("dmActive", dm_state.getIsActiveMode()); - setProperty("rightHandDM", dm_state.getIsRHD()); + dmActive = dm_state.getIsActiveMode(); + rightHandDM = dm_state.getIsRHD(); // DM icon transition dm_fade_state = std::clamp(dm_fade_state+0.2*(0.5-dmActive), 0.0, 1.0); diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 778322ed08..b3ba411453 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -18,7 +20,7 @@ class OnroadAlerts : public QWidget { Q_OBJECT public: - OnroadAlerts(QWidget *parent = 0) : QWidget(parent) {}; + OnroadAlerts(QWidget *parent = 0) : QWidget(parent) {} void updateAlert(const Alert &a); protected: @@ -63,19 +65,6 @@ private: // container window for the NVG UI class AnnotatedCameraWidget : public CameraWidget { Q_OBJECT - Q_PROPERTY(float speed MEMBER speed); - Q_PROPERTY(QString speedUnit MEMBER speedUnit); - Q_PROPERTY(float setSpeed MEMBER setSpeed); - Q_PROPERTY(float speedLimit MEMBER speedLimit); - Q_PROPERTY(bool is_cruise_set MEMBER is_cruise_set); - Q_PROPERTY(bool has_eu_speed_limit MEMBER has_eu_speed_limit); - Q_PROPERTY(bool has_us_speed_limit MEMBER has_us_speed_limit); - Q_PROPERTY(bool is_metric MEMBER is_metric); - - Q_PROPERTY(bool dmActive MEMBER dmActive); - Q_PROPERTY(bool hideBottomIcons MEMBER hideBottomIcons); - Q_PROPERTY(bool rightHandDM MEMBER rightHandDM); - Q_PROPERTY(int status MEMBER status); public: explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0); @@ -148,5 +137,6 @@ private: private slots: void offroadTransition(bool offroad); + void primeChanged(bool prime); void updateState(const UIState &s); }; diff --git a/selfdrive/ui/qt/python_helpers.py b/selfdrive/ui/qt/python_helpers.py index 905d41a634..88c36290d9 100644 --- a/selfdrive/ui/qt/python_helpers.py +++ b/selfdrive/ui/qt/python_helpers.py @@ -1,10 +1,10 @@ import os from cffi import FFI -import sip # pylint: disable=import-error +import sip -from common.ffi_wrapper import suffix -from common.basedir import BASEDIR +from openpilot.common.ffi_wrapper import suffix +from openpilot.common.basedir import BASEDIR def get_ffi(): diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index de5021c8bc..3b3666b19a 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -14,7 +15,8 @@ #include "system/hardware/hw.h" #include "selfdrive/ui/qt/api.h" #include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/offroad/networking.h" +#include "selfdrive/ui/qt/network/networking.h" +#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/input.h" const std::string USER_AGENT = "AGNOSSetup-"; @@ -306,6 +308,10 @@ void Setup::nextPage() { } Setup::Setup(QWidget *parent) : QStackedWidget(parent) { + if (std::getenv("MULTILANG")) { + selectLanguage(); + } + std::stringstream buffer; buffer << std::ifstream("/sys/class/hwmon/hwmon1/in1_input").rdbuf(); float voltage = (float)std::atoi(buffer.str().c_str()) / 1000.; @@ -368,6 +374,18 @@ Setup::Setup(QWidget *parent) : QStackedWidget(parent) { )"); } +void Setup::selectLanguage() { + QMap langs = getSupportedLanguages(); + QString selection = MultiOptionDialog::getSelection(tr("Select a language"), langs.keys(), "", this); + if (!selection.isEmpty()) { + QString selectedLang = langs[selection]; + Params().put("LanguageSetting", selectedLang.toStdString()); + if (translator.load(":/" + selectedLang)) { + qApp->installTranslator(&translator); + } + } +} + int main(int argc, char *argv[]) { QApplication a(argc, argv); Setup setup; diff --git a/selfdrive/ui/qt/setup/setup.h b/selfdrive/ui/qt/setup/setup.h index bf5d97070d..8c33acc380 100644 --- a/selfdrive/ui/qt/setup/setup.h +++ b/selfdrive/ui/qt/setup/setup.h @@ -3,6 +3,7 @@ #include #include #include +#include #include class Setup : public QStackedWidget { @@ -12,6 +13,7 @@ public: explicit Setup(QWidget *parent = 0); private: + void selectLanguage(); QWidget *low_voltage(); QWidget *getting_started(); QWidget *network_setup(); @@ -20,6 +22,7 @@ private: QWidget *failed_widget; QWidget *downloading_widget; + QTranslator translator; signals: void finished(const QString &url, const QString &error = ""); diff --git a/selfdrive/ui/qt/setup/updater.cc b/selfdrive/ui/qt/setup/updater.cc index ae5f26c77e..ed47590aa3 100644 --- a/selfdrive/ui/qt/setup/updater.cc +++ b/selfdrive/ui/qt/setup/updater.cc @@ -6,7 +6,7 @@ #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/setup/updater.h" -#include "selfdrive/ui/qt/offroad/networking.h" +#include "selfdrive/ui/qt/network/networking.h" Updater::Updater(const QString &updater_path, const QString &manifest_path, QWidget *parent) : updater(updater_path), manifest(manifest_path), QStackedWidget(parent) { diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index fb96e1d540..f627aac810 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include diff --git a/selfdrive/ui/qt/spinner.cc b/selfdrive/ui/qt/spinner.cc index 8f13576fb2..2404efa668 100644 --- a/selfdrive/ui/qt/spinner.cc +++ b/selfdrive/ui/qt/spinner.cc @@ -1,5 +1,6 @@ #include "selfdrive/ui/qt/spinner.h" +#include #include #include #include @@ -93,7 +94,7 @@ Spinner::Spinner(QWidget *parent) : QWidget(parent) { notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read); QObject::connect(notifier, &QSocketNotifier::activated, this, &Spinner::update); -}; +} void Spinner::update(int n) { std::string line; diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index 3804aacd78..78da183503 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -1,5 +1,9 @@ #include "selfdrive/ui/qt/util.h" +#include +#include +#include + #include #include #include @@ -39,7 +43,7 @@ std::optional getDongleId() { } QMap getSupportedLanguages() { - QFile f("translations/languages.json"); + QFile f(":/languages.json"); f.open(QIODevice::ReadOnly | QIODevice::Text); QString val = f.readAll(); @@ -130,7 +134,7 @@ void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, co } -QWidget* topWidget (QWidget* widget) { +QWidget* topWidget(QWidget* widget) { while (widget->parentWidget() != nullptr) widget=widget->parentWidget(); return widget; } @@ -193,8 +197,7 @@ QColor interpColor(float xv, std::vector xp, std::vector fp) { (xv - xp[low]) * (fp[hi].red() - fp[low].red()) / (xp[hi] - xp[low]) + fp[low].red(), (xv - xp[low]) * (fp[hi].green() - fp[low].green()) / (xp[hi] - xp[low]) + fp[low].green(), (xv - xp[low]) * (fp[hi].blue() - fp[low].blue()) / (xp[hi] - xp[low]) + fp[low].blue(), - (xv - xp[low]) * (fp[hi].alpha() - fp[low].alpha()) / (xp[hi] - xp[low]) + fp[low].alpha() - ); + (xv - xp[low]) * (fp[hi].alpha() - fp[low].alpha()) / (xp[hi] - xp[low]) + fp[low].alpha()); } } diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h index 2b1200a861..2aae97b860 100644 --- a/selfdrive/ui/qt/util.h +++ b/selfdrive/ui/qt/util.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -22,7 +23,7 @@ void sigTermHandler(int s); QString timeAgo(const QDateTime &date); void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); void initApp(int argc, char *argv[], bool disable_hidpi = true); -QWidget* topWidget (QWidget* widget); +QWidget* topWidget(QWidget* widget); QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); QPixmap bootstrapPixmap(const QString &id); diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 4da5b7b18e..ab1bafb883 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -7,6 +7,9 @@ #endif #include +#include +#include +#include #include #include @@ -38,6 +41,8 @@ const char frame_fragment_shader[] = "out vec4 colorOut;\n" "void main() {\n" " colorOut = texture(uTexture, vTexCoord);\n" + // gamma to improve worst case visibility when dark + " colorOut.rgb = pow(colorOut.rgb, vec3(1.0/1.28));\n" "}\n"; #else const char frame_fragment_shader[] = @@ -202,7 +207,9 @@ void CameraWidget::updateFrameMat() { if (zoomed_view) { if (active_stream_type == VISION_STREAM_DRIVER) { - frame_mat = get_driver_view_transform(w, h, stream_width, stream_height); + if (stream_width > 0 && stream_height > 0) { + frame_mat = get_driver_view_transform(w, h, stream_width, stream_height); + } } else { // Project point at "infinity" to compute x and y offsets // to ensure this ends up in the middle of the screen diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 67568ea55c..fcd5b1b18f 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -1,7 +1,12 @@ #pragma once +#include +#include #include #include +#include +#include +#include #include #include @@ -60,7 +65,7 @@ protected: bool zoomed_view; GLuint frame_vao, frame_vbo, frame_ibo; GLuint textures[2]; - mat4 frame_mat; + mat4 frame_mat = {}; std::unique_ptr program; QColor bg = QColor("#000000"); diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index e440bc6441..87304a4585 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -23,7 +23,7 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons // title title_label = new QPushButton(title); title_label->setFixedHeight(120); - title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left"); + title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; border: none;"); hlayout->addWidget(title_label, 1); // value next to control button diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index fac66de9ed..811595726d 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include #include #include diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index 49fbdff222..52d0c5cd6e 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -9,7 +9,7 @@ #include "selfdrive/ui/qt/widgets/scrollview.h" -QDialogBase::QDialogBase(QWidget *parent) : QDialog(parent) { +DialogBase::DialogBase(QWidget *parent) : QDialog(parent) { Q_ASSERT(parent != nullptr); parent->installEventFilter(this); @@ -19,7 +19,7 @@ QDialogBase::QDialogBase(QWidget *parent) : QDialog(parent) { color: white; font-family: Inter; } - QDialogBase { + DialogBase { background-color: black; } QPushButton { @@ -36,19 +36,19 @@ QDialogBase::QDialogBase(QWidget *parent) : QDialog(parent) { )"); } -bool QDialogBase::eventFilter(QObject *o, QEvent *e) { +bool DialogBase::eventFilter(QObject *o, QEvent *e) { if (o == parent() && e->type() == QEvent::Hide) { reject(); } return QDialog::eventFilter(o, e); } -int QDialogBase::exec() { +int DialogBase::exec() { setMainWindow(this); return QDialog::exec(); } -InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &subtitle, bool secret) : QDialogBase(parent) { +InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &subtitle, bool secret) : DialogBase(parent) { main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(50, 55, 50, 50); main_layout->setSpacing(0); @@ -188,7 +188,7 @@ void InputDialog::setMinLength(int length) { // ConfirmationDialog ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, const QString &cancel_text, - const bool rich, QWidget *parent) : QDialogBase(parent) { + const bool rich, QWidget *parent) : DialogBase(parent) { QFrame *container = new QFrame(this); container->setStyleSheet(R"( QFrame { background-color: #1B1B1B; color: #C9C9C9; } @@ -245,7 +245,7 @@ bool ConfirmationDialog::rich(const QString &prompt_text, QWidget *parent) { // MultiOptionDialog -MultiOptionDialog::MultiOptionDialog(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent) : QDialogBase(parent) { +MultiOptionDialog::MultiOptionDialog(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent) : DialogBase(parent) { QFrame *container = new QFrame(this); container->setStyleSheet(R"( QFrame { background-color: #1B1B1B; } diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h index e6c0fba86d..917ea21b57 100644 --- a/selfdrive/ui/qt/widgets/input.h +++ b/selfdrive/ui/qt/widgets/input.h @@ -10,18 +10,18 @@ #include "selfdrive/ui/qt/widgets/keyboard.h" -class QDialogBase : public QDialog { +class DialogBase : public QDialog { Q_OBJECT protected: - QDialogBase(QWidget *parent); + DialogBase(QWidget *parent); bool eventFilter(QObject *o, QEvent *e) override; public slots: int exec() override; }; -class InputDialog : public QDialogBase { +class InputDialog : public DialogBase { Q_OBJECT public: @@ -50,7 +50,7 @@ signals: void emitText(const QString &text); }; -class ConfirmationDialog : public QDialogBase { +class ConfirmationDialog : public DialogBase { Q_OBJECT public: @@ -61,7 +61,7 @@ public: static bool rich(const QString &prompt_text, QWidget *parent); }; -class MultiOptionDialog : public QDialogBase { +class MultiOptionDialog : public DialogBase { Q_OBJECT public: diff --git a/selfdrive/ui/qt/widgets/keyboard.cc b/selfdrive/ui/qt/widgets/keyboard.cc index 162d27db02..370e9a53cc 100644 --- a/selfdrive/ui/qt/widgets/keyboard.cc +++ b/selfdrive/ui/qt/widgets/keyboard.cc @@ -104,37 +104,37 @@ Keyboard::Keyboard(QWidget *parent) : QFrame(parent) { // lowercase std::vector> lowercase = { - {"q","w","e","r","t","y","u","i","o","p"}, - {"a","s","d","f","g","h","j","k","l"}, - {"↑","z","x","c","v","b","n","m",BACKSPACE_KEY}, - {"123"," ",".",ENTER_KEY}, + {"q", "w", "e", "r", "t", "y", "u", "i", "o", "p"}, + {"a", "s", "d", "f", "g", "h", "j", "k", "l"}, + {"↑", "z", "x", "c", "v", "b", "n", "m", BACKSPACE_KEY}, + {"123", " ", ".", ENTER_KEY}, }; main_layout->addWidget(new KeyboardLayout(this, lowercase)); // uppercase std::vector> uppercase = { - {"Q","W","E","R","T","Y","U","I","O","P"}, - {"A","S","D","F","G","H","J","K","L"}, - {"↓","Z","X","C","V","B","N","M",BACKSPACE_KEY}, - {"123"," ",".",ENTER_KEY}, + {"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"}, + {"A", "S", "D", "F", "G", "H", "J", "K", "L"}, + {"↓", "Z", "X", "C", "V", "B", "N", "M", BACKSPACE_KEY}, + {"123", " ", ".", ENTER_KEY}, }; main_layout->addWidget(new KeyboardLayout(this, uppercase)); // numbers + specials std::vector> numbers = { - {"1","2","3","4","5","6","7","8","9","0"}, - {"-","/",":",";","(",")","$","&&","@","\""}, - {"#+=",".",",","?","!","`",BACKSPACE_KEY}, - {"ABC"," ",".",ENTER_KEY}, + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}, + {"-", "/", ":", ";", "(", ")", "$", "&&", "@", "\""}, + {"#+=", ".", ",", "?", "!", "`", BACKSPACE_KEY}, + {"ABC", " ", ".", ENTER_KEY}, }; main_layout->addWidget(new KeyboardLayout(this, numbers)); // extra specials std::vector> specials = { - {"[","]","{","}","#","%","^","*","+","="}, - {"_","\\","|","~","<",">","€","£","¥","•"}, - {"123",".",",","?","!","'",BACKSPACE_KEY}, - {"ABC"," ",".",ENTER_KEY}, + {"[", "]", "{", "}", "#", "%", "^", "*", "+", "="}, + {"_", "\\", "|", "~", "<", ">", "€", "£", "¥", "•"}, + {"123", ".", ",", "?", "!", "'", BACKSPACE_KEY}, + {"ABC", " ", ".", ENTER_KEY}, }; main_layout->addWidget(new KeyboardLayout(this, specials)); diff --git a/selfdrive/ui/qt/widgets/keyboard.h b/selfdrive/ui/qt/widgets/keyboard.h index 516105719b..efc02d075d 100644 --- a/selfdrive/ui/qt/widgets/keyboard.h +++ b/selfdrive/ui/qt/widgets/keyboard.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.cc b/selfdrive/ui/qt/widgets/offroad_alerts.cc index cdfa86c8eb..74ece36d15 100644 --- a/selfdrive/ui/qt/widgets/offroad_alerts.cc +++ b/selfdrive/ui/qt/widgets/offroad_alerts.cc @@ -1,5 +1,10 @@ #include "selfdrive/ui/qt/widgets/offroad_alerts.h" +#include +#include +#include +#include + #include #include #include diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.h b/selfdrive/ui/qt/widgets/offroad_alerts.h index 69c12b0602..ace2e75456 100644 --- a/selfdrive/ui/qt/widgets/offroad_alerts.h +++ b/selfdrive/ui/qt/widgets/offroad_alerts.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc index 782b7cb5ce..324d6cf6ae 100644 --- a/selfdrive/ui/qt/widgets/prime.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -68,7 +68,7 @@ void PairingQRWidget::paintEvent(QPaintEvent *e) { } -PairingPopup::PairingPopup(QWidget *parent) : QDialogBase(parent) { +PairingPopup::PairingPopup(QWidget *parent) : DialogBase(parent) { QHBoxLayout *hlayout = new QHBoxLayout(this); hlayout->setContentsMargins(0, 0, 0, 0); hlayout->setSpacing(0); @@ -117,28 +117,19 @@ PairingPopup::PairingPopup(QWidget *parent) : QDialogBase(parent) { } -PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QFrame(parent) { +PrimeUserWidget::PrimeUserWidget(QWidget *parent) : QFrame(parent) { + setObjectName("primeWidget"); QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->setSpacing(30); - - // subscribed prime layout - QWidget *primeWidget = new QWidget; - primeWidget->setObjectName("primeWidget"); - QVBoxLayout *primeLayout = new QVBoxLayout(primeWidget); - primeLayout->setContentsMargins(56, 40, 56, 40); - primeLayout->setSpacing(20); + mainLayout->setContentsMargins(56, 40, 56, 40); + mainLayout->setSpacing(20); QLabel *subscribed = new QLabel(tr("✓ SUBSCRIBED")); subscribed->setStyleSheet("font-size: 41px; font-weight: bold; color: #86FF4E;"); - primeLayout->addWidget(subscribed); + mainLayout->addWidget(subscribed); QLabel *commaPrime = new QLabel(tr("comma prime")); commaPrime->setStyleSheet("font-size: 75px; font-weight: bold;"); - primeLayout->addWidget(commaPrime); - - mainLayout->addWidget(primeWidget); - mainLayout->addStretch(); + mainLayout->addWidget(commaPrime); } @@ -165,7 +156,7 @@ PrimeAdWidget::PrimeAdWidget(QWidget* parent) : QFrame(parent) { main_layout->addSpacing(30); QVector bullets = {tr("Remote access"), tr("24/7 LTE connectivity"), tr("1 year of drive storage"), tr("Turn-by-turn navigation")}; - for (auto &b: bullets) { + for (auto &b : bullets) { const QString check = " "; QLabel *l = new QLabel(check + b); l->setAlignment(Qt::AlignLeft); @@ -278,7 +269,7 @@ void SetupWidget::replyFinished(const QString &response, bool success) { } QJsonObject json = doc.object(); - int prime_type = json["prime_type"].toInt(); + PrimeType prime_type = static_cast(json["prime_type"].toInt()); uiState()->setPrimeType(prime_type); if (!json["is_paired"].toBool()) { diff --git a/selfdrive/ui/qt/widgets/prime.h b/selfdrive/ui/qt/widgets/prime.h index b41bab1695..63341c4cea 100644 --- a/selfdrive/ui/qt/widgets/prime.h +++ b/selfdrive/ui/qt/widgets/prime.h @@ -7,15 +7,6 @@ #include "selfdrive/ui/qt/widgets/input.h" -enum PrimeType { - NONE = 0, - MAGENTA = 1, - LITE = 2, - BLUE = 3, - MAGENTA_NEW = 4, -}; - - // pairing QR code class PairingQRWidget : public QWidget { Q_OBJECT @@ -37,7 +28,7 @@ private slots: // pairing popup widget -class PairingPopup : public QDialogBase { +class PairingPopup : public DialogBase { Q_OBJECT public: diff --git a/selfdrive/ui/qt/widgets/scrollview.cc b/selfdrive/ui/qt/widgets/scrollview.cc index 5536593016..978bf83a63 100644 --- a/selfdrive/ui/qt/widgets/scrollview.cc +++ b/selfdrive/ui/qt/widgets/scrollview.cc @@ -10,7 +10,7 @@ ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent) { setWidgetResizable(true); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setStyleSheet("background-color: transparent;"); + setStyleSheet("background-color: transparent; border:none"); QString style = R"( QScrollBar:vertical { diff --git a/selfdrive/ui/qt/widgets/ssh_keys.cc b/selfdrive/ui/qt/widgets/ssh_keys.cc index 1097a89268..26743952de 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.cc +++ b/selfdrive/ui/qt/widgets/ssh_keys.cc @@ -4,7 +4,10 @@ #include "selfdrive/ui/qt/api.h" #include "selfdrive/ui/qt/widgets/input.h" -SshControl::SshControl() : ButtonControl(tr("SSH Keys"), "", tr("Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username.")) { +SshControl::SshControl() : + ButtonControl(tr("SSH Keys"), "", tr("Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username " + "other than your own. A comma employee will NEVER ask you to add their GitHub username.")) { + QObject::connect(this, &ButtonControl::clicked, [=]() { if (text() == tr("ADD")) { QString username = InputDialog::getText(tr("Enter your GitHub username"), this); diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc index 8d5b2b96b0..a5884f113f 100644 --- a/selfdrive/ui/soundd/sound.cc +++ b/selfdrive/ui/soundd/sound.cc @@ -15,19 +15,20 @@ Sound::Sound(QObject *parent) : sm({"controlsState", "microphone"}) { qInfo() << "default audio device: " << QAudioDeviceInfo::defaultOutputDevice().deviceName(); - for (auto &[alert, fn, loops] : sound_list) { + for (auto &[alert, fn, loops, volume] : sound_list) { QSoundEffect *s = new QSoundEffect(this); QObject::connect(s, &QSoundEffect::statusChanged, [=]() { assert(s->status() != QSoundEffect::Error); }); s->setSource(QUrl::fromLocalFile("../../assets/sounds/" + fn)); + s->setVolume(volume); sounds[alert] = {s, loops}; } QTimer *timer = new QTimer(this); QObject::connect(timer, &QTimer::timeout, this, &Sound::update); timer->start(1000 / UI_FREQ); -}; +} void Sound::update() { sm.update(0); @@ -36,7 +37,10 @@ void Sound::update() { if (sm.updated("microphone")) { float volume = util::map_val(sm["microphone"].getMicrophone().getFilteredSoundPressureWeightedDb(), 30.f, 60.f, 0.f, 1.f); volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale); - Hardware::set_volume(volume); + // set volume on changes + if (std::exchange(current_volume, std::nearbyint(volume * 10)) != current_volume) { + Hardware::set_volume(volume); + } } setAlert(Alert::get(sm, 0)); diff --git a/selfdrive/ui/soundd/sound.h b/selfdrive/ui/soundd/sound.h index da9814c543..4fcb2e1bce 100644 --- a/selfdrive/ui/soundd/sound.h +++ b/selfdrive/ui/soundd/sound.h @@ -1,3 +1,7 @@ +#pragma once + +#include + #include #include #include @@ -5,18 +9,21 @@ #include "system/hardware/hw.h" #include "selfdrive/ui/ui.h" -const std::tuple sound_list[] = { + +const float MAX_VOLUME = 1.0; + +const std::tuple sound_list[] = { // AudibleAlert, file name, loop count - {AudibleAlert::ENGAGE, "engage.wav", 0}, - {AudibleAlert::DISENGAGE, "disengage.wav", 0}, - {AudibleAlert::REFUSE, "refuse.wav", 0}, + {AudibleAlert::ENGAGE, "engage.wav", 0, MAX_VOLUME}, + {AudibleAlert::DISENGAGE, "disengage.wav", 0, MAX_VOLUME}, + {AudibleAlert::REFUSE, "refuse.wav", 0, MAX_VOLUME}, - {AudibleAlert::PROMPT, "prompt.wav", 0}, - {AudibleAlert::PROMPT_REPEAT, "prompt.wav", QSoundEffect::Infinite}, - {AudibleAlert::PROMPT_DISTRACTED, "prompt_distracted.wav", QSoundEffect::Infinite}, + {AudibleAlert::PROMPT, "prompt.wav", 0, MAX_VOLUME}, + {AudibleAlert::PROMPT_REPEAT, "prompt.wav", QSoundEffect::Infinite, MAX_VOLUME}, + {AudibleAlert::PROMPT_DISTRACTED, "prompt_distracted.wav", QSoundEffect::Infinite, MAX_VOLUME}, - {AudibleAlert::WARNING_SOFT, "warning_soft.wav", QSoundEffect::Infinite}, - {AudibleAlert::WARNING_IMMEDIATE, "warning_immediate.wav", QSoundEffect::Infinite}, + {AudibleAlert::WARNING_SOFT, "warning_soft.wav", QSoundEffect::Infinite, MAX_VOLUME}, + {AudibleAlert::WARNING_IMMEDIATE, "warning_immediate.wav", QSoundEffect::Infinite, MAX_VOLUME}, }; class Sound : public QObject { @@ -30,4 +37,5 @@ protected: SubMaster sm; Alert current_alert = {}; QMap> sounds; + int current_volume = -1; }; diff --git a/selfdrive/ui/tests/cycle_offroad_alerts.py b/selfdrive/ui/tests/cycle_offroad_alerts.py index 8a3d9ec45a..75b19ceb90 100755 --- a/selfdrive/ui/tests/cycle_offroad_alerts.py +++ b/selfdrive/ui/tests/cycle_offroad_alerts.py @@ -4,9 +4,9 @@ import sys import time import json -from common.basedir import BASEDIR -from common.params import Params -from selfdrive.controls.lib.alertmanager import set_offroad_alert +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert if __name__ == "__main__": params = Params() diff --git a/selfdrive/ui/tests/test_sound.cc b/selfdrive/ui/tests/test_sound.cc index 43599f3828..d9cb5c0a7f 100644 --- a/selfdrive/ui/tests/test_sound.cc +++ b/selfdrive/ui/tests/test_sound.cc @@ -31,7 +31,7 @@ void controls_thread(int loop_cnt) { const int DT_CTRL = 10; // ms for (int i = 0; i < loop_cnt; ++i) { - for (auto &[alert, fn, loops] : sound_list) { + for (auto &[alert, fn, loops, volume] : sound_list) { printf("testing %s\n", qPrintable(fn)); for (int j = 0; j < 1000 / DT_CTRL; ++j) { MessageBuilder msg; diff --git a/selfdrive/ui/tests/test_soundd.py b/selfdrive/ui/tests/test_soundd.py index 8cc9215b74..80a261e6d9 100755 --- a/selfdrive/ui/tests/test_soundd.py +++ b/selfdrive/ui/tests/test_soundd.py @@ -5,10 +5,10 @@ import unittest from cereal import log, car import cereal.messaging as messaging -from selfdrive.test.helpers import phone_only, with_processes +from openpilot.selfdrive.test.helpers import phone_only, with_processes # TODO: rewrite for unittest -from common.realtime import DT_CTRL -from system.hardware import HARDWARE +from openpilot.common.realtime import DT_CTRL +from openpilot.system.hardware import HARDWARE AudibleAlert = car.CarControl.HUDControl.AudibleAlert diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 8321e2e4c0..1ff203b97d 100755 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -6,7 +6,7 @@ import shutil import unittest import xml.etree.ElementTree as ET -from selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE, update_translations +from openpilot.selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE, update_translations TMP_TRANSLATIONS_DIR = os.path.join(TRANSLATIONS_DIR, "tmp") UNFINISHED_TRANSLATION_TAG = " Cellular Metered - + قياس الخلوية Prevent large data uploads when on a metered connection - + منع تحميل البيانات الكبيرة عندما تكون على اتصال مقنن @@ -116,6 +116,33 @@ رفض ، قم بإلغاء تثبيت %1 + + DestinationWidget + + Home + بيت + + + Work + عمل + + + No destination set + لم يتم تحديد الوجهة + + + home + بيت + + + work + عمل + + + No %1 location set + لم يتم تعيين موقع %1 + + DevicePanel @@ -238,6 +265,14 @@ Disengage to Power Off فك الارتباط لإيقاف التشغيل + + Reset + إعادة ضبط + + + Review + مراجعة + DriveStats @@ -273,6 +308,17 @@ بدء تشغيل الكاميرا + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + تشغيل الوضع التجريبي + + + CHILL MODE ON + تشغيل وضع التبريد + + InputDialog @@ -297,18 +343,6 @@ Installing... جارٍ التثبيت ... - - Receiving objects: - استقبال الكائنات: - - - Resolving deltas: - حل دلتا: - - - Updating files: - جارٍ تحديث الملفات: - MapETA @@ -324,114 +358,118 @@ hr سع + + + MapSettings - km - كم + NAVIGATION + ملاحة - mi - مل + Manage at connect.comma.ai + إدارة في Connect.comma.ai - MapInstructions + MapWindow + + Map Loading + تحميل الخريطة + - km - كم + Waiting for GPS + في انتظار GPS - m - م + Waiting for route + في انتظار الطريق + + + MultiOptionDialog - mi - مل + Select + اختر - ft - قد + Cancel + إلغاء - MapPanel + Networking - Current Destination - الوجهة الحالية + Advanced + متقدم - CLEAR - مسح + Enter password + أدخل كلمة المرور - Recent Destinations - الوجهات الأخيرة + for "%1" + ل "%1" - Try the Navigation Beta - جرب التنقل التجريبي + Wrong password + كلمة مرور خاطئة + + + OffroadAlert - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - احصل على الاتجاهات خطوة بخطوة معروضة والمزيد باستخدام comma -الاشتراك الرئيسي. اشترك الآن: https://connect.comma.ai + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + درجة حرارة الجهاز مرتفعة جدًا. تبريد النظام قبل البدء. درجة حرارة المكونات الداخلية الحالية: %1 - No home -location set - لم يتم تعيين -موقع المنزل + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + اتصل بالإنترنت فورًا للتحقق من وجود تحديثات. إذا لم تكن متصلاً بالإنترنت، فلن يشارك openpilot في %1 - No work -location set - لم يتم تعيين -موقع العمل + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + اتصل بالإنترنت للتحقق من التحديثات. لن يبدأ تشغيل openpilot تلقائيًا حتى يتصل بالإنترنت للتحقق من وجود تحديثات. - no recent destinations - لا توجد وجهات حديثة + Unable to download updates +%1 + غير قادر على تنزيل التحديثات +%1 - - - MapWindow - Map Loading - تحميل الخريطة + Invalid date and time settings, system won't start. Connect to internet to set time. + إعدادات التاريخ والوقت غير صالحة، ولن يبدأ النظام. الاتصال بالإنترنت لضبط الوقت. - Waiting for GPS - في انتظار GPS + Taking camera snapshots. System won't start until finished. + التقاط لقطات الكاميرا. لن يبدأ النظام حتى ينتهي. - - - MultiOptionDialog - Select - اختر + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + يتم تنزيل تحديث لنظام تشغيل جهازك في الخلفية. سيُطلب منك التحديث عندما يكون جاهزًا للتثبيت. - Cancel - إلغاء + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + فشل الجهاز في التسجيل. لن يتم الاتصال بخوادم comma.ai أو التحميل إليها، ولا يتلقى أي دعم من comma.ai. إذا كان هذا جهازًا رسميًا، فتفضل بزيارة https://comma.ai/support. - - - Networking - Advanced - متقدم + NVMe drive not mounted. + محرك NVMe غير مثبت. - Enter password - أدخل كلمة المرور + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + تم اكتشاف محرك أقراص NVMe غير مدعوم. قد يستهلك الجهاز قدرًا أكبر بكثير من الطاقة ويرتفع درجة حرارته بسبب عدم دعم NVMe. - for "%1" - ل "%1" + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + لم يتمكن openpilot من التعرف على سيارتك. سيارتك إما غير مدعومة أو لم يتم التعرف على وحدات التحكم الإلكترونية الخاصة بها. يرجى تقديم طلب سحب لإضافة إصدارات البرامج الثابتة إلى السيارة المناسبة. تحتاج مساعدة؟ انضم إلى discord.comma.ai. - Wrong password - كلمة مرور خاطئة + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + لم يتمكن openpilot من التعرف على سيارتك. تحقق من سلامة الكابلات وتأكد من أن جميع التوصيلات آمنة، خاصة أن طاقة الفاصلة تم إدخالها بالكامل في منفذ OBD-II في السيارة. تحتاج مساعدة؟ انضم إلى discord.comma.ai. + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + اكتشف openpilot تغييرًا في موضع تركيب الجهاز. تأكد من تثبيت الجهاز بالكامل في الحامل وتثبيته بإحكام على الزجاج الأمامي. @@ -468,6 +506,17 @@ location set ضع إشارة مرجعية على connect.comma.ai على شاشتك الرئيسية لاستخدامه مثل أي تطبيق + + ParamControl + + Enable + تمكين + + + Cancel + إلغاء + + PrimeAdWidget @@ -487,12 +536,16 @@ location set الوصول عن بعد - 1 year of storage - سنة واحدة من التخزين + 24/7 LTE connectivity + اتصال 24/7 LTE - Developer perks - امتيازات المطور + 1 year of drive storage + سنة واحدة من تخزين القرص + + + Turn-by-turn navigation + التنقل خطوة بخطوة @@ -505,14 +558,6 @@ location set comma prime comma prime - - CONNECT.COMMA.AI - CONNECT.COMMA.AI - - - COMMA POINTS - COMMA POINTS - QObject @@ -565,6 +610,22 @@ location set منذ %n ايام + + km + كم + + + m + م + + + mi + مل + + + ft + قدم + Reset @@ -576,18 +637,10 @@ location set Are you sure you want to reset your device? هل أنت متأكد أنك تريد إعادة ضبط جهازك؟ - - Resetting device... - جارٍ إعادة ضبط الجهاز ... - System Reset إعادة تعيين النظام - - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - تم تشغيل إعادة تعيين النظام. اضغط على تأكيد لمسح كل المحتوى والإعدادات. اضغط على إلغاء لاستئناف التمهيد. - Cancel إلغاء @@ -601,15 +654,18 @@ location set تأكيد - Unable to mount data partition. Press confirm to reset your device. - تعذر تحميل قسم البيانات. اضغط على تأكيد لإعادة ضبط جهازك. + Resetting device... +This may take up to a minute. + إعادة تعيين الجهاز... +قد يستغرق هذا ما يصل إلى دقيقة. - - - RichTextDialog - Ok - موافق + Press confirm to erase all content and settings. Press cancel to resume boot. + اضغط على تأكيد لمسح جميع المحتويات والإعدادات. اضغط على إلغاء لاستئناف التمهيد. + + + Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. + غير قادر على تحميل قسم البيانات. قد يكون القسم تالفًا. اضغط على تأكيد لمسح جهازك وإعادة ضبطه. @@ -634,10 +690,6 @@ location set Software برمجة - - Navigation - ملاحة - Setup @@ -681,18 +733,6 @@ location set Waiting for internet في انتظار الاتصال بالإنترنت - - Choose Software to Install - اختر البرنامج المراد تثبيته - - - Dashcam - Dashcam - - - Custom Software - برامج مخصصة - Enter URL إدخال عنوان الموقع @@ -721,6 +761,18 @@ location set Start over ابدأ من جديد + + Something went wrong. Reboot the device. + حدث خطأ ما. قم بإعادة تشغيل الجهاز. + + + No custom software found at this URL. + لم يتم العثور على برامج مخصصة على عنوان URL هذا. + + + Select a language + اختر لغة + SetupWidget @@ -822,54 +874,6 @@ location set SoftwarePanel - - Git Branch - Git Branch - - - Git Commit - Git Commit - - - OS Version - إصدار نظام التشغيل - - - Version - إصدار - - - Last Update Check - التحقق من آخر تحديث - - - The last time openpilot successfully checked for an update. The updater only runs while the car is off. - آخر مرة نجح برنامج openpilot في التحقق من التحديث. يعمل المحدث فقط أثناء إيقاف تشغيل السيارة. - - - Check for Update - فحص التحديثات - - - CHECKING - تدقيق - - - Switch Branch - تبديل الفرع - - - ENTER - أدخل - - - The new branch will be pulled the next time the updater runs. - سيتم سحب الفرع الجديد في المرة التالية التي يتم فيها تشغيل أداة التحديث. - - - Enter branch name - أدخل اسم الفرع - UNINSTALL الغاء التثبيت @@ -882,45 +886,65 @@ location set Are you sure you want to uninstall? هل أنت متأكد أنك تريد إلغاء التثبيت؟ - - failed to fetch update - فشل في جلب التحديث - CHECK تأكد الان Updates are only downloaded while the car is off. - + يتم تنزيل التحديثات فقط أثناء إيقاف تشغيل السيارة. Current Version - + النسخة الحالية Download - + تحميل Install Update - + تثبيت التحديث INSTALL - + ثبيت Target Branch - + فرع الهدف SELECT - + اختر Select a branch - + اختر فرعا + + + Uninstall + الغاء التثبيت + + + failed to check for update + فشل في التحقق من التحديث + + + DOWNLOAD + تحميل + + + update available + يتوفر تحديث + + + never + أبداً + + + up to date, last checked %1 + محدث، آخر فحص %1 @@ -1047,36 +1071,84 @@ location set إظهار الخريطة على الجانب الأيسر عندما تكون في طريقة عرض الشاشة المنقسمة. - openpilot Longitudinal Control - openpilot التحكم الطولي + openpilot Longitudinal Control (Alpha) + التحكم الطولي المفتوح (ألفا) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + تحذير: التحكم الطولي في نظام الطيار المفتوح موجود في مرحلة ألفا لهذه السيارة وسيقوم بتعطيل مكابح الطوارئ التلقائية (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. + في هذه السيارة، يعمل نظام openpilot افتراضيًا على ACC المدمج في السيارة بدلاً من التحكم الطولي في نظام openpilot. قم بتمكين هذا للتبديل إلى التحكم الطولي المفتوح. يوصى بتمكين الوضع التجريبي عند تمكين التحكم الطولي المفتوح ألفا. - openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! - سوف يقوم برنامج openpilot بتعطيل رادار السيارة وسيتولى التحكم في الغاز والمكابح. تحذير: هذا يعطل AEB! + Experimental Mode + الوضع التجريبي - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - + Aggressive + عدوانية - Experimental openpilot Longitudinal Control - + Standard + قياسي - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - + Relaxed + مريح - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - + Driving Personality + شخصية القيادة - openpilot longitudinal control is not currently available for this car. - + 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. + ينصح القياسية. في الوضع العدواني، سيتبع الطيار المفتوح السيارات الرائدة بشكل أقرب ويكون أكثر عدوانية مع البنزين والفرامل. في الوضع المريح، سيبقى الطيار المفتوح بعيدًا عن السيارات الرائدة. - Enable experimental longitudinal control to enable this. - + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + يتم تعيين openpilot افتراضيًا على القيادة في <b>وضع التبريد</b>. يعمل الوضع التجريبي على تمكين <b>ميزات مستوى ألفا</b> غير الجاهزة لوضع التبريد. الميزات التجريبية مذكورة أدناه: + + + End-to-End Longitudinal Control + التحكم الطولي من النهاية إلى النهاية + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + دع نموذج القيادة يتحكم في الغاز والفرامل. سيقود نظام openpilot كما يعتقد الإنسان، بما في ذلك التوقف عند الأضواء الحمراء وإشارات التوقف. نظرًا لأن نموذج القيادة هو الذي يحدد سرعة القيادة، فإن السرعة المضبوطة ستكون بمثابة الحد الأعلى فقط. هذه ميزة جودة ألفا؛ ينبغي توقع الأخطاء. + + + Navigate on openpilot + التنقل على openpilot + + + When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. + عندما يكون للملاحة وجهة، سيقوم openpilot بإدخال معلومات الخريطة في النموذج. يوفر هذا سياقًا مفيدًا للنموذج ويسمح لـ openpilot بالبقاء يسارًا أو يمينًا بشكل مناسب عند المخارج/المخارج. لا يتغير سلوك تغيير المسار ولا يزال السائق ينشطه. هذه ميزة جودة ألفا؛ وينبغي توقع الأخطاء، وخاصة حول المخارج والتقاطعات. يمكن أن تشمل هذه الأخطاء عبور الخطوط غير المقصودة، والخروج المتأخر، أو القيادة نحو الحواجز الفاصلة في المناطق المثلثة بين الطريق الرئيسي والمخرج. + + + New Driving Visualization + تصور القيادة الجديد + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. + سينتقل تصور القيادة إلى الكاميرا ذات الزاوية الواسعة المواجهة للطريق بسرعات منخفضة لإظهار بعض المنعطفات بشكل أفضل. سيتم أيضًا عرض شعار الوضع التجريبي في الزاوية اليمنى العليا. عند تعيين وجهة التنقل ويستخدمها نموذج القيادة كمدخل، سيتحول مسار القيادة على الخريطة إلى اللون الأخضر. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + الوضع التجريبي غير متاح حاليًا في هذه السيارة نظرًا لاستخدام مخزون السيارة ACC للتحكم الطولي. + + + openpilot longitudinal control may come in a future update. + قد يأتي التحكم الطولي openpilot في التحديث المستقبلي. + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + يمكن اختبار نسخة ألفا من التحكم الطولي المفتوح، إلى جانب الوضع التجريبي، على الفروع غير الصادرة. + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + قم بتمكين تبديل التحكم الطولي المفتوح (alpha) للسماح بالوضع التجريبي. @@ -1114,6 +1186,29 @@ location set فشل التحديث + + WiFiPromptWidget + + Setup Wi-Fi + إعداد شبكة Wi-Fi + + + Connect to Wi-Fi to upload driving data and help improve openpilot + اتصل بشبكة Wi-Fi لتحميل بيانات القيادة والمساعدة في تحسين نظام openpilot + + + Open Settings + أفتح الإعدادات + + + Ready to upload + جاهز للتحميل + + + Training data will be pulled periodically while your device is on Wi-Fi + سيتم سحب بيانات التدريب بشكل دوري أثناء اتصال جهازك بشبكة Wi-Fi + + WifiUI @@ -1132,5 +1227,9 @@ location set Forget Wi-Fi Network "%1"? نزع شبكة اWi-Fi "%1"? + + Forget + إنساها + diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index a1b8faa2e3..d1820fd423 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -751,6 +751,10 @@ This may take up to a minute. Something went wrong. Reboot the device. + + Select a language + Sprache wählen + SetupWidget @@ -1072,10 +1076,6 @@ This may take up to a minute. Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. Der experimentelle Modus ist momentan für dieses Auto nicht verfügbar da es den eingebauten adaptiven Tempomaten des Autos benutzt. - - openpilot longitudinal control may come in a future update. - - openpilot Longitudinal Control (Alpha) @@ -1084,10 +1084,6 @@ This may take up to a minute. 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. - - Aggressive @@ -1104,12 +1100,16 @@ This may take up to a minute. Driving Personality + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + End-to-End Longitudinal Control @@ -1117,19 +1117,23 @@ This may take up to a minute. - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - End-to-End Longitudinal Control + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. + openpilot longitudinal control may come in a future update. - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 14ca4df954..79c4ae5596 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -517,7 +517,7 @@ PrimeAdWidget Upgrade Now - Mettre à niveau maintenant + Mettre à niveau Become a comma prime member at connect.comma.ai @@ -753,6 +753,10 @@ Cela peut prendre jusqu'à une minute. Start over Recommencer + + Select a language + Choisir une langue + SetupWidget @@ -856,7 +860,7 @@ Cela peut prendre jusqu'à une minute. SoftwarePanel Updates are only downloaded while the car is off. - Les mises à jour sont téléchargées uniquement lorsque la voiture est éteinte. + Les MàJ sont téléchargées uniquement si la voiture est éteinte. Current Version @@ -1170,7 +1174,7 @@ Cela peut prendre jusqu'à une minute. WiFiPromptWidget Setup Wi-Fi - Configurer le Wi-Fi + Configurer Wi-Fi Connect to Wi-Fi to upload driving data and help improve openpilot diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index e766204660..16595f8ebf 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -747,6 +747,10 @@ This may take up to a minute. Something went wrong. Reboot the device. + + Select a language + 言語を選択 + SetupWidget @@ -1064,10 +1068,6 @@ This may take up to a minute. Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. この車のACCがアクセル制御を行うため実験モードを利用することができません。 - - openpilot longitudinal control may come in a future update. - - openpilot Longitudinal Control (Alpha) @@ -1076,10 +1076,6 @@ This may take up to a minute. 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. - - Aggressive @@ -1096,12 +1092,16 @@ This may take up to a minute. Driving Personality + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + End-to-End Longitudinal Control @@ -1109,19 +1109,23 @@ This may take up to a minute. - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - End-to-End Longitudinal Control + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. + openpilot longitudinal control may come in a future update. - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 851c306a47..cbd8e668ac 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -749,6 +749,10 @@ This may take up to a minute. No custom software found at this URL. 이 URL에서 커스텀 소프트웨어를 찾을 수 없습니다. + + Select a language + 언어를 선택하세요 + SetupWidget @@ -1120,11 +1124,11 @@ This may take up to a minute. When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc. - + 내비게이션에 목적지가 있으면 openpilot이 지도 정보를 모델에 입력합니다. 이는 모델에 유용한 컨텍스트를 제공하고 openpilot이 분기점에서 적절하게 왼쪽 또는 오른쪽을 유지할 수 있도록 합니다. 차선 변경 기능은 여전히 운전자의 조작에 의해 활성화됩니다. 이것은 알파 상태의 기능입니다. 특히 출구 분기점 주변에서 실수가 발생될수 있으며 이러한 실수에는 의도하지 않은 차선 이탈, 늦은 출구 이용, 도로 가장자리의 분리대 또는 경계석을 향해 운전하는 등이 포함될 수 있습니다. The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green. - + 주행 시각화는 저속으로 주행시 도로를 향한 광각 카메라로 전환되어 일부 회전을 더 잘 보여줍니다. 실험적 모드 로고도 우측 상단에 표시됩니다. 내비게이션 목적지가 설정되고 주행 모델에 입력되면 지도의 주행 경로가 녹색으로 바뀝니다. diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 7aa0c7c49b..a55d31034e 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -753,6 +753,10 @@ Isso pode levar até um minuto. Something went wrong. Reboot the device. Algo deu errado. Reinicie o dispositivo. + + Select a language + Selecione o Idioma + SetupWidget diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 9ef2d8a913..abc6210956 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -749,6 +749,10 @@ This may take up to a minute. No custom software found at this URL. ไม่พบซอฟต์แวร์ที่กำหนดเองที่ URL นี้ + + Select a language + เลือกภาษา + SetupWidget diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index ad882f5786..febded8f59 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -747,6 +747,10 @@ This may take up to a minute. No custom software found at this URL. + + Select a language + Dil seçin + SetupWidget @@ -1048,10 +1052,6 @@ This may take up to a minute. 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. - - Experimental Mode @@ -1076,6 +1076,10 @@ This may take up to a minute. Driving Personality + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 48eb63338b..040dae0b30 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -749,6 +749,10 @@ This may take up to a minute. Something went wrong. Reboot the device. 发生了一些错误。请重新启动您的设备。 + + Select a language + 选择语言 + SetupWidget diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 6eacb6428c..57d9f91fec 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -749,6 +749,10 @@ This may take up to a minute. Something went wrong. Reboot the device. 發生了一些錯誤。請重新啟動您的設備。 + + Select a language + 選擇語言 + SetupWidget diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 32d8ae80ad..03df2cf57a 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -1,5 +1,6 @@ #include "selfdrive/ui/ui.h" +#include #include #include @@ -168,15 +169,15 @@ static void update_state(UIState *s) { Eigen::Matrix3d device_from_calib = euler2rot(rpy); Eigen::Matrix3d wide_from_device = euler2rot(wfde); Eigen::Matrix3d view_from_device; - view_from_device << 0,1,0, - 0,0,1, - 1,0,0; + view_from_device << 0, 1, 0, + 0, 0, 1, + 1, 0, 0; Eigen::Matrix3d view_from_calib = view_from_device * device_from_calib; - Eigen::Matrix3d view_from_wide_calib = view_from_device * wide_from_device * device_from_calib ; + Eigen::Matrix3d view_from_wide_calib = view_from_device * wide_from_device * device_from_calib; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { - scene.view_from_calib.v[i*3 + j] = view_from_calib(i,j); - scene.view_from_wide_calib.v[i*3 + j] = view_from_wide_calib(i,j); + scene.view_from_calib.v[i*3 + j] = view_from_calib(i, j); + scene.view_from_wide_calib.v[i*3 + j] = view_from_wide_calib(i, j); } } scene.calibration_valid = live_calib.getCalStatus() == cereal::LiveCalibrationData::Status::CALIBRATED; @@ -244,8 +245,11 @@ UIState::UIState(QObject *parent) : QObject(parent) { }); Params params; - prime_type = std::atoi(params.get("PrimeType").c_str()); language = QString::fromStdString(params.get("LanguageSetting")); + auto prime_value = params.get("PrimeType"); + if (!prime_value.empty()) { + prime_type = static_cast(std::atoi(prime_value.c_str())); + } // update timer timer = new QTimer(this); @@ -264,11 +268,18 @@ void UIState::update() { emit uiUpdate(*this); } -void UIState::setPrimeType(int type) { +void UIState::setPrimeType(PrimeType type) { if (type != prime_type) { + bool prev_prime = hasPrime(); + prime_type = type; Params().put("PrimeType", std::to_string(prime_type)); emit primeTypeChanged(prime_type); + + bool prime = hasPrime(); + if (prev_prime != prime) { + emit primeChanged(prime); + } } } diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 31488431b1..bd9d059422 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -95,6 +96,15 @@ typedef enum UIStatus { STATUS_ENGAGED, } UIStatus; +enum PrimeType { + UNKNOWN = -1, + NONE = 0, + MAGENTA = 1, + LITE = 2, + BLUE = 3, + MAGENTA_NEW = 4, +}; + const QColor bg_colors [] = { [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), @@ -152,8 +162,9 @@ public: return scene.started && (*sm)["controlsState"].getControlsState().getEnabled(); } - void setPrimeType(int type); - inline int primeType() const { return prime_type; } + void setPrimeType(PrimeType type); + inline PrimeType primeType() const { return prime_type; } + inline bool hasPrime() const { return prime_type != PrimeType::UNKNOWN && prime_type != PrimeType::NONE; } int fb_w = 0, fb_h = 0; @@ -169,7 +180,8 @@ public: signals: void uiUpdate(const UIState &s); void offroadTransition(bool offroad); - void primeTypeChanged(int prime_type); + void primeChanged(bool prime); + void primeTypeChanged(PrimeType prime_type); private slots: void update(); @@ -177,7 +189,7 @@ private slots: private: QTimer *timer; bool started_prev = false; - int prime_type = -1; + PrimeType prime_type = PrimeType::UNKNOWN; }; UIState *uiState(); diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index e8460c4274..004c6425dc 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -3,7 +3,7 @@ import argparse import json import os -from common.basedir import BASEDIR +from openpilot.common.basedir import BASEDIR UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") @@ -13,7 +13,7 @@ TRANSLATIONS_INCLUDE_FILE = os.path.join(TRANSLATIONS_DIR, "alerts_generated.h") def generate_translations_include(): # offroad alerts - # TODO translate events from selfdrive/controls/lib/events.py + # TODO translate events from openpilot.selfdrive/controls/lib/events.py content = "// THIS IS AN AUTOGENERATED FILE, PLEASE EDIT alerts_offroad.json\n" with open(os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json")) as f: for alert in json.load(f).values(): diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 5761f4ec61..1e128fb11c 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -14,13 +14,13 @@ from pathlib import Path from typing import List, Union, Optional from markdown_it import MarkdownIt -from common.basedir import BASEDIR -from common.params import Params -from common.time import system_time_valid -from system.hardware import AGNOS, HARDWARE -from system.swaglog import cloudlog -from selfdrive.controls.lib.alertmanager import set_offroad_alert -from system.version import is_tested_branch +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.common.time import system_time_valid +from openpilot.system.hardware import AGNOS, HARDWARE +from openpilot.system.swaglog import cloudlog +from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert +from openpilot.system.version import is_tested_branch LOCK_FILE = os.getenv("UPDATER_LOCK_FILE", "/tmp/safe_staging_overlay.lock") STAGING_ROOT = os.getenv("UPDATER_STAGING_ROOT", "/data/safe_staging") @@ -192,7 +192,7 @@ def finalize_update() -> None: def handle_agnos_update() -> None: - from system.hardware.tici.agnos import flash_agnos_update, get_target_slot_number + from openpilot.system.hardware.tici.agnos import flash_agnos_update, get_target_slot_number cur_version = HARDWARE.get_os_version() updated_version = run(["bash", "-c", r"unset AGNOS_VERSION && source launch_env.sh && \ diff --git a/system/camerad/SConscript b/system/camerad/SConscript index 3ecc3f6d72..ffd7278bbb 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -8,7 +8,7 @@ env.Program('camerad', [ camera_obj, ], LIBS=libs) -if GetOption("test") and arch == "x86_64": +if GetOption("extras") and arch == "x86_64": env.Program('test/ae_gray_test', ['test/ae_gray_test.cc', camera_obj], LIBS=libs) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 1b2594bc80..23b28357da 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -5,9 +5,10 @@ #include #include #include +#include #include -#include "libyuv.h" +#include "third_party/libyuv/include/libyuv.h" #include #include "system/camerad/imgproc/utils.h" @@ -16,7 +17,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "system/hardware/hw.h" -#include "msm_media_info.h" +#include "third_party/linux/include/msm_media_info.h" #include "system/camerad/cameras/camera_qcom2.h" #ifdef QCOM2 diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 6f4a134438..3a9ecb467b 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -6,12 +6,15 @@ #include #include +#include #include #include #include #include #include #include +#include +#include #include "media/cam_defs.h" #include "media/cam_isp.h" @@ -194,7 +197,7 @@ static cam_cmd_power *power_set_wait(cam_cmd_power *power, int16_t delay_ms) { unconditional_wait->delay = delay_ms; unconditional_wait->op_code = CAMERA_SENSOR_WAIT_OP_SW_UCND; return (struct cam_cmd_power *)(unconditional_wait + 1); -}; +} int CameraState::sensors_init() { uint32_t cam_packet_handle = 0; @@ -419,20 +422,20 @@ void CameraState::config_isp(int io_mem_handle, int fence, int request_id, int b if (io_mem_handle != 0) { io_cfg[0].mem_handle[0] = io_mem_handle; - io_cfg[0].planes[0] = (struct cam_plane_cfg){ - .width = ci.frame_width, - .height = ci.frame_height + ci.extra_height, - .plane_stride = ci.frame_stride, - .slice_height = ci.frame_height + ci.extra_height, - .meta_stride = 0x0, // YUV has meta(stride=0x400, size=0x5000) - .meta_size = 0x0, - .meta_offset = 0x0, - .packer_config = 0x0, // 0xb for YUV - .mode_config = 0x0, // 0x9ef for YUV - .tile_config = 0x0, - .h_init = 0x0, - .v_init = 0x0, - }; + io_cfg[0].planes[0] = (struct cam_plane_cfg){ + .width = ci.frame_width, + .height = ci.frame_height + ci.extra_height, + .plane_stride = ci.frame_stride, + .slice_height = ci.frame_height + ci.extra_height, + .meta_stride = 0x0, // YUV has meta(stride=0x400, size=0x5000) + .meta_size = 0x0, + .meta_offset = 0x0, + .packer_config = 0x0, // 0xb for YUV + .mode_config = 0x0, // 0x9ef for YUV + .tile_config = 0x0, + .h_init = 0x0, + .v_init = 0x0, + }; io_cfg[0].format = CAM_FORMAT_MIPI_RAW_12; // CAM_FORMAT_UBWC_TP10 for YUV io_cfg[0].color_space = CAM_COLOR_SPACE_BASE; // CAM_COLOR_SPACE_BT601_FULL for YUV io_cfg[0].color_pattern = 0x5; // 0x0 for YUV @@ -505,7 +508,7 @@ void CameraState::enqueue_buffer(int i, bool dp) { } void CameraState::enqueue_req_multi(int start, int n, bool dp) { - for (int i=start;ivideo0_fd, 984480, (uint32_t*)&buf0_handle, 0x20, CAM_MEM_FLAG_HW_READ_WRITE | CAM_MEM_FLAG_KMD_ACCESS | CAM_MEM_FLAG_UMD_ACCESS | CAM_MEM_FLAG_CMD_BUF_TYPE, multi_cam_state->device_iommu, multi_cam_state->cdm_iommu); + alloc_w_mmu_hdl(multi_cam_state->video0_fd, 984480, (uint32_t*)&buf0_handle, 0x20, CAM_MEM_FLAG_HW_READ_WRITE | CAM_MEM_FLAG_KMD_ACCESS | + CAM_MEM_FLAG_UMD_ACCESS | CAM_MEM_FLAG_CMD_BUF_TYPE, multi_cam_state->device_iommu, multi_cam_state->cdm_iommu); config_isp(0, 0, 1, buf0_handle, 0); // config csiphy @@ -1273,9 +1277,9 @@ void cameras_run(MultiCameraState *s) { if (ret == 0) { if (ev.type == V4L_EVENT_CAM_REQ_MGR_EVENT) { struct cam_req_mgr_message *event_data = (struct cam_req_mgr_message *)ev.u.data; - // LOGD("v4l2 event: sess_hdl 0x%X, link_hdl 0x%X, frame_id %d, req_id %lld, timestamp 0x%llx, sof_status %d\n", event_data->session_hdl, event_data->u.frame_msg.link_hdl, event_data->u.frame_msg.frame_id, event_data->u.frame_msg.request_id, event_data->u.frame_msg.timestamp, event_data->u.frame_msg.sof_status); if (env_debug_frames) { - printf("sess_hdl 0x%6X, link_hdl 0x%6X, frame_id %lu, req_id %lu, timestamp %.2f ms, sof_status %d\n", event_data->session_hdl, event_data->u.frame_msg.link_hdl, event_data->u.frame_msg.frame_id, event_data->u.frame_msg.request_id, event_data->u.frame_msg.timestamp/1e6, event_data->u.frame_msg.sof_status); + printf("sess_hdl 0x%6X, link_hdl 0x%6X, frame_id %lu, req_id %lu, timestamp %.2f ms, sof_status %d\n", event_data->session_hdl, event_data->u.frame_msg.link_hdl, + event_data->u.frame_msg.frame_id, event_data->u.frame_msg.request_id, event_data->u.frame_msg.timestamp/1e6, event_data->u.frame_msg.sof_status); } if (event_data->session_hdl == s->road_cam.session_handle) { diff --git a/system/camerad/cameras/camera_util.cc b/system/camerad/cameras/camera_util.cc index 5e8640e99a..74c81a878a 100644 --- a/system/camerad/cameras/camera_util.cc +++ b/system/camerad/cameras/camera_util.cc @@ -37,7 +37,7 @@ std::optional device_acquire(int fd, int32_t session_handle, void *data }; int err = do_cam_control(fd, CAM_ACQUIRE_DEV, &cmd, sizeof(cmd)); return err == 0 ? std::make_optional(cmd.dev_handle) : std::nullopt; -}; +} int device_config(int fd, int32_t session_handle, int32_t dev_handle, uint64_t packet_handle) { struct cam_config_dev_cmd cmd = { diff --git a/system/camerad/snapshot/snapshot.py b/system/camerad/snapshot/snapshot.py index 447062fb2e..ede97d1a79 100755 --- a/system/camerad/snapshot/snapshot.py +++ b/system/camerad/snapshot/snapshot.py @@ -7,11 +7,11 @@ from PIL import Image import cereal.messaging as messaging from cereal.visionipc import VisionIpcClient, VisionStreamType -from common.params import Params -from common.realtime import DT_MDL -from system.hardware import PC -from selfdrive.controls.lib.alertmanager import set_offroad_alert -from selfdrive.manager.process_config import managed_processes +from openpilot.common.params import Params +from openpilot.common.realtime import DT_MDL +from openpilot.system.hardware import PC +from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert +from openpilot.selfdrive.manager.process_config import managed_processes LM_THRESH = 120 # defined in system/camerad/imgproc/utils.h diff --git a/system/camerad/test/ae_gray_test.cc b/system/camerad/test/ae_gray_test.cc index aabd7534ee..8d18f7e93b 100644 --- a/system/camerad/test/ae_gray_test.cc +++ b/system/camerad/test/ae_gray_test.cc @@ -1,6 +1,6 @@ // unittest for set_exposure_target -#include "ae_gray_test.h" +#include "system/camerad/test/ae_gray_test.h" #include diff --git a/system/camerad/test/ae_gray_test.h b/system/camerad/test/ae_gray_test.h index fb54cd9584..8953fb017f 100644 --- a/system/camerad/test/ae_gray_test.h +++ b/system/camerad/test/ae_gray_test.h @@ -5,14 +5,13 @@ #define TONE_SPLITS 3 -float gts[TONE_SPLITS*TONE_SPLITS*TONE_SPLITS*TONE_SPLITS] = { - 0.917969,0.917969,0.375000,0.917969,0.375000,0.375000,0.187500,0.187500,0.187500,0.917969, - 0.375000,0.375000,0.187500,0.187500,0.187500,0.187500,0.187500,0.187500,0.093750,0.093750, - 0.093750,0.093750,0.093750,0.093750,0.093750,0.093750,0.093750,0.917969,0.375000,0.375000, - 0.187500,0.187500,0.187500,0.187500,0.187500,0.187500,0.093750,0.093750,0.093750,0.093750, - 0.093750,0.093750,0.093750,0.093750,0.093750,0.093750,0.093750,0.093750,0.093750,0.093750, - 0.093750,0.093750,0.093750,0.093750,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000, - 0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000, - 0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000, - 0.000000 -}; +float gts[TONE_SPLITS * TONE_SPLITS * TONE_SPLITS * TONE_SPLITS] = { + 0.917969, 0.917969, 0.375000, 0.917969, 0.375000, 0.375000, 0.187500, 0.187500, 0.187500, 0.917969, + 0.375000, 0.375000, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.093750, 0.093750, + 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.917969, 0.375000, 0.375000, + 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.093750, 0.093750, 0.093750, 0.093750, + 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, + 0.093750, 0.093750, 0.093750, 0.093750, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, + 0.000000}; diff --git a/system/camerad/test/get_thumbnails_for_segment.py b/system/camerad/test/get_thumbnails_for_segment.py index 898377b111..97667ede86 100755 --- a/system/camerad/test/get_thumbnails_for_segment.py +++ b/system/camerad/test/get_thumbnails_for_segment.py @@ -4,9 +4,9 @@ import os from tqdm import tqdm -from common.file_helpers import mkdirs_exists_ok -from tools.lib.logreader import LogReader -from tools.lib.route import Route +from openpilot.common.file_helpers import mkdirs_exists_ok +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.route import Route import argparse diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index f03c531b20..b2aca850d1 100755 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -7,12 +7,12 @@ from collections import defaultdict import cereal.messaging as messaging from cereal import log from cereal.services import service_list -from selfdrive.manager.process_config import managed_processes -from system.hardware import TICI +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.hardware import TICI TEST_TIMESPAN = 30 -LAG_FRAME_TOLERANCE = {log.FrameData.ImageSensor.ar0231: 0.5, # ARs use synced pulses for frame starts - log.FrameData.ImageSensor.ox03c10: 1.0} # OXs react to out-of-sync at next frame +LAG_FRAME_TOLERANCE = {log.FrameData.ImageSensor.ar0231: 0.5, # ARs use synced pulses for frame starts + log.FrameData.ImageSensor.ox03c10: 1.1} # OXs react to out-of-sync at next frame FRAME_DELTA_TOLERANCE = {log.FrameData.ImageSensor.ar0231: 1.0, log.FrameData.ImageSensor.ox03c10: 1.0} diff --git a/system/camerad/test/test_exposure.py b/system/camerad/test/test_exposure.py index 8f65744da3..50467f9db4 100755 --- a/system/camerad/test/test_exposure.py +++ b/system/camerad/test/test_exposure.py @@ -3,8 +3,8 @@ import time import unittest import numpy as np -from selfdrive.test.helpers import with_processes, phone_only -from system.camerad.snapshot.snapshot import get_snapshots +from openpilot.selfdrive.test.helpers import with_processes, phone_only +from openpilot.system.camerad.snapshot.snapshot import get_snapshots TEST_TIME = 45 REPEAT = 5 diff --git a/system/hardware/__init__.py b/system/hardware/__init__.py index 77bb0e5e2a..99079b5ef3 100644 --- a/system/hardware/__init__.py +++ b/system/hardware/__init__.py @@ -1,9 +1,9 @@ import os from typing import cast -from system.hardware.base import HardwareBase -from system.hardware.tici.hardware import Tici -from system.hardware.pc.hardware import Pc +from openpilot.system.hardware.base import HardwareBase +from openpilot.system.hardware.tici.hardware import Tici +from openpilot.system.hardware.pc.hardware import Pc TICI = os.path.isfile('/TICI') AGNOS = os.path.isfile('/AGNOS') diff --git a/system/hardware/base.h b/system/hardware/base.h index 5460099723..d86b316843 100644 --- a/system/hardware/base.h +++ b/system/hardware/base.h @@ -2,6 +2,9 @@ #include #include +#include +#include + #include "cereal/messaging/messaging.h" // no-op base hw class @@ -11,10 +14,10 @@ public: static constexpr float MIN_VOLUME = 0.2; static std::string get_os_version() { return ""; } - static std::string get_name() { return ""; }; - static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::UNKNOWN; }; - static int get_voltage() { return 0; }; - static int get_current() { return 0; }; + static std::string get_name() { return ""; } + static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::UNKNOWN; } + static int get_voltage() { return 0; } + static int get_current() { return 0; } static std::string get_serial() { return "cccccc"; } diff --git a/system/hardware/hw.h b/system/hardware/hw.h index 3b0583a10b..2f6ccfffda 100644 --- a/system/hardware/hw.h +++ b/system/hardware/hw.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "system/hardware/base.h" #include "common/util.h" @@ -12,16 +14,37 @@ #endif namespace Path { -inline std::string log_root() { - if (const char *env = getenv("LOG_ROOT")) { - return env; + inline std::string openpilot_prefix() { + return util::getenv("OPENPILOT_PREFIX", ""); + } + + inline std::string comma_home() { + return util::getenv("HOME") + "/.comma" + Path::openpilot_prefix(); + } + + inline std::string log_root() { + if (const char *env = getenv("LOG_ROOT")) { + return env; + } + return Hardware::PC() ? Path::comma_home() + "/media/0/realdata" : "/data/media/0/realdata"; + } + + inline std::string params() { + return Hardware::PC() ? util::getenv("PARAMS_ROOT", Path::comma_home() + "/params") : "/data/params"; + } + + inline std::string rsa_file() { + return Hardware::PC() ? Path::comma_home() + "/persist/comma/id_rsa" : "/persist/comma/id_rsa"; + } + + inline std::string swaglog_ipc() { + return "ipc:///tmp/logmessage" + Path::openpilot_prefix(); + } + + inline std::string download_cache_root() { + if (const char *env = getenv("COMMA_CACHE")) { + return env; + } + return "/tmp/comma_download_cache" + Path::openpilot_prefix() + "/"; } - return Hardware::PC() ? util::getenv("HOME") + "/.comma/media/0/realdata" : "/data/media/0/realdata"; -} -inline std::string params() { - return Hardware::PC() ? util::getenv("PARAMS_ROOT", util::getenv("HOME") + "/.comma/params") : "/data/params"; -} -inline std::string rsa_file() { - return Hardware::PC() ? util::getenv("HOME") + "/.comma/persist/comma/id_rsa" : "/persist/comma/id_rsa"; -} } // namespace Path diff --git a/system/hardware/hw.py b/system/hardware/hw.py new file mode 100644 index 0000000000..3f7eac02e9 --- /dev/null +++ b/system/hardware/hw.py @@ -0,0 +1,35 @@ +import os +from pathlib import Path + +from openpilot.selfdrive.hardware import PC + +class Paths: + @staticmethod + def comma_home() -> str: + return os.path.join(str(Path.home()), ".comma" + os.environ.get("OPENPILOT_PREFIX", "")) + + @staticmethod + def log_root() -> str: + if os.environ.get('LOG_ROOT', False): + return os.environ['LOG_ROOT'] + elif PC: + return str(Path(Paths.comma_home()) / "media" / "0" / "realdata") + else: + return '/data/media/0/realdata/' + + @staticmethod + def swaglog_root() -> str: + if PC: + return os.path.join(Paths.comma_home(), "log") + else: + return "/data/log/" + + @staticmethod + def swaglog_ipc() -> str: + return "ipc:///tmp/logmessage" + os.environ.get("OPENPILOT_PREFIX", "") + + @staticmethod + def download_cache_root() -> str: + if os.environ.get('COMMA_CACHE', False): + return os.environ['COMMA_CACHE'] + return "/tmp/comma_download_cache" + os.environ.get("OPENPILOT_PREFIX", "") + "/" diff --git a/system/hardware/pc/hardware.h b/system/hardware/pc/hardware.h index 529b4bfe9d..5cd825d61d 100644 --- a/system/hardware/pc/hardware.h +++ b/system/hardware/pc/hardware.h @@ -1,12 +1,14 @@ #pragma once +#include + #include "system/hardware/base.h" class HardwarePC : public HardwareNone { public: static std::string get_os_version() { return "openpilot for PC"; } - static std::string get_name() { return "pc"; }; - static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; }; + static std::string get_name() { return "pc"; } + static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; } static bool PC() { return true; } static bool TICI() { return util::getenv("TICI", 0) == 1; } static bool AGNOS() { return util::getenv("TICI", 0) == 1; } diff --git a/system/hardware/pc/hardware.py b/system/hardware/pc/hardware.py index 564f9e483a..27c05f5904 100644 --- a/system/hardware/pc/hardware.py +++ b/system/hardware/pc/hardware.py @@ -1,7 +1,7 @@ import random from cereal import log -from system.hardware.base import HardwareBase, ThermalConfig +from openpilot.system.hardware.base import HardwareBase, ThermalConfig NetworkType = log.DeviceState.NetworkType NetworkStrength = log.DeviceState.NetworkStrength diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 90933e8fef..7e8cd480be 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -67,6 +67,10 @@ "size": 10737418240, "sparse": true, "full_check": false, - "has_ab": true + "has_ab": true, + "alt": { + "hash": "256442a55fcb9e8f72969f003a4db91598dee1136f8dda85b553a557d36b93d8", + "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432.img.xz" + } } ] diff --git a/system/hardware/tici/agnos.py b/system/hardware/tici/agnos.py index 5f446a8e90..ef7d9adb79 100755 --- a/system/hardware/tici/agnos.py +++ b/system/hardware/tici/agnos.py @@ -10,7 +10,7 @@ from typing import Dict, Generator, List, Tuple, Union import requests -import system.hardware.tici.casync as casync +import openpilot.system.hardware.tici.casync as casync SPARSE_CHUNK_FMT = struct.Struct('H2xI4x') CAIBX_URL = "https://commadist.azureedge.net/agnosupdate/" diff --git a/system/hardware/tici/amplifier.py b/system/hardware/tici/amplifier.py index 5b656a40fa..e003f131cc 100755 --- a/system/hardware/tici/amplifier.py +++ b/system/hardware/tici/amplifier.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import time from smbus2 import SMBus from collections import namedtuple diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index 580dc83eec..c1a2dcb169 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include "common/params.h" #include "common/util.h" @@ -15,19 +17,19 @@ public: static bool AGNOS() { return true; } static std::string get_os_version() { return "AGNOS " + util::read_file("/VERSION"); - }; + } static std::string get_name() { std::string devicetree_model = util::read_file("/sys/firmware/devicetree/base/model"); return (devicetree_model.find("tizi") != std::string::npos) ? "tizi" : "tici"; - }; + } static cereal::InitData::DeviceType get_device_type() { return (get_name() == "tizi") ? cereal::InitData::DeviceType::TIZI : cereal::InitData::DeviceType::TICI; - }; + } - static int get_voltage() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/in1_input").c_str()); }; - static int get_current() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/curr1_input").c_str()); }; + static int get_voltage() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/in1_input").c_str()); } + static int get_current() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/curr1_input").c_str()); } static std::string get_serial() { static std::string serial(""); @@ -47,8 +49,8 @@ public: return serial; } - static void reboot() { std::system("sudo reboot"); }; - static void poweroff() { std::system("sudo poweroff"); }; + static void reboot() { std::system("sudo reboot"); } + static void poweroff() { std::system("sudo poweroff"); } static void set_brightness(int percent) { std::string max = util::read_file("/sys/class/backlight/panel0-backlight/max_brightness"); @@ -57,14 +59,14 @@ public: brightness_control << (int)(percent * (std::stof(max)/100.)) << "\n"; brightness_control.close(); } - }; + } static void set_display_power(bool on) { std::ofstream bl_power_control("/sys/class/backlight/panel0-backlight/bl_power"); if (bl_power_control.is_open()) { bl_power_control << (on ? "0" : "4") << "\n"; bl_power_control.close(); } - }; + } static void set_volume(float volume) { volume = util::map_val(volume, 0.f, 1.f, MIN_VOLUME, MAX_VOLUME); @@ -99,6 +101,6 @@ public: return ret; } - static bool get_ssh_enabled() { return Params().getBool("SshEnabled"); }; - static void set_ssh_enabled(bool enabled) { Params().putBool("SshEnabled", enabled); }; + static bool get_ssh_enabled() { return Params().getBool("SshEnabled"); } + static void set_ssh_enabled(bool enabled) { Params().putBool("SshEnabled", enabled); } }; diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 6932ea08b6..7f8ef37f1a 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -8,11 +8,11 @@ from functools import cached_property, lru_cache from pathlib import Path from cereal import log -from common.gpio import gpio_set, gpio_init, get_irqs_for_action -from system.hardware.base import HardwareBase, ThermalConfig -from system.hardware.tici import iwlist -from system.hardware.tici.pins import GPIO -from system.hardware.tici.amplifier import Amplifier +from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action +from openpilot.system.hardware.base import HardwareBase, ThermalConfig +from openpilot.system.hardware.tici import iwlist +from openpilot.system.hardware.tici.pins import GPIO +from openpilot.system.hardware.tici.amplifier import Amplifier NM = 'org.freedesktop.NetworkManager' NM_CON_ACT = NM + '.Connection.Active' @@ -97,7 +97,7 @@ def get_device_type(): class Tici(HardwareBase): @cached_property def bus(self): - import dbus # pylint: disable=import-error + import dbus return dbus.SystemBus() @cached_property @@ -586,7 +586,7 @@ class Tici(HardwareBase): gpio_init(GPIO.STM_RST_N, True) gpio_set(GPIO.STM_RST_N, 1) - time.sleep(2) + time.sleep(1) gpio_set(GPIO.STM_RST_N, 0) def recover_internal_panda(self): @@ -595,9 +595,9 @@ class Tici(HardwareBase): gpio_set(GPIO.STM_RST_N, 1) gpio_set(GPIO.STM_BOOT0, 1) - time.sleep(1) + time.sleep(0.5) gpio_set(GPIO.STM_RST_N, 0) - time.sleep(1) + time.sleep(0.5) gpio_set(GPIO.STM_BOOT0, 0) diff --git a/system/hardware/tici/pins.py b/system/hardware/tici/pins.py index 5ac0158082..4388351e1f 100644 --- a/system/hardware/tici/pins.py +++ b/system/hardware/tici/pins.py @@ -6,7 +6,7 @@ class GPIO: HUB_RST_N = 30 UBLOX_RST_N = 32 UBLOX_SAFEBOOT_N = 33 - UBLOX_PWR_EN = 34 + GNSS_PWR_EN = 34 # SCHEMATIC LABEL: GPIO_UBLOX_PWR_EN STM_RST_N = 124 STM_BOOT0 = 134 diff --git a/system/hardware/tici/power_draw_test.py b/system/hardware/tici/power_draw_test.py index 3c303ff61f..a0520638b7 100755 --- a/system/hardware/tici/power_draw_test.py +++ b/system/hardware/tici/power_draw_test.py @@ -2,9 +2,9 @@ import os import time import numpy as np -from system.hardware.tici.hardware import Tici -from system.hardware.tici.pins import GPIO -from common.gpio import gpio_init, gpio_set, gpio_export +from openpilot.system.hardware.tici.hardware import Tici +from openpilot.system.hardware.tici.pins import GPIO +from openpilot.common.gpio import gpio_init, gpio_set, gpio_export def read_power(): with open("/sys/bus/i2c/devices/0-0040/hwmon/hwmon1/in1_input") as f: @@ -53,7 +53,7 @@ if __name__ == "__main__": t.set_screen_brightness(0) gpio_init(GPIO.STM_RST_N, True) gpio_init(GPIO.HUB_RST_N, True) - gpio_init(GPIO.UBLOX_PWR_EN, True) + gpio_init(GPIO.GNSS_PWR_EN, True) gpio_init(GPIO.LTE_RST_N, True) gpio_init(GPIO.LTE_PWRKEY, True) gpio_init(GPIO.CAM0_AVDD_EN, True) @@ -65,7 +65,7 @@ if __name__ == "__main__": os.system("sudo su -c 'echo 0 > /sys/kernel/debug/regulator/camera_rear_ldo/enable'") # cam 1v2 off gpio_set(GPIO.CAM0_AVDD_EN, False) # cam 2v8 off gpio_set(GPIO.LTE_RST_N, True) # quectel off - gpio_set(GPIO.UBLOX_PWR_EN, False) # gps off + gpio_set(GPIO.GNSS_PWR_EN, False) # gps off gpio_set(GPIO.STM_RST_N, True) # panda off gpio_set(GPIO.HUB_RST_N, False) # hub off # cameras in reset @@ -92,7 +92,7 @@ if __name__ == "__main__": gpio_set(GPIO.STM_RST_N, False) time.sleep(5) print("panda: ", read_power_avg()) - gpio_set(GPIO.UBLOX_PWR_EN, True) + gpio_set(GPIO.GNSS_PWR_EN, True) time.sleep(5) print("gps: ", read_power_avg()) gpio_set(GPIO.LTE_RST_N, False) diff --git a/system/hardware/tici/power_monitor.py b/system/hardware/tici/power_monitor.py index f9d1e3cc41..ef3055ac47 100755 --- a/system/hardware/tici/power_monitor.py +++ b/system/hardware/tici/power_monitor.py @@ -4,9 +4,10 @@ import time import datetime import numpy as np from typing import List +from collections import deque -from common.realtime import Ratekeeper -from common.filter_simple import FirstOrderFilter +from openpilot.common.realtime import Ratekeeper +from openpilot.common.filter_simple import FirstOrderFilter def read_power(): @@ -27,6 +28,15 @@ def get_power(seconds=5): pwrs = sample_power(seconds) return np.mean(pwrs) +def wait_for_power(min_pwr, max_pwr, min_secs_in_range, timeout): + start_time = time.monotonic() + pwrs = deque([min_pwr - 1.]*min_secs_in_range, maxlen=min_secs_in_range) + while (time.monotonic() - start_time < timeout): + pwrs.append(get_power(1)) + if all(min_pwr <= p <= max_pwr for p in pwrs): + break + return np.mean(pwrs) + if __name__ == "__main__": duration = None diff --git a/system/hardware/tici/precise_power_measure.py b/system/hardware/tici/precise_power_measure.py index 5d68851367..e186ba4aea 100755 --- a/system/hardware/tici/precise_power_measure.py +++ b/system/hardware/tici/precise_power_measure.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import numpy as np -from system.hardware.tici.power_monitor import sample_power +from openpilot.system.hardware.tici.power_monitor import sample_power if __name__ == '__main__': print("measuring for 5 seconds") diff --git a/system/hardware/tici/tests/compare_casync_manifest.py b/system/hardware/tici/tests/compare_casync_manifest.py index 6d7232bd64..835985b0b5 100755 --- a/system/hardware/tici/tests/compare_casync_manifest.py +++ b/system/hardware/tici/tests/compare_casync_manifest.py @@ -8,7 +8,7 @@ from typing import Dict, List import requests from tqdm import tqdm -import system.hardware.tici.casync as casync +import openpilot.system.hardware.tici.casync as casync def get_chunk_download_size(chunk): diff --git a/system/hardware/tici/tests/test_amplifier.py b/system/hardware/tici/tests/test_amplifier.py index 6019495449..cd3b0f90fe 100755 --- a/system/hardware/tici/tests/test_amplifier.py +++ b/system/hardware/tici/tests/test_amplifier.py @@ -5,9 +5,9 @@ import unittest import subprocess from panda import Panda -from system.hardware import TICI, HARDWARE -from system.hardware.tici.hardware import Tici -from system.hardware.tici.amplifier import Amplifier +from openpilot.system.hardware import TICI, HARDWARE +from openpilot.system.hardware.tici.hardware import Tici +from openpilot.system.hardware.tici.amplifier import Amplifier class TestAmplifier(unittest.TestCase): diff --git a/system/hardware/tici/tests/test_casync.py b/system/hardware/tici/tests/test_casync.py index 8724575ad6..94b32a9f76 100755 --- a/system/hardware/tici/tests/test_casync.py +++ b/system/hardware/tici/tests/test_casync.py @@ -4,7 +4,7 @@ import unittest import tempfile import subprocess -import system.hardware.tici.casync as casync +import openpilot.system.hardware.tici.casync as casync # dd if=/dev/zero of=/tmp/img.raw bs=1M count=2 # sudo losetup -f /tmp/img.raw diff --git a/system/hardware/tici/tests/test_hardware.py b/system/hardware/tici/tests/test_hardware.py index bb22cb1594..7d377ac3fb 100755 --- a/system/hardware/tici/tests/test_hardware.py +++ b/system/hardware/tici/tests/test_hardware.py @@ -3,8 +3,8 @@ import time import unittest import numpy as np -from system.hardware import TICI -from system.hardware.tici.hardware import Tici +from openpilot.system.hardware import TICI +from openpilot.system.hardware.tici.hardware import Tici HARDWARE = Tici() diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index a35f231c4e..fbca95f6f6 100755 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -9,11 +9,11 @@ from typing import List import cereal.messaging as messaging from cereal.services import service_list -from system.hardware import HARDWARE, TICI -from system.hardware.tici.power_monitor import get_power -from selfdrive.manager.process_config import managed_processes -from selfdrive.manager.manager import manager_cleanup -from selfdrive.navd.tests.test_map_renderer import gen_llk +from openpilot.system.hardware import HARDWARE, TICI +from openpilot.system.hardware.tici.power_monitor import get_power +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.manager.manager import manager_cleanup +from openpilot.selfdrive.navd.tests.test_map_renderer import gen_llk SAMPLE_TIME = 8 # seconds to sample power diff --git a/system/logcatd/logcatd_systemd.cc b/system/logcatd/logcatd_systemd.cc index 70467a9c15..54b3782132 100644 --- a/system/logcatd/logcatd_systemd.cc +++ b/system/logcatd/logcatd_systemd.cc @@ -5,7 +5,7 @@ #include #include -#include "json11.hpp" +#include "third_party/json11/json11.hpp" #include "cereal/messaging/messaging.h" #include "common/timing.h" @@ -35,7 +35,7 @@ int main(int argc, char *argv[]) { // Wait for new message if we didn't receive anything if (err == 0) { err = sd_journal_wait(journal, 1000 * 1000); - assert (err >= 0); + assert(err >= 0); continue; // Try again } diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index 3b961bce6e..d4f52fb5f1 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -23,5 +23,5 @@ env.Program('loggerd', ['loggerd.cc'], LIBS=libs) env.Program('encoderd', ['encoderd.cc'], LIBS=libs) env.Program('bootlog.cc', LIBS=libs) -if GetOption('test'): +if GetOption('extras'): env.Program('tests/test_logger', ['tests/test_runner.cc', 'tests/test_logger.cc'], LIBS=libs + ['curl', 'crypto']) diff --git a/system/loggerd/bootlog.cc b/system/loggerd/bootlog.cc index becd293c02..771594d20c 100644 --- a/system/loggerd/bootlog.cc +++ b/system/loggerd/bootlog.cc @@ -50,11 +50,11 @@ static kj::Array build_boot_log() { int main(int argc, char** argv) { const std::string timestr = logger_get_route_name(); - const std::string path = LOG_ROOT + "/boot/" + timestr; + const std::string path = Path::log_root() + "/boot/" + timestr; LOGW("bootlog to %s", path.c_str()); // Open bootlog - bool r = util::create_directories(LOG_ROOT + "/boot/", 0775); + bool r = util::create_directories(Path::log_root() + "/boot/", 0775); assert(r); RawFile file(path.c_str()); diff --git a/system/loggerd/config.py b/system/loggerd/config.py index df187a48c0..0245585fc0 100644 --- a/system/loggerd/config.py +++ b/system/loggerd/config.py @@ -1,13 +1,7 @@ import os from pathlib import Path -from system.hardware import PC - -if os.environ.get('LOG_ROOT', False): - ROOT = os.environ['LOG_ROOT'] -elif PC: - ROOT = str(Path.home() / ".comma" / "media" / "0" / "realdata") -else: - ROOT = '/data/media/0/realdata/' +from openpilot.system.hardware import PC +from openpilot.selfdrive.hardware.hw import Paths CAMERA_FPS = 20 @@ -23,7 +17,7 @@ STATS_FLUSH_TIME_S = 60 def get_available_percent(default=None): try: - statvfs = os.statvfs(ROOT) + statvfs = os.statvfs(Paths.log_root()) available_percent = 100.0 * statvfs.f_bavail / statvfs.f_blocks except OSError: available_percent = default @@ -33,7 +27,7 @@ def get_available_percent(default=None): def get_available_bytes(default=None): try: - statvfs = os.statvfs(ROOT) + statvfs = os.statvfs(Paths.log_root()) available_bytes = statvfs.f_bavail * statvfs.f_frsize except OSError: available_bytes = default diff --git a/system/loggerd/deleter.py b/system/loggerd/deleter.py old mode 100644 new mode 100755 index 5e7b31f583..b060232b6e --- a/system/loggerd/deleter.py +++ b/system/loggerd/deleter.py @@ -3,11 +3,11 @@ import os import shutil import threading from typing import List - -from system.swaglog import cloudlog -from system.loggerd.config import ROOT, get_available_bytes, get_available_percent -from system.loggerd.uploader import listdir_by_creation -from system.loggerd.xattr_cache import getxattr +from openpilot.system.hardware.hw import Paths +from openpilot.system.swaglog import cloudlog +from openpilot.system.loggerd.config import get_available_bytes, get_available_percent +from openpilot.system.loggerd.uploader import listdir_by_creation +from openpilot.system.loggerd.xattr_cache import getxattr MIN_BYTES = 5 * 1024 * 1024 * 1024 MIN_PERCENT = 10 @@ -20,7 +20,7 @@ PRESERVE_COUNT = 5 def has_preserve_xattr(d: str) -> bool: - return getxattr(os.path.join(ROOT, d), PRESERVE_ATTR_NAME) == PRESERVE_ATTR_VALUE + return getxattr(os.path.join(Paths.log_root(), d), PRESERVE_ATTR_NAME) == PRESERVE_ATTR_VALUE def get_preserved_segments(dirs_by_creation: List[str]) -> List[str]: @@ -51,14 +51,14 @@ def deleter_thread(exit_event): out_of_percent = get_available_percent(default=MIN_PERCENT + 1) < MIN_PERCENT if out_of_percent or out_of_bytes: - dirs = listdir_by_creation(ROOT) + dirs = listdir_by_creation(Paths.log_root()) # skip deleting most recent N preserved segments (and their prior segment) preserved_dirs = get_preserved_segments(dirs) # remove the earliest directory we can for delete_dir in sorted(dirs, key=lambda d: (d in DELETE_LAST, d in preserved_dirs)): - delete_path = os.path.join(ROOT, delete_dir) + delete_path = os.path.join(Paths.log_root(), delete_dir) if any(name.endswith(".lock") for name in os.listdir(delete_path)): continue diff --git a/system/loggerd/encoder/encoder.h b/system/loggerd/encoder/encoder.h index 59ec4357ae..a8bfd5c054 100644 --- a/system/loggerd/encoder/encoder.h +++ b/system/loggerd/encoder/encoder.h @@ -2,6 +2,7 @@ #include #include +#include #include #include "cereal/messaging/messaging.h" @@ -15,7 +16,7 @@ class VideoEncoder { public: VideoEncoder(const EncoderInfo &encoder_info, int in_width, int in_height); - virtual ~VideoEncoder() {}; + virtual ~VideoEncoder() {} virtual int encode_frame(VisionBuf* buf, VisionIpcBufExtra *extra) = 0; virtual void encoder_open(const char* path) = 0; virtual void encoder_close() = 0; diff --git a/system/loggerd/encoder/ffmpeg_encoder.cc b/system/loggerd/encoder/ffmpeg_encoder.cc index b73f4e8f5d..f44f2fbed7 100644 --- a/system/loggerd/encoder/ffmpeg_encoder.cc +++ b/system/loggerd/encoder/ffmpeg_encoder.cc @@ -11,7 +11,7 @@ #define __STDC_CONSTANT_MACROS -#include "libyuv.h" +#include "third_party/libyuv/include/libyuv.h" extern "C" { #include @@ -135,7 +135,7 @@ int FfmpegEncoder::encode_frame(VisionBuf* buf, VisionIpcBufExtra *extra) { } if (env_debug_encoder) { - printf("%20s got %8d bytes flags %8x idx %4d id %8d\n", encoder_info.filename, pkt.size, pkt.flags, counter, extra->frame_id); + printf("%20s got %8d bytes flags %8x idx %4d id %8d\n", encoder_info.publish_name, pkt.size, pkt.flags, counter, extra->frame_id); } publisher_publish(this, segment_num, counter, *extra, diff --git a/system/loggerd/encoder/v4l_encoder.cc b/system/loggerd/encoder/v4l_encoder.cc index a319d414ca..571f5979e2 100644 --- a/system/loggerd/encoder/v4l_encoder.cc +++ b/system/loggerd/encoder/v4l_encoder.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -6,11 +7,11 @@ #include "common/util.h" #include "common/timing.h" -#include "libyuv.h" -#include "msm_media_info.h" +#include "third_party/libyuv/include/libyuv.h" +#include "third_party/linux/include/msm_media_info.h" // has to be in this order -#include "v4l2-controls.h" +#include "third_party/linux/include/v4l2-controls.h" #include #define V4L2_QCOM_BUF_FLAG_CODECCONFIG 0x00020000 #define V4L2_QCOM_BUF_FLAG_EOS 0x02000000 @@ -18,7 +19,13 @@ // echo 0x7fffffff > /sys/kernel/debug/msm_vidc/debug_level const int env_debug_encoder = (getenv("DEBUG_ENCODER") != NULL) ? atoi(getenv("DEBUG_ENCODER")) : 0; -#define checked_ioctl(x,y,z) { int _ret = HANDLE_EINTR(ioctl(x,y,z)); if (_ret!=0) { LOGE("checked_ioctl failed %d %lx %p", x, y, z); } assert(_ret==0); } +static void checked_ioctl(int fd, unsigned long request, void *argp) { + int ret = util::safe_ioctl(fd, request, argp); + if (ret != 0) { + LOGE("checked_ioctl failed with error %d (%d %lx %p)", errno, fd, request, argp); + assert(0); + } +} static void dequeue_buffer(int fd, v4l2_buf_type buf_type, unsigned int *index=NULL, unsigned int *bytesused=NULL, unsigned int *flags=NULL, struct timeval *timestamp=NULL) { v4l2_plane plane = {0}; @@ -68,7 +75,7 @@ static void request_buffers(int fd, v4l2_buf_type buf_type, unsigned int count) } void V4LEncoder::dequeue_handler(V4LEncoder *e) { - std::string dequeue_thread_name = "dq-"+std::string(e->encoder_info.filename); + std::string dequeue_thread_name = "dq-"+std::string(e->encoder_info.publish_name); util::set_thread_name(dequeue_thread_name.c_str()); e->segment_num++; @@ -85,10 +92,20 @@ void V4LEncoder::dequeue_handler(V4LEncoder *e) { while (!exit) { int rc = poll(&pfd, 1, 1000); - if (!rc) { LOGE("encoder dequeue poll timeout"); continue; } + if (rc < 0) { + if (errno != EINTR) { + // TODO: exit encoder? + // ignore the error and keep going + LOGE("poll failed (%d - %d)", rc, errno); + } + continue; + } else if (rc == 0) { + LOGE("encoder dequeue poll timeout"); + continue; + } if (env_debug_encoder >= 2) { - printf("%20s poll %x at %.2f ms\n", e->encoder_info.filename, pfd.revents, millis_since_boot()); + printf("%20s poll %x at %.2f ms\n", e->encoder_info.publish_name, pfd.revents, millis_since_boot()); } int frame_id = -1; @@ -116,7 +133,7 @@ void V4LEncoder::dequeue_handler(V4LEncoder *e) { if (env_debug_encoder) { printf("%20s got(%d) %6d bytes flags %8x idx %3d/%4d id %8d ts %ld lat %.2f ms (%lu frames free)\n", - e->encoder_info.filename, index, bytesused, flags, e->segment_num, idx, frame_id, ts, millis_since_boot()-(ts/1000.), e->free_buf_in.size()); + e->encoder_info.publish_name, index, bytesused, flags, e->segment_num, idx, frame_id, ts, millis_since_boot()-(ts/1000.), e->free_buf_in.size()); } // requeue the buffer diff --git a/system/loggerd/encoderd.cc b/system/loggerd/encoderd.cc index 6dd1f2ae32..1b45df6827 100644 --- a/system/loggerd/encoderd.cc +++ b/system/loggerd/encoderd.cc @@ -47,7 +47,7 @@ bool sync_encoders(EncoderdState *s, CameraType cam_type, uint32_t frame_id) { void encoder_thread(EncoderdState *s, const LogCameraInfo &cam_info) { util::set_thread_name(cam_info.thread_name); - std::vector encoders; + std::vector> encoders; VisionIpcClient vipc_client = VisionIpcClient("camerad", cam_info.stream_type, false); int cur_seg = 0; @@ -64,14 +64,11 @@ void encoder_thread(EncoderdState *s, const LogCameraInfo &cam_info) { assert(buf_info.width > 0 && buf_info.height > 0); for (const auto &encoder_info : cam_info.encoder_infos) { - encoders.push_back(new Encoder(encoder_info, buf_info.width, buf_info.height)); + auto &e = encoders.emplace_back(new Encoder(encoder_info, buf_info.width, buf_info.height)); + e->encoder_open(nullptr); } } - for (int i = 0; i < encoders.size(); ++i) { - encoders[i]->encoder_open(NULL); - } - bool lagging = false; while (!do_exit) { VisionIpcBufExtra extra; @@ -113,12 +110,6 @@ void encoder_thread(EncoderdState *s, const LogCameraInfo &cam_info) { } } } - - LOG("encoder destroy"); - for (auto &e : encoders) { - e->encoder_close(); - delete e; - } } template diff --git a/system/loggerd/logger.cc b/system/loggerd/logger.cc index 1e710759e6..31b302ffe6 100644 --- a/system/loggerd/logger.cc +++ b/system/loggerd/logger.cc @@ -13,7 +13,10 @@ #include #include #include +#include #include +#include +#include #include "common/params.h" #include "common/swaglog.h" diff --git a/system/loggerd/logger.h b/system/loggerd/logger.h index e7594cee88..06b11e72f9 100644 --- a/system/loggerd/logger.h +++ b/system/loggerd/logger.h @@ -1,11 +1,12 @@ #pragma once -#include #include +#include #include #include #include +#include #include #include @@ -15,8 +16,6 @@ #include "common/swaglog.h" #include "system/hardware/hw.h" -const std::string LOG_ROOT = Path::log_root(); - #define LOGGER_MAX_HANDLES 16 class RawFile { diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index 56fc11f62c..adb24c913c 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -1,6 +1,10 @@ #include +#include +#include +#include #include +#include #include "system/loggerd/encoder/encoder.h" #include "system/loggerd/loggerd.h" @@ -20,7 +24,7 @@ struct LoggerdState { void logger_rotate(LoggerdState *s) { int segment = -1; - int err = logger_next(&s->logger, LOG_ROOT.c_str(), s->segment_path, sizeof(s->segment_path), &segment); + int err = logger_next(&s->logger, Path::log_root().c_str(), s->segment_path, sizeof(s->segment_path), &segment); assert(err == 0); s->rotate_segment = segment; s->ready_to_rotate = 0; @@ -94,7 +98,7 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct re.marked_ready_to_rotate = false; // we are in this segment now, process any queued messages before this one if (!re.q.empty()) { - for (auto &qmsg: re.q) { + for (auto &qmsg : re.q) { bytes_count += handle_encoder_msg(s, qmsg, name, re, encoder_info); } re.q.clear(); @@ -112,6 +116,7 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct } // if we aren't actually recording, don't create the writer if (encoder_info.record) { + assert(encoder_info.filename != NULL); re.writer.reset(new VideoWriter(s->segment_path, encoder_info.filename, idx.getType() != cereal::EncodeIndex::Type::FULL_H_E_V_C, encoder_info.frame_width, encoder_info.frame_height, encoder_info.fps, idx.getType())); @@ -203,11 +208,11 @@ void loggerd_thread() { std::unique_ptr poller(Poller::create()); // subscribe to all socks - for (const auto& it : services) { - const bool encoder = strcmp(it.name+strlen(it.name)-strlen("EncodeData"), "EncodeData") == 0; - const bool livestream_encoder = strncmp(it.name, "livestream", strlen("livestream")) == 0; + for (const auto& [_, it] : services) { + const bool encoder = util::ends_with(it.name, "EncodeData"); + const bool livestream_encoder = util::starts_with(it.name, "livestream"); if (!it.should_log && (!encoder || livestream_encoder)) continue; - LOGD("logging %s (on port %d)", it.name, it.port); + LOGD("logging %s (on port %d)", it.name.c_str(), it.port); SubSocket * sock = SubSocket::create(ctx.get(), it.name); assert(sock != NULL); @@ -217,7 +222,7 @@ void loggerd_thread() { .counter = 0, .freq = it.decimation, .encoder = encoder, - .user_flag = (strcmp(it.name, "userFlag") == 0), + .user_flag = it.name == "userFlag", }; } @@ -229,7 +234,7 @@ void loggerd_thread() { std::map encoder_infos_dict; for (const auto &cam : cameras_logged) { - for (const auto &encoder_info: cam.encoder_infos) { + for (const auto &encoder_info : cam.encoder_infos) { encoder_infos_dict[encoder_info.publish_name] = encoder_info; s.max_waiting++; } diff --git a/system/loggerd/loggerd.h b/system/loggerd/loggerd.h index 531b30e9f2..cfc06c28d3 100644 --- a/system/loggerd/loggerd.h +++ b/system/loggerd/loggerd.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "cereal/messaging/messaging.h" #include "cereal/services.h" #include "cereal/visionipc/visionipc_client.h" diff --git a/system/loggerd/tests/fill.py b/system/loggerd/tests/fill.py index 4bf4f73604..5e6f887b12 100755 --- a/system/loggerd/tests/fill.py +++ b/system/loggerd/tests/fill.py @@ -3,15 +3,16 @@ from pathlib import Path -from system.loggerd.config import ROOT, get_available_percent -from system.loggerd.tests.loggerd_tests_common import create_random_file +from openpilot.system.hardware.hw import Paths +from openpilot.system.loggerd.config import get_available_percent +from openpilot.system.loggerd.tests.loggerd_tests_common import create_random_file if __name__ == "__main__": segment_idx = 0 while True: seg_name = f"1970-01-01--00-00-00--{segment_idx}" - seg_path = Path(ROOT) / seg_name + seg_path = Path(Paths.log_root()) / seg_name print(seg_path) diff --git a/system/loggerd/tests/loggerd_tests_common.py b/system/loggerd/tests/loggerd_tests_common.py index 7d71516dfe..8bfb571861 100644 --- a/system/loggerd/tests/loggerd_tests_common.py +++ b/system/loggerd/tests/loggerd_tests_common.py @@ -1,15 +1,13 @@ import os -import errno -import shutil import random -import tempfile import unittest from pathlib import Path from typing import Optional +from openpilot.system.hardware.hw import Paths -import system.loggerd.deleter as deleter -import system.loggerd.uploader as uploader -from system.loggerd.xattr_cache import setxattr +import openpilot.system.loggerd.deleter as deleter +import openpilot.system.loggerd.uploader as uploader +from openpilot.system.loggerd.xattr_cache import setxattr def create_random_file(file_path: Path, size_mb: float, lock: bool = False, upload_xattr: Optional[bytes] = None) -> None: @@ -87,8 +85,6 @@ class UploaderTestCase(unittest.TestCase): uploader.Api = MockApiIgnore def setUp(self): - self.root = Path(tempfile.mkdtemp()) - uploader.ROOT = str(self.root) # Monkey patch root dir uploader.Api = MockApi uploader.Params = MockParams uploader.fake_upload = True @@ -99,16 +95,9 @@ class UploaderTestCase(unittest.TestCase): self.seg_format2 = "2019-05-18--11-22-33--{}" self.seg_dir = self.seg_format.format(self.seg_num) - def tearDown(self): - try: - shutil.rmtree(self.root) - except OSError as e: - if e.errno != errno.ENOENT: - raise - def make_file_with_data(self, f_dir: str, fn: str, size_mb: float = .1, lock: bool = False, upload_xattr: Optional[bytes] = None, preserve_xattr: Optional[bytes] = None) -> Path: - file_path = self.root / f_dir / fn + file_path = Path(Paths.log_root()) / f_dir / fn create_random_file(file_path, size_mb, lock, upload_xattr) if preserve_xattr is not None: diff --git a/system/loggerd/tests/test_deleter.py b/system/loggerd/tests/test_deleter.py index 9474b30f82..e4112b7b4e 100755 --- a/system/loggerd/tests/test_deleter.py +++ b/system/loggerd/tests/test_deleter.py @@ -6,9 +6,9 @@ from collections import namedtuple from pathlib import Path from typing import Sequence -import system.loggerd.deleter as deleter -from common.timeout import Timeout, TimeoutException -from system.loggerd.tests.loggerd_tests_common import UploaderTestCase +import openpilot.system.loggerd.deleter as deleter +from openpilot.common.timeout import Timeout, TimeoutException +from openpilot.system.loggerd.tests.loggerd_tests_common import UploaderTestCase Stats = namedtuple("Stats", ['f_bavail', 'f_blocks', 'f_frsize']) @@ -22,7 +22,6 @@ class TestDeleter(UploaderTestCase): super().setUp() self.fake_stats = Stats(f_bavail=0, f_blocks=10, f_frsize=4096) deleter.os.statvfs = self.fake_statvfs - deleter.ROOT = str(self.root) def start_thread(self): self.end_event = threading.Event() diff --git a/system/loggerd/tests/test_encoder.py b/system/loggerd/tests/test_encoder.py index 81f4e9fb9d..bec956a316 100755 --- a/system/loggerd/tests/test_encoder.py +++ b/system/loggerd/tests/test_encoder.py @@ -11,12 +11,12 @@ from pathlib import Path from parameterized import parameterized from tqdm import trange -from common.params import Params -from common.timeout import Timeout -from system.hardware import TICI -from system.loggerd.config import ROOT -from selfdrive.manager.process_config import managed_processes -from tools.lib.logreader import LogReader +from openpilot.common.params import Params +from openpilot.common.timeout import Timeout +from openpilot.system.hardware import TICI +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.tools.lib.logreader import LogReader +from openpilot.selfdrive.hardware.hw import Paths SEGMENT_LENGTH = 2 FULL_SIZE = 2507572 @@ -48,12 +48,12 @@ class TestEncoder(unittest.TestCase): self._clear_logs() def _clear_logs(self): - if os.path.exists(ROOT): - shutil.rmtree(ROOT) + if os.path.exists(Paths.log_root()): + shutil.rmtree(Paths.log_root()) def _get_latest_segment_path(self): - last_route = sorted(Path(ROOT).iterdir())[-1] - return os.path.join(ROOT, last_route) + last_route = sorted(Path(Paths.log_root()).iterdir())[-1] + return os.path.join(Paths.log_root(), last_route) # TODO: this should run faster than real time @parameterized.expand([(True, ), (False, )]) @@ -146,7 +146,7 @@ class TestEncoder(unittest.TestCase): for i in trange(num_segments): # poll for next segment with Timeout(int(SEGMENT_LENGTH*10), error_msg=f"timed out waiting for segment {i}"): - while Path(f"{route_prefix_path}--{i+1}") not in Path(ROOT).iterdir(): + while Path(f"{route_prefix_path}--{i+1}") not in Path(Paths.log_root()).iterdir(): time.sleep(0.1) check_seg(i) finally: diff --git a/system/loggerd/tests/test_logger.cc b/system/loggerd/tests/test_logger.cc index 9c82299091..9f815c2189 100644 --- a/system/loggerd/tests/test_logger.cc +++ b/system/loggerd/tests/test_logger.cc @@ -97,7 +97,7 @@ TEST_CASE("logger") { auto logging_thread = [&]() -> void { LoggerHandle *lh = logger_get_handle(&logger); - REQUIRE(lh != nullptr); + assert(lh != nullptr); int segment = main_segment; int delayed_cnt = 0; while (!do_exit) { diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index c151afb24f..3ea29b0d82 100755 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -13,17 +13,17 @@ from typing import Dict, List import cereal.messaging as messaging from cereal import log from cereal.services import service_list -from common.basedir import BASEDIR -from common.params import Params -from common.timeout import Timeout -from system.loggerd.config import ROOT -from system.loggerd.xattr_cache import getxattr -from system.loggerd.deleter import PRESERVE_ATTR_NAME, PRESERVE_ATTR_VALUE -from selfdrive.manager.process_config import managed_processes -from system.version import get_version -from tools.lib.logreader import LogReader +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.common.timeout import Timeout +from openpilot.system.hardware.hw import Paths +from openpilot.system.loggerd.xattr_cache import getxattr +from openpilot.system.loggerd.deleter import PRESERVE_ATTR_NAME, PRESERVE_ATTR_VALUE +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.version import get_version +from openpilot.tools.lib.logreader import LogReader from cereal.visionipc import VisionIpcServer, VisionStreamType -from common.transformations.camera import tici_f_frame_size, tici_d_frame_size, tici_e_frame_size +from openpilot.common.transformations.camera import tici_f_frame_size, tici_d_frame_size, tici_e_frame_size SentinelType = log.Sentinel.SentinelType @@ -32,8 +32,11 @@ CEREAL_SERVICES = [f for f in log.Event.schema.union_fields if f in service_list class TestLoggerd(unittest.TestCase): + def setUp(self): + os.environ.pop("LOG_ROOT", None) + def _get_latest_log_dir(self): - log_dirs = sorted(Path(ROOT).iterdir(), key=lambda f: f.stat().st_mtime) + log_dirs = sorted(Path(Paths.log_root()).iterdir(), key=lambda f: f.stat().st_mtime) return log_dirs[-1] def _get_log_dir(self, x): @@ -154,35 +157,35 @@ class TestLoggerd(unittest.TestCase): vipc_server.create_buffers_with_sizes(stream_type, 40, False, *(frame_spec)) vipc_server.start_listener() - for _ in range(5): - num_segs = random.randint(2, 5) - length = random.randint(1, 3) - os.environ["LOGGERD_SEGMENT_LENGTH"] = str(length) - managed_processes["loggerd"].start() - managed_processes["encoderd"].start() - time.sleep(1) - - fps = 20.0 - for n in range(1, int(num_segs*length*fps)+1): - for stream_type, frame_spec, state in streams: - dat = np.empty(frame_spec[2], dtype=np.uint8) - vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n/fps, n/fps) - - camera_state = messaging.new_message(state) - frame = getattr(camera_state, state) - frame.frameId = n - pm.send(state, camera_state) - time.sleep(1.0/fps) - - managed_processes["loggerd"].stop() - managed_processes["encoderd"].stop() - - route_path = str(self._get_latest_log_dir()).rsplit("--", 1)[0] - for n in range(num_segs): - p = Path(f"{route_path}--{n}") - logged = {f.name for f in p.iterdir() if f.is_file()} - diff = logged ^ expected_files - self.assertEqual(len(diff), 0, f"didn't get all expected files. run={_} seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}") + num_segs = random.randint(2, 5) + length = random.randint(1, 3) + os.environ["LOGGERD_SEGMENT_LENGTH"] = str(length) + managed_processes["loggerd"].start() + managed_processes["encoderd"].start() + time.sleep(1) + + fps = 20.0 + for n in range(1, int(num_segs*length*fps)+1): + time_start = time.monotonic() + for stream_type, frame_spec, state in streams: + dat = np.empty(frame_spec[2], dtype=np.uint8) + vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n/fps, n/fps) + + camera_state = messaging.new_message(state) + frame = getattr(camera_state, state) + frame.frameId = n + pm.send(state, camera_state) + time.sleep(max((1.0/fps) - (time.monotonic() - time_start), 0)) + + managed_processes["loggerd"].stop() + managed_processes["encoderd"].stop() + + route_path = str(self._get_latest_log_dir()).rsplit("--", 1)[0] + for n in range(num_segs): + p = Path(f"{route_path}--{n}") + logged = {f.name for f in p.iterdir() if f.is_file()} + diff = logged ^ expected_files + self.assertEqual(len(diff), 0, f"didn't get all expected files. run={_} seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}") def test_bootlog(self): # generate bootlog with fake launch log diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py index 580d1efae2..a7349fc5e4 100755 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -7,14 +7,15 @@ import logging import json from pathlib import Path from typing import List, Optional +from openpilot.system.hardware.hw import Paths -from system.swaglog import cloudlog -from system.loggerd.uploader import uploader_fn, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE +from openpilot.system.swaglog import cloudlog +from openpilot.system.loggerd.uploader import uploader_fn, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE -from system.loggerd.tests.loggerd_tests_common import UploaderTestCase +from openpilot.system.loggerd.tests.loggerd_tests_common import UploaderTestCase -class TestLogHandler(logging.Handler): +class FakeLogHandler(logging.Handler): def __init__(self): logging.Handler.__init__(self) self.reset() @@ -33,7 +34,7 @@ class TestLogHandler(logging.Handler): except Exception: pass -log_handler = TestLogHandler() +log_handler = FakeLogHandler() cloudlog.addHandler(log_handler) @@ -84,7 +85,7 @@ class TestUploader(UploaderTestCase): self.assertFalse(len(log_handler.upload_order) < len(exp_order), "Some files failed to upload") self.assertFalse(len(log_handler.upload_order) > len(exp_order), "Some files were uploaded twice") for f_path in exp_order: - self.assertEqual(os.getxattr((self.root / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not uploaded") + self.assertEqual(os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not uploaded") self.assertTrue(log_handler.upload_order == exp_order, "Files uploaded in wrong order") @@ -102,7 +103,7 @@ class TestUploader(UploaderTestCase): self.assertFalse(len(log_handler.upload_order) < len(exp_order), "Some files failed to upload") self.assertFalse(len(log_handler.upload_order) > len(exp_order), "Some files were uploaded twice") for f_path in exp_order: - self.assertEqual(os.getxattr((self.root / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not uploaded") + self.assertEqual(os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not uploaded") self.assertTrue(log_handler.upload_order == exp_order, "Files uploaded in wrong order") @@ -121,7 +122,7 @@ class TestUploader(UploaderTestCase): self.assertFalse(len(log_handler.upload_ignored) < len(exp_order), "Some files failed to ignore") self.assertFalse(len(log_handler.upload_ignored) > len(exp_order), "Some files were ignored twice") for f_path in exp_order: - self.assertEqual(os.getxattr((self.root / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not ignored") + self.assertEqual(os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not ignored") self.assertTrue(log_handler.upload_ignored == exp_order, "Files ignored in wrong order") @@ -146,7 +147,7 @@ class TestUploader(UploaderTestCase): self.assertFalse(len(log_handler.upload_order) < len(exp_order), "Some files failed to upload") self.assertFalse(len(log_handler.upload_order) > len(exp_order), "Some files were uploaded twice") for f_path in exp_order: - self.assertEqual(os.getxattr((self.root / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not uploaded") + self.assertEqual(os.getxattr((Path(Paths.log_root()) / f_path).with_suffix(""), UPLOAD_ATTR_NAME), UPLOAD_ATTR_VALUE, "All files not uploaded") self.assertTrue(log_handler.upload_order == exp_order, "Files uploaded in wrong order") diff --git a/system/loggerd/tools/mark_all_uploaded.py b/system/loggerd/tools/mark_all_uploaded.py index c963014748..0502184380 100644 --- a/system/loggerd/tools/mark_all_uploaded.py +++ b/system/loggerd/tools/mark_all_uploaded.py @@ -1,8 +1,8 @@ import os -from system.loggerd.uploader import UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE +from openpilot.system.hardware.hw import Paths +from openpilot.system.loggerd.uploader import UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE -from system.loggerd.config import ROOT -for folder in os.walk(ROOT): +for folder in os.walk(Paths.log_root()): for file1 in folder[2]: full_path = os.path.join(folder[0], file1) os.setxattr(full_path, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE) diff --git a/system/loggerd/tools/mark_unuploaded.py b/system/loggerd/tools/mark_unuploaded.py index 3d1d4472b0..ef50280441 100755 --- a/system/loggerd/tools/mark_unuploaded.py +++ b/system/loggerd/tools/mark_unuploaded.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import os import sys -from system.loggerd.uploader import UPLOAD_ATTR_NAME +from openpilot.system.loggerd.uploader import UPLOAD_ATTR_NAME for fn in sys.argv[1:]: print(f"unmarking {fn}") diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py old mode 100644 new mode 100755 index 196e5b3c2b..12c6ecdca9 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -13,13 +13,13 @@ from typing import BinaryIO, Iterator, List, Optional, Tuple, Union from cereal import log import cereal.messaging as messaging -from common.api import Api -from common.params import Params -from common.realtime import set_core_affinity -from system.hardware import TICI -from system.loggerd.xattr_cache import getxattr, setxattr -from system.loggerd.config import ROOT -from system.swaglog import cloudlog +from openpilot.common.api import Api +from openpilot.common.params import Params +from openpilot.common.realtime import set_core_affinity +from openpilot.system.hardware import TICI +from openpilot.system.hardware.hw import Paths +from openpilot.system.loggerd.xattr_cache import getxattr, setxattr +from openpilot.system.swaglog import cloudlog NetworkType = log.DeviceState.NetworkType UPLOAD_ATTR_NAME = 'user.upload' @@ -244,7 +244,7 @@ def uploader_fn(exit_event: threading.Event) -> None: except Exception: cloudlog.exception("failed to set core affinity") - clear_locks(ROOT) + clear_locks(Paths.log_root()) params = Params() dongle_id = params.get("DongleId", encoding='utf8') @@ -258,7 +258,7 @@ def uploader_fn(exit_event: threading.Event) -> None: sm = messaging.SubMaster(['deviceState']) pm = messaging.PubMaster(['uploaderState']) - uploader = Uploader(dongle_id, ROOT) + uploader = Uploader(dongle_id, Paths.log_root()) backoff = 0.1 while not exit_event.is_set(): diff --git a/system/loggerd/video_writer.cc b/system/loggerd/video_writer.cc index 1b449056cb..90b5f1af3d 100644 --- a/system/loggerd/video_writer.cc +++ b/system/loggerd/video_writer.cc @@ -1,6 +1,5 @@ #pragma clang diagnostic ignored "-Wdeprecated-declarations" #include -#include #include "system/loggerd/video_writer.h" #include "common/swaglog.h" @@ -8,7 +7,6 @@ VideoWriter::VideoWriter(const char *path, const char *filename, bool remuxing, int width, int height, int fps, cereal::EncodeIndex::Type codec) : remuxing(remuxing) { - raw = codec == cereal::EncodeIndex::Type::BIG_BOX_LOSSLESS; vid_path = util::string_format("%s/%s", path, filename); lock_path = util::string_format("%s/%s.lock", path, filename); @@ -18,6 +16,7 @@ VideoWriter::VideoWriter(const char *path, const char *filename, bool remuxing, LOGD("encoder_open %s remuxing:%d", this->vid_path.c_str(), this->remuxing); if (this->remuxing) { + bool raw = (codec == cereal::EncodeIndex::Type::BIG_BOX_LOSSLESS); avformat_alloc_output_context2(&this->ofmt_ctx, NULL, raw ? "matroska" : NULL, this->vid_path.c_str()); assert(this->ofmt_ctx); @@ -98,7 +97,6 @@ void VideoWriter::write(uint8_t *data, int len, long long timestamp, bool codecc VideoWriter::~VideoWriter() { if (this->remuxing) { - if (this->raw) { avcodec_close(this->codec_ctx); } int err = av_write_trailer(this->ofmt_ctx); if (err != 0) LOGE("av_write_trailer failed %d", err); avcodec_free_context(&this->codec_ctx); diff --git a/system/loggerd/video_writer.h b/system/loggerd/video_writer.h index 01a243904c..1aa758b42b 100644 --- a/system/loggerd/video_writer.h +++ b/system/loggerd/video_writer.h @@ -16,11 +16,10 @@ public: ~VideoWriter(); private: std::string vid_path, lock_path; - FILE *of = nullptr; AVCodecContext *codec_ctx; AVFormatContext *ofmt_ctx; AVStream *out_stream; - bool remuxing, raw; -}; \ No newline at end of file + bool remuxing; +}; diff --git a/system/logmessaged.py b/system/logmessaged.py index 04101d042b..c53e20e483 100755 --- a/system/logmessaged.py +++ b/system/logmessaged.py @@ -3,8 +3,9 @@ import zmq from typing import NoReturn import cereal.messaging as messaging -from common.logging_extra import SwagLogFileFormatter -from system.swaglog import get_file_handler +from openpilot.common.logging_extra import SwagLogFileFormatter +from openpilot.system.hardware.hw import Paths +from openpilot.system.swaglog import get_file_handler def main() -> NoReturn: @@ -14,7 +15,7 @@ def main() -> NoReturn: ctx = zmq.Context.instance() sock = ctx.socket(zmq.PULL) - sock.bind("ipc:///tmp/logmessage") + sock.bind(Paths.swaglog_ipc()) # and we publish them log_message_sock = messaging.pub_sock('logMessage') diff --git a/system/micd.py b/system/micd.py index 97ba0c262e..c7af1b0ad2 100755 --- a/system/micd.py +++ b/system/micd.py @@ -2,9 +2,9 @@ import numpy as np from cereal import messaging -from common.filter_simple import FirstOrderFilter -from common.realtime import Ratekeeper -from system.swaglog import cloudlog +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.realtime import Ratekeeper +from openpilot.system.swaglog import cloudlog RATE = 10 FFT_SAMPLES = 4096 @@ -85,7 +85,7 @@ class Mic: def micd_thread(self): # sounddevice must be imported after forking processes - import sounddevice as sd # pylint: disable=import-outside-toplevel + import sounddevice as sd with sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") diff --git a/system/proclogd/SConscript b/system/proclogd/SConscript index 1b94a32f1b..1f4b767011 100644 --- a/system/proclogd/SConscript +++ b/system/proclogd/SConscript @@ -2,5 +2,5 @@ Import('env', 'cereal', 'messaging', 'common') libs = [cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj', 'common', 'zmq', 'json11'] env.Program('proclogd', ['main.cc', 'proclog.cc'], LIBS=libs) -if GetOption('test'): +if GetOption('extras'): env.Program('tests/test_proclog', ['tests/test_proclog.cc', 'proclog.cc'], LIBS=libs) diff --git a/system/proclogd/main.cc b/system/proclogd/main.cc index c4faa916d9..3f8a889eea 100644 --- a/system/proclogd/main.cc +++ b/system/proclogd/main.cc @@ -1,6 +1,7 @@ #include +#include "common/ratekeeper.h" #include "common/util.h" #include "system/proclogd/proclog.h" @@ -9,13 +10,15 @@ ExitHandler do_exit; int main(int argc, char **argv) { setpriority(PRIO_PROCESS, 0, -15); + RateKeeper rk("proclogd", 0.5); PubMaster publisher({"procLog"}); + while (!do_exit) { MessageBuilder msg; buildProcLogMessage(msg); publisher.send("procLog", msg); - util::sleep_for(2000); // 2 secs + rk.keepTime(); } return 0; diff --git a/system/proclogd/tests/test_proclog.cc b/system/proclogd/tests/test_proclog.cc index affde2f320..33fccd4f30 100644 --- a/system/proclogd/tests/test_proclog.cc +++ b/system/proclogd/tests/test_proclog.cc @@ -140,7 +140,6 @@ TEST_CASE("buildProcLogerMessage") { REQUIRE(p.getName() == "test_proclog"); REQUIRE(p.getState() == 'R'); REQUIRE_THAT(p.getExe().cStr(), Catch::Matchers::Contains("test_proclog")); - REQUIRE(p.getCmdline().size() == 1); REQUIRE_THAT(p.getCmdline()[0], Catch::Matchers::Contains("test_proclog")); } else { std::string cmd_path = "/proc/" + std::to_string(p.getPid()) + "/cmdline"; diff --git a/system/sensord/SConscript b/system/sensord/SConscript index 8f26c00853..63d1d0d690 100644 --- a/system/sensord/SConscript +++ b/system/sensord/SConscript @@ -1,9 +1,7 @@ Import('env', 'arch', 'common', 'cereal', 'messaging') sensors = [ - 'sensors/file_sensor.cc', 'sensors/i2c_sensor.cc', - 'sensors/light_sensor.cc', 'sensors/bmx055_accel.cc', 'sensors/bmx055_gyro.cc', 'sensors/bmx055_magn.cc', diff --git a/system/sensord/pigeond.py b/system/sensord/pigeond.py index f9ac2eaf95..fc5c05e64f 100755 --- a/system/sensord/pigeond.py +++ b/system/sensord/pigeond.py @@ -10,11 +10,11 @@ from datetime import datetime from typing import List, Optional, Tuple from cereal import messaging -from common.params import Params -from system.swaglog import cloudlog -from system.hardware import TICI -from common.gpio import gpio_init, gpio_set -from system.hardware.tici.pins import GPIO +from openpilot.common.params import Params +from openpilot.system.swaglog import cloudlog +from openpilot.system.hardware import TICI +from openpilot.common.gpio import gpio_init, gpio_set +from openpilot.system.hardware.tici.pins import GPIO UBLOX_TTY = "/dev/ttyHS0" @@ -27,11 +27,11 @@ UBLOX_ASSIST_ACK = b"\xb5\x62\x13\x60\x08\x00" def set_power(enabled: bool) -> None: gpio_init(GPIO.UBLOX_SAFEBOOT_N, True) - gpio_init(GPIO.UBLOX_PWR_EN, True) + gpio_init(GPIO.GNSS_PWR_EN, True) gpio_init(GPIO.UBLOX_RST_N, True) gpio_set(GPIO.UBLOX_SAFEBOOT_N, True) - gpio_set(GPIO.UBLOX_PWR_EN, enabled) + gpio_set(GPIO.GNSS_PWR_EN, enabled) gpio_set(GPIO.UBLOX_RST_N, enabled) def add_ubx_checksum(msg: bytes) -> bytes: diff --git a/system/sensord/rawgps/nmeaport.py b/system/sensord/rawgps/nmeaport.py new file mode 100644 index 0000000000..01b9b179b9 --- /dev/null +++ b/system/sensord/rawgps/nmeaport.py @@ -0,0 +1,169 @@ +import os +import sys +from dataclasses import dataclass, fields +from subprocess import check_output, CalledProcessError +from time import sleep +from typing import NoReturn + +DEBUG = int(os.environ.get("DEBUG", "0")) + +@dataclass +class GnssClockNmeaPort: + # flags bit mask: + # 0x01 = leap_seconds valid + # 0x02 = time_uncertainty_ns valid + # 0x04 = full_bias_ns valid + # 0x08 = bias_ns valid + # 0x10 = bias_uncertainty_ns valid + # 0x20 = drift_nsps valid + # 0x40 = drift_uncertainty_nsps valid + flags: int + leap_seconds: int + time_ns: int + time_uncertainty_ns: int # 1-sigma + full_bias_ns: int + bias_ns: float + bias_uncertainty_ns: float # 1-sigma + drift_nsps: float + drift_uncertainty_nsps: float # 1-sigma + + def __post_init__(self): + for field in fields(self): + val = getattr(self, field.name) + setattr(self, field.name, field.type(val) if val else None) + +@dataclass +class GnssMeasNmeaPort: + messageCount: int + messageNum: int + svCount: int + # constellation enum: + # 1 = GPS + # 2 = SBAS + # 3 = GLONASS + # 4 = QZSS + # 5 = BEIDOU + # 6 = GALILEO + constellation: int + svId: int + flags: int # always zero + time_offset_ns: int + # state bit mask: + # 0x0001 = CODE LOCK + # 0x0002 = BIT SYNC + # 0x0004 = SUBFRAME SYNC + # 0x0008 = TIME OF WEEK DECODED + # 0x0010 = MSEC AMBIGUOUS + # 0x0020 = SYMBOL SYNC + # 0x0040 = GLONASS STRING SYNC + # 0x0080 = GLONASS TIME OF DAY DECODED + # 0x0100 = BEIDOU D2 BIT SYNC + # 0x0200 = BEIDOU D2 SUBFRAME SYNC + # 0x0400 = GALILEO E1BC CODE LOCK + # 0x0800 = GALILEO E1C 2ND CODE LOCK + # 0x1000 = GALILEO E1B PAGE SYNC + # 0x2000 = GALILEO E1B PAGE SYNC + state: int + time_of_week_ns: int + time_of_week_uncertainty_ns: int # 1-sigma + carrier_to_noise_ratio: float + pseudorange_rate: float + pseudorange_rate_uncertainty: float # 1-sigma + + def __post_init__(self): + for field in fields(self): + val = getattr(self, field.name) + setattr(self, field.name, field.type(val) if val else None) + +def nmea_checksum_ok(s): + checksum = 0 + for i, c in enumerate(s[1:]): + if c == "*": + if i != len(s) - 4: # should be 3rd to last character + print("ERROR: NMEA string does not have checksum delimiter in correct location:", s) + return False + break + checksum ^= ord(c) + else: + print("ERROR: NMEA string does not have checksum delimiter:", s) + return False + + return True + +def process_nmea_port_messages(device:str="/dev/ttyUSB1") -> NoReturn: + while True: + try: + with open(device, "r") as nmeaport: + for line in nmeaport: + line = line.strip() + if DEBUG: + print(line) + if not line.startswith("$"): # all NMEA messages start with $ + continue + if not nmea_checksum_ok(line): + continue + + fields = line.split(",") + match fields[0]: + case "$GNCLK": + # fields at end are reserved (not used) + gnss_clock = GnssClockNmeaPort(*fields[1:10]) # type: ignore[arg-type] + print(gnss_clock) + case "$GNMEAS": + # fields at end are reserved (not used) + gnss_meas = GnssMeasNmeaPort(*fields[1:14]) # type: ignore[arg-type] + print(gnss_meas) + except Exception as e: + print(e) + sleep(1) + +def main() -> NoReturn: + from openpilot.common.gpio import gpio_init, gpio_set + from openpilot.system.hardware.tici.pins import GPIO + from openpilot.system.sensord.rawgps.rawgpsd import at_cmd + + try: + check_output(["pidof", "rawgpsd"]) + print("rawgpsd is running, please kill openpilot before running this script! (aborted)") + sys.exit(1) + except CalledProcessError as e: + if e.returncode != 1: # 1 == no process found (boardd not running) + raise e + + print("power up antenna ...") + gpio_init(GPIO.GNSS_PWR_EN, True) + gpio_set(GPIO.GNSS_PWR_EN, True) + + if b"+QGPS: 0" not in (at_cmd("AT+QGPS?") or b""): + print("stop location tracking ...") + at_cmd("AT+QGPSEND") + + if b'+QGPSCFG: "outport",usbnmea' not in (at_cmd('AT+QGPSCFG="outport"') or b""): + print("configure outport ...") + at_cmd('AT+QGPSCFG="outport","usbnmea"') # usbnmea = /dev/ttyUSB1 + + if b'+QGPSCFG: "gnssrawdata",3,0' not in (at_cmd('AT+QGPSCFG="gnssrawdata"') or b""): + print("configure gnssrawdata ...") + # AT+QGPSCFG="gnssrawdata",,' + # values: + # 0x01 = GPS + # 0x02 = GLONASS + # 0x04 = BEIDOU + # 0x08 = GALILEO + # 0x10 = QZSS + # values: + # 0 = NMEA port + # 1 = AT port + at_cmd('AT+QGPSCFG="gnssrawdata",3,0') # enable all constellations, output data to NMEA port + print("rebooting ...") + at_cmd('AT+CFUN=1,1') + print("re-run this script when it is back up") + sys.exit(2) + + print("starting location tracking ...") + at_cmd("AT+QGPS=1") + + process_nmea_port_messages() + +if __name__ == "__main__": + main() diff --git a/system/sensord/rawgps/rawgpsd.py b/system/sensord/rawgps/rawgpsd.py index d9b2c37731..e710a16920 100755 --- a/system/sensord/rawgps/rawgpsd.py +++ b/system/sensord/rawgps/rawgpsd.py @@ -15,14 +15,14 @@ from struct import unpack_from, calcsize, pack from cereal import log import cereal.messaging as messaging -from common.gpio import gpio_init, gpio_set +from openpilot.common.gpio import gpio_init, gpio_set from laika.gps_time import GPSTime, utc_to_gpst, get_leap_seconds from laika.helpers import get_prn_from_nmea_id from laika.constants import SECS_IN_HR, SECS_IN_DAY, SECS_IN_WEEK -from system.hardware.tici.pins import GPIO -from system.swaglog import cloudlog -from system.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv -from system.sensord.rawgps.structs import (dict_unpacker, position_report, relist, +from openpilot.system.hardware.tici.pins import GPIO +from openpilot.system.swaglog import cloudlog +from openpilot.system.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv +from openpilot.system.sensord.rawgps.structs import (dict_unpacker, position_report, relist, gps_measurement_report, gps_measurement_report_sv, glonass_measurement_report, glonass_measurement_report_sv, oemdre_measurement_report, oemdre_measurement_report_sv, oemdre_svpoly_report, @@ -271,7 +271,7 @@ def main() -> NoReturn: def cleanup(sig, frame): cloudlog.warning("caught sig disabling quectel gps") - gpio_set(GPIO.UBLOX_PWR_EN, False) + gpio_set(GPIO.GNSS_PWR_EN, False) teardown_quectel(diag) cloudlog.warning("quectel cleanup done") @@ -289,8 +289,8 @@ def main() -> NoReturn: want_assistance = not r current_gps_time = utc_to_gpst(GPSTime.from_datetime(datetime.utcnow())) cloudlog.warning("quectel setup done") - gpio_init(GPIO.UBLOX_PWR_EN, True) - gpio_set(GPIO.UBLOX_PWR_EN, True) + gpio_init(GPIO.GNSS_PWR_EN, True) + gpio_set(GPIO.GNSS_PWR_EN, True) pm = messaging.PubMaster(['qcomGnss', 'gpsLocation']) diff --git a/system/sensord/rawgps/test_rawgps.py b/system/sensord/rawgps/test_rawgps.py index 8c2e246764..02777d5a1d 100755 --- a/system/sensord/rawgps/test_rawgps.py +++ b/system/sensord/rawgps/test_rawgps.py @@ -8,10 +8,10 @@ import subprocess import numpy as np import cereal.messaging as messaging -from system.hardware import TICI -from system.sensord.rawgps.rawgpsd import at_cmd, wait_for_modem -from selfdrive.manager.process_config import managed_processes -from common.transformations.coordinates import ecef_from_geodetic +from openpilot.system.hardware import TICI +from openpilot.system.sensord.rawgps.rawgpsd import at_cmd, wait_for_modem +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.common.transformations.coordinates import ecef_from_geodetic GOOD_SIGNAL = bool(int(os.getenv("GOOD_SIGNAL", '0'))) diff --git a/system/sensord/sensors/bmx055_accel.cc b/system/sensord/sensors/bmx055_accel.cc index 0c48d1e3ba..bdb0113de3 100644 --- a/system/sensord/sensors/bmx055_accel.cc +++ b/system/sensord/sensors/bmx055_accel.cc @@ -1,4 +1,4 @@ -#include "bmx055_accel.h" +#include "system/sensord/sensors/bmx055_accel.h" #include diff --git a/system/sensord/sensors/bmx055_gyro.cc b/system/sensord/sensors/bmx055_gyro.cc index ba41f3b47c..411b2f445e 100644 --- a/system/sensord/sensors/bmx055_gyro.cc +++ b/system/sensord/sensors/bmx055_gyro.cc @@ -1,4 +1,4 @@ -#include "bmx055_gyro.h" +#include "system/sensord/sensors/bmx055_gyro.h" #include #include diff --git a/system/sensord/sensors/bmx055_magn.cc b/system/sensord/sensors/bmx055_magn.cc index 7716ce25c0..3d0d3d2fc6 100644 --- a/system/sensord/sensors/bmx055_magn.cc +++ b/system/sensord/sensors/bmx055_magn.cc @@ -1,4 +1,4 @@ -#include "bmx055_magn.h" +#include "system/sensord/sensors/bmx055_magn.h" #include @@ -77,7 +77,7 @@ int BMX055_Magn::init() { // suspend -> sleep int ret = set_register(BMX055_MAGN_I2C_REG_PWR_0, 0x01); - if(ret < 0) { + if (ret < 0) { LOGE("Enabling power failed: %d", ret); goto fail; } @@ -90,21 +90,21 @@ int BMX055_Magn::init() { // Load magnetometer trim ret = read_register(BMX055_MAGN_I2C_REG_DIG_X1, trim_x1y1, 2); - if(ret < 0) goto fail; + if (ret < 0) goto fail; ret = read_register(BMX055_MAGN_I2C_REG_DIG_X2, trim_x2y2, 2); - if(ret < 0) goto fail; + if (ret < 0) goto fail; ret = read_register(BMX055_MAGN_I2C_REG_DIG_XY2, trim_xy1xy2, 2); - if(ret < 0) goto fail; + if (ret < 0) goto fail; ret = read_register(BMX055_MAGN_I2C_REG_DIG_Z1_LSB, trim_z1, 2); - if(ret < 0) goto fail; + if (ret < 0) goto fail; ret = read_register(BMX055_MAGN_I2C_REG_DIG_Z2_LSB, trim_z2, 2); - if(ret < 0) goto fail; + if (ret < 0) goto fail; ret = read_register(BMX055_MAGN_I2C_REG_DIG_Z3_LSB, trim_z3, 2); - if(ret < 0) goto fail; + if (ret < 0) goto fail; ret = read_register(BMX055_MAGN_I2C_REG_DIG_Z4_LSB, trim_z4, 2); - if(ret < 0) goto fail; + if (ret < 0) goto fail; ret = read_register(BMX055_MAGN_I2C_REG_DIG_XYZ1_LSB, trim_xyz1, 2); - if(ret < 0) goto fail; + if (ret < 0) goto fail; // Read trim data trim_data.dig_x1 = trim_x1y1[0]; @@ -171,17 +171,17 @@ bool BMX055_Magn::perform_self_test() { uint8_t forced = BMX055_MAGN_FORCED; // Negative current - set_register(BMX055_MAGN_I2C_REG_MAG, forced | (uint8_t(0b10) << 6)); - util::sleep_for(100); + set_register(BMX055_MAGN_I2C_REG_MAG, forced | (uint8_t(0b10) << 6)); + util::sleep_for(100); - read_register(BMX055_MAGN_I2C_REG_DATAX_LSB, buffer, sizeof(buffer)); - parse_xyz(buffer, &x, &y, &neg_z); + read_register(BMX055_MAGN_I2C_REG_DATAX_LSB, buffer, sizeof(buffer)); + parse_xyz(buffer, &x, &y, &neg_z); // Positive current - set_register(BMX055_MAGN_I2C_REG_MAG, forced | (uint8_t(0b11) << 6)); - util::sleep_for(100); + set_register(BMX055_MAGN_I2C_REG_MAG, forced | (uint8_t(0b11) << 6)); + util::sleep_for(100); - read_register(BMX055_MAGN_I2C_REG_DATAX_LSB, buffer, sizeof(buffer)); + read_register(BMX055_MAGN_I2C_REG_DATAX_LSB, buffer, sizeof(buffer)); parse_xyz(buffer, &x, &y, &pos_z); // Put back in normal mode @@ -206,9 +206,9 @@ bool BMX055_Magn::parse_xyz(uint8_t buffer[8], int16_t *x, int16_t *y, int16_t * uint16_t data_r = (uint16_t) (((uint16_t)buffer[7] << 8) | buffer[6]) >> 2; assert(data_r != 0); - *x = compensate_x(trim_data, mdata_x, data_r); - *y = compensate_y(trim_data, mdata_y, data_r); - *z = compensate_z(trim_data, mdata_z, data_r); + *x = compensate_x(trim_data, mdata_x, data_r); + *y = compensate_y(trim_data, mdata_y, data_r); + *z = compensate_z(trim_data, mdata_z, data_r); } return ready; } diff --git a/system/sensord/sensors/bmx055_temp.cc b/system/sensord/sensors/bmx055_temp.cc index 68ee0da1d6..da7b86476c 100644 --- a/system/sensord/sensors/bmx055_temp.cc +++ b/system/sensord/sensors/bmx055_temp.cc @@ -1,4 +1,4 @@ -#include "bmx055_temp.h" +#include "system/sensord/sensors/bmx055_temp.h" #include diff --git a/system/sensord/sensors/file_sensor.cc b/system/sensord/sensors/file_sensor.cc deleted file mode 100644 index a74ae1ae1e..0000000000 --- a/system/sensord/sensors/file_sensor.cc +++ /dev/null @@ -1,17 +0,0 @@ -#include "file_sensor.h" - -#include - -FileSensor::FileSensor(std::string filename) : file(filename) {} - -int FileSensor::init() { - return file.is_open() ? 0 : 1; -} - -FileSensor::~FileSensor() { - file.close(); -} - -bool FileSensor::has_interrupt_enabled() { - return false; -} \ No newline at end of file diff --git a/system/sensord/sensors/file_sensor.h b/system/sensord/sensors/file_sensor.h deleted file mode 100644 index 07d7e8f946..0000000000 --- a/system/sensord/sensors/file_sensor.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include - -#include "cereal/gen/cpp/log.capnp.h" -#include "system/sensord/sensors/sensor.h" - -class FileSensor : public Sensor { -protected: - std::ifstream file; - -public: - FileSensor(std::string filename); - ~FileSensor(); - int init(); - bool has_interrupt_enabled(); - virtual bool get_event(MessageBuilder &msg, uint64_t ts = 0) = 0; -}; diff --git a/system/sensord/sensors/i2c_sensor.cc b/system/sensord/sensors/i2c_sensor.cc index f563f93d2b..90220f551d 100644 --- a/system/sensord/sensors/i2c_sensor.cc +++ b/system/sensord/sensors/i2c_sensor.cc @@ -1,4 +1,4 @@ -#include "i2c_sensor.h" +#include "system/sensord/sensors/i2c_sensor.h" int16_t read_12_bit(uint8_t lsb, uint8_t msb) { uint16_t combined = (uint16_t(msb) << 8) | uint16_t(lsb & 0xF0); diff --git a/system/sensord/sensors/i2c_sensor.h b/system/sensord/sensors/i2c_sensor.h index ccac526c12..ba100c3b01 100644 --- a/system/sensord/sensors/i2c_sensor.h +++ b/system/sensord/sensors/i2c_sensor.h @@ -2,6 +2,7 @@ #include #include +#include #include "cereal/gen/cpp/log.capnp.h" #include "common/i2c.h" diff --git a/system/sensord/sensors/light_sensor.cc b/system/sensord/sensors/light_sensor.cc deleted file mode 100644 index 99e321b47d..0000000000 --- a/system/sensord/sensors/light_sensor.cc +++ /dev/null @@ -1,27 +0,0 @@ -#include "light_sensor.h" - -#include - -#include "common/timing.h" -#include "system/sensord/sensors/constants.h" - -LightSensor::LightSensor(std::string filename) : FileSensor(filename) {} - -bool LightSensor::get_event(MessageBuilder &msg, uint64_t ts) { - uint64_t start_time = nanos_since_boot(); - file.clear(); - file.seekg(0); - - int value; - file >> value; - - auto event = msg.initEvent().initLightSensor(); - event.setSource(cereal::SensorEventData::SensorSource::RPR0521); - event.setVersion(1); - event.setSensor(SENSOR_LIGHT); - event.setType(SENSOR_TYPE_LIGHT); - event.setTimestamp(start_time); - event.setLight(value); - - return true; -} diff --git a/system/sensord/sensors/light_sensor.h b/system/sensord/sensors/light_sensor.h deleted file mode 100644 index 7ed1c1f70c..0000000000 --- a/system/sensord/sensors/light_sensor.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include "file_sensor.h" - -class LightSensor : public FileSensor { -public: - LightSensor(std::string filename); - bool get_event(MessageBuilder &msg, uint64_t ts = 0); - int shutdown() { return 0; } -}; diff --git a/system/sensord/sensors/lsm6ds3_accel.cc b/system/sensord/sensors/lsm6ds3_accel.cc index 2a09702c96..03533e0657 100644 --- a/system/sensord/sensors/lsm6ds3_accel.cc +++ b/system/sensord/sensors/lsm6ds3_accel.cc @@ -1,4 +1,4 @@ -#include "lsm6ds3_accel.h" +#include "system/sensord/sensors/lsm6ds3_accel.h" #include #include @@ -121,7 +121,7 @@ int LSM6DS3_Accel::init() { uint8_t value = 0; bool do_self_test = false; - const char* env_lsm_selftest =env_lsm_selftest = std::getenv("LSM_SELF_TEST"); + const char* env_lsm_selftest = std::getenv("LSM_SELF_TEST"); if (env_lsm_selftest != nullptr && strncmp(env_lsm_selftest, "1", 1) == 0) { do_self_test = true; } diff --git a/system/sensord/sensors/lsm6ds3_gyro.cc b/system/sensord/sensors/lsm6ds3_gyro.cc index 9bc43485af..0459b6ad64 100644 --- a/system/sensord/sensors/lsm6ds3_gyro.cc +++ b/system/sensord/sensors/lsm6ds3_gyro.cc @@ -1,4 +1,4 @@ -#include "lsm6ds3_gyro.h" +#include "system/sensord/sensors/lsm6ds3_gyro.h" #include #include @@ -128,7 +128,7 @@ int LSM6DS3_Gyro::init() { } ret = self_test(LSM6DS3_GYRO_POSITIVE_TEST); - if (ret < 0 ) { + if (ret < 0) { LOGE("LSM6DS3 gyro positive self-test failed!"); if (do_self_test) goto fail; } diff --git a/system/sensord/sensors/lsm6ds3_temp.cc b/system/sensord/sensors/lsm6ds3_temp.cc index c2e2c83c1d..f481614154 100644 --- a/system/sensord/sensors/lsm6ds3_temp.cc +++ b/system/sensord/sensors/lsm6ds3_temp.cc @@ -1,4 +1,4 @@ -#include "lsm6ds3_temp.h" +#include "system/sensord/sensors/lsm6ds3_temp.h" #include diff --git a/system/sensord/sensors/mmc5603nj_magn.cc b/system/sensord/sensors/mmc5603nj_magn.cc index 048095786e..0e8ba967e3 100644 --- a/system/sensord/sensors/mmc5603nj_magn.cc +++ b/system/sensord/sensors/mmc5603nj_magn.cc @@ -1,9 +1,12 @@ -#include "mmc5603nj_magn.h" +#include "system/sensord/sensors/mmc5603nj_magn.h" +#include #include +#include #include "common/swaglog.h" #include "common/timing.h" +#include "common/util.h" MMC5603NJ_Magn::MMC5603NJ_Magn(I2CBus *bus) : I2CSensor(bus) {} @@ -11,8 +14,8 @@ int MMC5603NJ_Magn::init() { int ret = verify_chip_id(MMC5603NJ_I2C_REG_ID, {MMC5603NJ_CHIP_ID}); if (ret == -1) return -1; - // Set 100 Hz - ret = set_register(MMC5603NJ_I2C_REG_ODR, 100); + // Set ODR to 0 + ret = set_register(MMC5603NJ_I2C_REG_ODR, 0); if (ret < 0) { goto fail; } @@ -23,18 +26,6 @@ int MMC5603NJ_Magn::init() { goto fail; } - // Set compute measurement rate - ret = set_register(MMC5603NJ_I2C_REG_INTERNAL_0, MMC5603NJ_CMM_FREQ_EN | MMC5603NJ_AUTO_SR_EN); - if (ret < 0) { - goto fail; - } - - // Enable continuous mode, set every 100 measurements - ret = set_register(MMC5603NJ_I2C_REG_INTERNAL_2, MMC5603NJ_CMM_EN | MMC5603NJ_EN_PRD_SET | 0b11); - if (ret < 0) { - goto fail; - } - fail: return ret; } @@ -67,16 +58,36 @@ fail: return ret; } -bool MMC5603NJ_Magn::get_event(MessageBuilder &msg, uint64_t ts) { - uint64_t start_time = nanos_since_boot(); +void MMC5603NJ_Magn::start_measurement() { + set_register(MMC5603NJ_I2C_REG_INTERNAL_0, 0b01); + util::sleep_for(5); +} + +std::vector MMC5603NJ_Magn::read_measurement() { + int len; uint8_t buffer[9]; - int len = read_register(MMC5603NJ_I2C_REG_XOUT0, buffer, sizeof(buffer)); + len = read_register(MMC5603NJ_I2C_REG_XOUT0, buffer, sizeof(buffer)); assert(len == sizeof(buffer)); - float scale = 1.0 / 16384.0; - float x = read_20_bit(buffer[6], buffer[1], buffer[0]) * scale; - float y = read_20_bit(buffer[7], buffer[3], buffer[2]) * scale; - float z = read_20_bit(buffer[8], buffer[5], buffer[4]) * scale; + float x = (read_20_bit(buffer[6], buffer[1], buffer[0]) * scale) - 32.0; + float y = (read_20_bit(buffer[7], buffer[3], buffer[2]) * scale) - 32.0; + float z = (read_20_bit(buffer[8], buffer[5], buffer[4]) * scale) - 32.0; + std::vector xyz = {x, y, z}; + return xyz; +} + +bool MMC5603NJ_Magn::get_event(MessageBuilder &msg, uint64_t ts) { + uint64_t start_time = nanos_since_boot(); + // SET - RESET cycle + set_register(MMC5603NJ_I2C_REG_INTERNAL_0, MMC5603NJ_SET); + util::sleep_for(5); + MMC5603NJ_Magn::start_measurement(); + std::vector xyz = MMC5603NJ_Magn::read_measurement(); + + set_register(MMC5603NJ_I2C_REG_INTERNAL_0, MMC5603NJ_RESET); + util::sleep_for(5); + MMC5603NJ_Magn::start_measurement(); + std::vector reset_xyz = MMC5603NJ_Magn::read_measurement(); auto event = msg.initEvent().initMagnetometer(); event.setSource(cereal::SensorEventData::SensorSource::MMC5603NJ); @@ -85,10 +96,13 @@ bool MMC5603NJ_Magn::get_event(MessageBuilder &msg, uint64_t ts) { event.setType(SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED); event.setTimestamp(start_time); - float xyz[] = {x, y, z}; + float vals[] = {xyz[0], xyz[1], xyz[2], reset_xyz[0], reset_xyz[1], reset_xyz[2]}; + bool valid = true; + if (std::any_of(std::begin(vals), std::end(vals), [](float val) { return val == -32.0; })) { + valid = false; + } auto svec = event.initMagneticUncalibrated(); - svec.setV(xyz); - svec.setStatus(true); - + svec.setV(vals); + svec.setStatus(valid); return true; } diff --git a/system/sensord/sensors/mmc5603nj_magn.h b/system/sensord/sensors/mmc5603nj_magn.h index fce3f3fecb..9c0fbd2521 100644 --- a/system/sensord/sensors/mmc5603nj_magn.h +++ b/system/sensord/sensors/mmc5603nj_magn.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "system/sensord/sensors/i2c_sensor.h" // Address of the chip on the bus @@ -19,9 +21,14 @@ #define MMC5603NJ_AUTO_SR_EN (1 << 5) #define MMC5603NJ_CMM_EN (1 << 4) #define MMC5603NJ_EN_PRD_SET (1 << 3) +#define MMC5603NJ_SET (1 << 3) +#define MMC5603NJ_RESET (1 << 4) class MMC5603NJ_Magn : public I2CSensor { +private: uint8_t get_device_address() {return MMC5603NJ_I2C_ADDR;} + void start_measurement(); + std::vector read_measurement(); public: MMC5603NJ_Magn(I2CBus *bus); int init(); diff --git a/system/sensord/sensors/sensor.h b/system/sensord/sensors/sensor.h index 603aa3586e..1b0e3be0dc 100644 --- a/system/sensord/sensors/sensor.h +++ b/system/sensord/sensors/sensor.h @@ -5,14 +5,18 @@ class Sensor { public: int gpio_fd = -1; + uint64_t start_ts = 0; uint64_t init_delay = 500e6; // default dealy 500ms - virtual ~Sensor() {}; + virtual ~Sensor() {} virtual int init() = 0; virtual bool get_event(MessageBuilder &msg, uint64_t ts = 0) = 0; virtual bool has_interrupt_enabled() = 0; virtual int shutdown() = 0; - virtual bool is_data_valid(uint64_t st, uint64_t ct) { - return (ct - st) > init_delay; + virtual bool is_data_valid(uint64_t current_ts) { + if (start_ts == 0) { + start_ts = current_ts; + } + return (current_ts - start_ts) > init_delay; } }; diff --git a/system/sensord/sensors_qcom2.cc b/system/sensord/sensors_qcom2.cc index 349c67f498..36d9b4a13e 100644 --- a/system/sensord/sensors_qcom2.cc +++ b/system/sensord/sensors_qcom2.cc @@ -7,8 +7,10 @@ #include #include +#include "cereal/services.h" #include "cereal/messaging/messaging.h" #include "common/i2c.h" +#include "common/ratekeeper.h" #include "common/swaglog.h" #include "common/timing.h" #include "common/util.h" @@ -17,24 +19,26 @@ #include "system/sensord/sensors/bmx055_magn.h" #include "system/sensord/sensors/bmx055_temp.h" #include "system/sensord/sensors/constants.h" -#include "system/sensord/sensors/light_sensor.h" #include "system/sensord/sensors/lsm6ds3_accel.h" #include "system/sensord/sensors/lsm6ds3_gyro.h" #include "system/sensord/sensors/lsm6ds3_temp.h" #include "system/sensord/sensors/mmc5603nj_magn.h" -#include "system/sensord/sensors/sensor.h" #define I2C_BUS_IMU 1 ExitHandler do_exit; -uint64_t init_ts = 0; -void interrupt_loop(std::vector& sensors, - std::map& sensor_service) -{ - PubMaster pm_int({"gyroscope", "accelerometer"}); +void interrupt_loop(std::vector> sensors) { + PubMaster pm({"gyroscope", "accelerometer"}); + + int fd = -1; + for (auto &[sensor, msg_name] : sensors) { + if (sensor->has_interrupt_enabled()) { + fd = sensor->gpio_fd; + break; + } + } - int fd = sensors[0]->gpio_fd; struct pollfd fd_list[1] = {0}; fd_list[0].fd = fd; fd_list[0].events = POLLIN | POLLPRI; @@ -68,95 +72,63 @@ void interrupt_loop(std::vector& sensors, uint64_t offset = nanos_since_epoch() - nanos_since_boot(); uint64_t ts = evdata[num_events - 1].timestamp - offset; - for (Sensor *sensor : sensors) { + for (auto &[sensor, msg_name] : sensors) { + if (!sensor->has_interrupt_enabled()) { + continue; + } + MessageBuilder msg; if (!sensor->get_event(msg, ts)) { continue; } - if (!sensor->is_data_valid(init_ts, ts)) { + if (!sensor->is_data_valid(ts)) { continue; } - pm_int.send(sensor_service[sensor].c_str(), msg); + pm.send(msg_name.c_str(), msg); } } +} - // poweroff sensors, disable interrupts - for (Sensor *sensor : sensors) { - sensor->shutdown(); +void polling_loop(Sensor *sensor, std::string msg_name) { + PubMaster pm({msg_name.c_str()}); + RateKeeper rk(msg_name, services.at(msg_name).frequency); + while (!do_exit) { + MessageBuilder msg; + if (sensor->get_event(msg) && sensor->is_data_valid(nanos_since_boot())) { + pm.send(msg_name.c_str(), msg); + } + rk.keepTime(); } } int sensor_loop(I2CBus *i2c_bus_imu) { - BMX055_Accel bmx055_accel(i2c_bus_imu); - BMX055_Gyro bmx055_gyro(i2c_bus_imu); - BMX055_Magn bmx055_magn(i2c_bus_imu); - BMX055_Temp bmx055_temp(i2c_bus_imu); - - LSM6DS3_Accel lsm6ds3_accel(i2c_bus_imu, GPIO_LSM_INT); - LSM6DS3_Gyro lsm6ds3_gyro(i2c_bus_imu, GPIO_LSM_INT, true); // GPIO shared with accel - LSM6DS3_Temp lsm6ds3_temp(i2c_bus_imu); - - MMC5603NJ_Magn mmc5603nj_magn(i2c_bus_imu); - - LightSensor light("/sys/class/i2c-adapter/i2c-2/2-0038/iio:device1/in_intensity_both_raw"); - - std::map sensor_service = { - {&bmx055_accel, "accelerometer2"}, - {&bmx055_gyro, "gyroscope2"}, - {&bmx055_magn, "magnetometer"}, - {&bmx055_temp, "temperatureSensor"}, - - {&lsm6ds3_accel, "accelerometer"}, - {&lsm6ds3_gyro, "gyroscope"}, - {&lsm6ds3_temp, "temperatureSensor"}, - - {&mmc5603nj_magn, "magnetometer"}, - {&light, "lightSensor"} - }; - // Sensor init - std::vector> sensors_init; // Sensor, required - sensors_init.push_back({&bmx055_accel, false}); - sensors_init.push_back({&bmx055_gyro, false}); - sensors_init.push_back({&bmx055_magn, false}); - sensors_init.push_back({&bmx055_temp, false}); - - sensors_init.push_back({&lsm6ds3_accel, true}); - sensors_init.push_back({&lsm6ds3_gyro, true}); - sensors_init.push_back({&lsm6ds3_temp, true}); - - sensors_init.push_back({&mmc5603nj_magn, false}); + std::vector> sensors_init = { + {new BMX055_Accel(i2c_bus_imu), "accelerometer2"}, + {new BMX055_Gyro(i2c_bus_imu), "gyroscope2"}, + {new BMX055_Magn(i2c_bus_imu), "magnetometer"}, + {new BMX055_Temp(i2c_bus_imu), "temperatureSensor2"}, - sensors_init.push_back({&light, true}); + {new LSM6DS3_Accel(i2c_bus_imu, GPIO_LSM_INT), "accelerometer"}, + {new LSM6DS3_Gyro(i2c_bus_imu, GPIO_LSM_INT, true), "gyroscope"}, + {new LSM6DS3_Temp(i2c_bus_imu), "temperatureSensor"}, - bool has_magnetometer = false; + {new MMC5603NJ_Magn(i2c_bus_imu), "magnetometer"}, + }; // Initialize sensors - std::vector sensors; - for (auto &[sensor, required] : sensors_init) { + std::vector threads; + for (auto &[sensor, msg_name] : sensors_init) { int err = sensor->init(); if (err < 0) { - if (required) { - LOGE("Error initializing sensors"); - return -1; - } - } else { - - if (sensor == &bmx055_magn || sensor == &mmc5603nj_magn) { - has_magnetometer = true; - } - - if (!sensor->has_interrupt_enabled()) { - sensors.push_back(sensor); - } + continue; } - } - if (!has_magnetometer) { - LOGE("No magnetometer present"); - return -1; + if (!sensor->has_interrupt_enabled()) { + threads.emplace_back(polling_loop, sensor, msg_name); + } } // increase interrupt quality by pinning interrupt and process to core 1 @@ -164,41 +136,18 @@ int sensor_loop(I2CBus *i2c_bus_imu) { util::set_core_affinity({1}); std::system("sudo su -c 'echo 1 > /proc/irq/336/smp_affinity_list'"); - PubMaster pm_non_int({"gyroscope2", "accelerometer2", "temperatureSensor", - "lightSensor", "magnetometer"}); - init_ts = nanos_since_boot(); - // thread for reading events via interrupts - std::vector lsm_interrupt_sensors = {&lsm6ds3_accel, &lsm6ds3_gyro}; - std::thread lsm_interrupt_thread(&interrupt_loop, std::ref(lsm_interrupt_sensors), - std::ref(sensor_service)); - - // polling loop for non interrupt handled sensors - while (!do_exit) { - std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); - - for (Sensor *sensor : sensors) { - MessageBuilder msg; - if (!sensor->get_event(msg)) { - continue; - } + threads.emplace_back(&interrupt_loop, std::ref(sensors_init)); - if (!sensor->is_data_valid(init_ts, nanos_since_boot())) { - continue; - } - - pm_non_int.send(sensor_service[sensor].c_str(), msg); - } - - std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); - std::this_thread::sleep_for(std::chrono::milliseconds(10) - (end - begin)); + // wait for all threads to finish + for (auto &t : threads) { + t.join(); } - for (Sensor *sensor : sensors) { + for (auto &[sensor, msg_name] : sensors_init) { sensor->shutdown(); + delete sensor; } - - lsm_interrupt_thread.join(); return 0; } diff --git a/system/sensord/tests/test_pigeond.py b/system/sensord/tests/test_pigeond.py index 9519183aac..90cbf06340 100755 --- a/system/sensord/tests/test_pigeond.py +++ b/system/sensord/tests/test_pigeond.py @@ -4,11 +4,11 @@ import unittest import cereal.messaging as messaging from cereal.services import service_list -from common.gpio import gpio_read -from selfdrive.test.helpers import with_processes -from selfdrive.manager.process_config import managed_processes -from system.hardware import TICI -from system.hardware.tici.pins import GPIO +from openpilot.common.gpio import gpio_read +from openpilot.selfdrive.test.helpers import with_processes +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.hardware import TICI +from openpilot.system.hardware.tici.pins import GPIO # TODO: test TTFF when we have good A-GNSS @@ -56,7 +56,7 @@ class TestPigeond(unittest.TestCase): managed_processes['pigeond'].stop() assert gpio_read(GPIO.UBLOX_RST_N) == 0 - assert gpio_read(GPIO.UBLOX_PWR_EN) == 0 + assert gpio_read(GPIO.GNSS_PWR_EN) == 0 if __name__ == "__main__": diff --git a/system/sensord/tests/test_sensord.py b/system/sensord/tests/test_sensord.py index eea527ed63..dbe28ab9db 100755 --- a/system/sensord/tests/test_sensord.py +++ b/system/sensord/tests/test_sensord.py @@ -7,9 +7,11 @@ from collections import namedtuple, defaultdict import cereal.messaging as messaging from cereal import log -from common.gpio import get_irqs_for_action -from system.hardware import TICI -from selfdrive.manager.process_config import managed_processes +from cereal.services import service_list +from openpilot.common.gpio import get_irqs_for_action +from openpilot.common.timeout import Timeout +from openpilot.system.hardware import TICI +from openpilot.selfdrive.manager.process_config import managed_processes BMX = { ('bmx055', 'acceleration'), @@ -29,45 +31,37 @@ MMC = { ('mmc5603nj', 'magneticUncalibrated'), } -RPR = { - ('rpr0521', 'light'), -} - SENSOR_CONFIGURATIONS = ( - (BMX | LSM | RPR), - (MMC | LSM | RPR), - (BMX | LSM_C | RPR), - (MMC| LSM_C | RPR), + (BMX | LSM), + (MMC | LSM), + (BMX | LSM_C), + (MMC| LSM_C), ) Sensor = log.SensorEventData.SensorSource -SensorConfig = namedtuple('SensorConfig', ['type', 'sanity_min', 'sanity_max', 'expected_freq']) +SensorConfig = namedtuple('SensorConfig', ['type', 'sanity_min', 'sanity_max']) ALL_SENSORS = { - Sensor.rpr0521: { - SensorConfig("light", 0, 1023, 100), - }, - Sensor.lsm6ds3: { - SensorConfig("acceleration", 5, 15, 100), - SensorConfig("gyroUncalibrated", 0, .2, 100), - SensorConfig("temperature", 0, 60, 100), + SensorConfig("acceleration", 5, 15), + SensorConfig("gyroUncalibrated", 0, .2), + SensorConfig("temperature", 0, 60), }, Sensor.lsm6ds3trc: { - SensorConfig("acceleration", 5, 15, 104), - SensorConfig("gyroUncalibrated", 0, .2, 104), - SensorConfig("temperature", 0, 60, 100), + SensorConfig("acceleration", 5, 15), + SensorConfig("gyroUncalibrated", 0, .2), + SensorConfig("temperature", 0, 60), }, Sensor.bmx055: { - SensorConfig("acceleration", 5, 15, 100), - SensorConfig("gyroUncalibrated", 0, .2, 100), - SensorConfig("magneticUncalibrated", 0, 300, 100), - SensorConfig("temperature", 0, 60, 100), + SensorConfig("acceleration", 5, 15), + SensorConfig("gyroUncalibrated", 0, .2), + SensorConfig("magneticUncalibrated", 0, 300), + SensorConfig("temperature", 0, 60), }, Sensor.mmc5603nj: { - SensorConfig("magneticUncalibrated", 0, 300, 100), + SensorConfig("magneticUncalibrated", 0, 300), } } @@ -79,21 +73,30 @@ def get_irq_count(irq: int): def read_sensor_events(duration_sec): sensor_types = ['accelerometer', 'gyroscope', 'magnetometer', 'accelerometer2', - 'gyroscope2', 'lightSensor', 'temperatureSensor'] - esocks = {} + 'gyroscope2', 'temperatureSensor', 'temperatureSensor2'] + socks = {} + poller = messaging.Poller() events = defaultdict(list) for stype in sensor_types: - esocks[stype] = messaging.sub_sock(stype, timeout=0.1) - - start_time_sec = time.monotonic() - while time.monotonic() - start_time_sec < duration_sec: - for esock in esocks: - events[esock] += messaging.drain_sock(esocks[esock]) + socks[stype] = messaging.sub_sock(stype, poller=poller, timeout=100) + + # wait for sensors to come up + with Timeout(int(os.environ.get("SENSOR_WAIT", "5")), "sensors didn't come up"): + while len(poller.poll(250)) == 0: + pass + time.sleep(1) + for s in socks.values(): + messaging.drain_sock_raw(s) + + st = time.monotonic() + while time.monotonic() - st < duration_sec: + for s in socks: + events[s] += messaging.drain_sock(socks[s]) time.sleep(0.1) assert sum(map(len, events.values())) != 0, "No sensor events collected!" - return events + return {k: v for k, v in events.items() if len(v) > 0} class TestSensord(unittest.TestCase): @classmethod @@ -108,8 +111,7 @@ class TestSensord(unittest.TestCase): os.system("pkill -f ./_sensord") try: managed_processes["sensord"].start() - time.sleep(3) - cls.sample_secs = 10 + cls.sample_secs = int(os.getenv("SAMPLE_SECS", "10")) cls.events = read_sensor_events(cls.sample_secs) # determine sensord's irq @@ -127,7 +129,6 @@ class TestSensord(unittest.TestCase): def test_sensors_present(self): # verify correct sensors configuration - seen = set() for etype in self.events: for measurement in self.events[etype]: @@ -167,22 +168,12 @@ class TestSensord(unittest.TestCase): stddev = np.std(tdiffs) assert stddev < 2.0, f"Standard-dev to big {stddev}" - def test_events_check(self): - # verify if all sensors produce events - - sensor_events = dict() - for etype in self.events: - for measurement in self.events[etype]: - m = getattr(measurement, measurement.which()) - - if m.type in sensor_events: - sensor_events[m.type] += 1 - else: - sensor_events[m.type] = 1 - - for s in sensor_events: - err_msg = f"Sensor {s}: 200 < {sensor_events[s]}" - assert sensor_events[s] > 200, err_msg + def test_sensor_frequency(self): + for s, msgs in self.events.items(): + with self.subTest(sensor=s): + freq = len(msgs) / self.sample_secs + ef = service_list[s].frequency + assert ef*0.85 <= freq <= ef*1.15 def test_logmonottime_timestamp_diff(self): # ensure diff between the message logMonotime and sample timestamp is small @@ -201,16 +192,14 @@ class TestSensord(unittest.TestCase): # before the sensor is read tdiffs.append(abs(measurement.logMonoTime - m.timestamp) / 1e6) - high_delay_diffs = set(filter(lambda d: d >= 15., tdiffs)) - assert len(high_delay_diffs) < 20, f"Too many measurements published : {high_delay_diffs}" + # some sensors have a read procedure that will introduce an expected diff on the order of 20ms + high_delay_diffs = set(filter(lambda d: d >= 25., tdiffs)) + assert len(high_delay_diffs) < 20, f"Too many measurements published: {high_delay_diffs}" avg_diff = round(sum(tdiffs)/len(tdiffs), 4) assert avg_diff < 4, f"Avg packet diff: {avg_diff:.1f}ms" - stddev = np.std(tdiffs) - assert stddev < 2, f"Timing diffs have too high stddev: {stddev}" - - def test_sensor_values_sanity_check(self): + def test_sensor_values(self): sensor_values = dict() for etype in self.events: for measurement in self.events[etype]: @@ -227,18 +216,13 @@ class TestSensord(unittest.TestCase): else: sensor_values[key] = [values] - # Sanity check sensor values and counts + # Sanity check sensor values for sensor, stype in sensor_values: for s in ALL_SENSORS[sensor]: if s.type != stype: continue key = (sensor, s.type) - val_cnt = len(sensor_values[key]) - min_samples = self.sample_secs * s.expected_freq - err_msg = f"Sensor {sensor} {s.type} got {val_cnt} measurements, expected {min_samples}" - assert min_samples*0.9 < val_cnt < min_samples*1.1, err_msg - mean_norm = np.mean(np.linalg.norm(sensor_values[key], axis=1)) err_msg = f"Sensor '{sensor} {s.type}' failed sanity checks {mean_norm} is not between {s.sanity_min} and {s.sanity_max}" assert s.sanity_min <= mean_norm <= s.sanity_max, err_msg diff --git a/system/sensord/tests/ttff_test.py b/system/sensord/tests/ttff_test.py index e2cbc6d144..e023489ed5 100755 --- a/system/sensord/tests/ttff_test.py +++ b/system/sensord/tests/ttff_test.py @@ -4,7 +4,7 @@ import time import atexit from cereal import messaging -from selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.manager.process_config import managed_processes TIMEOUT = 10*60 diff --git a/system/swaglog.py b/system/swaglog.py index 28beb5a4d1..ba81318234 100644 --- a/system/swaglog.py +++ b/system/swaglog.py @@ -7,17 +7,13 @@ from logging.handlers import BaseRotatingHandler import zmq -from common.logging_extra import SwagLogger, SwagFormatter, SwagLogFileFormatter -from system.hardware import PC +from openpilot.common.logging_extra import SwagLogger, SwagFormatter, SwagLogFileFormatter +from openpilot.system.hardware.hw import Paths -if PC: - SWAGLOG_DIR = os.path.join(str(Path.home()), ".comma", "log") -else: - SWAGLOG_DIR = "/data/log/" def get_file_handler(): - Path(SWAGLOG_DIR).mkdir(parents=True, exist_ok=True) - base_filename = os.path.join(SWAGLOG_DIR, "swaglog") + Path(Paths.swaglog_root()).mkdir(parents=True, exist_ok=True) + base_filename = os.path.join(Paths.swaglog_root(), "swaglog") handler = SwaglogRotatingFileHandler(base_filename) return handler @@ -77,6 +73,9 @@ class UnixDomainSocketHandler(logging.Handler): self.sock = None def __del__(self): + self.close() + + def close(self): if self.sock is not None: self.sock.close() if self.zctx is not None: @@ -86,7 +85,7 @@ class UnixDomainSocketHandler(logging.Handler): self.zctx = zmq.Context() self.sock = self.zctx.socket(zmq.PUSH) self.sock.setsockopt(zmq.LINGER, 10) - self.sock.connect("ipc:///tmp/logmessage") + self.sock.connect(Paths.swaglog_ipc()) self.pid = os.getpid() def emit(self, record): @@ -129,6 +128,8 @@ elif print_level == 'info': elif print_level == 'warning': outhandler.setLevel(logging.WARNING) +ipchandler = UnixDomainSocketHandler(SwagFormatter(log)) + log.addHandler(outhandler) # logs are sent through IPC before writing to disk to prevent disk I/O blocking -log.addHandler(UnixDomainSocketHandler(SwagFormatter(log))) +log.addHandler(ipchandler) diff --git a/system/tests/test_logmessaged.py b/system/tests/test_logmessaged.py index 08335517ae..5d0e1bfc82 100755 --- a/system/tests/test_logmessaged.py +++ b/system/tests/test_logmessaged.py @@ -1,27 +1,27 @@ #!/usr/bin/env python3 import glob import os -import shutil import time import unittest import cereal.messaging as messaging -from selfdrive.manager.process_config import managed_processes -from system.swaglog import cloudlog, SWAGLOG_DIR +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.system.hardware.hw import Paths +from openpilot.system.swaglog import cloudlog, ipchandler class TestLogmessaged(unittest.TestCase): - def setUp(self): - if os.path.exists(SWAGLOG_DIR): - shutil.rmtree(SWAGLOG_DIR) + # clear the IPC buffer in case some other tests used cloudlog and filled it + ipchandler.close() + ipchandler.connect() managed_processes['logmessaged'].start() self.sock = messaging.sub_sock("logMessage", timeout=1000, conflate=False) self.error_sock = messaging.sub_sock("logMessage", timeout=1000, conflate=False) # ensure sockets are connected - time.sleep(0.2) + time.sleep(1) messaging.drain_sock(self.sock) messaging.drain_sock(self.error_sock) @@ -31,7 +31,7 @@ class TestLogmessaged(unittest.TestCase): managed_processes['logmessaged'].stop(block=True) def _get_log_files(self): - return list(glob.glob(os.path.join(SWAGLOG_DIR, "swaglog.*"))) + return list(glob.glob(os.path.join(Paths.swaglog_root(), "swaglog.*"))) def test_simple_log(self): msgs = [f"abc {i}" for i in range(10)] diff --git a/system/timezoned.py b/system/timezoned.py index 884a5c3812..91424d33b5 100755 --- a/system/timezoned.py +++ b/system/timezoned.py @@ -8,9 +8,12 @@ from typing import NoReturn import requests from timezonefinder import TimezoneFinder -from common.params import Params -from system.hardware import AGNOS -from system.swaglog import cloudlog +from openpilot.common.params import Params +from openpilot.system.hardware import AGNOS +from openpilot.system.swaglog import cloudlog +from openpilot.system.version import get_version + +REQUEST_HEADERS = {'User-Agent': "openpilot-" + get_version()} def set_timezone(valid_timezones, timezone): @@ -18,7 +21,7 @@ def set_timezone(valid_timezones, timezone): cloudlog.error(f"Timezone not supported {timezone}") return - cloudlog.debug(f"Setting timezone to {timezone}") + cloudlog.info(f"Setting timezone to {timezone}") try: if AGNOS: tzpath = os.path.join("/usr/share/zoneinfo/", timezone) @@ -58,7 +61,7 @@ def main() -> NoReturn: if location is None: cloudlog.debug("Setting timezone based on IP lookup") try: - r = requests.get("https://ipapi.co/timezone", timeout=10) + r = requests.get("https://ipapi.co/timezone", headers=REQUEST_HEADERS, timeout=10) if r.status_code == 200: set_timezone(valid_timezones, r.text) else: diff --git a/system/ubloxd/SConscript b/system/ubloxd/SConscript index fff0986efd..67d9856dad 100644 --- a/system/ubloxd/SConscript +++ b/system/ubloxd/SConscript @@ -16,5 +16,5 @@ if GetOption('kaitai'): glonass_obj = env.Object('generated/glonass.cpp') env.Program("ubloxd", ["ubloxd.cc", "ublox_msg.cc", "generated/ubx.cpp", "generated/gps.cpp", glonass_obj], LIBS=loc_libs) -if GetOption('test'): +if GetOption('extras'): env.Program("tests/test_glonass_runner", ['tests/test_glonass_runner.cc', 'tests/test_glonass_kaitai.cc', glonass_obj], LIBS=[loc_libs]) \ No newline at end of file diff --git a/system/ubloxd/tests/test_glonass_kaitai.cc b/system/ubloxd/tests/test_glonass_kaitai.cc index 5ad274142a..96f43742b4 100644 --- a/system/ubloxd/tests/test_glonass_kaitai.cc +++ b/system/ubloxd/tests/test_glonass_kaitai.cc @@ -101,7 +101,7 @@ std::string generate_inp_data(string_data& data) { string_data.reserve(16); for (int i = 0; i < 128; i+=8) { std::string substr = inp_data.substr(i, 8); - string_data.push_back( (uint8_t)std::stoi(substr.c_str(), 0, 2)); + string_data.push_back((uint8_t)std::stoi(substr.c_str(), 0, 2)); } return string_data; diff --git a/system/ubloxd/tests/test_ublox_processing.py b/system/ubloxd/tests/test_ublox_processing.py index cd4ce0de04..311604881a 100755 --- a/system/ubloxd/tests/test_ublox_processing.py +++ b/system/ubloxd/tests/test_ublox_processing.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import unittest import time import numpy as np @@ -6,9 +7,10 @@ from laika import AstroDog from laika.helpers import ConstellationId from laika.raw_gnss import correct_measurements, process_measurements, read_raw_ublox from laika.opt import calc_pos_fix -from selfdrive.test.openpilotci import get_url -from tools.lib.logreader import LogReader -from selfdrive.test.helpers import with_processes +from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.system.hardware.hw import Paths +from openpilot.tools.lib.logreader import LogReader +from openpilot.selfdrive.test.helpers import with_processes import cereal.messaging as messaging def get_gnss_measurements(log_reader): @@ -55,7 +57,7 @@ class TestUbloxProcessing(unittest.TestCase): self.assertEqual(count_glonass, 3651) def test_get_fix(self): - dog = AstroDog() + dog = AstroDog(cache_dir=Paths.download_cache_root()) position_fix_found = 0 count_processed_measurements = 0 count_corrected_measurements = 0 @@ -97,7 +99,7 @@ class TestUbloxProcessing(unittest.TestCase): rcv_msgs = [] for msg in self.ublox_raw: ur_pm.send(msg.which(), msg.as_builder()) - time.sleep(0.01) + time.sleep(0.001) rcv_msgs += messaging.drain_sock(ugs) time.sleep(0.1) diff --git a/system/ubloxd/tests/ublox.py b/system/ubloxd/tests/ublox.py deleted file mode 100644 index eeb6cde30a..0000000000 --- a/system/ubloxd/tests/ublox.py +++ /dev/null @@ -1,939 +0,0 @@ -#!/usr/bin/env python3 -# pylint: skip-file -''' -UBlox binary protocol handling - -Copyright Andrew Tridgell, October 2012 -Released under GNU GPL version 3 or later - -WARNING: This code has originally intended for -ublox version 7, it has been adapted to work -for ublox version 8, not all functions may work. -''' - - -import struct -import time - -# protocol constants -PREAMBLE1 = 0xb5 -PREAMBLE2 = 0x62 - -# message classes -CLASS_NAV = 0x01 -CLASS_RXM = 0x02 -CLASS_INF = 0x04 -CLASS_ACK = 0x05 -CLASS_CFG = 0x06 -CLASS_MON = 0x0A -CLASS_AID = 0x0B -CLASS_TIM = 0x0D -CLASS_ESF = 0x10 - -# ACK messages -MSG_ACK_NACK = 0x00 -MSG_ACK_ACK = 0x01 - -# NAV messages -MSG_NAV_POSECEF = 0x1 -MSG_NAV_POSLLH = 0x2 -MSG_NAV_STATUS = 0x3 -MSG_NAV_DOP = 0x4 -MSG_NAV_SOL = 0x6 -MSG_NAV_PVT = 0x7 -MSG_NAV_POSUTM = 0x8 -MSG_NAV_VELNED = 0x12 -MSG_NAV_VELECEF = 0x11 -MSG_NAV_TIMEGPS = 0x20 -MSG_NAV_TIMEUTC = 0x21 -MSG_NAV_CLOCK = 0x22 -MSG_NAV_SVINFO = 0x30 -MSG_NAV_SAT = 0x35 -MSG_NAV_AOPSTATUS = 0x60 -MSG_NAV_DGPS = 0x31 -MSG_NAV_DOP = 0x04 -MSG_NAV_EKFSTATUS = 0x40 -MSG_NAV_SBAS = 0x32 -MSG_NAV_SOL = 0x06 -MSG_NAV_SAT = 0x35 - -# RXM messages -MSG_RXM_RAW = 0x15 -MSG_RXM_SFRB = 0x11 -MSG_RXM_SFRBX = 0x13 -MSG_RXM_SVSI = 0x20 -MSG_RXM_EPH = 0x31 -MSG_RXM_ALM = 0x30 -MSG_RXM_PMREQ = 0x41 - -# AID messages -MSG_AID_ALM = 0x30 -MSG_AID_EPH = 0x31 -MSG_AID_ALPSRV = 0x32 -MSG_AID_AOP = 0x33 -MSG_AID_DATA = 0x10 -MSG_AID_ALP = 0x50 -MSG_AID_DATA = 0x10 -MSG_AID_HUI = 0x02 -MSG_AID_INI = 0x01 -MSG_AID_REQ = 0x00 - -# CFG messages -MSG_CFG_PRT = 0x00 -MSG_CFG_ANT = 0x13 -MSG_CFG_DAT = 0x06 -MSG_CFG_EKF = 0x12 -MSG_CFG_ESFGWT = 0x29 -MSG_CFG_CFG = 0x09 -MSG_CFG_USB = 0x1b -MSG_CFG_RATE = 0x08 -MSG_CFG_SET_RATE = 0x01 -MSG_CFG_NAV5 = 0x24 -MSG_CFG_FXN = 0x0E -MSG_CFG_INF = 0x02 -MSG_CFG_ITFM = 0x39 -MSG_CFG_MSG = 0x01 -MSG_CFG_NAVX5 = 0x23 -MSG_CFG_NMEA = 0x17 -MSG_CFG_NVS = 0x22 -MSG_CFG_PM2 = 0x3B -MSG_CFG_PM = 0x32 -MSG_CFG_ITMF = 0x39 -MSG_CFG_RINV = 0x34 -MSG_CFG_RST = 0x04 -MSG_CFG_RXM = 0x11 -MSG_CFG_SBAS = 0x16 -MSG_CFG_TMODE2 = 0x3D -MSG_CFG_TMODE = 0x1D -MSG_CFG_TPS = 0x31 -MSG_CFG_TP = 0x07 -MSG_CFG_GNSS = 0x3E -MSG_CFG_ODO = 0x1E - -# ESF messages -MSG_ESF_MEAS = 0x02 -MSG_ESF_STATUS = 0x10 - -# INF messages -MSG_INF_DEBUG = 0x04 -MSG_INF_ERROR = 0x00 -MSG_INF_NOTICE = 0x02 -MSG_INF_TEST = 0x03 -MSG_INF_WARNING = 0x01 - -# MON messages -MSG_MON_SCHD = 0x01 -MSG_MON_HW = 0x09 -MSG_MON_HW2 = 0x0B -MSG_MON_IO = 0x02 -MSG_MON_MSGPP = 0x06 -MSG_MON_RXBUF = 0x07 -MSG_MON_RXR = 0x21 -MSG_MON_TXBUF = 0x08 -MSG_MON_VER = 0x04 - -# TIM messages -MSG_TIM_TP = 0x01 -MSG_TIM_TM2 = 0x03 -MSG_TIM_SVIN = 0x04 -MSG_TIM_VRFY = 0x06 - -# port IDs -PORT_DDC = 0 -PORT_SERIAL1 = 1 -PORT_SERIAL2 = 2 -PORT_USB = 3 -PORT_SPI = 4 - -# dynamic models -DYNAMIC_MODEL_PORTABLE = 0 -DYNAMIC_MODEL_STATIONARY = 2 -DYNAMIC_MODEL_PEDESTRIAN = 3 -DYNAMIC_MODEL_AUTOMOTIVE = 4 -DYNAMIC_MODEL_SEA = 5 -DYNAMIC_MODEL_AIRBORNE1G = 6 -DYNAMIC_MODEL_AIRBORNE2G = 7 -DYNAMIC_MODEL_AIRBORNE4G = 8 - -#reset items -RESET_HOT = 0 -RESET_WARM = 1 -RESET_COLD = 0xFFFF - -RESET_HW = 0 -RESET_SW = 1 -RESET_SW_GPS = 2 -RESET_HW_GRACEFUL = 4 -RESET_GPS_STOP = 8 -RESET_GPS_START = 9 - - -class UBloxError(Exception): - '''Ublox error class''' - - def __init__(self, msg): - Exception.__init__(self, msg) - self.message = msg - - -class UBloxAttrDict(dict): - '''allow dictionary members as attributes''' - - def __init__(self): - dict.__init__(self) - - def __getattr__(self, name): - try: - return self.__getitem__(name) - except KeyError as e: - raise RuntimeError(f"ublock invalid attr: {name}") from e - - def __setattr__(self, name, value): - if name in self.__dict__: - # allow set on normal attributes - dict.__setattr__(self, name, value) - else: - self.__setitem__(name, value) - - -def ArrayParse(field): - '''parse an array descriptor''' - arridx = field.find('[') - if arridx == -1: - return (field, -1) - alen = int(field[arridx + 1:-1]) - fieldname = field[:arridx] - return (fieldname, alen) - - -class UBloxDescriptor: - '''class used to describe the layout of a UBlox message''' - - def __init__(self, - name, - msg_format, - fields=None, - count_field=None, - format2=None, - fields2=None): - if fields is None: - fields = [] - - self.name = name - self.msg_format = msg_format - self.fields = fields - self.count_field = count_field - self.format2 = format2 - self.fields2 = fields2 - - def unpack(self, msg): - '''unpack a UBloxMessage, creating the .fields and ._recs attributes in msg''' - msg._fields = {} - - # unpack main message blocks. A comm - formats = self.msg_format.split(',') - buf = msg._buf[6:-2] - count = 0 - msg._recs = [] - fields = self.fields[:] - - for fmt in formats: - size1 = struct.calcsize(fmt) - if size1 > len(buf): - raise UBloxError("%s INVALID_SIZE1=%u" % (self.name, len(buf))) - f1 = list(struct.unpack(fmt, buf[:size1])) - i = 0 - while i < len(f1): - field = fields.pop(0) - (fieldname, alen) = ArrayParse(field) - if alen == -1: - msg._fields[fieldname] = f1[i] - if self.count_field == fieldname: - count = int(f1[i]) - i += 1 - else: - msg._fields[fieldname] = [0] * alen - for a in range(alen): - msg._fields[fieldname][a] = f1[i] - i += 1 - buf = buf[size1:] - if len(buf) == 0: - break - - if self.count_field == '_remaining': - count = len(buf) // struct.calcsize(self.format2) - - if count == 0: - msg._unpacked = True - if len(buf) != 0: - raise UBloxError("EXTRA_BYTES=%u" % len(buf)) - return - - size2 = struct.calcsize(self.format2) - for _ in range(count): - r = UBloxAttrDict() - if size2 > len(buf): - raise UBloxError("INVALID_SIZE=%u, " % len(buf)) - f2 = list(struct.unpack(self.format2, buf[:size2])) - for i in range(len(self.fields2)): - r[self.fields2[i]] = f2[i] - buf = buf[size2:] - msg._recs.append(r) - if len(buf) != 0: - raise UBloxError("EXTRA_BYTES=%u" % len(buf)) - msg._unpacked = True - - def pack(self, msg, msg_class=None, msg_id=None): - '''pack a UBloxMessage from the .fields and ._recs attributes in msg''' - f1 = [] - if msg_class is None: - msg_class = msg.msg_class() - if msg_id is None: - msg_id = msg.msg_id() - msg._buf = '' - - fields = self.fields[:] - for f in fields: - (fieldname, alen) = ArrayParse(f) - if fieldname not in msg._fields: - break - if alen == -1: - f1.append(msg._fields[fieldname]) - else: - for a in range(alen): - f1.append(msg._fields[fieldname][a]) - try: - # try full length message - fmt = self.msg_format.replace(',', '') - msg._buf = struct.pack(fmt, *tuple(f1)) - except Exception: - # try without optional part - fmt = self.msg_format.split(',')[0] - msg._buf = struct.pack(fmt, *tuple(f1)) - - length = len(msg._buf) - if msg._recs: - length += len(msg._recs) * struct.calcsize(self.format2) - header = struct.pack('= level: - print(msg) - - def unpack(self): - '''unpack a message''' - if not self.valid(): - raise UBloxError('INVALID MESSAGE') - msg_type = self.msg_type() - if msg_type not in msg_types: - raise UBloxError('Unknown message %s length=%u' % (str(msg_type), len(self._buf))) - msg_types[msg_type].unpack(self) - return self._fields, self._recs - - def pack(self): - '''pack a message''' - if not self.valid(): - raise UBloxError('INVALID MESSAGE') - msg_type = self.msg_type() - if msg_type not in msg_types: - raise UBloxError('Unknown message %s' % str(msg_type)) - msg_types[msg_type].pack(self) - - def name(self): - '''return the short string name for a message''' - if not self.valid(): - raise UBloxError('INVALID MESSAGE') - msg_type = self.msg_type() - if msg_type not in msg_types: - raise UBloxError('Unknown message %s length=%u' % (str(msg_types), len(self._buf))) - return msg_types[msg_type].name - - def msg_class(self): - '''return the message class''' - return self._buf[2] - - def msg_id(self): - '''return the message id within the class''' - return self._buf[3] - - def msg_type(self): - '''return the message type tuple (class, id)''' - return (self.msg_class(), self.msg_id()) - - def msg_length(self): - '''return the payload length''' - (payload_length, ) = struct.unpack(' 0 and self._buf[0] != PREAMBLE1: - return False - if len(self._buf) > 1 and self._buf[1] != PREAMBLE2: - self.debug(1, "bad pre2") - return False - if self.needed_bytes() == 0 and not self.valid(): - if len(self._buf) > 8: - self.debug(1, "bad checksum len=%u needed=%u" % (len(self._buf), - self.needed_bytes())) - else: - self.debug(1, "bad len len=%u needed=%u" % (len(self._buf), self.needed_bytes())) - return False - return True - - def add(self, data): - '''add some bytes to a message''' - self._buf += data - while not self.valid_so_far() and len(self._buf) > 0: - '''handle corrupted streams''' - self._buf = self._buf[1:] - if self.needed_bytes() < 0: - self._buf = "" - - def checksum(self, data=None): - '''return a checksum tuple for a message''' - if data is None: - data = self._buf[2:-2] - #cs = 0 - ck_a = 0 - ck_b = 0 - for i in data: - ck_a = (ck_a + i) & 0xFF - ck_b = (ck_b + ck_a) & 0xFF - return (ck_a, ck_b) - - def valid_checksum(self): - '''check if the checksum is OK''' - (ck_a, ck_b) = self.checksum() - #d = self._buf[2:-2] - (ck_a2, ck_b2) = struct.unpack('= 8 and self.needed_bytes() == 0 and self.valid_checksum() - - -class UBlox: - def __init__(self, dev, baudrate): - self.dev = dev - self.baudrate = baudrate - - self.use_sendrecv = False - self.read_only = False - self.debug_level = 0 - - self.logfile = None - self.log = None - self.preferred_dynamic_model = None - self.preferred_usePPP = None - self.preferred_dgps_timeout = None - - def close(self): - '''close the device''' - self.dev.close() - self.dev = None - - def set_debug(self, debug_level): - '''set debug level''' - self.debug_level = debug_level - - def debug(self, level, msg): - '''write a debug message''' - if self.debug_level >= level: - print(msg) - - def set_logfile(self, logfile, append=False): - '''setup logging to a file''' - if self.log is not None: - self.log.close() - self.log = None - self.logfile = logfile - if self.logfile is not None: - if append: - mode = 'ab' - else: - mode = 'wb' - self.log = open(self.logfile, mode=mode) - - def set_preferred_dynamic_model(self, model): - '''set the preferred dynamic model for receiver''' - self.preferred_dynamic_model = model - if model is not None: - self.configure_poll(CLASS_CFG, MSG_CFG_NAV5) - - def set_preferred_dgps_timeout(self, timeout): - '''set the preferred DGPS timeout for receiver''' - self.preferred_dgps_timeout = timeout - if timeout is not None: - self.configure_poll(CLASS_CFG, MSG_CFG_NAV5) - - def set_preferred_usePPP(self, usePPP): - '''set the preferred usePPP setting for the receiver''' - if usePPP is None: - self.preferred_usePPP = None - return - self.preferred_usePPP = int(usePPP) - self.configure_poll(CLASS_CFG, MSG_CFG_NAVX5) - - def nmea_checksum(self, msg): - d = msg[1:] - cs = 0 - for i in d: - cs ^= ord(i) - return cs - - def write(self, buf): - '''write some bytes''' - if not self.read_only: - if self.use_sendrecv: - return self.dev.send(buf) - if isinstance(buf, str): - return self.dev.write(str.encode(buf)) - else: - return self.dev.write(buf) - - def read(self, n): - '''read some bytes''' - if self.use_sendrecv: - try: - return self.dev.recv(n) - except OSError: - return '' - return self.dev.read(n) - - def send_nmea(self, msg): - if not self.read_only: - s = msg + "*%02X" % self.nmea_checksum(msg) + "\r\n" - self.write(s) - - def set_binary(self): - '''put a UBlox into binary mode using a NMEA string''' - if not self.read_only: - print("try set binary at %u" % self.baudrate) - self.send_nmea("$PUBX,41,0,0007,0001,%u,0" % self.baudrate) - self.send_nmea("$PUBX,41,1,0007,0001,%u,0" % self.baudrate) - self.send_nmea("$PUBX,41,2,0007,0001,%u,0" % self.baudrate) - self.send_nmea("$PUBX,41,3,0007,0001,%u,0" % self.baudrate) - self.send_nmea("$PUBX,41,4,0007,0001,%u,0" % self.baudrate) - self.send_nmea("$PUBX,41,5,0007,0001,%u,0" % self.baudrate) - - def disable_nmea(self): - ''' stop sending all types of nmea messages ''' - self.send_nmea("$PUBX,40,GSV,1,1,1,1,1,0") - self.send_nmea("$PUBX,40,GGA,0,0,0,0,0,0") - self.send_nmea("$PUBX,40,GSA,0,0,0,0,0,0") - self.send_nmea("$PUBX,40,VTG,0,0,0,0,0,0") - self.send_nmea("$PUBX,40,TXT,0,0,0,0,0,0") - self.send_nmea("$PUBX,40,RMC,0,0,0,0,0,0") - - def seek_percent(self, pct): - '''seek to the given percentage of a file''' - self.dev.seek(0, 2) - filesize = self.dev.tell() - self.dev.seek(pct * 0.01 * filesize) - - def special_handling(self, msg): - '''handle automatic configuration changes''' - if msg.name() == 'CFG_NAV5': - msg.unpack() - sendit = False - pollit = False - if self.preferred_dynamic_model is not None and msg.dynModel != self.preferred_dynamic_model: - msg.dynModel = self.preferred_dynamic_model - sendit = True - pollit = True - if self.preferred_dgps_timeout is not None and msg.dgpsTimeOut != self.preferred_dgps_timeout: - msg.dgpsTimeOut = self.preferred_dgps_timeout - self.debug(2, "Setting dgpsTimeOut=%u" % msg.dgpsTimeOut) - sendit = True - # we don't re-poll for this one, as some receivers refuse to set it - if sendit: - msg.pack() - self.send(msg) - if pollit: - self.configure_poll(CLASS_CFG, MSG_CFG_NAV5) - if msg.name() == 'CFG_NAVX5' and self.preferred_usePPP is not None: - msg.unpack() - if msg.usePPP != self.preferred_usePPP: - msg.usePPP = self.preferred_usePPP - msg.mask = 1 << 13 - msg.pack() - self.send(msg) - self.configure_poll(CLASS_CFG, MSG_CFG_NAVX5) - - def receive_message(self, ignore_eof=False): - '''blocking receive of one ublox message''' - msg = UBloxMessage() - while True: - n = msg.needed_bytes() - b = self.read(n) - if not b: - if ignore_eof: - time.sleep(0.01) - continue - if len(msg._buf) > 0: - self.debug(1, "dropping %d bytes" % len(msg._buf)) - return None - msg.add(b) - if self.log is not None: - self.log.write(b) - self.log.flush() - if msg.valid(): - self.special_handling(msg) - return msg - - def receive_message_noerror(self, ignore_eof=False): - '''blocking receive of one ublox message, ignoring errors''' - try: - return self.receive_message(ignore_eof=ignore_eof) - except UBloxError as e: - print(e) - return None - except OSError as e: - # Occasionally we get hit with 'resource temporarily unavailable' - # messages here on the serial device, catch them too. - print(e) - return None - - def send(self, msg): - '''send a preformatted ublox message''' - if not msg.valid(): - self.debug(1, "invalid send") - return - if not self.read_only: - self.write(msg._buf) - - def send_message(self, msg_class, msg_id, payload): - '''send a ublox message with class, id and payload''' - msg = UBloxMessage() - msg._buf = struct.pack(' +#include #include #include #include @@ -9,6 +10,7 @@ #include #include #include +#include #include "common/swaglog.h" @@ -21,26 +23,26 @@ inline static bool bit_to_bool(uint8_t val, int shifts) { inline int UbloxMsgParser::needed_bytes() { // Msg header incomplete? - if(bytes_in_parse_buf < ublox::UBLOX_HEADER_SIZE) + if (bytes_in_parse_buf < ublox::UBLOX_HEADER_SIZE) return ublox::UBLOX_HEADER_SIZE + ublox::UBLOX_CHECKSUM_SIZE - bytes_in_parse_buf; uint16_t needed = UBLOX_MSG_SIZE(msg_parse_buf) + ublox::UBLOX_HEADER_SIZE + ublox::UBLOX_CHECKSUM_SIZE; // too much data - if(needed < (uint16_t)bytes_in_parse_buf) + if (needed < (uint16_t)bytes_in_parse_buf) return -1; return needed - (uint16_t)bytes_in_parse_buf; } inline bool UbloxMsgParser::valid_cheksum() { uint8_t ck_a = 0, ck_b = 0; - for(int i = 2; i < bytes_in_parse_buf - ublox::UBLOX_CHECKSUM_SIZE;i++) { + for (int i = 2; i < bytes_in_parse_buf - ublox::UBLOX_CHECKSUM_SIZE; i++) { ck_a = (ck_a + msg_parse_buf[i]) & 0xFF; ck_b = (ck_b + ck_a) & 0xFF; } - if(ck_a != msg_parse_buf[bytes_in_parse_buf - 2]) { + if (ck_a != msg_parse_buf[bytes_in_parse_buf - 2]) { LOGD("Checksum a mismatch: %02X, %02X", ck_a, msg_parse_buf[6]); return false; } - if(ck_b != msg_parse_buf[bytes_in_parse_buf - 1]) { + if (ck_b != msg_parse_buf[bytes_in_parse_buf - 1]) { LOGD("Checksum b mismatch: %02X, %02X", ck_b, msg_parse_buf[7]); return false; } @@ -53,13 +55,13 @@ inline bool UbloxMsgParser::valid() { } inline bool UbloxMsgParser::valid_so_far() { - if(bytes_in_parse_buf > 0 && msg_parse_buf[0] != ublox::PREAMBLE1) { + if (bytes_in_parse_buf > 0 && msg_parse_buf[0] != ublox::PREAMBLE1) { return false; } - if(bytes_in_parse_buf > 1 && msg_parse_buf[1] != ublox::PREAMBLE2) { + if (bytes_in_parse_buf > 1 && msg_parse_buf[1] != ublox::PREAMBLE2) { return false; } - if(needed_bytes() == 0 && !valid()) { + if (needed_bytes() == 0 && !valid()) { return false; } return true; @@ -68,8 +70,8 @@ inline bool UbloxMsgParser::valid_so_far() { bool UbloxMsgParser::add_data(float log_time, const uint8_t *incoming_data, uint32_t incoming_data_len, size_t &bytes_consumed) { last_log_time = log_time; int needed = needed_bytes(); - if(needed > 0) { - bytes_consumed = std::min((uint32_t)needed, incoming_data_len ); + if (needed > 0) { + bytes_consumed = std::min((uint32_t)needed, incoming_data_len); // Add data to buffer memcpy(msg_parse_buf + bytes_in_parse_buf, incoming_data, bytes_consumed); bytes_in_parse_buf += bytes_consumed; @@ -78,15 +80,15 @@ bool UbloxMsgParser::add_data(float log_time, const uint8_t *incoming_data, uint } // Validate msg format, detect invalid header and invalid checksum. - while(!valid_so_far() && bytes_in_parse_buf != 0) { + while (!valid_so_far() && bytes_in_parse_buf != 0) { // Corrupted msg, drop a byte. bytes_in_parse_buf -= 1; - if(bytes_in_parse_buf > 0) + if (bytes_in_parse_buf > 0) memmove(&msg_parse_buf[0], &msg_parse_buf[1], bytes_in_parse_buf); } // There is redundant data at the end of buffer, reset the buffer. - if(needed_bytes() == -1) { + if (needed_bytes() == -1) { bytes_in_parse_buf = 0; } return valid(); @@ -295,8 +297,7 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ continue; if (glonass_string_superframes[msg->freq_id()][i] == 0 || gl_string.superframe_number() == 0) { superframe_unknown = true; - } - else if (glonass_string_superframes[msg->freq_id()][i] != gl_string.superframe_number()) { + } else if (glonass_string_superframes[msg->freq_id()][i] != gl_string.superframe_number()) { needs_clear = true; } // Check if string times add up to being from the same frame @@ -434,7 +435,7 @@ kj::Array UbloxMsgParser::gen_rxm_rawx(ubx_t::rxm_rawx_t *msg) { auto mb = mr.initMeasurements(msg->num_meas()); auto measurements = *msg->meas(); - for(int8_t i = 0; i < msg->num_meas(); i++) { + for (int8_t i = 0; i < msg->num_meas(); i++) { mb[i].setSvId(measurements[i]->sv_id()); mb[i].setPseudorange(measurements[i]->pr_mes()); mb[i].setCarrierCycles(measurements[i]->cp_mes()); @@ -469,7 +470,7 @@ kj::Array UbloxMsgParser::gen_nav_sat(ubx_t::nav_sat_t *msg) { auto svs = sr.initSvs(msg->num_svs()); auto svs_data = *msg->svs(); - for(int8_t i = 0; i < msg->num_svs(); i++) { + for (int8_t i = 0; i < msg->num_svs(); i++) { svs[i].setSvId(svs_data[i]->sv_id()); svs[i].setGnssId(svs_data[i]->gnss_id()); svs[i].setFlagsBitfield(svs_data[i]->flags()); diff --git a/system/ubloxd/ublox_msg.h b/system/ubloxd/ublox_msg.h index a52a7db3e5..d21760edc2 100644 --- a/system/ubloxd/ublox_msg.h +++ b/system/ubloxd/ublox_msg.h @@ -2,10 +2,11 @@ #include #include +#include #include #include #include -#include +#include #include "cereal/messaging/messaging.h" #include "common/util.h" @@ -51,7 +52,7 @@ namespace ublox { assert(msg.size() > 2); uint8_t ck_a = 0, ck_b = 0; - for(int i = 2; i < msg.size(); i++) { + for (int i = 2; i < msg.size(); i++) { ck_a = (ck_a + msg[i]) & 0xFF; ck_b = (ck_b + ck_a) & 0xFF; } diff --git a/system/ubloxd/ubloxd.cc b/system/ubloxd/ubloxd.cc index 1dae6dc866..668c1a7ec0 100644 --- a/system/ubloxd/ubloxd.cc +++ b/system/ubloxd/ubloxd.cc @@ -41,9 +41,9 @@ int main() { size_t len = ubloxRaw.size(); size_t bytes_consumed = 0; - while(bytes_consumed < len && !do_exit) { + while (bytes_consumed < len && !do_exit) { size_t bytes_consumed_this_time = 0U; - if(parser.add_data(log_time, data + bytes_consumed, (uint32_t)(len - bytes_consumed), bytes_consumed_this_time)) { + if (parser.add_data(log_time, data + bytes_consumed, (uint32_t)(len - bytes_consumed), bytes_consumed_this_time)) { try { auto ublox_msg = parser.gen_msg(); diff --git a/system/version.py b/system/version.py old mode 100644 new mode 100755 index c9ab443694..82e99fd416 --- a/system/version.py +++ b/system/version.py @@ -4,8 +4,8 @@ import subprocess from typing import List, Optional from functools import lru_cache -from common.basedir import BASEDIR -from system.swaglog import cloudlog +from openpilot.common.basedir import BASEDIR +from openpilot.system.swaglog import cloudlog RELEASE_BRANCHES = ['release3-staging', 'dashcam3-staging', 'release3', 'dashcam3', 'nightly'] TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging'] @@ -127,7 +127,7 @@ def is_dirty() -> bool: if __name__ == "__main__": - from common.params import Params + from openpilot.common.params import Params params = Params() params.put("TermsVersion", terms_version) diff --git a/third_party/libyuv/build.sh b/third_party/libyuv/build.sh old mode 100644 new mode 100755 diff --git a/tools/README.md b/tools/README.md index 28c819c28b..36929a4290 100644 --- a/tools/README.md +++ b/tools/README.md @@ -2,43 +2,55 @@ ## System Requirements -openpilot is developed and tested on **Ubuntu 20.04**, which is the primary development target aside from the [supported embedded hardware](https://github.com/commaai/openpilot#running-on-a-dedicated-device-in-a-car). We also have a CI test to verify that openpilot builds on macOS, but the tools are untested. For the best experience, stick to Ubuntu 20.04, otherwise openpilot and the tools should work with minimal to no modifications on macOS and other Linux systems. +openpilot is developed and tested on **Ubuntu 20.04**, which is the primary development target aside from the [supported embedded hardware](https://github.com/commaai/openpilot#running-on-a-dedicated-device-in-a-car). -## Setup your PC +Running natively on any other system is not recommended and will require modifications. On Windows you can use WSL, and on macOS or incompatible Linux systems, it is recommended to use the dev containers. -First, clone openpilot: +## Native setup on Ubuntu 20.04 + +**1. Clone openpilot** + +Either do a partial clone for faster download: ``` bash -cd ~ -git clone https://github.com/commaai/openpilot.git +git clone --filter=blob:none --recurse-submodules --also-filter-submodules https://github.com/commaai/openpilot.git +``` -cd openpilot -git submodule update --init +or do a full clone: +``` bash +git clone --recurse-submodules https://github.com/commaai/openpilot.git ``` -Then, run the setup script: +**2. Run the setup script** ``` bash -# for Ubuntu 20.04 LTS +cd openpilot tools/ubuntu_setup.sh - -# for macOS -tools/mac_setup.sh ``` Activate a shell with the Python dependencies installed: - ``` bash -cd openpilot && poetry shell +poetry shell ``` -Build openpilot with this command: +**3. Build openpilot** + ``` bash scons -u -j$(nproc) ``` -### Windows +## Dev Container on any Linux or macOS + +openpilot supports [Dev Containers](https://containers.dev/). Dev containers provide customizable and consistent development environment wrapped inside a container. This means you can develop in a designated environment matching our primary development target, regardless of your local setup. + +Dev containers are supported in [multiple editors and IDEs](https://containers.dev/supporting), including Visual Studio Code. Use the following [guide](https://code.visualstudio.com/docs/devcontainers/containers) to start using them with VSCode. + +#### X11 forwarding on macOS + +GUI apps like `ui` or `cabana` can also run inside the container by leveraging X11 forwarding. To make use of it on macOS, additional configuration steps must be taken. Follow [these](https://gist.github.com/sorny/969fe55d85c9b0035b0109a31cbcb088) steps to setup X11 forwarding on macOS. + +## WSL on Windows -Neither openpilot nor any of the tools are developed or tested on Windows, but the [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/about) should provide a similar experience to native Ubuntu. [WSL 2](https://docs.microsoft.com/en-us/windows/wsl/compare-versions) specifically has been reported by several users to be a seamless experience. +[Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/about) should provide a similar experience to native Ubuntu. [WSL 2](https://docs.microsoft.com/en-us/windows/wsl/compare-versions) specifically has been reported by several users to be a seamless experience. Follow [these instructions](https://docs.microsoft.com/en-us/windows/wsl/install) to setup the WSL and install the `Ubuntu-20.04` distribution. Once your Ubuntu WSL environment is setup, follow the Linux setup instructions to finish setting up your environment. See [these instructions](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) for running GUI apps. diff --git a/tools/bodyteleop/bodyav.py b/tools/bodyteleop/bodyav.py index fc9559f8dc..3f11f8d4f2 100644 --- a/tools/bodyteleop/bodyav.py +++ b/tools/bodyteleop/bodyav.py @@ -90,12 +90,12 @@ class WebClientSpeaker(MediaBlackhole): self.buffer.write(bio) async def start(self): - for track, task in self._MediaBlackhole__tracks.items(): # pylint: disable=access-member-before-definition + for track, task in self._MediaBlackhole__tracks.items(): if task is None: self._MediaBlackhole__tracks[track] = asyncio.ensure_future(self.consume(track)) async def stop(self): - for task in self._MediaBlackhole__tracks.values(): # pylint: disable=access-member-before-definition + for task in self._MediaBlackhole__tracks.values(): if task is not None: task.cancel() self._MediaBlackhole__tracks = {} diff --git a/tools/bodyteleop/static/js/webrtc.js b/tools/bodyteleop/static/js/webrtc.js index 8bc8e77317..7668c1c1e4 100644 --- a/tools/bodyteleop/static/js/webrtc.js +++ b/tools/bodyteleop/static/js/webrtc.js @@ -108,37 +108,47 @@ export function createDummyVideoTrack() { export function start(pc, dc) { pc = createPeerConnection(pc); - if (constraints.audio || constraints.video) { - // add audio track - navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { - stream.getTracks().forEach(function(track) { - pc.addTrack(track, stream); - // only audio? - // if (track.kind === 'audio'){ - // pc.addTrack(track, stream); - // } - }); - return negotiate(pc); - }, function(err) { - alert('Could not acquire media: ' + err); - }); - - // add a fake video? - // const dummyVideoTrack = createDummyVideoTrack(); - // const dummyMediaStream = new MediaStream(); - // dummyMediaStream.addTrack(dummyVideoTrack); - // pc.addTrack(dummyVideoTrack, dummyMediaStream); - - } else { - negotiate(pc); - } + // add audio track + navigator.mediaDevices.enumerateDevices() + .then(function(devices) { + const hasAudioInput = devices.find((device) => { device.kind === "audioinput" }); + var modifiedConstraints = {}; + modifiedConstraints.video = constraints.video; + modifiedConstraints.audio = hasAudioInput ? constraints.audio : false; + + return Promise.resolve(modifiedConstraints); + }) + .then(function(constraints) { + if (constraints.audio || constraints.video) { + return navigator.mediaDevices.getUserMedia(constraints); + } else{ + return Promise.resolve(null); + } + }) + .then(function(stream) { + if (stream) { + stream.getTracks().forEach(function(track) { + pc.addTrack(track, stream); + }); + } + + return negotiate(pc); + }) + .catch(function(err) { + alert('Could not acquire media: ' + err); + }); + + // add a fake video? + // const dummyVideoTrack = createDummyVideoTrack(); + // const dummyMediaStream = new MediaStream(); + // dummyMediaStream.addTrack(dummyVideoTrack); + // pc.addTrack(dummyVideoTrack, dummyMediaStream); // setInterval(() => {pc.getStats(null).then((stats) => {stats.forEach((report) => console.log(report))})}, 10000) // var video = document.querySelector('video'); // var print = function (e, f){console.log(e, f); video.requestVideoFrameCallback(print);}; // video.requestVideoFrameCallback(print); - var parameters = {"ordered": true}; dc = pc.createDataChannel('data', parameters); dc.onclose = function() { diff --git a/tools/bodyteleop/web.py b/tools/bodyteleop/web.py index ed94cb6d83..717afdeaf8 100644 --- a/tools/bodyteleop/web.py +++ b/tools/bodyteleop/web.py @@ -15,8 +15,8 @@ from aiohttp import web from aiortc import RTCPeerConnection, RTCSessionDescription import cereal.messaging as messaging -from common.basedir import BASEDIR -from tools.bodyteleop.bodyav import BodyMic, WebClientSpeaker, force_codec, play_sound, MediaBlackhole, EncodedBodyVideo +from openpilot.common.basedir import BASEDIR +from openpilot.tools.bodyteleop.bodyav import BodyMic, WebClientSpeaker, force_codec, play_sound, MediaBlackhole, EncodedBodyVideo logger = logging.getLogger("pc") logging.basicConfig(level=logging.INFO) diff --git a/tools/cabana/README.md b/tools/cabana/README.md index cfda056636..53723ef8a6 100644 --- a/tools/cabana/README.md +++ b/tools/cabana/README.md @@ -17,6 +17,7 @@ Options: --stream read can messages from live streaming --panda read can messages from panda --panda-serial read can messages from panda with given serial + --socketcan read can messages from given SocketCAN device --zmq the ip address on which to receive zmq messages --data_dir local directory with routes diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index cdc809f2b4..46958f14e5 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -9,9 +9,11 @@ base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', 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 = ['qt_util'] + base_libs @@ -27,18 +29,17 @@ assets_src = "assets/assets.qrc" cabana_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "assets/assets.o"])) -prev_moc_path = cabana_env['QT3_MOCHPREFIX'] -cabana_env['QT3_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' -cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', +cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcanstream.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) -if GetOption('test'): +if GetOption('extras'): cabana_env.Program('tests/test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs]) -generate_dbc = cabana_env.Command('generate_dbc_json', - [], - "python3 tools/cabana/dbc/generate_dbc_json.py --out tools/cabana/dbc/car_fingerprint_to_dbc.json") -cabana_env.Depends(generate_dbc, ["#common", "#selfdrive/boardd", "#opendbc", "#cereal"]) +output_json_file = 'tools/cabana/dbc/car_fingerprint_to_dbc.json' +generate_dbc = cabana_env.Command('#' + output_json_file, + ['dbc/generate_dbc_json.py'], + "python3 tools/cabana/dbc/generate_dbc_json.py --out " + output_json_file) +cabana_env.Depends(generate_dbc, ["#common", "#selfdrive/boardd", '#opendbc', "#cereal", Glob("#opendbc/*.dbc")]) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 6aac56cc78..e7edc0ecb9 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -1,5 +1,7 @@ #include "tools/cabana/binaryview.h" +#include + #include #include #include @@ -278,7 +280,7 @@ void BinaryViewModel::refresh() { updateState(); } -void BinaryViewModel::updateItem(int row, int col, const QString &val, const QColor &color) { +void BinaryViewModel::updateItem(int row, int col, uint8_t val, const QColor &color) { auto &item = items[row * column_count + col]; if (item.val != val || item.bg_color != color) { item.val = val; @@ -305,7 +307,7 @@ void BinaryViewModel::updateState() { for (int i = 0; i < binary.size(); ++i) { for (int j = 0; j < 8; ++j) { auto &item = items[i * column_count + j]; - QString val = ((binary[i] >> (7 - j)) & 1) != 0 ? "1" : "0"; + int val = ((binary[i] >> (7 - j)) & 1) != 0 ? 1 : 0; // Bit update frequency based highlighting double offset = !item.sigs.empty() ? 50 : 0; auto n = last_msg.bit_change_counts[i][7 - j]; @@ -315,7 +317,7 @@ void BinaryViewModel::updateState() { color.setAlpha(alpha); updateItem(i, j, val, color); } - updateItem(i, 8, toHex(binary[i]), last_msg.colors[i]); + updateItem(i, 8, binary[i], last_msg.colors[i]); } } @@ -346,6 +348,13 @@ BinaryItemDelegate::BinaryItemDelegate(QObject *parent) : QStyledItemDelegate(pa small_font.setPixelSize(8); hex_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); hex_font.setBold(true); + + bin_text_table[0].setText("0"); + bin_text_table[1].setText("1"); + for (int i = 0; i < 256; ++i) { + hex_text_table[i].setText(QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper()); + hex_text_table[i].prepare({}, hex_font); + } } bool BinaryItemDelegate::hasSignal(const QModelIndex &index, int dx, int dy, const cabana::Signal *sig) const { @@ -390,7 +399,9 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op } else if (!item->valid) { painter->fillRect(option.rect, QBrush(Qt::darkGray, Qt::BDiagPattern)); } - painter->drawText(option.rect, Qt::AlignCenter, item->val); + if (item->valid) { + utils::drawStaticText(painter, option.rect, index.column() == 8 ? hex_text_table[item->val] : bin_text_table[item->val]); + } if (item->is_msb || item->is_lsb) { painter->setFont(small_font); painter->drawText(option.rect.adjusted(8, 0, -8, -3), Qt::AlignRight | Qt::AlignBottom, item->is_msb ? "M" : "L"); diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 161b2aad8a..584910dc83 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include #include #include @@ -16,6 +19,8 @@ public: void drawSignalCell(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index, const cabana::Signal *sig) const; QFont small_font, hex_font; + std::array hex_text_table; + std::array bin_text_table; }; class BinaryViewModel : public QAbstractTableModel { @@ -23,7 +28,7 @@ public: BinaryViewModel(QObject *parent) : QAbstractTableModel(parent) {} void refresh(); void updateState(); - void updateItem(int row, int col, const QString &val, const QColor &color); + void updateItem(int row, int col, uint8_t val, const QColor &color); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } @@ -39,7 +44,7 @@ public: QColor bg_color = QColor(102, 86, 169, 255); bool is_msb = false; bool is_lsb = false; - QString val; + uint8_t val; QList sigs; bool valid = false; }; diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 49b8fcf6ca..0ccef7d3ab 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -7,6 +7,7 @@ #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/replaystream.h" +#include "tools/cabana/streams/socketcanstream.h" int main(int argc, char *argv[]) { QCoreApplication::setApplicationName("Cabana"); @@ -17,6 +18,8 @@ int main(int argc, char *argv[]) { app.setWindowIcon(QIcon(":cabana-icon.png")); UnixSignalHandler signalHandler; + + settings.load(); utils::setTheme(settings.theme); QCommandLineParser cmd_parser; @@ -28,6 +31,9 @@ int main(int argc, char *argv[]) { cmd_parser.addOption({"stream", "read can messages from live streaming"}); cmd_parser.addOption({"panda", "read can messages from panda"}); cmd_parser.addOption({"panda-serial", "read can messages from panda with given serial", "panda-serial"}); + if (SocketCanStream::available()) { + cmd_parser.addOption({"socketcan", "read can messages from given SocketCAN device", "socketcan"}); + } cmd_parser.addOption({"zmq", "the ip address on which to receive zmq messages", "zmq"}); cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"}); cmd_parser.addOption({"no-vipc", "do not output video"}); @@ -50,6 +56,10 @@ int main(int argc, char *argv[]) { qWarning() << e.what(); return 0; } + } else if (cmd_parser.isSet("socketcan")) { + SocketCanStreamConfig config = {}; + config.device = cmd_parser.value("socketcan"); + stream = new SocketCanStream(&app, config); } else { uint32_t replay_flags = REPLAY_FLAG_NONE; if (cmd_parser.isSet("ecam")) { @@ -67,12 +77,7 @@ int main(int argc, char *argv[]) { } else if (cmd_parser.isSet("demo")) { route = DEMO_ROUTE; } - - if (route.isEmpty()) { - StreamSelector dlg(&stream); - dlg.exec(); - dbc_file = dlg.dbcFile(); - } else { + if (!route.isEmpty()) { auto replay_stream = new ReplayStream(&app); if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) { return 0; @@ -81,17 +86,28 @@ int main(int argc, char *argv[]) { } } - MainWindow w; - if (!stream) { - stream = new DummyStream(&app); - } - stream->start(); - if (!dbc_file.isEmpty()) { - w.loadFile(dbc_file); + int ret = 0; + { + MainWindow w; + QTimer::singleShot(0, [&]() { + if (!stream) { + StreamSelector dlg(&stream); + dlg.exec(); + dbc_file = dlg.dbcFile(); + } + if (!stream) { + stream = new DummyStream(&app); + } + stream->start(); + if (!dbc_file.isEmpty()) { + w.loadFile(dbc_file); + } + w.show(); + }); + + ret = app.exec(); } - w.show(); - int ret = app.exec(); delete can; return ret; } diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index 08e8b54b73..70536cc0c2 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -1,5 +1,8 @@ #include "tools/cabana/chart/chart.h" +#include +#include + #include #include #include @@ -7,13 +10,12 @@ #include #include #include -#include #include #include #include +#include #include #include -#include #include #include "tools/cabana/chart/chartswidget.h" @@ -22,7 +24,8 @@ const int AXIS_X_TOP_MARGIN = 4; static inline bool xLessThan(const QPointF &p, float x) { return p.x() < x; } -ChartView::ChartView(const std::pair &x_range, ChartsWidget *parent) : charts_widget(parent), tip_label(this), QChartView(nullptr, parent) { +ChartView::ChartView(const std::pair &x_range, ChartsWidget *parent) + : charts_widget(parent), tip_label(this), QChartView(nullptr, parent) { series_type = (SeriesType)settings.chart_series_type; QChart *chart = new QChart(); chart->setBackgroundVisible(false); @@ -62,8 +65,8 @@ void ChartView::createToolButtons() { close_btn_proxy->setWidget(remove_btn); close_btn_proxy->setZValue(chart()->zValue() + 11); + menu = new QMenu(this); // series types - QMenu *menu = new QMenu(this); auto change_series_group = new QActionGroup(menu); change_series_group->setExclusive(true); QStringList types{tr("Line"), tr("Step Line"), tr("Scatter")}; @@ -86,7 +89,9 @@ void ChartView::createToolButtons() { manage_btn_proxy->setWidget(manage_btn); manage_btn_proxy->setZValue(chart()->zValue() + 11); - QObject::connect(remove_btn, &QToolButton::clicked, [this]() { charts_widget->removeChart(this); }); + close_act = new QAction(tr("Close"), this); + QObject::connect(close_act, &QAction::triggered, [this] () { charts_widget->removeChart(this); }); + QObject::connect(remove_btn, &QToolButton::clicked, close_act, &QAction::triggered); QObject::connect(change_series_group, &QActionGroup::triggered, [this](QAction *action) { setSeriesType((SeriesType)action->data().toInt()); }); @@ -147,6 +152,11 @@ void ChartView::removeIf(std::function predicate) { void ChartView::signalUpdated(const cabana::Signal *sig) { if (std::any_of(sigs.cbegin(), sigs.cend(), [=](auto &s) { return s.sig == sig; })) { + for (const auto &s : sigs) { + if (s.sig == sig && s.series->color() != sig->color) { + setSeriesColor(s.series, sig->color); + } + } updateTitle(); updateSeries(sig); } @@ -277,15 +287,12 @@ void ChartView::updateSeries(const cabana::Signal *sig, bool clear) { s.step_vals.clear(); s.last_value_mono_time = 0; } - s.series->setColor(s.sig->color); const auto &msgs = can->events(s.msg_id); s.vals.reserve(msgs.capacity()); s.step_vals.reserve(msgs.capacity() * 2); - auto first = std::upper_bound(msgs.cbegin(), msgs.cend(), s.last_value_mono_time, [](uint64_t ts, auto e) { - return ts < e->mono_time; - }); + auto first = std::upper_bound(msgs.cbegin(), msgs.cend(), s.last_value_mono_time, CompareCanEvent()); const double route_start_time = can->routeStartTime(); for (auto end = msgs.cend(); first != end; ++first) { const CanEvent *e = *first; @@ -358,7 +365,7 @@ void ChartView::updateAxisY() { axis_y->setRange(min_y, max_y); axis_y->setTickCount(tick_count); - int n = qMax(int(-qFloor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1; + int n = std::max(int(-std::floor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1; int max_label_width = 0; QFontMetrics fm(axis_y->labelsFont()); for (int i = 0; i < tick_count; i++) { @@ -376,15 +383,15 @@ void ChartView::updateAxisY() { std::tuple ChartView::getNiceAxisNumbers(qreal min, qreal max, int tick_count) { qreal range = niceNumber((max - min), true); // range with ceiling qreal step = niceNumber(range / (tick_count - 1), false); - min = qFloor(min / step); - max = qCeil(max / step); + min = std::floor(min / step); + max = std::ceil(max / step); tick_count = int(max - min) + 1; return {min * step, max * step, tick_count}; } // nice numbers can be expressed as form of 1*10^n, 2* 10^n or 5*10^n qreal ChartView::niceNumber(qreal x, bool ceiling) { - qreal z = qPow(10, qFloor(std::log10(x))); //find corresponding number of the form of 10^n than is smaller than x + qreal z = std::pow(10, std::floor(std::log10(x))); //find corresponding number of the form of 10^n than is smaller than x qreal q = x / z; //q<10 && q>=1; if (ceiling) { if (q <= 1.0) q = 1; @@ -446,6 +453,17 @@ static QPixmap getDropPixmap(const QPixmap &src) { return px; } +void ChartView::contextMenuEvent(QContextMenuEvent *event) { + QMenu context_menu(this); + context_menu.addActions(menu->actions()); + context_menu.addSeparator(); + context_menu.addAction(charts_widget->undo_zoom_action); + context_menu.addAction(charts_widget->redo_zoom_action); + context_menu.addSeparator(); + context_menu.addAction(close_act); + context_menu.exec(event->globalPos()); +} + void ChartView::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && move_icon->sceneBoundingRect().contains(event->pos())) { QMimeData *mimeData = new QMimeData; @@ -473,7 +491,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { auto rubber = findChild(); if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) { rubber->hide(); - QRectF rect = rubber->geometry().normalized(); + auto rect = rubber->geometry().normalized(); double min = chart()->mapToValue(rect.topLeft()).x(); double max = chart()->mapToValue(rect.bottomRight()).x(); @@ -667,6 +685,7 @@ void ChartView::drawBackground(QPainter *painter, const QRectF &rect) { void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { drawTimeline(painter); + drawSignalValue(painter); // draw track points painter->setPen(Qt::NoPen); qreal track_line_x = -1; @@ -697,7 +716,10 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { } } - // paint zoom range + drawRubberBandTimeRange(painter); +} + +void ChartView::drawRubberBandTimeRange(QPainter *painter) { auto rubber = findChild(); if (rubber && rubber->isVisible() && rubber->width() > 1) { painter->setPen(Qt::white); @@ -714,23 +736,24 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { void ChartView::drawTimeline(QPainter *painter) { const auto plot_area = chart()->plotArea(); - // draw line + // draw vertical time line qreal x = std::clamp(chart()->mapToPosition(QPointF{cur_sec, 0}).x(), plot_area.left(), plot_area.right()); painter->setPen(QPen(chart()->titleBrush().color(), 2)); painter->drawLine(QPointF{x, plot_area.top()}, QPointF{x, plot_area.bottom() + 1}); - // draw current time + // draw current time under the axis-x QString time_str = QString::number(cur_sec, 'f', 2); QSize time_str_size = QFontMetrics(axis_x->labelsFont()).size(Qt::TextSingleLine, time_str) + QSize(8, 2); - QRect time_str_rect(QPoint(x - time_str_size.width() / 2, plot_area.bottom() + AXIS_X_TOP_MARGIN), time_str_size); + QRectF time_str_rect(QPointF(x - time_str_size.width() / 2.0, plot_area.bottom() + AXIS_X_TOP_MARGIN), time_str_size); QPainterPath path; path.addRoundedRect(time_str_rect, 3, 3); painter->fillPath(path, settings.theme == DARK_THEME ? Qt::darkGray : Qt::gray); painter->setPen(palette().color(QPalette::BrightText)); painter->setFont(axis_x->labelsFont()); painter->drawText(time_str_rect, Qt::AlignCenter, time_str); +} - // draw signal value +void ChartView::drawSignalValue(QPainter *painter) { auto item_group = qgraphicsitem_cast(chart()->legend()->childItems()[0]); assert(item_group != nullptr); auto legend_markers = item_group->childItems(); @@ -782,6 +805,7 @@ QXYSeries *ChartView::createSeries(SeriesType type, QColor color) { } void ChartView::addSeries(QXYSeries *series) { + setSeriesColor(series, series->color()); chart()->addSeries(series); series->attachAxis(axis_x); series->attachAxis(axis_y); @@ -794,6 +818,21 @@ void ChartView::addSeries(QXYSeries *series) { } } +void ChartView::setSeriesColor(QXYSeries *series, QColor color) { + auto existing_series = chart()->series(); + for (auto s : existing_series) { + if (s != series && std::abs(color.hueF() - qobject_cast(s)->color().hueF()) < 0.1) { + // use different color to distinguish it from others. + auto last_color = qobject_cast(existing_series.back())->color(); + color.setHsvF(std::fmod(last_color.hueF() + 60 / 360.0, 1.0), + QRandomGenerator::global()->bounded(35, 100) / 100.0, + QRandomGenerator::global()->bounded(85, 100) / 100.0); + break; + } + } + series->setColor(color); +} + void ChartView::setSeriesType(SeriesType type) { if (type != series_type) { series_type = type; diff --git a/tools/cabana/chart/chart.h b/tools/cabana/chart/chart.h index 272ea0d193..f91b81cc91 100644 --- a/tools/cabana/chart/chart.h +++ b/tools/cabana/chart/chart.h @@ -1,5 +1,9 @@ #pragma once +#include +#include + +#include #include #include #include @@ -62,6 +66,7 @@ private slots: private: void createToolButtons(); void addSeries(QXYSeries *series); + void contextMenuEvent(QContextMenuEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *ev) override; @@ -80,10 +85,13 @@ private: void drawForeground(QPainter *painter, const QRectF &rect) override; void drawBackground(QPainter *painter, const QRectF &rect) override; void drawDropIndicator(bool draw) { if (std::exchange(can_drop, draw) != can_drop) viewport()->update(); } + void drawSignalValue(QPainter *painter); void drawTimeline(QPainter *painter); + void drawRubberBandTimeRange(QPainter *painter); std::tuple getNiceAxisNumbers(qreal min, qreal max, int tick_count); qreal niceNumber(qreal x, bool ceiling); QXYSeries *createSeries(SeriesType type, QColor color); + void setSeriesColor(QXYSeries *, QColor color); void updateSeriesPoints(); void removeIf(std::function predicate); inline void clearTrackPoints() { for (auto &s : sigs) s.track_pt = {}; } @@ -92,7 +100,9 @@ private: int align_to = 0; QValueAxis *axis_x; QValueAxis *axis_y; + QMenu *menu; QAction *split_chart_act; + QAction *close_act; QGraphicsPixmapItem *move_icon; QGraphicsProxyWidget *close_btn_proxy; QGraphicsProxyWidget *manage_btn_proxy; diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index ebc463af0e..83991d914f 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -1,5 +1,7 @@ #include "tools/cabana/chart/chartswidget.h" +#include + #include #include #include @@ -217,7 +219,7 @@ void ChartsWidget::updateToolBar() { undo_zoom_action->setVisible(is_zoomed); redo_zoom_action->setVisible(is_zoomed); reset_zoom_action->setVisible(is_zoomed); - reset_zoom_btn->setText(is_zoomed ? tr("%1-%2").arg(zoomed_range.first, 0, 'f', 1).arg(zoomed_range.second, 0, 'f', 1) : ""); + reset_zoom_btn->setText(is_zoomed ? tr("%1-%2").arg(zoomed_range.first, 0, 'f', 2).arg(zoomed_range.second, 0, 'f', 2) : ""); remove_all_btn->setEnabled(!charts.isEmpty()); dock_btn->setIcon(docking ? "arrow-up-right-square" : "arrow-down-left-square"); dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); @@ -275,6 +277,10 @@ void ChartsWidget::splitChart(ChartView *src_chart) { for (auto it = src_chart->sigs.begin() + 1; it != src_chart->sigs.end(); /**/) { auto c = createChart(); src_chart->chart()->removeSeries(it->series); + + // Restore to the original color + it->series->setColor(it->sig->color); + c->addSeries(it->series); c->sigs.push_back(*it); c->updateAxisY(); @@ -283,6 +289,7 @@ void ChartsWidget::splitChart(ChartView *src_chart) { } src_chart->updateAxisY(); src_chart->updateTitle(); + QTimer::singleShot(0, src_chart, &ChartView::resetChartCache); } } diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h index 0d9f79062a..3541f8d96e 100644 --- a/tools/cabana/chart/chartswidget.h +++ b/tools/cabana/chart/chartswidget.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include #include #include @@ -117,7 +120,7 @@ class ZoomCommand : public QUndoCommand { public: ZoomCommand(ChartsWidget *charts, std::pair range) : charts(charts), range(range), QUndoCommand() { prev_range = charts->is_zoomed ? charts->zoomed_range : charts->display_range; - setText(QObject::tr("Zoom to %1-%2").arg(range.first, 0, 'f', 1).arg(range.second, 0, 'f', 1)); + setText(QObject::tr("Zoom to %1-%2").arg(range.first, 0, 'f', 2).arg(range.second, 0, 'f', 2)); } void undo() override { charts->setZoom(prev_range.first, prev_range.second); } void redo() override { charts->setZoom(range.first, range.second); } diff --git a/tools/cabana/chart/sparkline.cc b/tools/cabana/chart/sparkline.cc index baedee8bb2..1ae6a1bfe0 100644 --- a/tools/cabana/chart/sparkline.cc +++ b/tools/cabana/chart/sparkline.cc @@ -1,5 +1,6 @@ #include "tools/cabana/chart/sparkline.h" +#include #include #include "tools/cabana/streams/abstractstream.h" @@ -8,12 +9,8 @@ void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, doubl const auto &msgs = can->events(msg_id); uint64_t ts = (last_msg_ts + can->routeStartTime()) * 1e9; uint64_t first_ts = (ts > range * 1e9) ? ts - range * 1e9 : 0; - auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), first_ts, [](auto e, uint64_t ts) { - return e->mono_time < ts; - }); - auto last = std::upper_bound(first, msgs.cend(), ts, [](uint64_t ts, auto e) { - return ts < e->mono_time; - }); + auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), first_ts, CompareCanEvent()); + auto last = std::upper_bound(first, msgs.cend(), ts, CompareCanEvent()); bool update_values = last_ts != last_msg_ts || time_range != range; last_ts = last_msg_ts; diff --git a/tools/cabana/chart/sparkline.h b/tools/cabana/chart/sparkline.h index 2061966ebb..21cbd40a3f 100644 --- a/tools/cabana/chart/sparkline.h +++ b/tools/cabana/chart/sparkline.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include diff --git a/tools/cabana/chart/tiplabel.cc b/tools/cabana/chart/tiplabel.cc index f34d7e8dfe..f5c9cc9cbd 100644 --- a/tools/cabana/chart/tiplabel.cc +++ b/tools/cabana/chart/tiplabel.cc @@ -1,5 +1,7 @@ #include "tools/cabana/chart/tiplabel.h" +#include + #include #include #include diff --git a/tools/cabana/commands.cc b/tools/cabana/commands.cc index cf48abb4c9..52861723f4 100644 --- a/tools/cabana/commands.cc +++ b/tools/cabana/commands.cc @@ -4,11 +4,13 @@ // EditMsgCommand -EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &name, int size, const QString &comment, QUndoCommand *parent) - : id(id), new_name(name), new_size(size), new_comment(comment), QUndoCommand(parent) { +EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &name, int size, + const QString &node, const QString &comment, QUndoCommand *parent) + : id(id), new_name(name), new_size(size), new_node(node), new_comment(comment), QUndoCommand(parent) { if (auto msg = dbc()->msg(id)) { old_name = msg->name; old_size = msg->size; + old_node = msg->transmitter; old_comment = msg->comment; setText(QObject::tr("edit message %1:%2").arg(name).arg(id.address)); } else { @@ -20,11 +22,11 @@ void EditMsgCommand::undo() { if (old_name.isEmpty()) dbc()->removeMsg(id); else - dbc()->updateMsg(id, old_name, old_size, old_comment); + dbc()->updateMsg(id, old_name, old_size, old_node, old_comment); } void EditMsgCommand::redo() { - dbc()->updateMsg(id, new_name, new_size, new_comment); + dbc()->updateMsg(id, new_name, new_size, new_node, new_comment); } // RemoveMsgCommand @@ -38,7 +40,7 @@ RemoveMsgCommand::RemoveMsgCommand(const MessageId &id, QUndoCommand *parent) : void RemoveMsgCommand::undo() { if (!message.name.isEmpty()) { - dbc()->updateMsg(id, message.name, message.size, message.comment); + dbc()->updateMsg(id, message.name, message.size, message.transmitter, message.comment); for (auto s : message.getSignals()) dbc()->addSignal(id, *s); } @@ -64,7 +66,7 @@ void AddSigCommand::undo() { void AddSigCommand::redo() { if (auto msg = dbc()->msg(id); !msg) { msg_created = true; - dbc()->updateMsg(id, dbc()->newMsgName(id), can->lastMessage(id).dat.size(), ""); + dbc()->updateMsg(id, dbc()->newMsgName(id), can->lastMessage(id).dat.size(), "", ""); } signal.name = dbc()->newSignalName(id); signal.max = std::pow(2, signal.size) - 1; diff --git a/tools/cabana/commands.h b/tools/cabana/commands.h index a1e667807e..0736d9b83f 100644 --- a/tools/cabana/commands.h +++ b/tools/cabana/commands.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include @@ -8,13 +10,14 @@ class EditMsgCommand : public QUndoCommand { public: - EditMsgCommand(const MessageId &id, const QString &name, int size, const QString &comment, QUndoCommand *parent = nullptr); + EditMsgCommand(const MessageId &id, const QString &name, int size, const QString &node, + const QString &comment, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: const MessageId id; - QString old_name, new_name, old_comment, new_comment; + QString old_name, new_name, old_comment, new_comment, old_node, new_node; int old_size = 0, new_size = 0; }; diff --git a/tools/cabana/dbc/dbc.cc b/tools/cabana/dbc/dbc.cc index 691a1c4fb8..a0e523d666 100644 --- a/tools/cabana/dbc/dbc.cc +++ b/tools/cabana/dbc/dbc.cc @@ -1,5 +1,7 @@ #include "tools/cabana/dbc/dbc.h" +#include + #include "tools/cabana/util.h" uint qHash(const MessageId &item) { @@ -76,6 +78,9 @@ QString cabana::Msg::newSignalName() { } void cabana::Msg::update() { + if (transmitter.isEmpty()) { + transmitter = DEFAULT_NODE_NAME; + } mask.assign(size, 0x00); multiplexor = nullptr; @@ -123,6 +128,9 @@ void cabana::Msg::update() { void cabana::Signal::update() { updateMsbLsb(*this); + if (receiver_name.isEmpty()) { + receiver_name = DEFAULT_NODE_NAME; + } float h = 19 * (float)lsb / 64.0; h = fmod(h, 1.0); diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h index 6a3084d943..bfb26c2842 100644 --- a/tools/cabana/dbc/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -1,14 +1,19 @@ #pragma once +#include +#include +#include +#include + #include #include #include #include -#include #include "opendbc/can/common_dbc.h" const QString UNTITLED = "untitled"; +const QString DEFAULT_NODE_NAME = "XXX"; struct MessageId { uint8_t source = 0; diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc index 2f93c1543e..063f516ead 100644 --- a/tools/cabana/dbc/dbcfile.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -60,11 +60,12 @@ bool DBCFile::writeContents(const QString &fn) { return false; } -void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &comment) { +void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment) { auto &m = msgs[id.address]; m.address = id.address; m.name = name; m.size = size; + m.transmitter = node.isEmpty() ? DEFAULT_NODE_NAME : node; m.comment = comment; } @@ -198,7 +199,8 @@ void DBCFile::parse(const QString &content) { QString DBCFile::generateDBC() { QString dbc_string, signal_comment, message_comment, val_desc; for (const auto &[address, m] : msgs) { - dbc_string += QString("BO_ %1 %2: %3 %4\n").arg(address).arg(m.name).arg(m.size).arg(m.transmitter.isEmpty() ? "XXX" : m.transmitter); + const QString transmitter = m.transmitter.isEmpty() ? DEFAULT_NODE_NAME : m.transmitter; + dbc_string += QString("BO_ %1 %2: %3 %4\n").arg(address).arg(m.name).arg(m.size).arg(transmitter); if (!m.comment.isEmpty()) { message_comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(m.comment); } @@ -221,7 +223,7 @@ QString DBCFile::generateDBC() { .arg(doubleToString(sig->min)) .arg(doubleToString(sig->max)) .arg(sig->unit) - .arg(sig->receiver_name.isEmpty() ? "XXX" : sig->receiver_name); + .arg(sig->receiver_name.isEmpty() ? DEFAULT_NODE_NAME : sig->receiver_name); if (!sig->comment.isEmpty()) { signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(sig->comment); } diff --git a/tools/cabana/dbc/dbcfile.h b/tools/cabana/dbc/dbcfile.h index 78a73d58e4..a3ab1cebe4 100644 --- a/tools/cabana/dbc/dbcfile.h +++ b/tools/cabana/dbc/dbcfile.h @@ -22,7 +22,7 @@ public: void cleanupAutoSaveFile(); QString generateDBC(); - void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &comment); + void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment); inline void removeMsg(const MessageId &id) { msgs.erase(id.address); } inline const std::map &getMessages() const { return msgs; } diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc index 5736ac1e89..459ca0111d 100644 --- a/tools/cabana/dbc/dbcmanager.cc +++ b/tools/cabana/dbc/dbcmanager.cc @@ -82,10 +82,10 @@ void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { } } -void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &comment) { +void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment) { auto dbc_file = findDBCFile(id); assert(dbc_file); // This should be impossible - dbc_file->updateMsg(id, name, size, comment); + dbc_file->updateMsg(id, name, size, node, comment); emit msgUpdated(id); } diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h index f20d4888e2..5f782fc930 100644 --- a/tools/cabana/dbc/dbcmanager.h +++ b/tools/cabana/dbc/dbcmanager.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "tools/cabana/dbc/dbcfile.h" @@ -27,7 +28,7 @@ public: void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig); void removeSignal(const MessageId &id, const QString &sig_name); - void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &comment); + void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment); void removeMsg(const MessageId &id); QString newMsgName(const MessageId &id); diff --git a/tools/cabana/dbc/generate_dbc_json.py b/tools/cabana/dbc/generate_dbc_json.py index cb122e2eb2..da19e27b77 100755 --- a/tools/cabana/dbc/generate_dbc_json.py +++ b/tools/cabana/dbc/generate_dbc_json.py @@ -2,7 +2,7 @@ import argparse import json -from selfdrive.car.car_helpers import get_interface_attr +from openpilot.selfdrive.car.car_helpers import get_interface_attr def generate_dbc_json() -> str: diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 2f6e9cbfe8..d70aeff0c8 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -2,7 +2,6 @@ #include #include -#include #include "tools/cabana/commands.h" #include "tools/cabana/mainwin.h" @@ -135,11 +134,12 @@ void DetailWidget::refresh() { for (auto s : binary_view->getOverlappingSignals()) { warnings.push_back(tr("%1 has overlapping bits.").arg(s->name)); } + name_label->setText(QString("%1 (%2)").arg(msgName(msg_id), msg->transmitter)); } else { warnings.push_back(tr("Drag-Select in binary view to create new signal.")); + name_label->setText(msgName(msg_id)); } remove_btn->setEnabled(msg != nullptr); - name_label->setText(msgName(msg_id)); if (!warnings.isEmpty()) { warning_label->setText(warnings.join('\n')); @@ -164,8 +164,8 @@ void DetailWidget::editMsg() { int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); EditMessageDialog dlg(msg_id, msgName(msg_id), size, this); if (dlg.exec()) { - UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed(), - dlg.size_spin->value(), dlg.comment_edit->toPlainText().trimmed())); + UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed(), dlg.size_spin->value(), + dlg.node->text().trimmed(), dlg.comment_edit->toPlainText().trimmed())); } } @@ -182,25 +182,24 @@ EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &tit form_layout->addRow("", error_label = new QLabel); error_label->setVisible(false); - name_edit = new QLineEdit(title, this); + form_layout->addRow(tr("Name"), name_edit = new QLineEdit(title, this)); name_edit->setValidator(new NameValidator(name_edit)); - form_layout->addRow(tr("Name"), name_edit); - size_spin = new QSpinBox(this); + form_layout->addRow(tr("Size"), size_spin = new QSpinBox(this)); // TODO: limit the maximum? size_spin->setMinimum(1); size_spin->setValue(size); - form_layout->addRow(tr("Size"), size_spin); + form_layout->addRow(tr("Node"), node = new QLineEdit(this)); + node->setValidator(new NameValidator(name_edit)); form_layout->addRow(tr("Comment"), comment_edit = new QTextEdit(this)); + form_layout->addRow(btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)); + if (auto msg = dbc()->msg(msg_id)) { + node->setText(msg->transmitter); comment_edit->setText(msg->comment); } - - btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); validateName(name_edit->text()); - form_layout->addRow(btn_box); - setFixedWidth(parent->width() * 0.9); connect(name_edit, &QLineEdit::textEdited, this, &EditMessageDialog::validateName); connect(btn_box, &QDialogButtonBox::accepted, this, &QDialog::accept); diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 10c6a6c46e..ed6a865f53 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -21,6 +21,7 @@ public: QString original_name; QDialogButtonBox *btn_box; QLineEdit *name_edit; + QLineEdit *node; QTextEdit *comment_edit; QLabel *error_label; QSpinBox *size_spin; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index cf9dea88db..5330549963 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -1,5 +1,7 @@ #include "tools/cabana/historylog.h" +#include + #include #include #include @@ -153,20 +155,18 @@ std::deque HistoryLogModel::fetchData(uint64_t from_ti auto msgs = fetchData(first, events.rend(), min_time); if (update_colors && (min_time > 0 || messages.empty())) { for (auto it = msgs.rbegin(); it != msgs.rend(); ++it) { - hex_colors.compute(it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, nullptr, freq); + hex_colors.compute(msg_id, it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, nullptr, freq); it->colors = hex_colors.colors; } } return msgs; } else { assert(min_time == 0); - auto first = std::upper_bound(events.cbegin(), events.cend(), from_time, [](uint64_t ts, auto e) { - return ts < e->mono_time; - }); + auto first = std::upper_bound(events.cbegin(), events.cend(), from_time, CompareCanEvent()); auto msgs = fetchData(first, events.cend(), 0); if (update_colors) { for (auto it = msgs.begin(); it != msgs.end(); ++it) { - hex_colors.compute(it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, nullptr, freq); + hex_colors.compute(msg_id, it->data.data(), it->data.size(), it->mono_time / (double)1e9, speed, nullptr, freq); it->colors = hex_colors.colors; } } diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index 2dea698f68..a68fbdbf43 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -1,6 +1,8 @@ #pragma once #include +#include + #include #include #include diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index fa91095706..5cd5e70267 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -1,6 +1,9 @@ #include "tools/cabana/mainwin.h" +#include #include +#include + #include #include #include @@ -436,11 +439,11 @@ void MainWindow::saveFile(DBCFile *dbc_file) { if (!dbc_file->filename.isEmpty()) { dbc_file->save(); updateLoadSaveMenus(); + UndoStack::instance()->setClean(); + statusBar()->showMessage(tr("File saved"), 2000); } else if (!dbc_file->isEmpty()) { saveFileAs(dbc_file); } - UndoStack::instance()->setClean(); - statusBar()->showMessage(tr("File saved"), 2000); } void MainWindow::saveFileAs(DBCFile *dbc_file) { @@ -448,6 +451,8 @@ void MainWindow::saveFileAs(DBCFile *dbc_file) { QString fn = QFileDialog::getSaveFileName(this, title, QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)")); if (!fn.isEmpty()) { dbc_file->saveAs(fn); + UndoStack::instance()->setClean(); + statusBar()->showMessage(tr("File saved as %1").arg(fn), 2000); updateRecentFiles(fn); updateLoadSaveMenus(); } @@ -603,7 +608,13 @@ void MainWindow::closeEvent(QCloseEvent *event) { settings.video_splitter_state = video_splitter->saveState(); } settings.message_header_state = messages_widget->saveHeaderState(); - settings.save(); + + auto status = settings.save(); + if (status == QSettings::AccessError) { + QString error = tr("Failed to write settings to [%1]: access denied").arg(Settings::filePath()); + qDebug() << error; + QMessageBox::warning(this, tr("Failed to write settings"), error); + } QWidget::closeEvent(event); } diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index fe4c7afb03..626f9875da 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -1,4 +1,10 @@ #include "tools/cabana/messageswidget.h" + +#include +#include +#include +#include + #include #include #include @@ -9,7 +15,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(0 ,0, 0, 0); + main_layout->setContentsMargins(0, 0, 0, 0); QHBoxLayout *title_layout = new QHBoxLayout(); num_msg_label = new QLabel(this); @@ -168,7 +174,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { auto getFreq = [](const CanData &d) -> QString { if (d.freq > 0 && (can->currentSec() - d.ts - 1.0 / settings.fps) < (5.0 / d.freq)) { - return d.freq >= 1 ? QString::number(std::nearbyint(d.freq)) : QString::number(d.freq, 'f', 2); + return d.freq >= 0.95 ? QString::number(std::nearbyint(d.freq)) : QString::number(d.freq, 'f', 2); } else { return "--"; } @@ -177,7 +183,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { switch (index.column()) { case Column::NAME: return msgName(id); - case Column::SOURCE: return id.source != INVALID_SOURCE ? QString::number(id.source) : "N/A" ; + case Column::SOURCE: return id.source != INVALID_SOURCE ? QString::number(id.source) : "N/A"; case Column::ADDRESS: return QString::number(id.address, 16); case Column::FREQ: return id.source != INVALID_SOURCE ? getFreq(can_data) : "N/A"; case Column::COUNT: return id.source != INVALID_SOURCE ? QString::number(can_data.count) : "N/A"; @@ -275,7 +281,8 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co case Column::NAME: { const auto msg = dbc()->msg(id); match = re.match(msg ? msg->name : UNTITLED).hasMatch(); - match |= msg && std::any_of(msg->sigs.cbegin(), msg->sigs.cend(), [&re](const auto &s) { return re.match(s->name).hasMatch(); }); + match = match || (msg && std::any_of(msg->sigs.cbegin(), msg->sigs.cend(), + [&re](const auto &s) { return re.match(s->name).hasMatch(); })); break; } case Column::SOURCE: @@ -283,7 +290,7 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co break; case Column::ADDRESS: { match = re.match(QString::number(id.address, 16)).hasMatch(); - match |= parseRange(txt, id.address, 16); + match = match || parseRange(txt, id.address, 16); break; } case Column::FREQ: @@ -295,8 +302,8 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co break; case Column::DATA: { match = QString(data.dat.toHex()).contains(txt, Qt::CaseInsensitive); - match |= re.match(QString(data.dat.toHex())).hasMatch(); - match |= re.match(QString(data.dat.toHex(' '))).hasMatch(); + match = match || re.match(QString(data.dat.toHex())).hasMatch(); + match = match || re.match(QString(data.dat.toHex(' '))).hasMatch(); break; } } @@ -417,7 +424,7 @@ void MessageView::updateBytesSectionSize() { } void MessageView::headerContextMenuEvent(const QPoint &pos) { - QMenu *menu = new QMenu(this); + QMenu menu(this); int cur_index = header()->logicalIndexAt(pos); QAction *action; @@ -427,9 +434,9 @@ void MessageView::headerContextMenuEvent(const QPoint &pos) { // Hide show action if (header()->isSectionHidden(logical_index)) { - action = menu->addAction(tr("  %1").arg(column_name), [=]() { header()->showSection(logical_index); }); + action = menu.addAction(tr("  %1").arg(column_name), [=]() { header()->showSection(logical_index); }); } else { - action = menu->addAction(tr("✓ %1").arg(column_name), [=]() { header()->hideSection(logical_index); }); + action = menu.addAction(tr("✓ %1").arg(column_name), [=]() { header()->hideSection(logical_index); }); } // Can't hide the name column @@ -443,7 +450,7 @@ void MessageView::headerContextMenuEvent(const QPoint &pos) { } } - menu->popup(header()->mapToGlobal(pos)); + menu.exec(header()->mapToGlobal(pos)); } MessageViewHeader::MessageViewHeader(QWidget *parent) : QHeaderView(Qt::Horizontal, parent) { diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index 2e165f5b7c..f6d71a5a2e 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include + #include #include #include diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index d0cada680a..ee345c490c 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -6,19 +6,14 @@ #include #include #include -#include #include #include "tools/cabana/util.h" Settings settings; -Settings::Settings() { - load(); -} - -void Settings::save() { - QSettings s("settings", QSettings::IniFormat); +QSettings::Status Settings::save() { + QSettings s(filePath(), QSettings::IniFormat); s.setValue("fps", fps); s.setValue("max_cached_minutes", max_cached_minutes); s.setValue("chart_height", chart_height); @@ -39,10 +34,12 @@ void Settings::save() { s.setValue("log_path", log_path); s.setValue("drag_direction", drag_direction); s.setValue("suppress_defined_signals", suppress_defined_signals); + s.sync(); + return s.status(); } void Settings::load() { - QSettings s("settings", QSettings::IniFormat); + QSettings s(filePath(), QSettings::IniFormat); fps = s.value("fps", 10).toInt(); max_cached_minutes = s.value("max_cached_minutes", 30).toInt(); chart_height = s.value("chart_height", 200).toInt(); diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index b8a3797f86..42073a72de 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -1,11 +1,13 @@ #pragma once +#include #include #include #include #include #include #include +#include #include #define LIGHT_THEME 1 @@ -22,9 +24,10 @@ public: AlwaysBE, }; - Settings(); - void save(); + Settings() {} + QSettings::Status save(); void load(); + inline static QString filePath() { return QApplication::applicationDirPath() + "/settings"; } int fps = 10; int max_cached_minutes = 30; diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index ce10576ffe..b2798e9f3b 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -1,5 +1,7 @@ #include "tools/cabana/signalview.h" +#include + #include #include #include @@ -34,7 +36,7 @@ SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(p void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const cabana::Signal *sig) { Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name, .type = Item::Sig}; parent_item->children.insert(pos, item); - QString titles[]{"Name", "Size", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info", + QString titles[]{"Name", "Size", "Node", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info", "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Descriptions"}; for (int i = 0; i < std::size(titles); ++i) { item->children.push_back(new Item{.sig = sig, .parent = item, .title = titles[i], .type = (Item::Type)(i + Item::Name)}); @@ -132,6 +134,7 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const { case Item::Sig: return item->sig_val; case Item::Name: return item->sig->name; case Item::Size: return item->sig->size; + case Item::Node: return item->sig->receiver_name; case Item::SignalType: return signalTypeToString(item->sig->type); case Item::MultiplexValue: return item->sig->multiplex_value; case Item::Offset: return doubleToString(item->sig->offset); @@ -170,6 +173,7 @@ bool SignalModel::setData(const QModelIndex &index, const QVariant &value, int r switch (item->type) { case Item::Name: s.name = value.toString(); break; case Item::Size: s.size = value.toInt(); break; + case Item::Node: s.receiver_name = value.toString().trimmed(); break; case Item::SignalType: s.type = (cabana::Signal::Type)value.toInt(); break; case Item::MultiplexValue: s.multiplex_value = value.toInt(); break; case Item::Endian: s.is_little_endian = value.toBool(); break; @@ -193,11 +197,11 @@ void SignalModel::showExtraInfo(const QModelIndex &index) { if (item->type == Item::ExtraInfo) { if (!item->parent->extra_expanded) { item->parent->extra_expanded = true; - beginInsertRows(index.parent(), 7, 13); + beginInsertRows(index.parent(), Item::ExtraInfo - 2, Item::Desc - 2); endInsertRows(); } else { item->parent->extra_expanded = false; - beginRemoveRows(index.parent(), 7, 13); + beginRemoveRows(index.parent(), Item::ExtraInfo - 2, Item::Desc - 2); endRemoveRows(); } } @@ -265,6 +269,7 @@ void SignalModel::handleSignalRemoved(const cabana::Signal *sig) { SignalItemDelegate::SignalItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { name_validator = new NameValidator(this); + node_validator = new QRegExpValidator(QRegExp("^\\w+(,\\w+)*$"), this); double_validator = new DoubleValidator(this); label_font.setPointSize(8); @@ -380,12 +385,14 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { auto item = (SignalModel::Item *)index.internalPointer(); - if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Offset || + if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Node || item->type == SignalModel::Item::Offset || item->type == SignalModel::Item::Factor || item->type == SignalModel::Item::MultiplexValue || item->type == SignalModel::Item::Min || item->type == SignalModel::Item::Max) { QLineEdit *e = new QLineEdit(parent); e->setFrame(false); - e->setValidator(item->type == SignalModel::Item::Name ? name_validator : double_validator); + if (item->type == SignalModel::Item::Name) e->setValidator(name_validator); + else if (item->type == SignalModel::Item::Node) e->setValidator(node_validator); + else e->setValidator(double_validator); if (item->type == SignalModel::Item::Name) { QCompleter *completer = new QCompleter(dbc()->signalNames()); @@ -393,7 +400,6 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie completer->setFilterMode(Qt::MatchContains); e->setCompleter(completer); } - return e; } else if (item->type == SignalModel::Item::Size) { QSpinBox *spin = new QSpinBox(parent); diff --git a/tools/cabana/signalview.h b/tools/cabana/signalview.h index 9541ac8a3b..bcf0019bc4 100644 --- a/tools/cabana/signalview.h +++ b/tools/cabana/signalview.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -15,7 +17,7 @@ class SignalModel : public QAbstractItemModel { Q_OBJECT public: struct Item { - enum Type {Root, Sig, Name, Size, Endian, Signed, Offset, Factor, SignalType, MultiplexValue, ExtraInfo, Unit, Comment, Min, Max, Desc }; + enum Type {Root, Sig, Name, Size, Node, Endian, Signed, Offset, Factor, SignalType, MultiplexValue, ExtraInfo, Unit, Comment, Min, Max, Desc }; ~Item() { qDeleteAll(children); } inline int row() { return parent->children.indexOf(this); } @@ -85,7 +87,7 @@ public: void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; - QValidator *name_validator, *double_validator; + QValidator *name_validator, *double_validator, *node_validator; QFont label_font, minmax_font; const int color_label_width = 18; mutable QSize button_size; @@ -129,7 +131,7 @@ private: QAbstractItemView::dataChanged(topLeft, bottomRight, roles); } void leaveEvent(QEvent *event) override { - emit ((SignalView *)parentWidget())->highlight(nullptr); + emit static_cast(parentWidget())->highlight(nullptr); QTreeView::leaveEvent(event); } }; diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index 7cd210d8f1..6fa479815d 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -1,7 +1,12 @@ #include "tools/cabana/streams/abstractstream.h" +#include +#include + #include +static const int EVENT_NEXT_BUFFER_SIZE = 6 * 1024 * 1024; // 6MB + AbstractStream *can = nullptr; StreamNotifier *StreamNotifier::instance() { @@ -9,8 +14,11 @@ StreamNotifier *StreamNotifier::instance() { return ¬ifier; } -AbstractStream::AbstractStream(QObject *parent) : new_msgs(new QHash()), QObject(parent) { +AbstractStream::AbstractStream(QObject *parent) : QObject(parent) { assert(parent != nullptr); + new_msgs = std::make_unique>(); + event_buffer = std::make_unique(EVENT_NEXT_BUFFER_SIZE); + QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo); QObject::connect(&settings, &Settings::changed, this, &AbstractStream::updateMasks); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &AbstractStream::updateMasks); @@ -19,6 +27,11 @@ AbstractStream::AbstractStream(QObject *parent) : new_msgs(new QHashchangingStream(); delete can; can = this; + // TODO: add method stop() to class AbstractStream + QObject::connect(qApp, &QApplication::aboutToQuit, can, []() { + qDebug() << "stopping stream thread"; + can->pause(true); + }); emit StreamNotifier::instance()->streamStarted(); }); } @@ -59,7 +72,7 @@ void AbstractStream::updateEvent(const MessageId &id, double sec, const uint8_t std::lock_guard lk(mutex); auto mask_it = masks.find(id); std::vector *mask = mask_it == masks.end() ? nullptr : &mask_it->second; - all_msgs[id].compute((const char *)data, size, sec, getSpeed(), mask); + all_msgs[id].compute(id, (const char *)data, size, sec, getSpeed(), mask); if (!new_msgs->contains(id)) { new_msgs->insert(id, {}); } @@ -110,9 +123,8 @@ void AbstractStream::updateLastMsgsTo(double sec) { if (it != ev.crend()) { double ts = (*it)->mono_time / 1e9 - routeStartTime(); auto &m = all_msgs[id]; - m.compute((const char *)(*it)->dat, (*it)->size, ts, getSpeed(), mask); + m.compute(id, (const char *)(*it)->dat, (*it)->size, ts, getSpeed(), mask); m.count = std::distance(it, ev.crend()); - m.freq = m.count / std::max(1.0, ts); } } @@ -127,61 +139,48 @@ void AbstractStream::updateLastMsgsTo(double sec) { } void AbstractStream::mergeEvents(std::vector::const_iterator first, std::vector::const_iterator last) { - size_t memory_size = 0; - size_t events_cnt = 0; - for (auto it = first; it != last; ++it) { - if ((*it)->which == cereal::Event::Which::CAN) { - for (const auto &c : (*it)->event.getCan()) { - memory_size += sizeof(CanEvent) + sizeof(uint8_t) * c.getDat().size(); - ++events_cnt; - } - } - } - if (memory_size == 0) return; + static std::unordered_map> new_events_map; + static std::vector new_events; + new_events_map.clear(); + new_events.clear(); - char *ptr = memory_blocks.emplace_back(new char[memory_size]).get(); - std::unordered_map> new_events_map; - std::vector new_events; - new_events.reserve(events_cnt); for (auto it = first; it != last; ++it) { if ((*it)->which == cereal::Event::Which::CAN) { uint64_t ts = (*it)->mono_time; for (const auto &c : (*it)->event.getCan()) { - CanEvent *e = (CanEvent *)ptr; + auto dat = c.getDat(); + CanEvent *e = (CanEvent *)event_buffer->allocate(sizeof(CanEvent) + sizeof(uint8_t) * dat.size()); e->src = c.getSrc(); e->address = c.getAddress(); e->mono_time = ts; - auto dat = c.getDat(); e->size = dat.size(); memcpy(e->dat, (uint8_t *)dat.begin(), e->size); new_events_map[{.source = e->src, .address = e->address}].push_back(e); new_events.push_back(e); - ptr += sizeof(CanEvent) + sizeof(uint8_t) * e->size; } } } - bool append = new_events.front()->mono_time > lastest_event_ts; for (auto &[id, new_e] : new_events_map) { auto &e = events_[id]; - auto pos = append ? e.end() : std::upper_bound(e.cbegin(), e.cend(), new_e.front(), [](const CanEvent *l, const CanEvent *r) { - return l->mono_time < r->mono_time; - }); - e.insert(pos, new_e.cbegin(), new_e.cend()); + auto insert_pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent()); + e.insert(insert_pos, new_e.cbegin(), new_e.cend()); } - auto pos = append ? all_events_.end() : std::upper_bound(all_events_.begin(), all_events_.end(), new_events.front(), [](auto l, auto r) { - return l->mono_time < r->mono_time; - }); - all_events_.insert(pos, new_events.cbegin(), new_events.cend()); + if (!new_events.empty()) { + auto insert_pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), new_events.front()->mono_time, CompareCanEvent()); + all_events_.insert(insert_pos, new_events.cbegin(), new_events.cend()); + } - lastest_event_ts = all_events_.back()->mono_time; + lastest_event_ts = all_events_.empty() ? 0 : all_events_.back()->mono_time; emit eventsMerged(); } // CanData +namespace { + constexpr int periodic_threshold = 10; constexpr int start_alpha = 128; constexpr float fade_time = 2.0; @@ -192,15 +191,37 @@ const QColor CYAN_LIGHTER = QColor(0, 187, 255, start_alpha).lighter(135); const QColor RED_LIGHTER = QColor(255, 0, 0, start_alpha).lighter(135); const QColor GREYISH_BLUE_LIGHTER = QColor(102, 86, 169, start_alpha / 2).lighter(135); -static inline QColor blend(const QColor &a, const QColor &b) { +inline QColor blend(const QColor &a, const QColor &b) { return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2); } -void CanData::compute(const char *can_data, const int size, double current_sec, double playback_speed, const std::vector *mask, uint32_t in_freq) { +// Calculate the frequency of the past minute. +double calc_freq(const MessageId &msg_id, double current_sec) { + const auto &events = can->events(msg_id); + uint64_t cur_mono_time = (can->routeStartTime() + current_sec) * 1e9; + uint64_t first_mono_time = std::max(0, cur_mono_time - 59 * 1e9); + auto first = std::lower_bound(events.begin(), events.end(), first_mono_time, CompareCanEvent()); + auto second = std::lower_bound(first, events.end(), cur_mono_time, CompareCanEvent()); + if (first != events.end() && second != events.end()) { + double duration = ((*second)->mono_time - (*first)->mono_time) / 1e9; + uint32_t count = std::distance(first, second); + return count / std::max(1.0, duration); + } + return 0; +} + +} // namespace + +void CanData::compute(const MessageId &msg_id, const char *can_data, const int size, double current_sec, + double playback_speed, const std::vector *mask, double in_freq) { ts = current_sec; ++count; - const double sec_to_first_event = current_sec - (can->allEvents().front()->mono_time / 1e9 - can->routeStartTime()); - freq = in_freq == 0 ? count / std::max(1.0, sec_to_first_event) : in_freq; + + if (auto sec = seconds_since_boot(); (sec - last_freq_update_ts) >= 1) { + last_freq_update_ts = sec; + freq = !in_freq ? calc_freq(msg_id, ts) : in_freq; + } + if (dat.size() != size) { dat.resize(size); bit_change_counts.resize(size); diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index fb42a58e24..3a89d4e57d 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -3,17 +3,23 @@ #include #include #include +#include +#include #include +#include + #include #include +#include "common/timing.h" #include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/settings.h" #include "tools/cabana/util.h" #include "tools/replay/replay.h" struct CanData { - void compute(const char *dat, const int size, double current_sec, double playback_speed, const std::vector *mask, uint32_t in_freq = 0); + void compute(const MessageId &msg_id, const char *dat, const int size, double current_sec, + double playback_speed, const std::vector *mask = nullptr, double in_freq = 0); double ts = 0.; uint32_t count = 0; @@ -24,6 +30,7 @@ struct CanData { std::vector> bit_change_counts; std::vector last_delta; std::vector same_delta_counter; + double last_freq_update_ts = 0; }; struct CanEvent { @@ -34,12 +41,23 @@ struct CanEvent { uint8_t dat[]; }; +struct CompareCanEvent { + constexpr bool operator()(const CanEvent *const e, uint64_t ts) const { return e->mono_time < ts; } + constexpr bool operator()(uint64_t ts, const CanEvent *const e) const { return ts < e->mono_time; } +}; + +struct BusConfig { + int can_speed_kbps = 500; + int data_speed_kbps = 2000; + bool can_fd = false; +}; + class AbstractStream : public QObject { Q_OBJECT public: AbstractStream(QObject *parent); - virtual ~AbstractStream() {}; + virtual ~AbstractStream() {} virtual void start() = 0; inline bool liveStreaming() const { return route() == nullptr; } virtual void seekTo(double ts) {} @@ -88,7 +106,7 @@ protected: QHash all_msgs; std::unordered_map> events_; std::vector all_events_; - std::deque> memory_blocks; + std::unique_ptr event_buffer; std::mutex mutex; std::unordered_map> masks; }; diff --git a/tools/cabana/streams/devicestream.cc b/tools/cabana/streams/devicestream.cc index 5631f64d68..349a2d7a1c 100644 --- a/tools/cabana/streams/devicestream.cc +++ b/tools/cabana/streams/devicestream.cc @@ -1,5 +1,8 @@ #include "tools/cabana/streams/devicestream.h" +#include +#include + #include #include #include diff --git a/tools/cabana/streams/livestream.cc b/tools/cabana/streams/livestream.cc index 91c88c97ca..17805a0b66 100644 --- a/tools/cabana/streams/livestream.cc +++ b/tools/cabana/streams/livestream.cc @@ -1,5 +1,8 @@ #include "tools/cabana/streams/livestream.h" +#include +#include + struct LiveStream::Logger { Logger() : start_ts(seconds_since_epoch()), segment_num(-1) {} @@ -99,12 +102,8 @@ void LiveStream::updateEvents() { uint64_t last_ts = post_last_event && speed_ == 1.0 ? all_events_.back()->mono_time : first_event_ts + (nanos_since_boot() - first_update_ts) * speed_; - auto first = std::upper_bound(all_events_.cbegin(), all_events_.cend(), current_event_ts, [](uint64_t ts, auto e) { - return ts < e->mono_time; - }); - auto last = std::upper_bound(first, all_events_.cend(), last_ts, [](uint64_t ts, auto e) { - return ts < e->mono_time; - }); + auto first = std::upper_bound(all_events_.cbegin(), all_events_.cend(), current_event_ts, CompareCanEvent()); + auto last = std::upper_bound(first, all_events_.cend(), last_ts, CompareCanEvent()); for (auto it = first; it != last; ++it) { const CanEvent *e = *it; diff --git a/tools/cabana/streams/livestream.h b/tools/cabana/streams/livestream.h index b4816d090f..38ef2c67f9 100644 --- a/tools/cabana/streams/livestream.h +++ b/tools/cabana/streams/livestream.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include + #include #include "tools/cabana/streams/abstractstream.h" diff --git a/tools/cabana/streams/pandastream.cc b/tools/cabana/streams/pandastream.cc index e19b2450bd..4a6c588e51 100644 --- a/tools/cabana/streams/pandastream.cc +++ b/tools/cabana/streams/pandastream.cc @@ -1,5 +1,7 @@ #include "tools/cabana/streams/pandastream.h" +#include + #include #include #include diff --git a/tools/cabana/streams/pandastream.h b/tools/cabana/streams/pandastream.h index f726c5cfb6..43803950f9 100644 --- a/tools/cabana/streams/pandastream.h +++ b/tools/cabana/streams/pandastream.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include #include #include @@ -10,12 +13,6 @@ const uint32_t speeds[] = {10U, 20U, 50U, 100U, 125U, 250U, 500U, 1000U}; const uint32_t data_speeds[] = {10U, 20U, 50U, 100U, 125U, 250U, 500U, 1000U, 2000U, 5000U}; -struct BusConfig { - int can_speed_kbps = 500; - int data_speed_kbps = 2000; - bool can_fd = false; -}; - struct PandaStreamConfig { QString serial = ""; std::vector bus_config; diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index eddcf514f7..de69d9e86c 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -1,5 +1,11 @@ #pragma once +#include +#include +#include +#include +#include + #include "common/prefix.h" #include "tools/cabana/streams/abstractstream.h" diff --git a/tools/cabana/streams/socketcanstream.cc b/tools/cabana/streams/socketcanstream.cc new file mode 100644 index 0000000000..3df8e31f3b --- /dev/null +++ b/tools/cabana/streams/socketcanstream.cc @@ -0,0 +1,114 @@ +#include "tools/cabana/streams/socketcanstream.h" + +#include +#include +#include + +SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) : config(config_), LiveStream(parent) { + if (!available()) { + throw std::runtime_error("SocketCAN plugin not available"); + } + + qDebug() << "Connecting to SocketCAN device" << config.device; + if (!connect()) { + throw std::runtime_error("Failed to connect to SocketCAN device"); + } +} + +bool SocketCanStream::available() { + return QCanBus::instance()->plugins().contains("socketcan"); +} + +bool SocketCanStream::connect() { + // Connecting might generate some warnings about missing socketcan/libsocketcan libraries + // These are expected and can be ignored, we don't need the advanced features of libsocketcan + QString errorString; + device.reset(QCanBus::instance()->createDevice("socketcan", config.device, &errorString)); + + if (!device) { + qDebug() << "Failed to create SocketCAN device" << errorString; + return false; + } + + if (!device->connectDevice()) { + qDebug() << "Failed to connect to device"; + return false; + } + + return true; +} + +void SocketCanStream::streamThread() { + while (!QThread::currentThread()->isInterruptionRequested()) { + QThread::msleep(1); + + auto frames = device->readAllFrames(); + if (frames.size() == 0) continue; + + MessageBuilder msg; + auto evt = msg.initEvent(); + auto canData = evt.initCan(frames.size()); + + + for (uint i = 0; i < frames.size(); i++) { + if (!frames[i].isValid()) continue; + + canData[i].setAddress(frames[i].frameId()); + canData[i].setSrc(0); + + auto payload = frames[i].payload(); + canData[i].setDat(kj::arrayPtr((uint8_t*)payload.data(), payload.size())); + } + + auto bytes = msg.toBytes(); + handleEvent((const char*)bytes.begin(), bytes.size()); + } +} + +AbstractOpenStreamWidget *SocketCanStream::widget(AbstractStream **stream) { + return new OpenSocketCanWidget(stream); +} + +OpenSocketCanWidget::OpenSocketCanWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->addStretch(1); + + QFormLayout *form_layout = new QFormLayout(); + + QHBoxLayout *device_layout = new QHBoxLayout(); + device_edit = new QComboBox(); + device_edit->setFixedWidth(300); + device_layout->addWidget(device_edit); + + QPushButton *refresh = new QPushButton(tr("Refresh")); + refresh->setFixedWidth(100); + device_layout->addWidget(refresh); + form_layout->addRow(tr("Device"), device_layout); + main_layout->addLayout(form_layout); + + main_layout->addStretch(1); + + QObject::connect(refresh, &QPushButton::clicked, this, &OpenSocketCanWidget::refreshDevices); + QObject::connect(device_edit, &QComboBox::currentTextChanged, this, [=]{ config.device = device_edit->currentText(); }); + + // Populate devices + refreshDevices(); +} + +void OpenSocketCanWidget::refreshDevices() { + device_edit->clear(); + for (auto device : QCanBus::instance()->availableDevices(QStringLiteral("socketcan"))) { + device_edit->addItem(device.name()); + } +} + + +bool OpenSocketCanWidget::open() { + try { + *stream = new SocketCanStream(qApp, config); + } catch (std::exception &e) { + QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to SocketCAN device: '%1'").arg(e.what())); + return false; + } + return true; +} diff --git a/tools/cabana/streams/socketcanstream.h b/tools/cabana/streams/socketcanstream.h new file mode 100644 index 0000000000..6f2d7aa353 --- /dev/null +++ b/tools/cabana/streams/socketcanstream.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include + +#include "tools/cabana/streams/livestream.h" + +struct SocketCanStreamConfig { + QString device = ""; // TODO: support multiple devices/buses at once +}; + +class SocketCanStream : public LiveStream { + Q_OBJECT +public: + SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {}); + static AbstractOpenStreamWidget *widget(AbstractStream **stream); + + static bool available(); + + inline QString routeName() const override { + return QString("Live Streaming From Socket CAN %1").arg(config.device); + } + +protected: + void streamThread() override; + bool connect(); + + SocketCanStreamConfig config = {}; + std::unique_ptr device; +}; + +class OpenSocketCanWidget : public AbstractOpenStreamWidget { + Q_OBJECT + +public: + OpenSocketCanWidget(AbstractStream **stream); + bool open() override; + QString title() override { return tr("&SocketCAN"); } + +private: + void refreshDevices(); + + QComboBox *device_edit; + SocketCanStreamConfig config = {}; +}; diff --git a/tools/cabana/streamselector.cc b/tools/cabana/streamselector.cc index 6da44ecd1a..719ba72920 100644 --- a/tools/cabana/streamselector.cc +++ b/tools/cabana/streamselector.cc @@ -6,9 +6,11 @@ #include #include +#include "streams/socketcanstream.h" #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/replaystream.h" +#include "tools/cabana/streams/socketcanstream.h" StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Open stream")); @@ -40,6 +42,9 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial addStreamWidget(ReplayStream::widget(stream)); addStreamWidget(PandaStream::widget(stream)); + if (SocketCanStream::available()) { + addStreamWidget(SocketCanStream::widget(stream)); + } addStreamWidget(DeviceStream::widget(stream)); QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index a3d014dd2c..d114f72ea5 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -6,8 +6,7 @@ #include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" -// demo route, first segment -const std::string TEST_RLOG_URL = "https://commadata2.blob.core.windows.net/commadata2/a2a0ccea32023010/2023-07-27--13-01-19/0/rlog.bz2"; +const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; TEST_CASE("DBCFile::generateDBC") { QString fn = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, "tesla_can"); diff --git a/tools/cabana/tools/findsignal.cc b/tools/cabana/tools/findsignal.cc index fceb823bbb..51d86f5964 100644 --- a/tools/cabana/tools/findsignal.cc +++ b/tools/cabana/tools/findsignal.cc @@ -37,10 +37,10 @@ void FindSignalModel::search(std::function cmp) { filtered_signals.reserve(prev_sigs.size()); QtConcurrent::blockingMap(prev_sigs, [&](auto &s) { const auto &events = can->events(s.id); - auto first = std::upper_bound(events.cbegin(), events.cend(), s.mono_time, [](uint64_t ts, auto &e) { return ts < e->mono_time; }); + auto first = std::upper_bound(events.cbegin(), events.cend(), s.mono_time, CompareCanEvent()); auto last = events.cend(); if (last_time < std::numeric_limits::max()) { - last = std::upper_bound(events.cbegin(), events.cend(), last_time, [](uint64_t ts, auto &e) { return ts < e->mono_time; }); + last = std::upper_bound(events.cbegin(), events.cend(), last_time, CompareCanEvent()); } auto it = std::find_if(first, last, [&](const CanEvent *e) { return cmp(get_raw_value(e->dat, e->size, s.sig)); }); @@ -180,8 +180,8 @@ void FindSignalDlg::search() { std::function cmp = nullptr; switch (compare_cb->currentIndex()) { case 0: cmp = [v1](double v) { return v == v1;}; break; - case 1: cmp = [v1](double v) { return v > v1;};break; - case 2: cmp = [v1](double v) { return v >= v1;};break; + case 1: cmp = [v1](double v) { return v > v1;}; break; + case 2: cmp = [v1](double v) { return v >= v1;}; break; case 3: cmp = [v1](double v) { return v != v1;}; break; case 4: cmp = [v1](double v) { return v < v1;}; break; case 5: cmp = [v1](double v) { return v <= v1;}; break; @@ -225,7 +225,7 @@ void FindSignalDlg::setInitialSignals() { for (auto it = can->last_msgs.cbegin(); it != can->last_msgs.cend(); ++it) { if (buses.isEmpty() || buses.contains(it.key().source) && (addresses.isEmpty() || addresses.contains(it.key().address))) { const auto &events = can->events(it.key()); - auto e = std::lower_bound(events.cbegin(), events.cend(), first_time, [](auto e, uint64_t ts) { return e->mono_time < ts; }); + auto e = std::lower_bound(events.cbegin(), events.cend(), first_time, CompareCanEvent()); if (e != events.cend()) { const int total_size = it.value().dat.size() * 8; for (int size = min_size->value(); size <= max_size->value(); ++size) { diff --git a/tools/cabana/tools/findsignal.h b/tools/cabana/tools/findsignal.h index f046bdf7e3..e9e5f9f180 100644 --- a/tools/cabana/tools/findsignal.h +++ b/tools/cabana/tools/findsignal.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include #include #include diff --git a/tools/cabana/tools/findsimilarbits.cc b/tools/cabana/tools/findsimilarbits.cc index 72429104a5..c3c659791a 100644 --- a/tools/cabana/tools/findsimilarbits.cc +++ b/tools/cabana/tools/findsimilarbits.cc @@ -1,5 +1,7 @@ #include "tools/cabana/tools/findsimilarbits.h" +#include + #include #include #include diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc index 31a4486772..4c21530774 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/util.cc @@ -1,12 +1,15 @@ #include "tools/cabana/util.h" -#include +#include #include +#include +#include +#include #include #include -#include #include +#include #include #include #include @@ -52,6 +55,10 @@ std::pair SegmentTree::get_minmax(int n, int left, int right, in MessageBytesDelegate::MessageBytesDelegate(QObject *parent, bool multiple_lines) : multiple_lines(multiple_lines), QStyledItemDelegate(parent) { fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); byte_size = QFontMetrics(fixed_font).size(Qt::TextSingleLine, "00 ") + QSize(0, 2); + for (int i = 0; i < 256; ++i) { + hex_text_table[i].setText(QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper()); + hex_text_table[i].prepare({}, fixed_font); + } } int MessageBytesDelegate::widthForBytes(int n) const { @@ -103,7 +110,7 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem & } else if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.color(QPalette::HighlightedText)); } - painter->drawText(r, Qt::AlignCenter, toHex(byte_list[i])); + utils::drawStaticText(painter, r, hex_text_table[(uint8_t)(byte_list[i])]); } painter->setFont(old_font); painter->setPen(old_pen); @@ -261,3 +268,26 @@ QString signalToolTip(const cabana::Signal *sig) { )").arg(sig->name).arg(sig->start_bit).arg(sig->size).arg(sig->msb).arg(sig->lsb) .arg(sig->is_little_endian ? "Y" : "N").arg(sig->is_signed ? "Y" : "N"); } + +// MonotonicBuffer + +void *MonotonicBuffer::allocate(size_t bytes, size_t alignment) { + assert(bytes > 0); + void *p = std::align(alignment, bytes, current_buf, available); + if (p == nullptr) { + available = next_buffer_size = std::max(next_buffer_size, bytes); + current_buf = buffers.emplace_back(std::aligned_alloc(alignment, next_buffer_size)); + next_buffer_size *= growth_factor; + p = current_buf; + } + + current_buf = (char *)current_buf + bytes; + available -= bytes; + return p; +} + +MonotonicBuffer::~MonotonicBuffer() { + for (auto buf : buffers) { + free(buf); + } +} diff --git a/tools/cabana/util.h b/tools/cabana/util.h index 76238569f0..2c1bc5cf7b 100644 --- a/tools/cabana/util.h +++ b/tools/cabana/util.h @@ -1,14 +1,20 @@ #pragma once +#include #include +#include +#include +#include #include #include #include #include #include +#include #include #include +#include #include #include #include @@ -57,7 +63,7 @@ public: private: std::pair get_minmax(int n, int left, int right, int range_left, int range_right) const; void build_tree(const QVector &arr, int n, int left, int right); - std::vector> tree; + std::vector> tree; int size = 0; }; @@ -72,6 +78,7 @@ public: int widthForBytes(int n) const; private: + std::array hex_text_table; QFont fixed_font; QSize byte_size = {}; bool multiple_lines = false; @@ -99,6 +106,10 @@ void setTheme(int theme); inline QString formatSeconds(int seconds) { return QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); } +inline void drawStaticText(QPainter *p, const QRect &r, const QStaticText &text) { + auto size = (r.size() - text.size()) / 2; + p->drawStaticText(r.left() + size.width(), r.top() + size.height(), text); +} } class ToolButton : public QToolButton { @@ -151,5 +162,20 @@ private: QSocketNotifier *sn; }; +class MonotonicBuffer { +public: + MonotonicBuffer(size_t initial_size) : next_buffer_size(initial_size) {} + ~MonotonicBuffer(); + void *allocate(size_t bytes, size_t alignment = 16ul); + void deallocate(void *p) {} + +private: + void *current_buf = nullptr; + size_t next_buffer_size = 0; + size_t available = 0; + std::deque buffers; + static constexpr float growth_factor = 1.5; +}; + int num_decimals(double num); QString signalToolTip(const cabana::Signal *sig); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 46642e5244..e7845bca47 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -1,5 +1,10 @@ #include "tools/cabana/videowidget.h" +#include +#include +#include +#include + #include #include #include @@ -30,13 +35,24 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { } // btn controls + QButtonGroup *group = new QButtonGroup(this); + group->setExclusive(true); + QHBoxLayout *control_layout = new QHBoxLayout(); play_btn = new QPushButton(); play_btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); control_layout->addWidget(play_btn); + if (can->liveStreaming()) { + control_layout->addWidget(skip_to_end_btn = new QPushButton(utils::icon("skip-end-fill"), {})); + skip_to_end_btn->setToolTip(tr("Skip to the end")); + QObject::connect(skip_to_end_btn, &QPushButton::clicked, [group]() { + // set speed to 1.0 + group->buttons()[2]->click(); + can->pause(false); + can->seekTo(can->totalSeconds() + 1); + }); + } - QButtonGroup *group = new QButtonGroup(this); - group->setExclusive(true); for (float speed : {0.1, 0.5, 1., 2.}) { QPushButton *btn = new QPushButton(QString("%1x").arg(speed), this); btn->setCheckable(true); @@ -116,7 +132,10 @@ void VideoWidget::setMaximumTime(double sec) { } void VideoWidget::updateTimeRange(double min, double max, bool is_zoomed) { - if (can->liveStreaming()) return; + if (can->liveStreaming()) { + skip_to_end_btn->setEnabled(!is_zoomed); + return; + } if (!is_zoomed) { min = 0; @@ -154,13 +173,13 @@ Slider::Slider(QWidget *parent) : thumbnail_label(parent), QSlider(Qt::Horizonta qlog_future = std::make_unique>(QtConcurrent::run(this, &Slider::parseQLog)); } }); -} - -Slider::~Slider() { - abort_parse_qlog = true; - if (qlog_future) { - qlog_future->waitForFinished(); - } + QObject::connect(qApp, &QApplication::aboutToQuit, [this]() { + abort_parse_qlog = true; + if (qlog_future && qlog_future->isRunning()) { + qDebug() << "stopping thumbnail thread"; + qlog_future->waitForFinished(); + } + }); } AlertInfo Slider::alertInfo(double seconds) { diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 670e866534..bece039a21 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -1,7 +1,11 @@ #pragma once #include +#include +#include #include +#include +#include #include #include @@ -33,7 +37,6 @@ class Slider : public QSlider { public: Slider(QWidget *parent); - ~Slider(); double currentSecond() const { return value() / factor; } void setCurrentSecond(double sec) { setValue(sec * factor); } void setTimeRange(double min, double max); @@ -78,6 +81,7 @@ protected: QLabel *end_time_label; QLabel *time_label; QPushButton *play_btn; + QPushButton *skip_to_end_btn = nullptr; InfoLabel *alert_label; Slider *slider; }; diff --git a/tools/camerastream/compressed_vipc.py b/tools/camerastream/compressed_vipc.py index 495397d242..ac7ca2520b 100755 --- a/tools/camerastream/compressed_vipc.py +++ b/tools/camerastream/compressed_vipc.py @@ -26,7 +26,7 @@ def decoder(addr, vipc_server, vst, nvidia, debug=False): if nvidia: os.environ["NV_LOW_LATENCY"] = "3" # both bLowLatency and CUVID_PKT_ENDOFPICTURE sys.path += os.environ["LD_LIBRARY_PATH"].split(":") - import PyNvCodec as nvc # pylint: disable=import-error + import PyNvCodec as nvc nvDec = nvc.PyNvDecoder(W, H, nvc.PixelFormat.NV12, nvc.CudaVideoCodec.HEVC, 0) cc1 = nvc.ColorspaceConversionContext(nvc.ColorSpace.BT_709, nvc.ColorRange.JPEG) diff --git a/tools/camerastream/receive.py b/tools/camerastream/receive.py index 6f8d67c78f..bf6929c039 100755 --- a/tools/camerastream/receive.py +++ b/tools/camerastream/receive.py @@ -4,7 +4,7 @@ import sys import numpy as np os.environ['ZMQ'] = '1' -from common.window import Window +from openpilot.common.window import Window import cereal.messaging as messaging # start camerad with 'SEND_ROAD=1 SEND_DRIVER=1 SEND_WIDE_ROAD=1 XMIN=771 XMAX=1156 YMIN=483 YMAX=724 ./camerad' diff --git a/tools/gpstest/fuzzy_testing.py b/tools/gpstest/fuzzy_testing.py index a2e130342c..3bad2770cd 100755 --- a/tools/gpstest/fuzzy_testing.py +++ b/tools/gpstest/fuzzy_testing.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import argparse import multiprocessing -import rpyc # pylint: disable=import-error +import rpyc from collections import defaultdict from helper import download_rinex, exec_LimeGPS_bin diff --git a/tools/gpstest/rpc_server.py b/tools/gpstest/rpc_server.py index be8dbb61b8..798237142d 100644 --- a/tools/gpstest/rpc_server.py +++ b/tools/gpstest/rpc_server.py @@ -3,13 +3,14 @@ import time import shutil from datetime import datetime from collections import defaultdict +from openpilot.system.hardware.hw import Paths -import rpyc # pylint: disable=import-error -from rpyc.utils.server import ThreadedServer # pylint: disable=import-error +import rpyc +from rpyc.utils.server import ThreadedServer -#from common.params import Params +#from openpilot.common.params import Params import cereal.messaging as messaging -from selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.manager.process_config import managed_processes from laika.lib.coordinates import ecef2geodetic DELTA = 0.001 @@ -18,7 +19,6 @@ MATCH_NUM = 10 REPORT_STATS = 10 EPHEM_CACHE = "/data/params/d/LaikadEphemerisV3" -DOWNLOAD_CACHE = "/tmp/comma_download_cache" SERVER_LOG_FILE = "/tmp/fuzzy_server.log" server_log = open(SERVER_LOG_FILE, "w+") @@ -162,7 +162,7 @@ class RemoteCheckerService(rpyc.Service): if os.path.exists(EPHEM_CACHE): os.remove(EPHEM_CACHE) - shutil.rmtree(DOWNLOAD_CACHE, ignore_errors=True) + shutil.rmtree(Paths.download_cache_root(), ignore_errors=True) ret = self.run_checker(slat, slon, salt, sockets, procs, timeout) kill_procs(procs) diff --git a/tools/gpstest/test_gps.py b/tools/gpstest/test_gps.py old mode 100644 new mode 100755 index 5043e484fd..98f1ad84b8 --- a/tools/gpstest/test_gps.py +++ b/tools/gpstest/test_gps.py @@ -3,11 +3,11 @@ import time import unittest import struct -from common.params import Params +from openpilot.common.params import Params import cereal.messaging as messaging -import system.sensord.pigeond as pd -from system.hardware import TICI -from selfdrive.test.helpers import with_processes +import openpilot.system.sensord.pigeond as pd +from openpilot.system.hardware import TICI +from openpilot.selfdrive.test.helpers import with_processes def read_events(service, duration_sec): diff --git a/tools/gpstest/test_gps_qcom.py b/tools/gpstest/test_gps_qcom.py old mode 100644 new mode 100755 index b3ce93fc81..c399671715 --- a/tools/gpstest/test_gps_qcom.py +++ b/tools/gpstest/test_gps_qcom.py @@ -3,10 +3,10 @@ import time import unittest import subprocess as sp -from common.params import Params -from system.hardware import TICI +from openpilot.common.params import Params +from openpilot.system.hardware import TICI import cereal.messaging as messaging -from selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.manager.process_config import managed_processes def exec_mmcli(cmd): diff --git a/tools/gpstest/test_laikad.py b/tools/gpstest/test_laikad.py old mode 100644 new mode 100755 index 689b0f0b9f..abb47d4bfc --- a/tools/gpstest/test_laikad.py +++ b/tools/gpstest/test_laikad.py @@ -4,12 +4,12 @@ import time import unittest import cereal.messaging as messaging -import system.sensord.pigeond as pd +import openpilot.system.sensord.pigeond as pd -from common.params import Params -from system.hardware import TICI -from selfdrive.manager.process_config import managed_processes -from selfdrive.test.helpers import with_processes +from openpilot.common.params import Params +from openpilot.system.hardware import TICI +from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.test.helpers import with_processes def wait_for_location(sm, timeout, con=10): diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh index ffa9a6efef..07bb8ac9a4 100755 --- a/tools/install_python_dependencies.sh +++ b/tools/install_python_dependencies.sh @@ -13,22 +13,30 @@ fi if ! command -v "pyenv" > /dev/null 2>&1; then echo "pyenv install ..." curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash + PYENV_PATH_SETUP="export PATH=\$HOME/.pyenv/bin:\$HOME/.pyenv/shims:\$PATH" +fi - echo -e "\n. ~/.pyenvrc" >> $RC_FILE +if [ -z "$PYENV_SHELL" ] || [ -n "$PYENV_PATH_SETUP" ]; then + echo "pyenvrc setup ..." cat < "${HOME}/.pyenvrc" if [ -z "\$PYENV_ROOT" ]; then - export PATH=\$HOME/.pyenv/bin:\$HOME/.pyenv/shims:\$PATH + $PYENV_PATH_SETUP export PYENV_ROOT="\$HOME/.pyenv" eval "\$(pyenv init -)" eval "\$(pyenv virtualenv-init -)" fi EOF - # setup now without restarting shell - export PATH=$HOME/.pyenv/bin:$HOME/.pyenv/shims:$PATH - export PYENV_ROOT="$HOME/.pyenv" - eval "$(pyenv init -)" - eval "$(pyenv virtualenv-init -)" + SOURCE_PYENVRC="source ~/.pyenvrc" + if ! grep "^$SOURCE_PYENVRC$" $RC_FILE > /dev/null; then + printf "\n$SOURCE_PYENVRC\n" >> $RC_FILE + fi + + eval "$SOURCE_PYENVRC" + # $(pyenv init -) produces a function which is broken on bash 3.2 which ships on macOS + if [ $(uname) == "Darwin" ]; then + unset -f pyenv + fi fi export MAKEFLAGS="-j$(nproc)" @@ -53,6 +61,12 @@ poetry config virtualenvs.prefer-active-python true --local poetry config virtualenvs.in-project true --local echo "PYTHONPATH=${PWD}" > $ROOT/.env +if [[ "$(uname)" == 'Darwin' ]]; then + echo "# msgq doesn't work on mac" >> $ROOT/.env + echo "export ZMQ=1" >> $ROOT/.env + echo "export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES" >> $ROOT/.env +fi + poetry self add poetry-dotenv-plugin@^0.1.0 echo "pip packages install..." diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh new file mode 100755 index 0000000000..78a01f2b75 --- /dev/null +++ b/tools/install_ubuntu_dependencies.sh @@ -0,0 +1,135 @@ +#!/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 + +# Install packages present in all supported versions of Ubuntu +function install_ubuntu_common_requirements() { + $SUDO apt-get update + $SUDO apt-get install -y --no-install-recommends \ + autoconf \ + build-essential \ + ca-certificates \ + casync \ + clang \ + cmake \ + make \ + cppcheck \ + libtool \ + gcc-arm-none-eabi \ + bzip2 \ + liblzma-dev \ + libarchive-dev \ + libbz2-dev \ + capnproto \ + libcapnp-dev \ + curl \ + libcurl4-openssl-dev \ + git \ + git-lfs \ + ffmpeg \ + libavformat-dev \ + libavcodec-dev \ + libavdevice-dev \ + libavutil-dev \ + libavfilter-dev \ + libeigen3-dev \ + libffi-dev \ + libglew-dev \ + libgles2-mesa-dev \ + libglfw3-dev \ + libglib2.0-0 \ + libncurses5-dev \ + libncursesw5-dev \ + libomp-dev \ + libopencv-dev \ + libpng16-16 \ + libportaudio2 \ + libssl-dev \ + libsqlite3-dev \ + libusb-1.0-0-dev \ + libzmq3-dev \ + libsystemd-dev \ + locales \ + opencl-headers \ + ocl-icd-libopencl1 \ + ocl-icd-opencl-dev \ + clinfo \ + portaudio19-dev \ + qml-module-qtquick2 \ + qtmultimedia5-dev \ + qtlocation5-dev \ + qtpositioning5-dev \ + qttools5-dev-tools \ + libqt5sql5-sqlite \ + libqt5svg5-dev \ + libqt5charts5-dev \ + libqt5serialbus5-dev \ + libqt5x11extras5-dev \ + libreadline-dev \ + libdw1 \ + valgrind +} + +# Install Ubuntu 22.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 \ + qtchooser \ + qt5-qmake \ + qtbase5-dev-tools \ + python3-dev +} + +# Install Ubuntu 20.04 packages +function install_ubuntu_focal_requirements() { + install_ubuntu_common_requirements + + $SUDO apt-get install -y --no-install-recommends \ + libavresample-dev \ + qt5-default \ + python-dev +} + +# Detect OS using /etc/os-release file +if [ -f "/etc/os-release" ]; then + source /etc/os-release + case "$VERSION_CODENAME" in + "jammy") + install_ubuntu_lts_latest_requirements + ;; + "kinetic") + install_ubuntu_lts_latest_requirements + ;; + "focal") + install_ubuntu_focal_requirements + ;; + *) + echo "$ID $VERSION_ID is unsupported. This setup script is written for Ubuntu 20.04." + read -p "Would you like to attempt installation anyway? " -n 1 -r + echo "" + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi + if [ "$UBUNTU_CODENAME" = "jammy" ] || [ "$UBUNTU_CODENAME" = "kinetic" ]; then + install_ubuntu_lts_latest_requirements + else + install_ubuntu_focal_requirements + fi + esac +else + echo "No /etc/os-release in the system" + exit 1 +fi diff --git a/tools/joystick/joystickd.py b/tools/joystick/joystickd.py index b31dab83fe..82847e3fa1 100755 --- a/tools/joystick/joystickd.py +++ b/tools/joystick/joystickd.py @@ -5,10 +5,10 @@ import threading from inputs import get_gamepad import cereal.messaging as messaging -from common.realtime import Ratekeeper -from common.numpy_fast import interp, clip -from common.params import Params -from tools.lib.kbhit import KBHit +from openpilot.common.realtime import Ratekeeper +from openpilot.common.numpy_fast import interp, clip +from openpilot.common.params import Params +from openpilot.tools.lib.kbhit import KBHit class Keyboard: diff --git a/tools/latencylogger/latency_logger.py b/tools/latencylogger/latency_logger.py index 81a4790dea..f2adc97e01 100755 --- a/tools/latencylogger/latency_logger.py +++ b/tools/latencylogger/latency_logger.py @@ -8,7 +8,7 @@ import sys from bisect import bisect_left, bisect_right from collections import defaultdict -from tools.lib.logreader import logreader_from_route_or_segment +from openpilot.tools.lib.logreader import logreader_from_route_or_segment DEMO_ROUTE = "9f583b1d93915c31|2022-05-18--10-49-51--0" diff --git a/tools/lib/README.md b/tools/lib/README.md index 8f540a5c82..daf74aaf40 100644 --- a/tools/lib/README.md +++ b/tools/lib/README.md @@ -3,8 +3,8 @@ Route is a class for conveniently accessing all the [logs](/system/loggerd/) from your routes. The LogReader class reads the non-video logs, i.e. rlog.bz2 and qlog.bz2. There's also a matching FrameReader class for reading the videos. ```python -from tools.lib.route import Route -from tools.lib.logreader import LogReader +from openpilot.tools.lib.route import Route +from openpilot.tools.lib.logreader import LogReader r = Route("a2a0ccea32023010|2023-07-27--13-01-19") @@ -37,8 +37,8 @@ for msg in lr: `MultiLogIterator` is similar to `LogReader`, but reads multiple logs. ```python -from tools.lib.route import Route -from tools.lib.logreader import MultiLogIterator +from openpilot.tools.lib.route import Route +from openpilot.tools.lib.logreader import MultiLogIterator # setup a MultiLogIterator to read all the logs in the route r = Route("a2a0ccea32023010|2023-07-27--13-01-19") diff --git a/tools/lib/auth.py b/tools/lib/auth.py index a1bcdb1c58..997d1f860d 100755 --- a/tools/lib/auth.py +++ b/tools/lib/auth.py @@ -29,8 +29,8 @@ from http.server import BaseHTTPRequestHandler, HTTPServer from typing import Any, Dict from urllib.parse import parse_qs, urlencode -from tools.lib.api import APIError, CommaApi, UnauthorizedError -from tools.lib.auth_config import set_token, get_token +from openpilot.tools.lib.api import APIError, CommaApi, UnauthorizedError +from openpilot.tools.lib.auth_config import set_token, get_token PORT = 3000 @@ -54,7 +54,7 @@ class ClientRedirectHandler(BaseHTTPRequestHandler): self.end_headers() self.wfile.write(b'Return to the CLI to continue') - def log_message(self, *args): # pylint: disable=redefined-builtin + def log_message(self, *args): pass # this prevent http server from dumping messages to stdout diff --git a/tools/lib/auth_config.py b/tools/lib/auth_config.py index 7952fee495..bd76761043 100644 --- a/tools/lib/auth_config.py +++ b/tools/lib/auth_config.py @@ -1,7 +1,7 @@ import json import os -from common.file_helpers import mkdirs_exists_ok -from system.hardware import PC +from openpilot.common.file_helpers import mkdirs_exists_ok +from openpilot.system.hardware import PC class MissingAuthConfigError(Exception): @@ -13,9 +13,6 @@ if PC: else: CONFIG_DIR = "/tmp/.comma" -mkdirs_exists_ok(CONFIG_DIR) - - def get_token(): try: with open(os.path.join(CONFIG_DIR, 'auth.json')) as f: @@ -26,9 +23,13 @@ def get_token(): def set_token(token): + mkdirs_exists_ok(CONFIG_DIR) with open(os.path.join(CONFIG_DIR, 'auth.json'), 'w') as f: json.dump({'access_token': token}, f) def clear_token(): - os.unlink(os.path.join(CONFIG_DIR, 'auth.json')) + try: + os.unlink(os.path.join(CONFIG_DIR, 'auth.json')) + except FileNotFoundError: + pass diff --git a/tools/lib/bootlog.py b/tools/lib/bootlog.py index 1e474e5dde..01756bb5e9 100644 --- a/tools/lib/bootlog.py +++ b/tools/lib/bootlog.py @@ -3,9 +3,9 @@ import functools import re from typing import List, Optional -from tools.lib.auth_config import get_token -from tools.lib.api import CommaApi -from tools.lib.helpers import RE, timestamp_to_datetime +from openpilot.tools.lib.auth_config import get_token +from openpilot.tools.lib.api import CommaApi +from openpilot.tools.lib.helpers import RE, timestamp_to_datetime @functools.total_ordering diff --git a/tools/lib/cache.py b/tools/lib/cache.py index 82b2298730..fd214f6bb5 100644 --- a/tools/lib/cache.py +++ b/tools/lib/cache.py @@ -1,11 +1,11 @@ import os import urllib.parse -from common.file_helpers import mkdirs_exists_ok +from openpilot.common.file_helpers import mkdirs_exists_ok -DEFAULT_CACHE_DIR = os.path.expanduser("~/.commacache") +DEFAULT_CACHE_DIR = os.getenv("CACHE_ROOT", os.path.expanduser("~/.commacache")) -def cache_path_for_file_path(fn, cache_prefix=None): - dir_ = os.path.join(DEFAULT_CACHE_DIR, "local") +def cache_path_for_file_path(fn, cache_dir=DEFAULT_CACHE_DIR): + dir_ = os.path.join(cache_dir, "local") mkdirs_exists_ok(dir_) fn_parsed = urllib.parse.urlparse(fn) if fn_parsed.scheme == '': diff --git a/tools/lib/filereader.py b/tools/lib/filereader.py index 718b853b1a..5ac23d57ec 100644 --- a/tools/lib/filereader.py +++ b/tools/lib/filereader.py @@ -1,5 +1,5 @@ import os -from tools.lib.url_file import URLFile +from openpilot.tools.lib.url_file import URLFile DATA_ENDPOINT = os.getenv("DATA_ENDPOINT", "http://data-raw.comma.internal/") diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index b4845b9f0d..06fee6857d 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -1,4 +1,3 @@ -# pylint: skip-file import json import os import pickle @@ -13,11 +12,11 @@ import numpy as np from lru import LRU import _io -from tools.lib.cache import cache_path_for_file_path -from tools.lib.exceptions import DataUnreadableError -from common.file_helpers import atomic_write_in_dir +from openpilot.tools.lib.cache import cache_path_for_file_path, DEFAULT_CACHE_DIR +from openpilot.tools.lib.exceptions import DataUnreadableError +from openpilot.common.file_helpers import atomic_write_in_dir -from tools.lib.filereader import FileReader +from openpilot.tools.lib.filereader import FileReader HEVC_SLICE_B = 0 HEVC_SLICE_P = 1 @@ -107,8 +106,8 @@ def cache_fn(func): if kwargs.pop('no_cache', None): cache_path = None else: - cache_prefix = kwargs.pop('cache_prefix', None) - cache_path = cache_path_for_file_path(fn, cache_prefix) + cache_dir = kwargs.pop('cache_dir', DEFAULT_CACHE_DIR) + cache_path = cache_path_for_file_path(fn, cache_dir) if cache_path and os.path.exists(cache_path): with open(cache_path, "rb") as cache_file: @@ -141,18 +140,18 @@ def index_stream(fn, typ): } -def index_videos(camera_paths, cache_prefix=None): +def index_videos(camera_paths, cache_dir=DEFAULT_CACHE_DIR): """Requires that paths in camera_paths are contiguous and of the same type.""" if len(camera_paths) < 1: raise ValueError("must provide at least one video to index") frame_type = fingerprint_video(camera_paths[0]) for fn in camera_paths: - index_video(fn, frame_type, cache_prefix) + index_video(fn, frame_type, cache_dir) -def index_video(fn, frame_type=None, cache_prefix=None): - cache_path = cache_path_for_file_path(fn, cache_prefix) +def index_video(fn, frame_type=None, cache_dir=DEFAULT_CACHE_DIR): + cache_path = cache_path_for_file_path(fn, cache_dir) if os.path.exists(cache_path): return @@ -161,16 +160,16 @@ def index_video(fn, frame_type=None, cache_prefix=None): frame_type = fingerprint_video(fn[0]) if frame_type == FrameType.h265_stream: - index_stream(fn, "hevc", cache_prefix=cache_prefix) + index_stream(fn, "hevc", cache_dir=cache_dir) else: raise NotImplementedError("Only h265 supported") -def get_video_index(fn, frame_type, cache_prefix=None): - cache_path = cache_path_for_file_path(fn, cache_prefix) +def get_video_index(fn, frame_type, cache_dir=DEFAULT_CACHE_DIR): + cache_path = cache_path_for_file_path(fn, cache_dir) if not os.path.exists(cache_path): - index_video(fn, frame_type, cache_prefix) + index_video(fn, frame_type, cache_dir) if not os.path.exists(cache_path): return None @@ -285,13 +284,13 @@ class BaseFrameReader: raise NotImplementedError -def FrameReader(fn, cache_prefix=None, readahead=False, readbehind=False, index_data=None): +def FrameReader(fn, cache_dir=DEFAULT_CACHE_DIR, readahead=False, readbehind=False, index_data=None): frame_type = fingerprint_video(fn) if frame_type == FrameType.raw: return RawFrameReader(fn) elif frame_type in (FrameType.h265_stream,): if not index_data: - index_data = get_video_index(fn, frame_type, cache_prefix) + index_data = get_video_index(fn, frame_type, cache_dir) return StreamFrameReader(fn, frame_type, index_data, readahead=readahead, readbehind=readbehind) else: raise NotImplementedError(frame_type) diff --git a/tools/lib/kbhit.py b/tools/lib/kbhit.py old mode 100644 new mode 100755 diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 8f1e5b9f80..e528996f32 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -8,8 +8,8 @@ import warnings from cereal import log as capnp_log -from tools.lib.filereader import FileReader -from tools.lib.route import Route, SegmentName +from openpilot.tools.lib.filereader import FileReader +from openpilot.tools.lib.route import Route, SegmentName # this is an iterator itself, and uses private variables from LogReader class MultiLogIterator: diff --git a/tools/lib/route.py b/tools/lib/route.py index 388cb003e6..e37b7d4434 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -5,9 +5,9 @@ from collections import defaultdict from itertools import chain from typing import Optional -from tools.lib.auth_config import get_token -from tools.lib.api import CommaApi -from tools.lib.helpers import RE +from openpilot.tools.lib.auth_config import get_token +from openpilot.tools.lib.api import CommaApi +from openpilot.tools.lib.helpers import RE QLOG_FILENAMES = ['qlog', 'qlog.bz2'] QCAMERA_FILENAMES = ['qcamera.ts'] diff --git a/tools/lib/tests/test_caching.py b/tools/lib/tests/test_caching.py old mode 100644 new mode 100755 index d2a2a6872f..b893d9815d --- a/tools/lib/tests/test_caching.py +++ b/tools/lib/tests/test_caching.py @@ -1,18 +1,13 @@ #!/usr/bin/env python3 import os -import shutil import unittest - -os.environ["COMMA_CACHE"] = "/tmp/__test_cache__" -from tools.lib.url_file import URLFile, CACHE_DIR +from openpilot.tools.lib.url_file import URLFile class TestFileDownload(unittest.TestCase): def compare_loads(self, url, start=0, length=None): """Compares range between cached and non cached version""" - shutil.rmtree(CACHE_DIR) - file_cached = URLFile(url, cache=True) file_downloaded = URLFile(url, cache=False) diff --git a/tools/lib/tests/test_readers.py b/tools/lib/tests/test_readers.py index 6efa0dc351..1f24ae5c8e 100755 --- a/tools/lib/tests/test_readers.py +++ b/tools/lib/tests/test_readers.py @@ -5,8 +5,8 @@ import tempfile from collections import defaultdict import numpy as np -from tools.lib.framereader import FrameReader -from tools.lib.logreader import LogReader +from openpilot.tools.lib.framereader import FrameReader +from openpilot.tools.lib.logreader import LogReader class TestReaders(unittest.TestCase): diff --git a/tools/lib/tests/test_route_library.py b/tools/lib/tests/test_route_library.py index 814017c9cc..7977f17be2 100755 --- a/tools/lib/tests/test_route_library.py +++ b/tools/lib/tests/test_route_library.py @@ -2,7 +2,7 @@ import unittest from collections import namedtuple -from tools.lib.route import SegmentName +from openpilot.tools.lib.route import SegmentName class TestRouteLibrary(unittest.TestCase): def test_segment_name_formats(self): diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 1e94a588f1..98a52cae27 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -1,5 +1,3 @@ -# pylint: skip-file - import os import time import tempfile @@ -9,13 +7,12 @@ import pycurl from hashlib import sha256 from io import BytesIO from tenacity import retry, wait_random_exponential, stop_after_attempt -from common.file_helpers import mkdirs_exists_ok, atomic_write_in_dir +from openpilot.common.file_helpers import mkdirs_exists_ok, atomic_write_in_dir +from openpilot.system.hardware.hw import Paths # Cache chunk size K = 1000 CHUNK_SIZE = 1000 * K -CACHE_DIR = os.environ.get("COMMA_CACHE", "/tmp/comma_download_cache/") - def hash_256(link): hsh = str(sha256((link.split("?")[0]).encode('utf-8')).hexdigest()) @@ -40,7 +37,7 @@ class URLFile: self._curl = self._tlocal.curl except AttributeError: self._curl = self._tlocal.curl = pycurl.Curl() - mkdirs_exists_ok(CACHE_DIR) + mkdirs_exists_ok(Paths.download_cache_root()) def __enter__(self): return self @@ -68,7 +65,7 @@ class URLFile: def get_length(self): if self._length is not None: return self._length - file_length_path = os.path.join(CACHE_DIR, hash_256(self._url) + "_length") + file_length_path = os.path.join(Paths.download_cache_root(), hash_256(self._url) + "_length") if os.path.exists(file_length_path) and not self._force_download: with open(file_length_path) as file_length: content = file_length.read() @@ -95,7 +92,7 @@ class URLFile: self._pos = position chunk_number = self._pos / CHUNK_SIZE file_name = hash_256(self._url) + "_" + str(chunk_number) - full_path = os.path.join(CACHE_DIR, str(file_name)) + full_path = os.path.join(Paths.download_cache_root(), str(file_name)) data = None # If we don't have a file, download it if not os.path.exists(full_path): diff --git a/tools/lib/vidindex/bitstream.c b/tools/lib/vidindex/bitstream.c index d174ffa8a7..dc6f43f14f 100644 --- a/tools/lib/vidindex/bitstream.c +++ b/tools/lib/vidindex/bitstream.c @@ -1,8 +1,8 @@ +#include "./bitstream.h" + #include #include -#include "bitstream.h" - static const uint32_t BS_MASKS[33] = { 0, 0x1L, 0x3L, 0x7L, 0xFL, 0x1FL, 0x3FL, 0x7FL, 0xFFL, 0x1FFL, 0x3FFL, 0x7FFL, diff --git a/tools/lib/vidindex/vidindex.c b/tools/lib/vidindex/vidindex.c index 4857c60dd2..1d7da2e193 100644 --- a/tools/lib/vidindex/vidindex.c +++ b/tools/lib/vidindex/vidindex.c @@ -9,7 +9,7 @@ #include #include -#include "bitstream.h" +#include "./bitstream.h" #define START_CODE 0x000001 diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 45bdf90383..9ec2097ea4 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -1,7 +1,21 @@ -#!/bin/bash +#!/usr/bin/env bash set -e +if [ -z "$SKIP_PROMPT" ]; then + echo "--------------- macOS support ---------------" + echo "Running openpilot natively on macOS is not officially supported." + echo "It might build, some parts of it might work, but it's not fully tested, so there might be some issues." + echo + echo "Check out devcontainers for a seamless experience (see tools/README.md)." + echo "-------------------------------------------------" + echo -n "Are you sure you want to continue? [y/N] " + read -r response + if [[ ! "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + exit 1 + fi +fi + DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" ROOT="$(cd $DIR/../ && pwd)" ARCH=$(uname -m) @@ -46,9 +60,10 @@ brew "libtool" brew "llvm" brew "openssl@3.0" brew "pyenv" +brew "pyenv-virtualenv" brew "qt@5" brew "zeromq" -brew "gcc@12" +brew "gcc@13" cask "gcc-arm-embedded" brew "portaudio" EOS @@ -69,18 +84,31 @@ export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/openssl@3/include" export PYCURL_CURL_CONFIG=/usr/bin/curl-config export PYCURL_SSL_LIBRARY=openssl -# openpilot environment -if [ -z "$OPENPILOT_ENV" ] && [ -n "$RC_FILE" ] && [ -z "$CI" ]; then - echo "source $ROOT/tools/openpilot_env.sh" >> $RC_FILE - source "$ROOT/tools/openpilot_env.sh" - echo "Added openpilot_env to RC file: $RC_FILE" -fi - # install python dependencies $DIR/install_python_dependencies.sh -eval "$(pyenv init --path)" 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:" diff --git a/tools/openpilot_env.sh b/tools/openpilot_env.sh deleted file mode 100755 index 59108312ac..0000000000 --- a/tools/openpilot_env.sh +++ /dev/null @@ -1,22 +0,0 @@ -if [ -z "$OPENPILOT_ENV" ]; then - export PATH="$HOME/.pyenv/bin:$PATH" - - # Pyenv suggests we place the below two lines in .profile before we source - # .bashrc, but there is no simple way to guarantee we do this correctly - # programmatically across heterogeneous systems. For end-user convenience, - # we add the lines here as a workaround. - # https://github.com/pyenv/pyenv/issues/1906 - export PYENV_ROOT="$HOME/.pyenv" - - if [[ "$(uname)" == 'Linux' ]]; then - eval "$(pyenv virtualenv-init -)" - elif [[ "$(uname)" == 'Darwin' ]]; then - # msgq doesn't work on mac - export ZMQ=1 - export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES - fi - eval "$(pyenv init --path)" - eval "$(pyenv init -)" - - export OPENPILOT_ENV=1 -fi diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index a5d6974f9c..02858b1edf 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -10,11 +10,11 @@ import tempfile import requests import argparse -from common.basedir import BASEDIR -from selfdrive.test.openpilotci import get_url -from tools.lib.logreader import LogReader -from tools.lib.route import Route, SegmentName -from tools.lib.helpers import save_log +from openpilot.common.basedir import BASEDIR +from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.route import Route, SegmentName +from openpilot.tools.lib.helpers import save_log from urllib.parse import urlparse, parse_qs juggle_dir = os.path.dirname(os.path.realpath(__file__)) @@ -129,7 +129,7 @@ def juggle_route(route_or_segment_name, segment_count, qlog, can, layout, dbc=No if dbc is None: for cp in [m for m in all_data if m.which() == 'carParams']: try: - DBC = __import__(f"selfdrive.car.{cp.carParams.carName}.values", fromlist=['DBC']).DBC + DBC = __import__(f"openpilot.selfdrive.car.{cp.carParams.carName}.values", fromlist=['DBC']).DBC dbc = DBC[cp.carParams.carFingerprint]['pt'] except Exception: pass diff --git a/tools/plotjuggler/layouts/longitudinal.xml b/tools/plotjuggler/layouts/longitudinal.xml index ff8f3ad193..33a24f76d6 100644 --- a/tools/plotjuggler/layouts/longitudinal.xml +++ b/tools/plotjuggler/layouts/longitudinal.xml @@ -3,10 +3,10 @@ - + - + @@ -15,7 +15,16 @@ - + + + + + + + + + + @@ -23,7 +32,7 @@ - + diff --git a/tools/plotjuggler/test_plotjuggler.py b/tools/plotjuggler/test_plotjuggler.py index 8fc032042a..b002331cd7 100755 --- a/tools/plotjuggler/test_plotjuggler.py +++ b/tools/plotjuggler/test_plotjuggler.py @@ -6,9 +6,9 @@ import subprocess import time import unittest -from common.basedir import BASEDIR -from common.timeout import Timeout -from tools.plotjuggler.juggle import install +from openpilot.common.basedir import BASEDIR +from openpilot.common.timeout import Timeout +from openpilot.tools.plotjuggler.juggle import install PJ_DIR = os.path.join(BASEDIR, "tools/plotjuggler") @@ -18,19 +18,18 @@ class TestPlotJuggler(unittest.TestCase): install() pj = os.path.join(PJ_DIR, "juggle.py") - p = subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {pj} --demo None 1 --qlog', - stderr=subprocess.PIPE, shell=True, start_new_session=True) - - # Wait for "Done reading Rlog data" signal from the plugin - output = "\n" - with Timeout(180, error_msg=output): - while output.splitlines()[-1] != "Done reading Rlog data": - output += p.stderr.readline().decode("utf-8") - - # ensure plotjuggler didn't crash after exiting the plugin - time.sleep(15) - self.assertEqual(p.poll(), None) - os.killpg(os.getpgid(p.pid), signal.SIGTERM) + with subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {pj} --demo None 1 --qlog', + stderr=subprocess.PIPE, shell=True, start_new_session=True) as p: + # Wait for "Done reading Rlog data" signal from the plugin + output = "\n" + with Timeout(180, error_msg=output): + while output.splitlines()[-1] != "Done reading Rlog data": + output += p.stderr.readline().decode("utf-8") + + # ensure plotjuggler didn't crash after exiting the plugin + time.sleep(15) + self.assertEqual(p.poll(), None) + os.killpg(os.getpgid(p.pid), signal.SIGTERM) # TODO: also test that layouts successfully load def test_layouts(self): diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 4ddeb662e0..bce7512e44 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -21,5 +21,5 @@ Export('replay_lib') replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv', 'ncurses'] + qt_libs qt_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) -if GetOption('test'): - qt_env.Program('tests/test_replay', ['tests/test_runner.cc', 'tests/test_replay.cc'], LIBS=[replay_libs]) +if GetOption('extras'): + qt_env.Program('tests/test_replay', ['tests/test_runner.cc', 'tests/test_replay.cc'], LIBS=[replay_libs, qt_libs]) diff --git a/tools/replay/camera.cc b/tools/replay/camera.cc index 66898c9244..49f3010c6c 100644 --- a/tools/replay/camera.cc +++ b/tools/replay/camera.cc @@ -1,7 +1,19 @@ #include "tools/replay/camera.h" -#include "tools/replay/util.h" #include +#include + +#include "third_party/linux/include/msm_media_info.h" +#include "tools/replay/util.h" + +std::tuple get_nv12_info(int width, int height) { + int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, width); + int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height); + assert(nv12_width == VENUS_UV_STRIDE(COLOR_FMT_NV12, width)); + assert(nv12_height / 2 == VENUS_UV_SCANLINES(COLOR_FMT_NV12, height)); + size_t nv12_buffer_size = 2346 * nv12_width; // comes from v4l2_format.fmt.pix_mp.plane_fmt[0].sizeimage + return {nv12_width, nv12_height, nv12_buffer_size}; +} CameraServer::CameraServer(std::pair camera_size[MAX_CAMERAS]) { for (int i = 0; i < MAX_CAMERAS; ++i) { @@ -25,7 +37,9 @@ void CameraServer::startVipcServer() { for (auto &cam : cameras_) { if (cam.width > 0 && cam.height > 0) { rInfo("camera[%d] frame size %dx%d", cam.type, cam.width, cam.height); - vipc_server_->create_buffers(cam.stream_type, YUV_BUFFER_COUNT, false, cam.width, cam.height); + auto [nv12_width, nv12_height, nv12_buffer_size] = get_nv12_info(cam.width, cam.height); + vipc_server_->create_buffers_with_sizes(cam.stream_type, YUV_BUFFER_COUNT, false, cam.width, cam.height, + nv12_buffer_size, nv12_width, nv12_width * nv12_height); if (!cam.thread.joinable()) { cam.thread = std::thread(&CameraServer::cameraThread, this, std::ref(cam)); } @@ -38,7 +52,7 @@ void CameraServer::cameraThread(Camera &cam) { auto read_frame = [&](FrameReader *fr, int frame_id) { VisionBuf *yuv_buf = vipc_server_->get_buffer(cam.stream_type); assert(yuv_buf); - bool ret = fr->get(frame_id, (uint8_t *)yuv_buf->addr); + bool ret = fr->get(frame_id, yuv_buf); return ret ? yuv_buf : nullptr; }; diff --git a/tools/replay/camera.h b/tools/replay/camera.h index 66d33142fb..9f43c5a362 100644 --- a/tools/replay/camera.h +++ b/tools/replay/camera.h @@ -1,11 +1,18 @@ #pragma once #include + +#include +#include +#include + #include "cereal/visionipc/visionipc_server.h" #include "common/queue.h" #include "tools/replay/framereader.h" #include "tools/replay/logreader.h" +std::tuple get_nv12_info(int width, int height); + class CameraServer { public: CameraServer(std::pair camera_size[MAX_CAMERAS] = nullptr); diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index 44b109514f..032c29824a 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -8,10 +8,10 @@ from tqdm import tqdm os.environ['FILEREADER_CACHE'] = '1' -from common.realtime import config_realtime_process, Ratekeeper, DT_CTRL -from selfdrive.boardd.boardd import can_capnp_to_can_list -from tools.plotjuggler.juggle import load_segment -from tools.lib.logreader import logreader_from_route_or_segment +from openpilot.common.realtime import config_realtime_process, Ratekeeper, DT_CTRL +from openpilot.selfdrive.boardd.boardd import can_capnp_to_can_list +from openpilot.tools.plotjuggler.juggle import load_segment +from openpilot.tools.lib.logreader import logreader_from_route_or_segment from panda import Panda, PandaJungle def send_thread(s, flock): diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index 5cbd3818a6..719bbd69ca 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -1,7 +1,11 @@ #include "tools/replay/consoleui.h" -#include #include +#include +#include +#include + +#include #include "common/version.h" diff --git a/tools/replay/filereader.cc b/tools/replay/filereader.cc index 88879067c9..22af7f5f86 100644 --- a/tools/replay/filereader.cc +++ b/tools/replay/filereader.cc @@ -3,11 +3,12 @@ #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 = util::getenv("COMMA_CACHE", "/tmp/comma_download_cache/"); + const std::string comma_cache = Path::download_cache_root(); util::create_directories(comma_cache, 0755); return comma_cache.back() == '/' ? comma_cache : comma_cache + "/"; }(); diff --git a/tools/replay/framereader.cc b/tools/replay/framereader.cc index ed276c627c..303f0c60ca 100644 --- a/tools/replay/framereader.cc +++ b/tools/replay/framereader.cc @@ -2,9 +2,8 @@ #include "tools/replay/util.h" #include -#include "libyuv.h" - -#include "cereal/visionipc/visionbuf.h" +#include +#include "third_party/libyuv/include/libyuv.h" #ifdef __APPLE__ #define HW_DEVICE_TYPE AV_HWDEVICE_TYPE_VIDEOTOOLBOX @@ -118,7 +117,6 @@ bool FrameReader::load(const std::byte *data, size_t size, bool no_hw_decoder, s width = (decoder_ctx->width + 3) & ~3; height = decoder_ctx->height; - visionbuf_compute_aligned_width_and_height(width, height, &aligned_width, &aligned_height); if (has_hw_decoder && !no_hw_decoder) { if (!initHardwareDecoder(HW_DEVICE_TYPE)) { @@ -177,15 +175,15 @@ bool FrameReader::initHardwareDecoder(AVHWDeviceType hw_device_type) { return true; } -bool FrameReader::get(int idx, uint8_t *yuv) { - assert(yuv != nullptr); +bool FrameReader::get(int idx, VisionBuf *buf) { + assert(buf != nullptr); if (!valid_ || idx < 0 || idx >= packets.size()) { return false; } - return decode(idx, yuv); + return decode(idx, buf); } -bool FrameReader::decode(int idx, uint8_t *yuv) { +bool FrameReader::decode(int idx, VisionBuf *buf) { int from_idx = idx; if (idx != prev_idx + 1 && key_frames_count_ > 1) { // seeking to the nearest key frame @@ -201,7 +199,7 @@ bool FrameReader::decode(int idx, uint8_t *yuv) { for (int i = from_idx; i <= idx; ++i) { AVFrame *f = decodeFrame(packets[i]); if (f && i == idx) { - return copyBuffers(f, yuv); + return copyBuffers(f, buf); } } return false; @@ -233,22 +231,20 @@ AVFrame *FrameReader::decodeFrame(AVPacket *pkt) { } } -bool FrameReader::copyBuffers(AVFrame *f, uint8_t *yuv) { - assert(f != nullptr && yuv != nullptr); - uint8_t *y = yuv; - uint8_t *uv = y + width * height; +bool FrameReader::copyBuffers(AVFrame *f, VisionBuf *buf) { + assert(f != nullptr && buf != nullptr); if (hw_pix_fmt == HW_PIX_FMT) { for (int i = 0; i < height/2; i++) { - memcpy(y + (i*2 + 0)*width, f->data[0] + (i*2 + 0)*f->linesize[0], width); - memcpy(y + (i*2 + 1)*width, f->data[0] + (i*2 + 1)*f->linesize[0], width); - memcpy(uv + i*width, f->data[1] + i*f->linesize[1], width); + memcpy(buf->y + (i*2 + 0)*buf->stride, f->data[0] + (i*2 + 0)*f->linesize[0], width); + memcpy(buf->y + (i*2 + 1)*buf->stride, f->data[0] + (i*2 + 1)*f->linesize[0], width); + memcpy(buf->uv + i*buf->stride, f->data[1] + i*f->linesize[1], width); } } else { libyuv::I420ToNV12(f->data[0], f->linesize[0], f->data[1], f->linesize[1], f->data[2], f->linesize[2], - y, width, - uv, width, + buf->y, buf->stride, + buf->uv, buf->stride, width, height); } return true; diff --git a/tools/replay/framereader.h b/tools/replay/framereader.h index e50b61d7f4..bb72ac8f8d 100644 --- a/tools/replay/framereader.h +++ b/tools/replay/framereader.h @@ -4,6 +4,7 @@ #include #include +#include "cereal/visionipc/visionbuf.h" #include "tools/replay/filereader.h" extern "C" { @@ -22,19 +23,18 @@ public: bool load(const std::string &url, bool no_hw_decoder = false, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); bool load(const std::byte *data, size_t size, bool no_hw_decoder = false, std::atomic *abort = nullptr); - bool get(int idx, uint8_t *yuv); + bool get(int idx, VisionBuf *buf); int getYUVSize() const { return width * height * 3 / 2; } size_t getFrameCount() const { return packets.size(); } bool valid() const { return valid_; } int width = 0, height = 0; - int aligned_width = 0, aligned_height = 0; private: bool initHardwareDecoder(AVHWDeviceType hw_device_type); - bool decode(int idx, uint8_t *yuv); + bool decode(int idx, VisionBuf *buf); AVFrame * decodeFrame(AVPacket *pkt); - bool copyBuffers(AVFrame *f, uint8_t *yuv); + bool copyBuffers(AVFrame *f, VisionBuf *buf); std::vector packets; std::unique_ptrav_frame_, hw_frame; diff --git a/tools/replay/lib/ui_helpers.py b/tools/replay/lib/ui_helpers.py index 4ab8efab61..e350b89bac 100644 --- a/tools/replay/lib/ui_helpers.py +++ b/tools/replay/lib/ui_helpers.py @@ -3,14 +3,14 @@ from typing import Any, Dict, Tuple import matplotlib.pyplot as plt import numpy as np -import pygame # pylint: disable=import-error +import pygame from matplotlib.backends.backend_agg import FigureCanvasAgg -from common.transformations.camera import (eon_f_frame_size, eon_f_focal_length, +from openpilot.common.transformations.camera import (eon_f_frame_size, eon_f_focal_length, tici_f_frame_size, tici_f_focal_length, get_view_frame_from_calib_frame) -from selfdrive.controls.radard import RADAR_TO_CAMERA +from openpilot.selfdrive.controls.radard import RADAR_TO_CAMERA RED = (255, 0, 0) diff --git a/tools/replay/logreader.h b/tools/replay/logreader.h index 1aa8c98b45..77d751a91b 100644 --- a/tools/replay/logreader.h +++ b/tools/replay/logreader.h @@ -5,7 +5,10 @@ #include #endif +#include #include +#include +#include #include "cereal/gen/cpp/log.capnp.h" #include "system/camerad/cameras/camera_common.h" diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 9029213af5..d8a68a06e9 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -16,19 +16,20 @@ Replay::Replay(QString route, QStringList allow, QStringList block, QStringList auto event_struct = capnp::Schema::from().asStruct(); sockets_.resize(event_struct.getUnionFields().size()); for (const auto &it : services) { - uint16_t which = event_struct.getFieldByName(it.name).getProto().getDiscriminantValue(); + auto name = it.second.name.c_str(); + uint16_t which = event_struct.getFieldByName(name).getProto().getDiscriminantValue(); if ((which == cereal::Event::Which::UI_DEBUG || which == cereal::Event::Which::USER_FLAG) && !(flags & REPLAY_FLAG_ALL_SERVICES) && - !allow.contains(it.name)) { + !allow.contains(name)) { continue; } - if ((allow.empty() || allow.contains(it.name)) && !block.contains(it.name)) { - sockets_[which] = it.name; + if ((allow.empty() || allow.contains(name)) && !block.contains(name)) { + sockets_[which] = name; if (!allow.empty() || !block.empty()) { allow_list.insert((cereal::Event::Which)which); } - s.push_back(it.name); + s.push_back(name); } } @@ -150,9 +151,10 @@ void Replay::buildTimeline() { [(int)cereal::ControlsState::AlertStatus::CRITICAL] = TimelineType::AlertCritical, }; - for (auto it = segments_.cbegin(); it != segments_.cend() && !exit_; ++it) { + const auto &route_segments = route_->segments(); + for (auto it = route_segments.cbegin(); it != route_segments.cend() && !exit_; ++it) { LogReader log; - if (!log.load(route_->at(it->first).qlog.toStdString(), &exit_, + if (!log.load(it->second.qlog.toStdString(), &exit_, {cereal::Event::Which::CONTROLS_STATE, cereal::Event::Which::USER_FLAG}, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; @@ -226,7 +228,10 @@ void Replay::segmentLoadFinished(bool success) { if (!success) { Segment *seg = qobject_cast(sender()); rWarning("failed to load segment %d, removing it from current replay list", seg->seg_num); - segments_.erase(seg->seg_num); + updateEvents([&]() { + segments_.erase(seg->seg_num); + return true; + }); } queueSegment(); } @@ -455,7 +460,7 @@ void Replay::stream() { } if (eit == events_->end() && !hasFlag(REPLAY_FLAG_NO_LOOP)) { - int last_segment = segments_.rbegin()->first; + int last_segment = segments_.empty() ? 0 : segments_.rbegin()->first; if (current_segment_ >= last_segment && isSegmentMerged(last_segment)) { rInfo("reaches the end of route, restart from beginning"); QMetaObject::invokeMethod(this, std::bind(&Replay::seekTo, this, 0, false), Qt::QueuedConnection); diff --git a/tools/replay/replay.h b/tools/replay/replay.h index a9a6bfd910..5ed4ff11b5 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -1,6 +1,14 @@ #pragma once +#include +#include +#include #include +#include +#include +#include +#include +#include #include @@ -72,7 +80,7 @@ public: inline void setSpeed(float speed) { speed_ = speed; } inline float getSpeed() const { return speed_; } inline const std::vector *events() const { return events_.get(); } - inline const std::map> &segments() const { return segments_; }; + inline const std::map> &segments() const { return segments_; } inline const std::string &carFingerprint() const { return car_fingerprint_; } inline const std::vector> getTimeline() { std::lock_guard lk(timeline_lock); diff --git a/tools/replay/route.cc b/tools/replay/route.cc index 619aeb3f5f..15a57e5e42 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -6,11 +6,13 @@ #include #include #include - #include +#include +#include +#include -#include "system/hardware/hw.h" #include "selfdrive/ui/qt/api.h" +#include "system/hardware/hw.h" #include "tools/replay/replay.h" #include "tools/replay/util.h" diff --git a/tools/replay/route.h b/tools/replay/route.h index 86adf6a14d..7207ff4f5c 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -1,5 +1,10 @@ #pragma once +#include +#include +#include +#include + #include #include diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc index 441bac0cbd..05c0ca8ac4 100644 --- a/tools/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -12,7 +12,7 @@ const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; const std::string TEST_RLOG_CHECKSUM = "5b966d4bb21a100a8c4e59195faeb741b975ccbe268211765efd1763d892bfb3"; -bool donload_to_file(const std::string &url, const std::string &local_file, int chunk_size = 5 * 1024 * 1024, int retries = 3) { +bool download_to_file(const std::string &url, const std::string &local_file, int chunk_size = 5 * 1024 * 1024, int retries = 3) { do { if (httpDownload(url, local_file, chunk_size)) { return true; @@ -29,7 +29,7 @@ TEST_CASE("httpMultiPartDownload") { const size_t chunk_size = 5 * 1024 * 1024; std::string content; SECTION("download to file") { - REQUIRE(donload_to_file(TEST_RLOG_URL, filename, chunk_size)); + REQUIRE(download_to_file(TEST_RLOG_URL, filename, chunk_size)); content = util::read_file(filename); } SECTION("download to buffer") { @@ -95,10 +95,13 @@ void read_segment(int n, const SegmentFile &segment_file, uint32_t flags) { if (cam == RoadCam || cam == WideRoadCam) { REQUIRE(fr->getFrameCount() == 1200); } - std::unique_ptr yuv_buf = std::make_unique(fr->getYUVSize()); + auto [nv12_width, nv12_height, nv12_buffer_size] = get_nv12_info(fr->width, fr->height); + VisionBuf buf; + buf.allocate(nv12_buffer_size); + buf.init_yuv(fr->width, fr->height, nv12_width, nv12_width * nv12_height); // sequence get 100 frames for (int i = 0; i < 100; ++i) { - REQUIRE(fr->get(i, yuv_buf.get())); + REQUIRE(fr->get(i, &buf)); } } @@ -107,23 +110,36 @@ void read_segment(int n, const SegmentFile &segment_file, uint32_t flags) { loop.exec(); } -TEST_CASE("Route") { - // Create a local route from remote for testing - Route remote_route(DEMO_ROUTE); - REQUIRE(remote_route.load()); - char tmp_path[] = "/tmp/root_XXXXXX"; - const std::string data_dir = mkdtemp(tmp_path); - const std::string route_name = DEMO_ROUTE.mid(17).toStdString(); - for (int i = 0; i < 2; ++i) { - std::string log_path = util::string_format("%s/%s--%d/", data_dir.c_str(), route_name.c_str(), i); - util::create_directories(log_path, 0755); - REQUIRE(donload_to_file(remote_route.at(i).rlog.toStdString(), log_path + "rlog.bz2")); - REQUIRE(donload_to_file(remote_route.at(i).road_cam.toStdString(), log_path + "fcamera.hevc")); - REQUIRE(donload_to_file(remote_route.at(i).driver_cam.toStdString(), log_path + "dcamera.hevc")); - REQUIRE(donload_to_file(remote_route.at(i).wide_road_cam.toStdString(), log_path + "ecamera.hevc")); - REQUIRE(donload_to_file(remote_route.at(i).qcamera.toStdString(), log_path + "qcamera.ts")); +std::string download_demo_route() { + static std::string data_dir; + + if (data_dir == "") { + char tmp_path[] = "/tmp/root_XXXXXX"; + data_dir = mkdtemp(tmp_path); + + Route remote_route(DEMO_ROUTE); + assert(remote_route.load()); + + // Create a local route from remote for testing + const std::string route_name = DEMO_ROUTE.mid(17).toStdString(); + for (int i = 0; i < 2; ++i) { + std::string log_path = util::string_format("%s/%s--%d/", data_dir.c_str(), route_name.c_str(), i); + util::create_directories(log_path, 0755); + REQUIRE(download_to_file(remote_route.at(i).rlog.toStdString(), log_path + "rlog.bz2")); + REQUIRE(download_to_file(remote_route.at(i).road_cam.toStdString(), log_path + "fcamera.hevc")); + REQUIRE(download_to_file(remote_route.at(i).driver_cam.toStdString(), log_path + "dcamera.hevc")); + REQUIRE(download_to_file(remote_route.at(i).wide_road_cam.toStdString(), log_path + "ecamera.hevc")); + REQUIRE(download_to_file(remote_route.at(i).qcamera.toStdString(), log_path + "qcamera.ts")); + } } + return data_dir; +} + + +TEST_CASE("Route") { + std::string data_dir = download_demo_route(); + SECTION("Local route") { auto flags = GENERATE(REPLAY_FLAG_DCAM | REPLAY_FLAG_ECAM, REPLAY_FLAG_QCAMERA); Route route(DEMO_ROUTE, QString::fromStdString(data_dir)); diff --git a/tools/replay/ui.py b/tools/replay/ui.py index a222e8e5b6..43195a481d 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -3,14 +3,14 @@ import argparse import os import sys -import cv2 # pylint: disable=import-error +import cv2 import numpy as np -import pygame # pylint: disable=import-error +import pygame import cereal.messaging as messaging -from common.numpy_fast import clip -from common.basedir import BASEDIR -from tools.replay.lib.ui_helpers import (_BB_TO_FULL_FRAME, UP, +from openpilot.common.numpy_fast import clip +from openpilot.common.basedir import BASEDIR +from openpilot.tools.replay.lib.ui_helpers import (_BB_TO_FULL_FRAME, UP, _INTRINSICS, BLACK, GREEN, YELLOW, Calibration, get_blank_lid_overlay, init_plots, diff --git a/tools/replay/unlog_ci_segment.py b/tools/replay/unlog_ci_segment.py index c008a7aeff..ae97ad45d6 100755 --- a/tools/replay/unlog_ci_segment.py +++ b/tools/replay/unlog_ci_segment.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# pylint: skip-file import argparse import bisect @@ -11,9 +10,9 @@ import tty from collections import defaultdict import cereal.messaging as messaging -from tools.lib.framereader import FrameReader -from tools.lib.logreader import LogReader -from selfdrive.test.openpilotci import get_url +from openpilot.tools.lib.framereader import FrameReader +from openpilot.tools.lib.logreader import LogReader +from openpilot.selfdrive.test.openpilotci import get_url IGNORE = ['initData', 'sentinel'] diff --git a/tools/replay/util.cc b/tools/replay/util.cc index a6ebbec615..2c2de69e78 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -4,13 +4,16 @@ #include #include +#include #include #include #include #include #include +#include #include #include +#include #include "common/timing.h" #include "common/util.h" diff --git a/tools/scripts/fetch_image_from_route.py b/tools/scripts/fetch_image_from_route.py index 139373f665..471db7ee3e 100755 --- a/tools/scripts/fetch_image_from_route.py +++ b/tools/scripts/fetch_image_from_route.py @@ -8,8 +8,8 @@ if len(sys.argv) < 4: import requests from PIL import Image -from tools.lib.auth_config import get_token -from tools.lib.framereader import FrameReader +from openpilot.tools.lib.auth_config import get_token +from openpilot.tools.lib.framereader import FrameReader jwt = get_token() diff --git a/tools/scripts/save_ubloxraw_stream.py b/tools/scripts/save_ubloxraw_stream.py old mode 100644 new mode 100755 index 3fefd45ba8..541252d270 --- a/tools/scripts/save_ubloxraw_stream.py +++ b/tools/scripts/save_ubloxraw_stream.py @@ -2,9 +2,9 @@ import argparse import os import sys -from common.basedir import BASEDIR -from tools.lib.logreader import MultiLogIterator -from tools.lib.route import Route +from openpilot.common.basedir import BASEDIR +from openpilot.tools.lib.logreader import MultiLogIterator +from openpilot.tools.lib.route import Route os.environ['BASEDIR'] = BASEDIR diff --git a/tools/scripts/setup_ssh_keys.py b/tools/scripts/setup_ssh_keys.py index 8f03303b59..699765eee1 100755 --- a/tools/scripts/setup_ssh_keys.py +++ b/tools/scripts/setup_ssh_keys.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import requests -from common.params import Params +from openpilot.common.params import Params import sys diff --git a/tools/sim/Dockerfile.sim b/tools/sim/Dockerfile.sim index 48aa12ebc6..9fd8a56101 100644 --- a/tools/sim/Dockerfile.sim +++ b/tools/sim/Dockerfile.sim @@ -9,25 +9,29 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN cd $HOME && \ curl -O https://raw.githubusercontent.com/commaai/eon-neos-builder/master/devices/eon/home/.tmux.conf -ENV PYTHONPATH $HOME/openpilot:${PYTHONPATH} -RUN mkdir -p $HOME/openpilot - -COPY SConstruct $HOME/openpilot/ - -COPY ./body $HOME/openpilot/body -COPY ./third_party $HOME/openpilot/third_party -COPY ./site_scons $HOME/openpilot/site_scons -COPY ./rednose $HOME/openpilot/rednose -COPY ./laika $HOME/openpilot/laika -COPY ./common $HOME/openpilot/common -COPY ./opendbc $HOME/openpilot/opendbc -COPY ./cereal $HOME/openpilot/cereal -COPY ./panda $HOME/openpilot/panda -COPY ./selfdrive $HOME/openpilot/selfdrive -COPY ./system $HOME/openpilot/system -COPY ./tools $HOME/openpilot/tools - -WORKDIR $HOME/openpilot -RUN scons --cache-readonly -j12 - -RUN python -c "from selfdrive.test.helpers import set_params_enabled; set_params_enabled()" +ENV OPENPILOT_PATH /tmp/openpilot +ENV PYTHONPATH ${OPENPILOT_PATH}:${PYTHONPATH} + +RUN mkdir -p ${OPENPILOT_PATH} +WORKDIR ${OPENPILOT_PATH} + +COPY SConstruct ${OPENPILOT_PATH} + +COPY ./openpilot ${OPENPILOT_PATH}/openpilot +COPY ./body ${OPENPILOT_PATH}/body +COPY ./third_party ${OPENPILOT_PATH}/third_party +COPY ./site_scons ${OPENPILOT_PATH}/site_scons +COPY ./rednose ${OPENPILOT_PATH}/rednose +COPY ./laika_repo ${OPENPILOT_PATH}/laika_repo +RUN ln -s ${OPENPILOT_PATH}/laika_repo/laika/ ${OPENPILOT_PATH}/laika +COPY ./common ${OPENPILOT_PATH}/common +COPY ./opendbc ${OPENPILOT_PATH}/opendbc +COPY ./cereal ${OPENPILOT_PATH}/cereal +COPY ./panda ${OPENPILOT_PATH}/panda +COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive +COPY ./system ${OPENPILOT_PATH}/system +COPY ./tools ${OPENPILOT_PATH}/tools + +RUN --mount=type=bind,source=.ci_cache/scons_cache,target=/tmp/scons_cache,rw scons -j$(nproc) --cache-readonly + +RUN python -c "from openpilot.selfdrive.test.helpers import set_params_enabled; set_params_enabled()" diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py deleted file mode 100755 index 64446e1115..0000000000 --- a/tools/sim/bridge.py +++ /dev/null @@ -1,561 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import math -import os -import signal -import threading -import time -from multiprocessing import Process, Queue -from typing import Any - -import carla # pylint: disable=import-error -import numpy as np -import pyopencl as cl -import pyopencl.array as cl_array - -import cereal.messaging as messaging -from cereal import log -from cereal.visionipc import VisionIpcServer, VisionStreamType -from common.basedir import BASEDIR -from common.numpy_fast import clip -from common.params import Params -from common.realtime import DT_DMON, Ratekeeper -from selfdrive.car.honda.values import CruiseButtons -from selfdrive.test.helpers import set_params_enabled -from tools.sim.lib.can import can_function - -W, H = 1928, 1208 -REPEAT_COUNTER = 5 -PRINT_DECIMATION = 100 -STEER_RATIO = 15. - -pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'accelerometer', 'gyroscope', 'can', "gpsLocationExternal"]) -sm = messaging.SubMaster(['carControl', 'controlsState']) - -def parse_args(add_args=None): - parser = argparse.ArgumentParser(description='Bridge between CARLA and openpilot.') - parser.add_argument('--joystick', action='store_true') - parser.add_argument('--high_quality', action='store_true') - parser.add_argument('--dual_camera', action='store_true') - parser.add_argument('--town', type=str, default='Town04_Opt') - parser.add_argument('--spawn_point', dest='num_selected_spawn_point', type=int, default=16) - parser.add_argument('--host', dest='host', type=str, default='127.0.0.1') - parser.add_argument('--port', dest='port', type=int, default=2000) - - return parser.parse_args(add_args) - - -class VehicleState: - def __init__(self): - self.speed = 0.0 - self.angle = 0.0 - self.bearing_deg = 0.0 - self.vel = carla.Vector3D() - self.cruise_button = 0 - self.is_engaged = False - self.ignition = True - - -def steer_rate_limit(old, new): - # Rate limiting to 0.5 degrees per step - limit = 0.5 - if new > old + limit: - return old + limit - elif new < old - limit: - return old - limit - else: - return new - - -class Camerad: - def __init__(self, dual_camera): - self.frame_road_id = 0 - self.frame_wide_id = 0 - self.vipc_server = VisionIpcServer("camerad") - - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 5, False, W, H) - if dual_camera: - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 5, False, W, H) - self.vipc_server.start_listener() - - # set up for pyopencl rgb to yuv conversion - self.ctx = cl.create_some_context() - self.queue = cl.CommandQueue(self.ctx) - cl_arg = f" -DHEIGHT={H} -DWIDTH={W} -DRGB_STRIDE={W * 3} -DUV_WIDTH={W // 2} -DUV_HEIGHT={H // 2} -DRGB_SIZE={W * H} -DCL_DEBUG " - - kernel_fn = os.path.join(BASEDIR, "tools/sim/rgb_to_nv12.cl") - with open(kernel_fn) as f: - prg = cl.Program(self.ctx, f.read()).build(cl_arg) - self.krnl = prg.rgb_to_nv12 - self.Wdiv4 = W // 4 if (W % 4 == 0) else (W + (4 - W % 4)) // 4 - self.Hdiv4 = H // 4 if (H % 4 == 0) else (H + (4 - H % 4)) // 4 - - def cam_callback_road(self, image): - self._cam_callback(image, self.frame_road_id, 'roadCameraState', VisionStreamType.VISION_STREAM_ROAD) - self.frame_road_id += 1 - - def cam_callback_wide_road(self, image): - self._cam_callback(image, self.frame_wide_id, 'wideRoadCameraState', VisionStreamType.VISION_STREAM_WIDE_ROAD) - self.frame_wide_id += 1 - - def _cam_callback(self, image, frame_id, pub_type, yuv_type): - img = np.frombuffer(image.raw_data, dtype=np.dtype("uint8")) - img = np.reshape(img, (H, W, 4)) - img = img[:, :, [0, 1, 2]].copy() - - # convert RGB frame to YUV - rgb = np.reshape(img, (H, W * 3)) - rgb_cl = cl_array.to_device(self.queue, rgb) - yuv_cl = cl_array.empty_like(rgb_cl) - self.krnl(self.queue, (np.int32(self.Wdiv4), np.int32(self.Hdiv4)), None, rgb_cl.data, yuv_cl.data).wait() - yuv = np.resize(yuv_cl.get(), rgb.size // 2) - eof = int(frame_id * 0.05 * 1e9) - - self.vipc_server.send(yuv_type, yuv.data.tobytes(), frame_id, eof, eof) - - dat = messaging.new_message(pub_type) - msg = { - "frameId": frame_id, - "transform": [1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0] - } - setattr(dat, pub_type, msg) - pm.send(pub_type, dat) - -def imu_callback(imu, vehicle_state): - # send 5x since 'sensor_tick' doesn't seem to work. limited by the world tick? - for _ in range(5): - vehicle_state.bearing_deg = math.degrees(imu.compass) - dat = messaging.new_message('accelerometer') - dat.accelerometer.sensor = 4 - dat.accelerometer.type = 0x10 - dat.accelerometer.timestamp = dat.logMonoTime # TODO: use the IMU timestamp - dat.accelerometer.init('acceleration') - dat.accelerometer.acceleration.v = [imu.accelerometer.x, imu.accelerometer.y, imu.accelerometer.z] - pm.send('accelerometer', dat) - - # copied these numbers from locationd - dat = messaging.new_message('gyroscope') - dat.gyroscope.sensor = 5 - dat.gyroscope.type = 0x10 - dat.gyroscope.timestamp = dat.logMonoTime # TODO: use the IMU timestamp - dat.gyroscope.init('gyroUncalibrated') - dat.gyroscope.gyroUncalibrated.v = [imu.gyroscope.x, imu.gyroscope.y, imu.gyroscope.z] - pm.send('gyroscope', dat) - time.sleep(0.01) - - -def panda_state_function(vs: VehicleState, exit_event: threading.Event): - pm = messaging.PubMaster(['pandaStates']) - while not exit_event.is_set(): - dat = messaging.new_message('pandaStates', 1) - dat.valid = True - dat.pandaStates[0] = { - 'ignitionLine': vs.ignition, - 'pandaType': "blackPanda", - 'controlsAllowed': True, - 'safetyModel': 'hondaNidec' - } - pm.send('pandaStates', dat) - time.sleep(0.5) - - -def peripheral_state_function(exit_event: threading.Event): - pm = messaging.PubMaster(['peripheralState']) - while not exit_event.is_set(): - dat = messaging.new_message('peripheralState') - dat.valid = True - # fake peripheral state data - dat.peripheralState = { - 'pandaType': log.PandaState.PandaType.blackPanda, - 'voltage': 12000, - 'current': 5678, - 'fanSpeedRpm': 1000 - } - pm.send('peripheralState', dat) - time.sleep(0.5) - - -def gps_callback(gps, vehicle_state): - dat = messaging.new_message('gpsLocationExternal') - - # transform vel from carla to NED - # north is -Y in CARLA - velNED = [ - -vehicle_state.vel.y, # north/south component of NED is negative when moving south - vehicle_state.vel.x, # positive when moving east, which is x in carla - vehicle_state.vel.z, - ] - - dat.gpsLocationExternal = { - "unixTimestampMillis": int(time.time() * 1000), - "flags": 1, # valid fix - "accuracy": 1.0, - "verticalAccuracy": 1.0, - "speedAccuracy": 0.1, - "bearingAccuracyDeg": 0.1, - "vNED": velNED, - "bearingDeg": vehicle_state.bearing_deg, - "latitude": gps.latitude, - "longitude": gps.longitude, - "altitude": gps.altitude, - "speed": vehicle_state.speed, - "source": log.GpsLocationData.SensorSource.ublox, - } - - pm.send('gpsLocationExternal', dat) - - -def fake_driver_monitoring(exit_event: threading.Event): - pm = messaging.PubMaster(['driverStateV2', 'driverMonitoringState']) - while not exit_event.is_set(): - # dmonitoringmodeld output - dat = messaging.new_message('driverStateV2') - dat.driverStateV2.leftDriverData.faceOrientation = [0., 0., 0.] - dat.driverStateV2.leftDriverData.faceProb = 1.0 - dat.driverStateV2.rightDriverData.faceOrientation = [0., 0., 0.] - dat.driverStateV2.rightDriverData.faceProb = 1.0 - pm.send('driverStateV2', dat) - - # dmonitoringd output - dat = messaging.new_message('driverMonitoringState') - dat.driverMonitoringState = { - "faceDetected": True, - "isDistracted": False, - "awarenessStatus": 1., - } - pm.send('driverMonitoringState', dat) - - time.sleep(DT_DMON) - - -def can_function_runner(vs: VehicleState, exit_event: threading.Event): - i = 0 - while not exit_event.is_set(): - can_function(pm, vs.speed, vs.angle, i, vs.cruise_button, vs.is_engaged) - time.sleep(0.01) - i += 1 - - -def connect_carla_client(host: str, port: int): - client = carla.Client(host, port) - client.set_timeout(5) - return client - - -class CarlaBridge: - - def __init__(self, arguments): - set_params_enabled() - - self.params = Params() - - msg = messaging.new_message('liveCalibration') - msg.liveCalibration.validBlocks = 20 - msg.liveCalibration.rpyCalib = [0.0, 0.0, 0.0] - self.params.put("CalibrationParams", msg.to_bytes()) - self.params.put_bool("DisengageOnAccelerator", True) - - self._args = arguments - self._carla_objects = [] - self._camerad = None - self._exit_event = threading.Event() - self._threads = [] - self._keep_alive = True - self.started = False - signal.signal(signal.SIGTERM, self._on_shutdown) - self._exit = threading.Event() - - def _on_shutdown(self, signal, frame): - self._keep_alive = False - - def bridge_keep_alive(self, q: Queue, retries: int): - try: - while self._keep_alive: - try: - self._run(q) - break - except RuntimeError as e: - self.close() - if retries == 0: - raise - - # Reset for another try - self._carla_objects = [] - self._threads = [] - self._exit_event = threading.Event() - - retries -= 1 - if retries <= -1: - print(f"Restarting bridge. Error: {e} ") - else: - print(f"Restarting bridge. Retries left {retries}. Error: {e} ") - finally: - # Clean up resources in the opposite order they were created. - self.close() - - def _run(self, q: Queue): - client = connect_carla_client(self._args.host, self._args.port) - world = client.load_world(self._args.town) - - settings = world.get_settings() - settings.synchronous_mode = True # Enables synchronous mode - settings.fixed_delta_seconds = 0.05 - world.apply_settings(settings) - - world.set_weather(carla.WeatherParameters.ClearSunset) - - if not self._args.high_quality: - world.unload_map_layer(carla.MapLayer.Foliage) - world.unload_map_layer(carla.MapLayer.Buildings) - world.unload_map_layer(carla.MapLayer.ParkedVehicles) - world.unload_map_layer(carla.MapLayer.Props) - world.unload_map_layer(carla.MapLayer.StreetLights) - world.unload_map_layer(carla.MapLayer.Particles) - - blueprint_library = world.get_blueprint_library() - - world_map = world.get_map() - - vehicle_bp = blueprint_library.filter('vehicle.tesla.*')[1] - vehicle_bp.set_attribute('role_name', 'hero') - spawn_points = world_map.get_spawn_points() - assert len(spawn_points) > self._args.num_selected_spawn_point, f'''No spawn point {self._args.num_selected_spawn_point}, try a value between 0 and - {len(spawn_points)} for this town.''' - spawn_point = spawn_points[self._args.num_selected_spawn_point] - vehicle = world.spawn_actor(vehicle_bp, spawn_point) - self._carla_objects.append(vehicle) - max_steer_angle = vehicle.get_physics_control().wheels[0].max_steer_angle - - # make tires less slippery - # wheel_control = carla.WheelPhysicsControl(tire_friction=5) - physics_control = vehicle.get_physics_control() - physics_control.mass = 2326 - # physics_control.wheels = [wheel_control]*4 - physics_control.torque_curve = [[20.0, 500.0], [5000.0, 500.0]] - physics_control.gear_switch_time = 0.0 - vehicle.apply_physics_control(physics_control) - - transform = carla.Transform(carla.Location(x=0.8, z=1.13)) - - def create_camera(fov, callback): - blueprint = blueprint_library.find('sensor.camera.rgb') - blueprint.set_attribute('image_size_x', str(W)) - blueprint.set_attribute('image_size_y', str(H)) - blueprint.set_attribute('fov', str(fov)) - if not self._args.high_quality: - blueprint.set_attribute('enable_postprocess_effects', 'False') - camera = world.spawn_actor(blueprint, transform, attach_to=vehicle) - camera.listen(callback) - return camera - - self._camerad = Camerad(self._args.dual_camera) - - if self._args.dual_camera: - road_wide_camera = create_camera(fov=120, callback=self._camerad.cam_callback_wide_road) # fov bigger than 120 shows unwanted artifacts - self._carla_objects.append(road_wide_camera) - road_camera = create_camera(fov=40, callback=self._camerad.cam_callback_road) - self._carla_objects.append(road_camera) - - vehicle_state = VehicleState() - - # re-enable IMU - imu_bp = blueprint_library.find('sensor.other.imu') - imu_bp.set_attribute('sensor_tick', '0.01') - imu = world.spawn_actor(imu_bp, transform, attach_to=vehicle) - imu.listen(lambda imu: imu_callback(imu, vehicle_state)) - - gps_bp = blueprint_library.find('sensor.other.gnss') - gps = world.spawn_actor(gps_bp, transform, attach_to=vehicle) - gps.listen(lambda gps: gps_callback(gps, vehicle_state)) - self.params.put_bool("UbloxAvailable", True) - - self._carla_objects.extend([imu, gps]) - # launch fake car threads - self._threads.append(threading.Thread(target=panda_state_function, args=(vehicle_state, self._exit_event,))) - self._threads.append(threading.Thread(target=peripheral_state_function, args=(self._exit_event,))) - self._threads.append(threading.Thread(target=fake_driver_monitoring, args=(self._exit_event,))) - self._threads.append(threading.Thread(target=can_function_runner, args=(vehicle_state, self._exit_event,))) - for t in self._threads: - t.start() - - # init - throttle_ease_out_counter = REPEAT_COUNTER - brake_ease_out_counter = REPEAT_COUNTER - steer_ease_out_counter = REPEAT_COUNTER - - vc = carla.VehicleControl(throttle=0, steer=0, brake=0, reverse=False) - - is_openpilot_engaged = False - throttle_out = steer_out = brake_out = 0. - throttle_op = steer_op = brake_op = 0. - throttle_manual = steer_manual = brake_manual = 0. - - old_steer = old_brake = old_throttle = 0. - throttle_manual_multiplier = 0.7 # keyboard signal is always 1 - brake_manual_multiplier = 0.7 # keyboard signal is always 1 - steer_manual_multiplier = 45 * STEER_RATIO # keyboard signal is always 1 - - # Simulation tends to be slow in the initial steps. This prevents lagging later - for _ in range(20): - world.tick() - - # loop - rk = Ratekeeper(100, print_delay_threshold=0.05) - - while self._keep_alive: - # 1. Read the throttle, steer and brake from op or manual controls - # 2. Set instructions in Carla - # 3. Send current carstate to op via can - - cruise_button = 0 - throttle_out = steer_out = brake_out = 0.0 - throttle_op = steer_op = brake_op = 0.0 - throttle_manual = steer_manual = brake_manual = 0.0 - - # --------------Step 1------------------------------- - if not q.empty(): - message = q.get() - m = message.split('_') - if m[0] == "steer": - steer_manual = float(m[1]) - is_openpilot_engaged = False - elif m[0] == "throttle": - throttle_manual = float(m[1]) - is_openpilot_engaged = False - elif m[0] == "brake": - brake_manual = float(m[1]) - is_openpilot_engaged = False - elif m[0] == "reverse": - cruise_button = CruiseButtons.CANCEL - is_openpilot_engaged = False - elif m[0] == "cruise": - if m[1] == "down": - cruise_button = CruiseButtons.DECEL_SET - is_openpilot_engaged = True - elif m[1] == "up": - cruise_button = CruiseButtons.RES_ACCEL - is_openpilot_engaged = True - elif m[1] == "cancel": - cruise_button = CruiseButtons.CANCEL - is_openpilot_engaged = False - elif m[0] == "ignition": - vehicle_state.ignition = not vehicle_state.ignition - elif m[0] == "quit": - break - - throttle_out = throttle_manual * throttle_manual_multiplier - steer_out = steer_manual * steer_manual_multiplier - brake_out = brake_manual * brake_manual_multiplier - - old_steer = steer_out - old_throttle = throttle_out - old_brake = brake_out - - if is_openpilot_engaged: - sm.update(0) - - # TODO gas and brake is deprecated - throttle_op = clip(sm['carControl'].actuators.accel / 1.6, 0.0, 1.0) - brake_op = clip(-sm['carControl'].actuators.accel / 4.0, 0.0, 1.0) - steer_op = sm['carControl'].actuators.steeringAngleDeg - - throttle_out = throttle_op - steer_out = steer_op - brake_out = brake_op - - steer_out = steer_rate_limit(old_steer, steer_out) - old_steer = steer_out - - else: - if throttle_out == 0 and old_throttle > 0: - if throttle_ease_out_counter > 0: - throttle_out = old_throttle - throttle_ease_out_counter += -1 - else: - throttle_ease_out_counter = REPEAT_COUNTER - old_throttle = 0 - - if brake_out == 0 and old_brake > 0: - if brake_ease_out_counter > 0: - brake_out = old_brake - brake_ease_out_counter += -1 - else: - brake_ease_out_counter = REPEAT_COUNTER - old_brake = 0 - - if steer_out == 0 and old_steer != 0: - if steer_ease_out_counter > 0: - steer_out = old_steer - steer_ease_out_counter += -1 - else: - steer_ease_out_counter = REPEAT_COUNTER - old_steer = 0 - - # --------------Step 2------------------------------- - steer_carla = steer_out / (max_steer_angle * STEER_RATIO * -1) - - steer_carla = np.clip(steer_carla, -1, 1) - steer_out = steer_carla * (max_steer_angle * STEER_RATIO * -1) - old_steer = steer_carla * (max_steer_angle * STEER_RATIO * -1) - - vc.throttle = throttle_out / 0.6 - vc.steer = steer_carla - vc.brake = brake_out - vehicle.apply_control(vc) - - # --------------Step 3------------------------------- - vel = vehicle.get_velocity() - speed = math.sqrt(vel.x ** 2 + vel.y ** 2 + vel.z ** 2) # in m/s - vehicle_state.speed = speed - vehicle_state.vel = vel - vehicle_state.angle = steer_out - vehicle_state.cruise_button = cruise_button - vehicle_state.is_engaged = is_openpilot_engaged - - if rk.frame % PRINT_DECIMATION == 0: - print("frame: ", "engaged:", is_openpilot_engaged, "; throttle: ", round(vc.throttle, 3), "; steer(c/deg): ", - round(vc.steer, 3), round(steer_out, 3), "; brake: ", round(vc.brake, 3)) - - if rk.frame % 5 == 0: - world.tick() - rk.keep_time() - self.started = True - - def close(self): - self.started = False - self._exit_event.set() - - for s in self._carla_objects: - try: - s.destroy() - except Exception as e: - print("Failed to destroy carla object", e) - for t in reversed(self._threads): - t.join() - - def run(self, queue, retries=-1): - bridge_p = Process(target=self.bridge_keep_alive, args=(queue, retries), daemon=True) - bridge_p.start() - return bridge_p - - -if __name__ == "__main__": - q: Any = Queue() - args = parse_args() - - carla_bridge = CarlaBridge(args) - p = carla_bridge.run(q) - - if args.joystick: - # start input poll for joystick - from tools.sim.lib.manual_ctrl import wheel_poll_thread - - wheel_poll_thread(q) - else: - # start input poll for keyboard - from tools.sim.lib.keyboard_ctrl import keyboard_poll_thread - - keyboard_poll_thread(q) - p.join() diff --git a/tools/sim/bridge/__init__.py b/tools/sim/bridge/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/sim/bridge/carla.py b/tools/sim/bridge/carla.py new file mode 100644 index 0000000000..df25734e19 --- /dev/null +++ b/tools/sim/bridge/carla.py @@ -0,0 +1,163 @@ +import numpy as np + +from openpilot.common.params import Params +from openpilot.tools.sim.lib.common import SimulatorState, vec3 +from openpilot.tools.sim.bridge.common import World, SimulatorBridge +from openpilot.tools.sim.lib.camerad import W, H + + +class CarlaWorld(World): + def __init__(self, world, vehicle, high_quality=False, dual_camera=False): + super().__init__(dual_camera) + import carla + self.world = world + self.vc: carla.VehicleControl = carla.VehicleControl(throttle=0, steer=0, brake=0, reverse=False) + self.vehicle = vehicle + self.max_steer_angle: float = vehicle.get_physics_control().wheels[0].max_steer_angle + self.params = Params() + + self.steer_ratio = 15 + + self.carla_objects = [] + + blueprint_library = self.world.get_blueprint_library() + transform = carla.Transform(carla.Location(x=0.8, z=1.13)) + + def create_camera(fov, callback): + blueprint = blueprint_library.find('sensor.camera.rgb') + blueprint.set_attribute('image_size_x', str(W)) + blueprint.set_attribute('image_size_y', str(H)) + blueprint.set_attribute('fov', str(fov)) + blueprint.set_attribute('sensor_tick', str(1/20)) + if not high_quality: + blueprint.set_attribute('enable_postprocess_effects', 'False') + camera = world.spawn_actor(blueprint, transform, attach_to=vehicle) + camera.listen(callback) + return camera + + self.road_camera = create_camera(fov=40, callback=self.cam_callback_road) + if dual_camera: + self.road_wide_camera = create_camera(fov=120, callback=self.cam_callback_wide_road) # fov bigger than 120 shows unwanted artifacts + else: + self.road_wide_camera = None + + # re-enable IMU + imu_bp = blueprint_library.find('sensor.other.imu') + imu_bp.set_attribute('sensor_tick', '0.01') + self.imu = world.spawn_actor(imu_bp, transform, attach_to=vehicle) + + gps_bp = blueprint_library.find('sensor.other.gnss') + self.gps = world.spawn_actor(gps_bp, transform, attach_to=vehicle) + self.params.put_bool("UbloxAvailable", True) + + self.carla_objects = [self.imu, self.gps, self.road_camera, self.road_wide_camera] + + def close(self): + for s in self.carla_objects: + if s is not None: + try: + s.destroy() + except Exception as e: + print("Failed to destroy carla object", e) + + def carla_image_to_rgb(self, image): + rgb = np.frombuffer(image.raw_data, dtype=np.dtype("uint8")) + rgb = np.reshape(rgb, (H, W, 4)) + return np.ascontiguousarray(rgb[:, :, [0, 1, 2]]) + + def cam_callback_road(self, image): + with self.image_lock: + self.road_image = self.carla_image_to_rgb(image) + + def cam_callback_wide_road(self, image): + with self.image_lock: + self.wide_road_image = self.carla_image_to_rgb(image) + + def apply_controls(self, steer_angle, throttle_out, brake_out): + self.vc.throttle = throttle_out + + steer_carla = steer_angle * -1 / (self.max_steer_angle * self.steer_ratio) + steer_carla = np.clip(steer_carla, -1, 1) + + self.vc.steer = steer_carla + self.vc.brake = brake_out + self.vehicle.apply_control(self.vc) + + def read_sensors(self, simulator_state: SimulatorState): + simulator_state.imu.bearing = self.imu.get_transform().rotation.yaw + + simulator_state.imu.accelerometer = vec3( + self.imu.get_acceleration().x, + self.imu.get_acceleration().y, + self.imu.get_acceleration().z + ) + + simulator_state.imu.gyroscope = vec3( + self.imu.get_angular_velocity().x, + self.imu.get_angular_velocity().y, + self.imu.get_angular_velocity().z + ) + + simulator_state.gps.from_xy([self.vehicle.get_location().x, self.vehicle.get_location().y]) + + simulator_state.velocity = self.vehicle.get_velocity() + simulator_state.valid = True + simulator_state.steering_angle = self.vc.steer * self.max_steer_angle + + def read_cameras(self): + pass # cameras are read within a callback for carla + + def tick(self): + self.world.tick() + + +class CarlaBridge(SimulatorBridge): + TICKS_PER_FRAME = 5 + + def __init__(self, arguments): + super().__init__(arguments) + self.host = arguments.host + self.port = arguments.port + self.town = arguments.town + self.num_selected_spawn_point = arguments.num_selected_spawn_point + + def spawn_world(self): + import carla + client = carla.Client(self.host, self.port) + client.set_timeout(5) + + world = client.load_world(self.town) + + settings = world.get_settings() + settings.fixed_delta_seconds = 0.01 + world.apply_settings(settings) + + world.set_weather(carla.WeatherParameters.ClearSunset) + + if not self.high_quality: + world.unload_map_layer(carla.MapLayer.Foliage) + world.unload_map_layer(carla.MapLayer.Buildings) + world.unload_map_layer(carla.MapLayer.ParkedVehicles) + world.unload_map_layer(carla.MapLayer.Props) + world.unload_map_layer(carla.MapLayer.StreetLights) + world.unload_map_layer(carla.MapLayer.Particles) + + blueprint_library = world.get_blueprint_library() + + world_map = world.get_map() + + vehicle_bp = blueprint_library.filter('vehicle.tesla.*')[1] + vehicle_bp.set_attribute('role_name', 'hero') + spawn_points = world_map.get_spawn_points() + assert len(spawn_points) > self.num_selected_spawn_point, \ + f'''No spawn point {self.num_selected_spawn_point}, try a value between 0 and {len(spawn_points)} for this town.''' + spawn_point = spawn_points[self.num_selected_spawn_point] + vehicle = world.spawn_actor(vehicle_bp, spawn_point) + + physics_control = vehicle.get_physics_control() + physics_control.mass = 2326 + physics_control.torque_curve = [[20.0, 500.0], [5000.0, 500.0]] + physics_control.gear_switch_time = 0.0 + vehicle.apply_physics_control(physics_control) + + return CarlaWorld(world, vehicle, dual_camera=self.dual_camera) \ No newline at end of file diff --git a/tools/sim/bridge/common.py b/tools/sim/bridge/common.py new file mode 100644 index 0000000000..464ea08823 --- /dev/null +++ b/tools/sim/bridge/common.py @@ -0,0 +1,163 @@ +import signal +import threading +import functools + +from multiprocessing import Process, Queue +from abc import ABC, abstractmethod +from typing import Optional + +import cereal.messaging as messaging + +from openpilot.common.params import Params +from openpilot.common.numpy_fast import clip +from openpilot.common.realtime import Ratekeeper +from openpilot.selfdrive.test.helpers import set_params_enabled +from openpilot.selfdrive.car.honda.values import CruiseButtons +from openpilot.tools.sim.lib.common import SimulatorState, World +from openpilot.tools.sim.lib.simulated_car import SimulatedCar +from openpilot.tools.sim.lib.simulated_sensors import SimulatedSensors + + +def rk_loop(function, hz, exit_event: threading.Event): + rk = Ratekeeper(hz) + while not exit_event.is_set(): + function() + rk.keep_time() + + +class SimulatorBridge(ABC): + TICKS_PER_FRAME = 5 + + def __init__(self, arguments): + set_params_enabled() + self.params = Params() + + self.rk = Ratekeeper(100) + + msg = messaging.new_message('liveCalibration') + msg.liveCalibration.validBlocks = 20 + msg.liveCalibration.rpyCalib = [0.0, 0.0, 0.0] + self.params.put("CalibrationParams", msg.to_bytes()) + + self.dual_camera = arguments.dual_camera + self.high_quality = arguments.high_quality + + self._exit_event = threading.Event() + self._threads = [] + self._keep_alive = True + self.started = False + signal.signal(signal.SIGTERM, self._on_shutdown) + self._exit = threading.Event() + self.simulator_state = SimulatorState() + + self.world: Optional[World] = None + + def _on_shutdown(self, signal, frame): + self.shutdown() + + def shutdown(self): + self._keep_alive = False + + def bridge_keep_alive(self, q: Queue, retries: int): + try: + self._run(q) + finally: + self.close() + + def close(self): + self.started = False + self._exit_event.set() + + if self.world is not None: + self.world.close() + + def run(self, queue, retries=-1): + bridge_p = Process(target=self.bridge_keep_alive, args=(queue, retries), daemon=True) + bridge_p.start() + return bridge_p + + @abstractmethod + def spawn_world(self) -> World: + pass + + def _run(self, q: Queue): + self.world = self.spawn_world() + + self.simulated_car = SimulatedCar() + self.simulated_sensors = SimulatedSensors(self.dual_camera) + + self.simulated_car_thread = threading.Thread(target=rk_loop, args=(functools.partial(self.simulated_car.update, self.simulator_state), + 100, self._exit_event)) + self.simulated_car_thread.start() + + rk = Ratekeeper(100, print_delay_threshold=None) + + # Simulation tends to be slow in the initial steps. This prevents lagging later + for _ in range(20): + self.world.tick() + + throttle_manual = steer_manual = brake_manual = 0. + + while self._keep_alive: + throttle_out = steer_out = brake_out = 0.0 + throttle_op = steer_op = brake_op = 0.0 + + self.simulator_state.cruise_button = 0 + + throttle_manual = steer_manual = brake_manual = 0. + + # Read manual controls + if not q.empty(): + message = q.get() + m = message.split('_') + if m[0] == "steer": + steer_manual = float(m[1]) + elif m[0] == "throttle": + throttle_manual = float(m[1]) + elif m[0] == "brake": + brake_manual = float(m[1]) + elif m[0] == "cruise": + if m[1] == "down": + self.simulator_state.cruise_button = CruiseButtons.DECEL_SET + elif m[1] == "up": + self.simulator_state.cruise_button = CruiseButtons.RES_ACCEL + elif m[1] == "cancel": + self.simulator_state.cruise_button = CruiseButtons.CANCEL + elif m[1] == "main": + self.simulator_state.cruise_button = CruiseButtons.MAIN + elif m[0] == "ignition": + self.simulator_state.ignition = not self.simulator_state.ignition + elif m[0] == "quit": + break + + self.simulator_state.user_brake = brake_manual + self.simulator_state.user_gas = throttle_manual + + steer_manual = steer_manual * -40 + + # Update openpilot on current sensor state + self.simulated_sensors.update(self.simulator_state, self.world) + + is_openpilot_engaged = self.simulated_car.sm['controlsState'].active + + self.simulated_car.sm.update(0) + + if is_openpilot_engaged: + throttle_op = clip(self.simulated_car.sm['carControl'].actuators.accel / 1.6, 0.0, 1.0) + brake_op = clip(-self.simulated_car.sm['carControl'].actuators.accel / 4.0, 0.0, 1.0) + steer_op = self.simulated_car.sm['carControl'].actuators.steeringAngleDeg + + throttle_out = throttle_op if is_openpilot_engaged else throttle_manual + brake_out = brake_op if is_openpilot_engaged else brake_manual + steer_out = steer_op if is_openpilot_engaged else steer_manual + + self.world.apply_controls(steer_out, throttle_out, brake_out) + self.world.read_sensors(self.simulator_state) + + if self.rk.frame % self.TICKS_PER_FRAME == 0: + self.world.tick() + self.world.read_cameras() + + self.started = True + + rk.keep_time() diff --git a/tools/sim/launch_openpilot.sh b/tools/sim/launch_openpilot.sh index d5922a4819..361f7b18ea 100755 --- a/tools/sim/launch_openpilot.sh +++ b/tools/sim/launch_openpilot.sh @@ -12,5 +12,8 @@ if [[ "$CI" ]]; then export BLOCK="${BLOCK},ui" fi +SCRIPT_DIR=$(dirname "$0") +OPENPILOT_DIR=$SCRIPT_DIR/../../ + DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -cd ../../selfdrive/manager && exec ./manager.py +cd $OPENPILOT_DIR/selfdrive/manager && exec ./manager.py diff --git a/tools/sim/lib/camerad.py b/tools/sim/lib/camerad.py new file mode 100644 index 0000000000..4621347787 --- /dev/null +++ b/tools/sim/lib/camerad.py @@ -0,0 +1,70 @@ +import numpy as np +import os +import pyopencl as cl +import pyopencl.array as cl_array + +from cereal.visionipc import VisionIpcServer, VisionStreamType +from cereal import messaging + +from openpilot.common.basedir import BASEDIR +from openpilot.tools.sim.lib.common import W, H + +class Camerad: + """Simulates the camerad daemon""" + def __init__(self, dual_camera): + self.pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState']) + + self.frame_road_id = 0 + self.frame_wide_id = 0 + self.vipc_server = VisionIpcServer("camerad") + + self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 5, False, W, H) + if dual_camera: + self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 5, False, W, H) + + self.vipc_server.start_listener() + + # set up for pyopencl rgb to yuv conversion + self.ctx = cl.create_some_context() + self.queue = cl.CommandQueue(self.ctx) + cl_arg = f" -DHEIGHT={H} -DWIDTH={W} -DRGB_STRIDE={W * 3} -DUV_WIDTH={W // 2} -DUV_HEIGHT={H // 2} -DRGB_SIZE={W * H} -DCL_DEBUG " + + kernel_fn = os.path.join(BASEDIR, "tools/sim/rgb_to_nv12.cl") + with open(kernel_fn) as f: + prg = cl.Program(self.ctx, f.read()).build(cl_arg) + self.krnl = prg.rgb_to_nv12 + self.Wdiv4 = W // 4 if (W % 4 == 0) else (W + (4 - W % 4)) // 4 + self.Hdiv4 = H // 4 if (H % 4 == 0) else (H + (4 - H % 4)) // 4 + + def cam_send_yuv_road(self, yuv): + self._send_yuv(yuv, self.frame_road_id, 'roadCameraState', VisionStreamType.VISION_STREAM_ROAD) + self.frame_road_id += 1 + + def cam_send_yuv_wide_road(self, yuv): + self._send_yuv(yuv, self.frame_wide_id, 'wideRoadCameraState', VisionStreamType.VISION_STREAM_WIDE_ROAD) + self.frame_wide_id += 1 + + # Returns: yuv bytes + def rgb_to_yuv(self, rgb): + assert rgb.shape == (H, W, 3), f"{rgb.shape}" + assert rgb.dtype == np.uint8 + + rgb_cl = cl_array.to_device(self.queue, rgb) + yuv_cl = cl_array.empty_like(rgb_cl) + self.krnl(self.queue, (self.Wdiv4, self.Hdiv4), None, rgb_cl.data, yuv_cl.data).wait() + yuv = np.resize(yuv_cl.get(), rgb.size // 2) + return yuv.data.tobytes() + + def _send_yuv(self, yuv, frame_id, pub_type, yuv_type): + eof = int(frame_id * 0.05 * 1e9) + self.vipc_server.send(yuv_type, yuv, frame_id, eof, eof) + + dat = messaging.new_message(pub_type) + msg = { + "frameId": frame_id, + "transform": [1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0] + } + setattr(dat, pub_type, msg) + self.pm.send(pub_type, dat) \ No newline at end of file diff --git a/tools/sim/lib/can.py b/tools/sim/lib/can.py deleted file mode 100755 index d0b5888392..0000000000 --- a/tools/sim/lib/can.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 -import cereal.messaging as messaging -from opendbc.can.packer import CANPacker -from opendbc.can.parser import CANParser -from selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp # pylint: disable=no-name-in-module,import-error -from selfdrive.car import crc8_pedal - -packer = CANPacker("honda_civic_touring_2016_can_generated") -rpacker = CANPacker("acura_ilx_2016_nidec") - - -def get_car_can_parser(): - dbc_f = 'honda_civic_touring_2016_can_generated' - checks = [ - (0xe4, 100), - (0x1fa, 50), - (0x200, 50), - ] - return CANParser(dbc_f, checks, 0) -cp = get_car_can_parser() - -def can_function(pm, speed, angle, idx, cruise_button, is_engaged): - - msg = [] - - # *** powertrain bus *** - - speed = speed * 3.6 # convert m/s to kph - msg.append(packer.make_can_msg("ENGINE_DATA", 0, {"XMISSION_SPEED": speed})) - msg.append(packer.make_can_msg("WHEEL_SPEEDS", 0, { - "WHEEL_SPEED_FL": speed, - "WHEEL_SPEED_FR": speed, - "WHEEL_SPEED_RL": speed, - "WHEEL_SPEED_RR": speed - })) - - msg.append(packer.make_can_msg("SCM_BUTTONS", 0, {"CRUISE_BUTTONS": cruise_button})) - - values = {"COUNTER_PEDAL": idx & 0xF} - checksum = crc8_pedal(packer.make_can_msg("GAS_SENSOR", 0, {"COUNTER_PEDAL": idx & 0xF})[2][:-1]) - values["CHECKSUM_PEDAL"] = checksum - msg.append(packer.make_can_msg("GAS_SENSOR", 0, values)) - - msg.append(packer.make_can_msg("GEARBOX", 0, {"GEAR": 4, "GEAR_SHIFTER": 8})) - msg.append(packer.make_can_msg("GAS_PEDAL_2", 0, {})) - msg.append(packer.make_can_msg("SEATBELT_STATUS", 0, {"SEATBELT_DRIVER_LATCHED": 1})) - msg.append(packer.make_can_msg("STEER_STATUS", 0, {})) - msg.append(packer.make_can_msg("STEERING_SENSORS", 0, {"STEER_ANGLE": angle})) - msg.append(packer.make_can_msg("VSA_STATUS", 0, {})) - msg.append(packer.make_can_msg("STANDSTILL", 0, {"WHEELS_MOVING": 1 if speed >= 1.0 else 0})) - msg.append(packer.make_can_msg("STEER_MOTOR_TORQUE", 0, {})) - msg.append(packer.make_can_msg("EPB_STATUS", 0, {})) - msg.append(packer.make_can_msg("DOORS_STATUS", 0, {})) - msg.append(packer.make_can_msg("CRUISE_PARAMS", 0, {})) - msg.append(packer.make_can_msg("CRUISE", 0, {})) - msg.append(packer.make_can_msg("SCM_FEEDBACK", 0, {"MAIN_ON": 1})) - msg.append(packer.make_can_msg("POWERTRAIN_DATA", 0, {"ACC_STATUS": int(is_engaged)})) - msg.append(packer.make_can_msg("HUD_SETTING", 0, {})) - msg.append(packer.make_can_msg("CAR_SPEED", 0, {})) - - # *** cam bus *** - msg.append(packer.make_can_msg("STEERING_CONTROL", 2, {})) - msg.append(packer.make_can_msg("ACC_HUD", 2, {})) - msg.append(packer.make_can_msg("LKAS_HUD", 2, {})) - msg.append(packer.make_can_msg("BRAKE_COMMAND", 2, {})) - - # *** radar bus *** - if idx % 5 == 0: - msg.append(rpacker.make_can_msg("RADAR_DIAGNOSTIC", 1, {"RADAR_STATE": 0x79})) - for i in range(16): - msg.append(rpacker.make_can_msg("TRACK_%d" % i, 1, {"LONG_DIST": 255.5})) - - pm.send('can', can_list_to_can_capnp(msg)) - -def sendcan_function(sendcan): - sc = messaging.drain_sock_raw(sendcan) - cp.update_strings(sc, sendcan=True) - - if cp.vl[0x1fa]['COMPUTER_BRAKE_REQUEST']: - brake = cp.vl[0x1fa]['COMPUTER_BRAKE'] / 1024. - else: - brake = 0.0 - - if cp.vl[0x200]['GAS_COMMAND'] > 0: - gas = ( cp.vl[0x200]['GAS_COMMAND'] + 83.3 ) / (0.253984064 * 2**16) - else: - gas = 0.0 - - if cp.vl[0xe4]['STEER_TORQUE_REQUEST']: - steer_torque = cp.vl[0xe4]['STEER_TORQUE']/3840 - else: - steer_torque = 0.0 - - return gas, brake, steer_torque diff --git a/tools/sim/lib/common.py b/tools/sim/lib/common.py new file mode 100644 index 0000000000..3de89f39fc --- /dev/null +++ b/tools/sim/lib/common.py @@ -0,0 +1,86 @@ +import math +import threading +import numpy as np + +from abc import ABC, abstractmethod +from collections import namedtuple + +W, H = 1928, 1208 + + +vec3 = namedtuple("vec3", ["x", "y", "z"]) + +class GPSState: + def __init__(self): + self.latitude = 0 + self.longitude = 0 + self.altitude = 0 + + def from_xy(self, xy): + """Simulates a lat/lon from an xy coordinate on a plane, for simple simlation. TODO: proper global projection?""" + BASE_LAT = 32.75308505188913 + BASE_LON = -117.2095393365393 + DEG_TO_METERS = 100000 + + self.latitude = float(BASE_LAT + xy[0] / DEG_TO_METERS) + self.longitude = float(BASE_LON + xy[1] / DEG_TO_METERS) + self.altitude = 0 + + +class IMUState: + def __init__(self): + self.accelerometer: vec3 = vec3(0,0,0) + self.gyroscope: vec3 = vec3(0,0,0) + self.bearing: float = 0 + + +class SimulatorState: + def __init__(self): + self.valid = False + self.is_engaged = False + self.ignition = True + + self.velocity: vec3 = None + self.bearing: float = 0 + self.gps = GPSState() + self.imu = IMUState() + + self.steering_angle: float = 0 + + self.user_gas: float = 0 + self.user_brake: float = 0 + + self.cruise_button = 0 + + @property + def speed(self): + return math.sqrt(self.velocity.x ** 2 + self.velocity.y ** 2 + self.velocity.z ** 2) + + +class World(ABC): + def __init__(self, dual_camera): + self.dual_camera = dual_camera + + self.image_lock = threading.Lock() + self.road_image = np.zeros((H, W, 3), dtype=np.uint8) + self.wide_road_image = np.zeros((H, W, 3), dtype=np.uint8) + + @abstractmethod + def apply_controls(self, steer_sim, throttle_out, brake_out): + pass + + @abstractmethod + def tick(self): + pass + + @abstractmethod + def read_sensors(self, simulator_state: SimulatorState): + pass + + @abstractmethod + def read_cameras(self): + pass + + @abstractmethod + def close(self): + pass \ No newline at end of file diff --git a/tools/sim/lib/keyboard_ctrl.py b/tools/sim/lib/keyboard_ctrl.py index 803aa091a3..57d5834026 100644 --- a/tools/sim/lib/keyboard_ctrl.py +++ b/tools/sim/lib/keyboard_ctrl.py @@ -1,6 +1,7 @@ import sys import termios import time + from termios import (BRKINT, CS8, CSIZE, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISTRIP, IXON, PARENB, VMIN, VTIME) from typing import NoReturn @@ -38,7 +39,6 @@ def getch() -> str: def keyboard_poll_thread(q: 'Queue[str]'): while True: c = getch() - print("got %s" % c) if c == '1': q.put("cruise_up") elif c == '2': diff --git a/tools/sim/lib/manual_ctrl.py b/tools/sim/lib/manual_ctrl.py index 8f1bcc2b57..1687a2e6ba 100755 --- a/tools/sim/lib/manual_ctrl.py +++ b/tools/sim/lib/manual_ctrl.py @@ -135,8 +135,8 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: print('%d buttons found: %s' % (num_buttons, ', '.join(button_map))) # Enable FF - import evdev # pylint: disable=import-error - from evdev import ecodes, InputDevice # pylint: disable=import-error + import evdev + from evdev import ecodes, InputDevice device = evdev.list_devices()[0] evtdev = InputDevice(device) val = 24000 diff --git a/tools/sim/lib/simulated_car.py b/tools/sim/lib/simulated_car.py new file mode 100644 index 0000000000..7cd01e1610 --- /dev/null +++ b/tools/sim/lib/simulated_car.py @@ -0,0 +1,112 @@ +import cereal.messaging as messaging + +from opendbc.can.packer import CANPacker +from opendbc.can.parser import CANParser +from openpilot.selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp +from openpilot.selfdrive.car import crc8_pedal +from openpilot.tools.sim.lib.common import SimulatorState + + +class SimulatedCar: + """Simulates a honda civic 2016 (panda state + can messages) to OpenPilot""" + packer = CANPacker("honda_civic_touring_2016_can_generated") + rpacker = CANPacker("acura_ilx_2016_nidec") + + def __init__(self): + self.pm = messaging.PubMaster(['can', 'pandaStates']) + self.sm = messaging.SubMaster(['carControl', 'controlsState', 'carParams']) + self.cp = self.get_car_can_parser() + self.idx = 0 + + @staticmethod + def get_car_can_parser(): + dbc_f = 'honda_civic_touring_2016_can_generated' + checks = [ + (0xe4, 100), + (0x1fa, 50), + (0x200, 50), + ] + return CANParser(dbc_f, checks, 0) + + def send_can_messages(self, simulator_state: SimulatorState): + if not simulator_state.valid: + return + + msg = [] + + # *** powertrain bus *** + + speed = simulator_state.speed * 3.6 # convert m/s to kph + msg.append(self.packer.make_can_msg("ENGINE_DATA", 0, {"XMISSION_SPEED": speed})) + msg.append(self.packer.make_can_msg("WHEEL_SPEEDS", 0, { + "WHEEL_SPEED_FL": speed, + "WHEEL_SPEED_FR": speed, + "WHEEL_SPEED_RL": speed, + "WHEEL_SPEED_RR": speed + })) + + msg.append(self.packer.make_can_msg("SCM_BUTTONS", 0, {"CRUISE_BUTTONS": simulator_state.cruise_button})) + + values = { + "COUNTER_PEDAL": self.idx & 0xF, + "INTERCEPTOR_GAS": simulator_state.user_gas * 2**12, + "INTERCEPTOR_GAS2": simulator_state.user_gas * 2**12, + } + checksum = crc8_pedal(self.packer.make_can_msg("GAS_SENSOR", 0, values)[2][:-1]) + values["CHECKSUM_PEDAL"] = checksum + msg.append(self.packer.make_can_msg("GAS_SENSOR", 0, values)) + + msg.append(self.packer.make_can_msg("GEARBOX", 0, {"GEAR": 4, "GEAR_SHIFTER": 8})) + msg.append(self.packer.make_can_msg("GAS_PEDAL_2", 0, {})) + msg.append(self.packer.make_can_msg("SEATBELT_STATUS", 0, {"SEATBELT_DRIVER_LATCHED": 1})) + msg.append(self.packer.make_can_msg("STEER_STATUS", 0, {})) + msg.append(self.packer.make_can_msg("STEERING_SENSORS", 0, {"STEER_ANGLE": simulator_state.steering_angle})) + msg.append(self.packer.make_can_msg("VSA_STATUS", 0, {})) + msg.append(self.packer.make_can_msg("STANDSTILL", 0, {"WHEELS_MOVING": 1 if simulator_state.speed >= 1.0 else 0})) + msg.append(self.packer.make_can_msg("STEER_MOTOR_TORQUE", 0, {})) + msg.append(self.packer.make_can_msg("EPB_STATUS", 0, {})) + msg.append(self.packer.make_can_msg("DOORS_STATUS", 0, {})) + msg.append(self.packer.make_can_msg("CRUISE_PARAMS", 0, {})) + msg.append(self.packer.make_can_msg("CRUISE", 0, {})) + msg.append(self.packer.make_can_msg("SCM_FEEDBACK", 0, {"MAIN_ON": 1})) + msg.append(self.packer.make_can_msg("POWERTRAIN_DATA", 0, + { + "ACC_STATUS": int(simulator_state.is_engaged), + "PEDAL_GAS": simulator_state.user_gas, + "BRAKE_PRESSED": simulator_state.user_brake > 0 + })) + msg.append(self.packer.make_can_msg("HUD_SETTING", 0, {})) + msg.append(self.packer.make_can_msg("CAR_SPEED", 0, {})) + + # *** cam bus *** + msg.append(self.packer.make_can_msg("STEERING_CONTROL", 2, {})) + msg.append(self.packer.make_can_msg("ACC_HUD", 2, {})) + msg.append(self.packer.make_can_msg("LKAS_HUD", 2, {})) + msg.append(self.packer.make_can_msg("BRAKE_COMMAND", 2, {})) + + # *** radar bus *** + if self.idx % 5 == 0: + msg.append(self.rpacker.make_can_msg("RADAR_DIAGNOSTIC", 1, {"RADAR_STATE": 0x79})) + for i in range(16): + msg.append(self.rpacker.make_can_msg("TRACK_%d" % i, 1, {"LONG_DIST": 255.5})) + + self.pm.send('can', can_list_to_can_capnp(msg)) + + def send_panda_state(self, simulator_state): + self.sm.update(0) + dat = messaging.new_message('pandaStates', 1) + dat.valid = True + dat.pandaStates[0] = { + 'ignitionLine': simulator_state.ignition, + 'pandaType': "blackPanda", + 'controlsAllowed': True, + 'safetyModel': 'hondaNidec', + 'alternativeExperience': self.sm["carParams"].alternativeExperience + } + self.pm.send('pandaStates', dat) + + def update(self, simulator_state: SimulatorState): + self.send_can_messages(simulator_state) + self.send_panda_state(simulator_state) + + self.idx += 1 \ No newline at end of file diff --git a/tools/sim/lib/simulated_sensors.py b/tools/sim/lib/simulated_sensors.py new file mode 100644 index 0000000000..dd55c02f02 --- /dev/null +++ b/tools/sim/lib/simulated_sensors.py @@ -0,0 +1,127 @@ +import time + +from cereal import log +import cereal.messaging as messaging + +from openpilot.common.params import Params +from openpilot.common.realtime import DT_DMON +from openpilot.tools.sim.lib.camerad import Camerad + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from openpilot.tools.sim.lib.common import World, SimulatorState + + +class SimulatedSensors: + """Simulates the C3 sensors (acc, gyro, gps, peripherals, dm state, cameras) to OpenPilot""" + + def __init__(self, dual_camera=False): + self.pm = messaging.PubMaster(['accelerometer', 'gyroscope', 'gpsLocationExternal', 'driverStateV2', 'driverMonitoringState', 'peripheralState']) + self.camerad = Camerad(dual_camera=dual_camera) + self.last_perp_update = 0 + self.last_dmon_update = 0 + + def send_imu_message(self, simulator_state: 'SimulatorState'): + for _ in range(5): + dat = messaging.new_message('accelerometer') + dat.accelerometer.sensor = 4 + dat.accelerometer.type = 0x10 + dat.accelerometer.timestamp = dat.logMonoTime # TODO: use the IMU timestamp + dat.accelerometer.init('acceleration') + dat.accelerometer.acceleration.v = [simulator_state.imu.accelerometer.x, simulator_state.imu.accelerometer.y, simulator_state.imu.accelerometer.z] + self.pm.send('accelerometer', dat) + + # copied these numbers from locationd + dat = messaging.new_message('gyroscope') + dat.gyroscope.sensor = 5 + dat.gyroscope.type = 0x10 + dat.gyroscope.timestamp = dat.logMonoTime # TODO: use the IMU timestamp + dat.gyroscope.init('gyroUncalibrated') + dat.gyroscope.gyroUncalibrated.v = [simulator_state.imu.gyroscope.x, simulator_state.imu.gyroscope.y, simulator_state.imu.gyroscope.z] + self.pm.send('gyroscope', dat) + + def send_gps_message(self, simulator_state: 'SimulatorState'): + if not simulator_state.valid: + return + + # transform vel from carla to NED + # north is -Y in CARLA + velNED = [ + -simulator_state.velocity.y, # north/south component of NED is negative when moving south + simulator_state.velocity.x, # positive when moving east, which is x in carla + simulator_state.velocity.z, + ] + + for _ in range(10): + dat = messaging.new_message('gpsLocationExternal') + dat.gpsLocationExternal = { + "unixTimestampMillis": int(time.time() * 1000), + "flags": 1, # valid fix + "accuracy": 1.0, + "verticalAccuracy": 1.0, + "speedAccuracy": 0.1, + "bearingAccuracyDeg": 0.1, + "vNED": velNED, + "bearingDeg": simulator_state.imu.bearing, + "latitude": simulator_state.gps.latitude, + "longitude": simulator_state.gps.longitude, + "altitude": simulator_state.gps.altitude, + "speed": simulator_state.speed, + "source": log.GpsLocationData.SensorSource.ublox, + } + + self.pm.send('gpsLocationExternal', dat) + + def send_peripheral_state(self): + dat = messaging.new_message('peripheralState') + dat.valid = True + dat.peripheralState = { + 'pandaType': log.PandaState.PandaType.blackPanda, + 'voltage': 12000, + 'current': 5678, + 'fanSpeedRpm': 1000 + } + Params().put_bool("ObdMultiplexingEnabled", False) + self.pm.send('peripheralState', dat) + + def send_fake_driver_monitoring(self): + # dmonitoringmodeld output + dat = messaging.new_message('driverStateV2') + dat.driverStateV2.leftDriverData.faceOrientation = [0., 0., 0.] + dat.driverStateV2.leftDriverData.faceProb = 1.0 + dat.driverStateV2.rightDriverData.faceOrientation = [0., 0., 0.] + dat.driverStateV2.rightDriverData.faceProb = 1.0 + self.pm.send('driverStateV2', dat) + + # dmonitoringd output + dat = messaging.new_message('driverMonitoringState') + dat.driverMonitoringState = { + "faceDetected": True, + "isDistracted": False, + "awarenessStatus": 1., + } + self.pm.send('driverMonitoringState', dat) + + def send_camera_images(self, world: 'World'): + with world.image_lock: + yuv = self.camerad.rgb_to_yuv(world.road_image) + self.camerad.cam_send_yuv_road(yuv) + + if world.dual_camera: + yuv = self.camerad.rgb_to_yuv(world.wide_road_image) + self.camerad.cam_send_yuv_wide_road(yuv) + + def update(self, simulator_state: 'SimulatorState', world: 'World'): + now = time.time() + self.send_imu_message(simulator_state) + self.send_gps_message(simulator_state) + + if (now - self.last_dmon_update) > DT_DMON/2: + self.send_fake_driver_monitoring() + self.last_dmon_update = now + + if (now - self.last_perp_update) > 0.25: + self.send_peripheral_state() + self.last_perp_update = now + + self.send_camera_images(world) \ No newline at end of file diff --git a/tools/sim/run_bridge.py b/tools/sim/run_bridge.py new file mode 100755 index 0000000000..8e6dc8f2ef --- /dev/null +++ b/tools/sim/run_bridge.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +import argparse + +from typing import Any +from multiprocessing import Queue + +from openpilot.tools.sim.bridge.common import SimulatorBridge +from openpilot.tools.sim.bridge.carla import CarlaBridge + + +def parse_args(add_args=None): + parser = argparse.ArgumentParser(description='Bridge between CARLA and openpilot.') + parser.add_argument('--joystick', action='store_true') + parser.add_argument('--high_quality', action='store_true') + parser.add_argument('--dual_camera', action='store_true') + parser.add_argument('--simulator', dest='simulator', type=str, default='carla') + + # Carla specific + parser.add_argument('--town', type=str, default='Town04_Opt') + parser.add_argument('--spawn_point', dest='num_selected_spawn_point', type=int, default=16) + parser.add_argument('--host', dest='host', type=str, default='127.0.0.1') + parser.add_argument('--port', dest='port', type=int, default=2000) + + return parser.parse_args(add_args) + +if __name__ == "__main__": + q: Any = Queue() + args = parse_args() + + simulator_bridge: SimulatorBridge + if args.simulator == "carla": + simulator_bridge = CarlaBridge(args) + else: + raise AssertionError("simulator type not supported") + p = simulator_bridge.run(q) + + if args.joystick: + # start input poll for joystick + from openpilot.tools.sim.lib.manual_ctrl import wheel_poll_thread + + wheel_poll_thread(q) + else: + # start input poll for keyboard + from openpilot.tools.sim.lib.keyboard_ctrl import keyboard_poll_thread + + keyboard_poll_thread(q) + + simulator_bridge.shutdown() + + p.join() diff --git a/tools/sim/start_openpilot_docker.sh b/tools/sim/start_openpilot_docker.sh index dba89b8b70..54f4995e45 100755 --- a/tools/sim/start_openpilot_docker.sh +++ b/tools/sim/start_openpilot_docker.sh @@ -3,7 +3,7 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" cd $DIR -OPENPILOT_DIR="/openpilot" +OPENPILOT_DIR="/tmp/openpilot" if ! [[ -z "$MOUNT_OPENPILOT" ]]; then OPENPILOT_DIR="$(dirname $(dirname $DIR))" EXTRA_ARGS="-v $OPENPILOT_DIR:$OPENPILOT_DIR -e PYTHONPATH=$OPENPILOT_DIR:$PYTHONPATH" diff --git a/tools/sim/tests/test_carla_integration.py b/tools/sim/tests/test_carla_integration.py index cfaf95ca01..b5a60ee0e3 100755 --- a/tools/sim/tests/test_carla_integration.py +++ b/tools/sim/tests/test_carla_integration.py @@ -6,10 +6,10 @@ import os from multiprocessing import Queue from cereal import messaging -from common.basedir import BASEDIR -from selfdrive.manager.helpers import unblock_stdout -from tools.sim import bridge -from tools.sim.bridge import CarlaBridge +from openpilot.common.basedir import BASEDIR +from openpilot.selfdrive.manager.helpers import unblock_stdout +from openpilot.tools.sim.run_bridge import parse_args +from openpilot.tools.sim.bridge.carla import CarlaBridge CI = "CI" in os.environ @@ -42,7 +42,7 @@ class TestCarlaIntegration(unittest.TestCase): sm = messaging.SubMaster(['controlsState', 'carEvents', 'managerState']) q = Queue() - carla_bridge = CarlaBridge(bridge.parse_args([])) + carla_bridge = CarlaBridge(parse_args([])) p_bridge = carla_bridge.run(q, retries=10) self.processes.append(p_bridge) diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index b816d82f91..c615608852 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -8,7 +8,7 @@ import signal from collections import defaultdict import cereal.messaging as messaging -from tools.lib.logreader import logreader_from_route_or_segment +from openpilot.tools.lib.logreader import logreader_from_route_or_segment def sigint_handler(signal, frame): exit(0) diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh index 581b785252..1bdeb50e0d 100755 --- a/tools/ubuntu_setup.sh +++ b/tools/ubuntu_setup.sh @@ -1,153 +1,14 @@ -#!/bin/bash +#!/usr/bin/env bash set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -ROOT="$(cd $DIR/../ && pwd)" -SUDO="" # NOTE: this is used in a docker build, so do not run any scripts here. -# Use sudo if not root -if [[ ! $(id -u) -eq 0 ]]; then - if [[ -z $(which sudo) ]]; then - echo "Please install sudo or run as root" - exit 1 - fi - SUDO="sudo" -fi - -# Install packages present in all supported versions of Ubuntu -function install_ubuntu_common_requirements() { - $SUDO apt-get update - $SUDO apt-get install -y --no-install-recommends \ - autoconf \ - build-essential \ - ca-certificates \ - casync \ - clang \ - cmake \ - make \ - cppcheck \ - libtool \ - gcc-arm-none-eabi \ - bzip2 \ - liblzma-dev \ - libarchive-dev \ - libbz2-dev \ - capnproto \ - libcapnp-dev \ - curl \ - libcurl4-openssl-dev \ - git \ - git-lfs \ - ffmpeg \ - libavformat-dev \ - libavcodec-dev \ - libavdevice-dev \ - libavutil-dev \ - libavfilter-dev \ - libeigen3-dev \ - libffi-dev \ - libglew-dev \ - libgles2-mesa-dev \ - libglfw3-dev \ - libglib2.0-0 \ - libncurses5-dev \ - libncursesw5-dev \ - libomp-dev \ - libopencv-dev \ - libpng16-16 \ - libportaudio2 \ - libssl-dev \ - libsqlite3-dev \ - libusb-1.0-0-dev \ - libzmq3-dev \ - libsystemd-dev \ - locales \ - opencl-headers \ - ocl-icd-libopencl1 \ - ocl-icd-opencl-dev \ - clinfo \ - portaudio19-dev \ - qml-module-qtquick2 \ - qtmultimedia5-dev \ - qtlocation5-dev \ - qtpositioning5-dev \ - qttools5-dev-tools \ - libqt5sql5-sqlite \ - libqt5svg5-dev \ - libqt5charts5-dev \ - libqt5x11extras5-dev \ - libreadline-dev \ - libdw1 \ - valgrind -} - -# Install Ubuntu 22.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 \ - qtchooser \ - qt5-qmake \ - qtbase5-dev-tools \ - python3-dev -} - -# Install Ubuntu 20.04 packages -function install_ubuntu_focal_requirements() { - install_ubuntu_common_requirements - - $SUDO apt-get install -y --no-install-recommends \ - libavresample-dev \ - qt5-default \ - python-dev -} - -# Detect OS using /etc/os-release file -if [ -f "/etc/os-release" ]; then - source /etc/os-release - case "$VERSION_CODENAME" in - "jammy") - install_ubuntu_lts_latest_requirements - ;; - "kinetic") - install_ubuntu_lts_latest_requirements - ;; - "focal") - install_ubuntu_focal_requirements - ;; - *) - echo "$ID $VERSION_ID is unsupported. This setup script is written for Ubuntu 20.04." - read -p "Would you like to attempt installation anyway? " -n 1 -r - echo "" - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 - fi - if [ "$UBUNTU_CODENAME" = "jammy" ] || [ "$UBUNTU_CODENAME" = "kinetic" ]; then - install_ubuntu_lts_latest_requirements - else - install_ubuntu_focal_requirements - fi - esac -else - echo "No /etc/os-release in the system" - exit 1 -fi - -# python setup +$DIR/install_ubuntu_dependencies.sh $DIR/install_python_dependencies.sh -source ~/.bashrc -if [ -z "$OPENPILOT_ENV" ]; then - printf "\nsource %s/tools/openpilot_env.sh" "$ROOT" >> ~/.bashrc - source ~/.bashrc - echo "added openpilot_env to bashrc" -fi - echo echo "---- OPENPILOT SETUP DONE ----" echo "Open a new shell or configure your active shell env by running:" diff --git a/tools/webcam/front_mount_helper.py b/tools/webcam/front_mount_helper.py index 2ae35992ec..0b7b676f6e 100755 --- a/tools/webcam/front_mount_helper.py +++ b/tools/webcam/front_mount_helper.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import numpy as np -# copied from common.transformations/camera.py +# copied from openpilot.common.transformations/camera.py eon_dcam_focal_length = 860.0 # pixels webcam_focal_length = 908.0 # pixels @@ -18,7 +18,7 @@ webcam_intrinsics = np.array([ cam_id = 2 if __name__ == "__main__": - import cv2 # pylint: disable=import-error + import cv2 trans_webcam_to_eon_front = np.dot(eon_dcam_intrinsics, np.linalg.inv(webcam_intrinsics)) diff --git a/tools/webcam/warp_vis.py b/tools/webcam/warp_vis.py index 4331614acf..e3f1284a45 100755 --- a/tools/webcam/warp_vis.py +++ b/tools/webcam/warp_vis.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import numpy as np -# copied from common.transformations/camera.py +# copied from openpilot.common.transformations/camera.py eon_focal_length = 910.0 # pixels eon_dcam_focal_length = 860.0 # pixels @@ -23,7 +23,7 @@ webcam_intrinsics = np.array([ [ 0., 0., 1.]]) if __name__ == "__main__": - import cv2 # pylint: disable=import-error + import cv2 trans_webcam_to_eon_rear = np.dot(eon_intrinsics, np.linalg.inv(webcam_intrinsics)) trans_webcam_to_eon_front = np.dot(eon_dcam_intrinsics, np.linalg.inv(webcam_intrinsics)) print("trans_webcam_to_eon_rear:\n", trans_webcam_to_eon_rear) diff --git a/tools/zookeeper/__init__.py b/tools/zookeeper/__init__.py index 42438cb209..598e0a0587 100644 --- a/tools/zookeeper/__init__.py +++ b/tools/zookeeper/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import ft4222 import ft4222.I2CMaster diff --git a/tools/zookeeper/check_consumption.py b/tools/zookeeper/check_consumption.py index 1345b90571..dab948318f 100755 --- a/tools/zookeeper/check_consumption.py +++ b/tools/zookeeper/check_consumption.py @@ -2,7 +2,7 @@ import sys import time -from tools.zookeeper import Zookeeper +from openpilot.tools.zookeeper import Zookeeper # Usage: check_consumption.py # Exit code: 0 -> passed diff --git a/tools/zookeeper/disable.py b/tools/zookeeper/disable.py index 6c1eecc18b..4bea3e7ed0 100755 --- a/tools/zookeeper/disable.py +++ b/tools/zookeeper/disable.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from tools.zookeeper import Zookeeper +from openpilot.tools.zookeeper import Zookeeper if __name__ == "__main__": z = Zookeeper() diff --git a/tools/zookeeper/enable_and_wait.py b/tools/zookeeper/enable_and_wait.py index 6907e6017c..51b8dc0a5c 100755 --- a/tools/zookeeper/enable_and_wait.py +++ b/tools/zookeeper/enable_and_wait.py @@ -3,7 +3,7 @@ import os import sys import time from socket import gethostbyname, gaierror -from tools.zookeeper import Zookeeper +from openpilot.tools.zookeeper import Zookeeper def is_online(ip): try: diff --git a/tools/zookeeper/ignition.py b/tools/zookeeper/ignition.py index 5c85be203d..d5f01c362f 100755 --- a/tools/zookeeper/ignition.py +++ b/tools/zookeeper/ignition.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import sys -from tools.zookeeper import Zookeeper +from openpilot.tools.zookeeper import Zookeeper if __name__ == "__main__": diff --git a/tools/zookeeper/power_monitor.py b/tools/zookeeper/power_monitor.py index fa1f442bbc..a081baa72a 100755 --- a/tools/zookeeper/power_monitor.py +++ b/tools/zookeeper/power_monitor.py @@ -3,9 +3,9 @@ import sys import time import datetime -from common.realtime import Ratekeeper -from common.filter_simple import FirstOrderFilter -from tools.zookeeper import Zookeeper +from openpilot.common.realtime import Ratekeeper +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.tools.zookeeper import Zookeeper if __name__ == "__main__": z = Zookeeper() diff --git a/tools/zookeeper/test_zookeeper.py b/tools/zookeeper/test_zookeeper.py index ba395ad7ba..89f7f28975 100755 --- a/tools/zookeeper/test_zookeeper.py +++ b/tools/zookeeper/test_zookeeper.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import time -from tools.zookeeper import Zookeeper +from openpilot.tools.zookeeper import Zookeeper if __name__ == "__main__":