diff --git a/.github/ISSUE_TEMPLATE/car_bug_report.yml b/.github/ISSUE_TEMPLATE/car_bug_report.yml index 23527c3a4..7f368f11b 100644 --- a/.github/ISSUE_TEMPLATE/car_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/car_bug_report.yml @@ -1,6 +1,6 @@ name: Car bug report description: For issues with a particular car make or model -labels: ["car bug"] +labels: ["car", "bug"] body: - type: markdown diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml new file mode 100644 index 000000000..223c73486 --- /dev/null +++ b/.github/workflows/badges.yaml @@ -0,0 +1,54 @@ +name: badges +on: + schedule: + - cron: '0 * * * *' + workflow_dispatch: + +env: + BASE_IMAGE: openpilot-base + DOCKER_REGISTRY: ghcr.io/commaai + + BUILD: | + docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true + docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true + docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . + 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 /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c + +jobs: + badges: + name: create badges + runs-on: ubuntu-20.04 + if: github.repository == 'commaai/openpilot' + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Cache scons + id: scons-cache + # TODO: Change the version to the released version when https://github.com/actions/cache/pull/489 (or 571) is merged. + uses: actions/cache@03e00da99d75a2204924908e1cca7902cafce66b + env: + CACHE_SKIP_SAVE: true + with: + path: /tmp/scons_cache + key: scons-${{ hashFiles('.github/workflows/selfdrive_tests.yaml') }}- + restore-keys: | + scons-${{ hashFiles('.github/workflows/selfdrive_tests.yaml') }}- + scons- + + - name: Build Docker image + run: eval "$BUILD" + + - name: Push badges + run: | + ${{ env.RUN }} "scons -j$(nproc) && python selfdrive/ui/translations/create_badges.py" + + git checkout --orphan badges + git rm -rf --cached . + git config user.email "badge-researcher@comma.ai" + git config user.name "Badge Researcher" + + git add translation_badge_*.svg + git commit -m "Add/Update badges" + git push -f origin HEAD diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index 7acc8a225..b659d4cee 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -9,7 +9,7 @@ env: BASE_IMAGE: openpilot-base DOCKER_REGISTRY: ghcr.io/commaai - DOCKER_LOGIN: docker login ghcr.io -u adeebshihadeh -p ${{ secrets.CONTAINER_TOKEN }} + DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: | docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true @@ -25,7 +25,7 @@ jobs: IMAGE_NAME: openpilot-prebuilt steps: - name: Wait for green check mark - uses: commaai/wait-on-check-action@f16fc3bb6cd4886520b4e9328db1d42104d5cadc + uses: lewagon/wait-on-check-action@e2558238c09778af25867eb5de5a3ce4bbae3dcd with: ref: master wait-interval: 30 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fb5a37eee..8df89dcc3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'commaai/openpilot' steps: - name: Wait for green check mark - uses: commaai/wait-on-check-action@f16fc3bb6cd4886520b4e9328db1d42104d5cadc + uses: lewagon/wait-on-check-action@e2558238c09778af25867eb5de5a3ce4bbae3dcd with: ref: master wait-interval: 30 diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 5bd9acc20..7397e30f8 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -11,18 +11,18 @@ env: DOCKER_REGISTRY: ghcr.io/commaai AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }} - DOCKER_LOGIN: docker login ghcr.io -u adeebshihadeh -p ${{ secrets.CONTAINER_TOKEN }} + DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: | docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . - 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 /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache $BASE_IMAGE /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 /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c BUILD_CL: | docker pull $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest || true docker build --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 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 /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache $CL_BASE_IMAGE /bin/sh -c + RUN_CL: 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 /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c UNIT_TEST: coverage run --append -m unittest discover @@ -39,6 +39,8 @@ jobs: with: fetch-depth: 0 submodules: true + - name: Pull LFS + run: git lfs pull - name: Check submodules if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' run: release/check-submodules.sh @@ -58,12 +60,22 @@ jobs: run: | TARGET_DIR=$STRIPPED_DIR release/build_devel.sh cp Dockerfile.openpilot_base $STRIPPED_DIR + cp .pre-commit-config.yaml $STRIPPED_DIR + cp .pylintrc $STRIPPED_DIR + cp mypy.ini $STRIPPED_DIR - name: Build Docker image - run: eval "$BUILD" + run: | + eval "$BUILD" + rm $STRIPPED_DIR/Dockerfile.openpilot_base - name: Build openpilot and run checks run: | cd $STRIPPED_DIR ${{ env.RUN }} "CI=1 python selfdrive/manager/build.py && \ + pre-commit run --all && \ + rm .pre-commit-config.yaml && \ + rm .pylintrc && \ + rm mypy.ini && \ + release/check-dirty.sh && \ python -m unittest discover selfdrive/car" build_all: @@ -89,7 +101,7 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: Build openpilot with all flags - run: ${{ env.RUN }} "scons -j$(nproc) --extras --test" + run: ${{ env.RUN }} "scons -j$(nproc) --extras --test && release/check-dirty.sh" - name: Cleanup scons cache run: | ${{ env.RUN }} "scons -j$(nproc) --extras --test && \ @@ -299,18 +311,22 @@ jobs: $UNIT_TEST selfdrive/loggerd && \ $UNIT_TEST selfdrive/car && \ $UNIT_TEST selfdrive/locationd && \ + selfdrive/locationd/test/_test_locationd_lib.py && \ $UNIT_TEST selfdrive/athena && \ $UNIT_TEST selfdrive/thermald && \ - $UNIT_TEST selfdrive/hardware/tici && \ + $UNIT_TEST system/hardware/tici && \ $UNIT_TEST selfdrive/modeld && \ $UNIT_TEST tools/lib/tests && \ + ./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 && \ ./selfdrive/loggerd/tests/test_logger &&\ ./system/proclogd/tests/test_proclog && \ - ./selfdrive/ui/replay/tests/test_replay && \ - ./selfdrive/camerad/test/ae_gray_test && \ + ./tools/replay/tests/test_replay && \ + ./system/camerad/test/ae_gray_test && \ coverage xml" - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v2 @@ -364,7 +380,7 @@ jobs: 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@v2 - + model_replay_onnx: name: model replay onnx runs-on: ubuntu-20.04 @@ -503,3 +519,67 @@ jobs: run: | $DOCKER_LOGIN docker push $DOCKER_REGISTRY/openpilot-docs:latest + + car_docs_diff: + name: comment on PR with car docs diff + runs-on: ubuntu-20.04 + timeout-minutes: 50 + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v3 + with: + submodules: true + ref: ${{ github.event.pull_request.base.ref }} + - name: Cache scons + id: scons-cache + # TODO: Change the version to the released version when https://github.com/actions/cache/pull/489 (or 571) is merged. + uses: actions/cache@03e00da99d75a2204924908e1cca7902cafce66b + env: + CACHE_SKIP_SAVE: true + with: + path: /tmp/scons_cache + key: scons-${{ hashFiles('.github/workflows/selfdrive_tests.yaml') }}- + restore-keys: | + scons-${{ hashFiles('.github/workflows/selfdrive_tests.yaml') }}- + scons- + - name: Build Docker image + run: eval "$BUILD" + - 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" + sudo chown -R $USER:$USER ${{ github.workspace }} + - uses: actions/checkout@v3 + with: + submodules: true + - name: Save car docs diff + id: save_diff + run: | + ${{ env.RUN }} "scons -j$(nproc)" + output=$(${{ env.RUN }} "python selfdrive/debug/print_docs_diff.py --path /tmp/openpilot_cache/base_car_info") || true + output="${output//$'\n'/'%0A'}" + echo "::set-output name=diff::$output" + - name: Find comment + if: ${{ env.AZURE_TOKEN != '' }} + uses: peter-evans/find-comment@1769778a0c5bd330272d749d12c036d65e70d39d + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + body-includes: This PR makes changes to + - name: Update comment + if: ${{ steps.save_diff.outputs.diff != '' && env.AZURE_TOKEN != '' }} + uses: peter-evans/create-or-update-comment@b95e16d2859ad843a14218d1028da5b2c4cbc4b4 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: "${{ steps.save_diff.outputs.diff }}" + edit-mode: replace + - name: Delete comment + if: ${{ steps.fc.outputs.comment-id != '' && steps.save_diff.outputs.diff == '' && env.AZURE_TOKEN != '' }} + uses: actions/github-script@v6 + with: + script: | + github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: ${{ steps.fc.outputs.comment-id }} + }) diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index b2433fba8..e93ce2bb3 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -7,7 +7,7 @@ env: BASE_IMAGE: openpilot-base CL_BASE_IMAGE: openpilot-base-cl DOCKER_REGISTRY: ghcr.io/commaai - DOCKER_LOGIN: docker login ghcr.io -u adeebshihadeh -p ${{ secrets.CONTAINER_TOKEN }} + DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: | docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true diff --git a/.gitignore b/.gitignore index 334b1b4fe..6aee0ed8e 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ a.out *.class *.pyxbldc *.vcd +*.qm config.json clcache compile_commands.json @@ -45,18 +46,15 @@ system/proclogd/proclogd selfdrive/ui/_ui selfdrive/test/longitudinal_maneuvers/out selfdrive/visiond/visiond -selfdrive/loggerd/loggerd -selfdrive/loggerd/bootlog selfdrive/sensord/_gpsd selfdrive/sensord/_sensord -selfdrive/camerad/camerad -selfdrive/camerad/test/ae_gray_test +system/camerad/camerad +system/camerad/test/ae_gray_test selfdrive/modeld/_modeld selfdrive/modeld/_dmonitoringmodeld /src/ one -/body/ openpilot notebooks xx diff --git a/.gitmodules b/.gitmodules index 1e6110b7a..bc439b451 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,18 @@ [submodule "panda"] - path = panda - url = ../../commaai/panda.git + path = panda + url = ../../commaai/panda.git [submodule "opendbc"] - path = opendbc - url = ../../commaai/opendbc.git + path = opendbc + url = ../../commaai/opendbc.git [submodule "laika_repo"] - path = laika_repo - url = ../../commaai/laika.git + path = laika_repo + url = ../../commaai/laika.git [submodule "cereal"] - path = cereal - url = ../../commaai/cereal.git + path = cereal + url = ../../commaai/cereal.git [submodule "rednose_repo"] - path = rednose_repo - url = ../../commaai/rednose.git + path = rednose_repo + url = ../../commaai/rednose.git +[submodule "body"] + path = body + url = ../../commaai/body.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 81ce927f6..e273cd9ae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: hooks: - id: mypy exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)/' - additional_dependencies: ['lxml', 'numpy', 'types-atomicwrites', 'types-pycurl', 'types-requests', 'types-certifi'] + additional_dependencies: ['types-PyYAML', 'lxml', 'numpy', 'types-atomicwrites', 'types-pycurl', 'types-requests', 'types-certifi'] args: - --warn-redundant-casts - --warn-return-any @@ -54,7 +54,7 @@ repos: entry: cppcheck language: system types: [c++] - exclude: '^(third_party/)|(pyextra/)|(cereal/)|(opendbc/)|(panda/)|(tools/)|(selfdrive/modeld/thneed/debug/)|(selfdrive/modeld/test/)|(selfdrive/camerad/test/)/|(installer/)' + exclude: '^(third_party/)|(pyextra/)|(cereal/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(tools/)|(selfdrive/modeld/thneed/debug/)|(selfdrive/modeld/test/)|(selfdrive/camerad/test/)|(installer/)' args: - --error-exitcode=1 - --language=c++ diff --git a/Jenkinsfile b/Jenkinsfile index ebc26a592..6b05e81d7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,6 +10,7 @@ export TEST_DIR=${env.TEST_DIR} export SOURCE_DIR=${env.SOURCE_DIR} export GIT_BRANCH=${env.GIT_BRANCH} export GIT_COMMIT=${env.GIT_COMMIT} +export AZURE_TOKEN='${env.AZURE_TOKEN}' source ~/.bash_profile if [ -f /TICI ]; then @@ -45,6 +46,7 @@ pipeline { CI = "1" TEST_DIR = "/data/openpilot" SOURCE_DIR = "/data/openpilot_source/" + AZURE_TOKEN = credentials('azure_token') } options { timeout(time: 4, unit: 'HOURS') @@ -68,7 +70,6 @@ pipeline { not { anyOf { branch 'master-ci'; branch 'devel'; branch 'devel-staging'; - branch 'release2'; branch 'release2-staging'; branch 'dashcam'; branch 'dashcam-staging'; branch 'release3'; branch 'release3-staging'; branch 'dashcam3'; branch 'dashcam3-staging'; branch 'testing-closet*'; branch 'hotfix-*' } @@ -88,8 +89,8 @@ pipeline { steps { sh "git config --global --add safe.directory ${WORKSPACE}" sh "git lfs pull" - sh "${WORKSPACE}/tools/sim/build_container.sh" 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" } @@ -113,6 +114,7 @@ pipeline { phone_steps("tici", [ ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR EXTRA_FILES='tools/' ./build_devel.sh"], ["build openpilot", "cd selfdrive/manager && ./build.py"], + ["check dirty", "release/check-dirty.sh"], ["test manager", "python selfdrive/manager/test/test_manager.py"], ["onroad tests", "cd selfdrive/test/ && ./test_onroad.py"], ["test car interfaces", "cd selfdrive/car/tests/ && ./test_car_interfaces.py"], @@ -125,7 +127,7 @@ pipeline { steps { phone_steps("tici2", [ ["build", "cd selfdrive/manager && ./build.py"], - ["test power draw", "python system/hardware/tici/test_power_draw.py"], + //["test power draw", "python system/hardware/tici/test_power_draw.py"], ["test boardd loopback", "python selfdrive/boardd/tests/test_boardd_loopback.py"], ["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"], ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib python selfdrive/loggerd/tests/test_encoder.py"], @@ -139,8 +141,8 @@ pipeline { steps { phone_steps("tici-party", [ ["build", "cd selfdrive/manager && ./build.py"], - ["test camerad", "python selfdrive/camerad/test/test_camerad.py"], - ["test exposure", "python selfdrive/camerad/test/test_exposure.py"], + ["test camerad", "python system/camerad/test/test_camerad.py"], + ["test exposure", "python system/camerad/test/test_exposure.py"], ]) } } diff --git a/Pipfile b/Pipfile index b8545b1a2..81669e480 100644 --- a/Pipfile +++ b/Pipfile @@ -41,6 +41,8 @@ tenacity = "*" mpld3 = "*" carla = {version = "==0.9.13", markers="platform_system != 'Darwin'"} ft4222 = "*" +pandas = "*" +tabulate = "*" [packages] atomicwrites = "*" @@ -86,8 +88,6 @@ urllib3 = "*" utm = "*" websocket_client = "*" hatanaka = "==2.4" -PyQt5 = "==5.15.4" -PyQt5-sip = "==12.9.0" [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock index cc347a60f..0612d0ff3 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d2629168f477b3a14f68f26e3b63ea9797b20599c066b2aa23a027bcee2ca40a" + "sha256": "c92514c0e6968af008916446514f41b4e004aa7aa4a2951cc1e9e258ac072111" }, "pipfile-spec": 6, "requires": { @@ -79,75 +79,89 @@ }, "certifi": { "hashes": [ - "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7", - "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a" + "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", + "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" ], "markers": "python_version >= '3.6'", - "version": "==2022.5.18.1" + "version": "==2022.6.15" }, "cffi": { "hashes": [ - "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", - "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", - "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", - "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", - "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", - "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", - "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", - "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", - "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", - "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", - "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", - "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", - "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", - "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", - "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", - "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", - "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", - "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", - "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", - "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", - "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", - "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", - "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", - "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", - "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", - "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", - "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", - "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", - "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", - "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", - "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", - "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", - "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", - "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", - "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", - "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", - "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", - "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", - "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", - "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", - "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", - "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", - "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", - "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", - "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", - "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", - "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", - "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", - "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", - "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" - ], - "index": "pypi", - "version": "==1.15.0" + "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", + "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", + "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", + "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", + "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", + "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", + "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", + "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", + "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", + "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", + "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", + "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", + "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", + "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", + "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", + "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", + "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", + "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", + "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", + "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", + "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", + "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", + "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", + "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", + "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", + "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", + "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", + "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", + "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", + "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", + "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", + "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", + "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", + "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", + "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", + "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", + "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", + "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", + "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", + "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", + "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", + "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", + "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", + "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", + "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", + "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", + "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", + "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", + "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", + "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", + "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", + "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", + "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", + "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", + "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", + "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", + "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", + "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", + "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", + "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", + "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", + "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", + "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", + "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" + ], + "index": "pypi", + "version": "==1.15.1" }, "charset-normalizer": { "hashes": [ - "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", - "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", + "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" ], - "markers": "python_full_version >= '3.5.0'", - "version": "==2.0.12" + "markers": "python_version >= '3.6'", + "version": "==2.1.0" }, "click": { "hashes": [ @@ -169,31 +183,31 @@ }, "cryptography": { "hashes": [ - "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804", - "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178", - "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717", - "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982", - "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004", - "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe", - "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452", - "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336", - "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4", - "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15", - "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d", - "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c", - "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0", - "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06", - "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9", - "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1", - "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023", - "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de", - "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f", - "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181", - "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e", - "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a" - ], - "index": "pypi", - "version": "==37.0.2" + "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", + "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", + "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", + "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", + "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", + "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", + "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", + "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", + "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", + "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", + "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", + "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", + "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", + "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", + "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", + "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", + "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", + "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", + "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", + "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", + "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", + "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" + ], + "index": "pypi", + "version": "==37.0.4" }, "cython": { "hashes": [ @@ -347,31 +361,31 @@ "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" ], - "markers": "python_full_version >= '3.5.0'", + "markers": "python_version >= '3.5'", "version": "==3.3" }, "importlib-metadata": { "hashes": [ - "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700", - "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec" + "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670", + "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" ], "markers": "python_version < '3.10'", - "version": "==4.11.4" + "version": "==4.12.0" }, "importlib-resources": { "hashes": [ - "sha256:b6062987dfc51f0fcb809187cffbd60f35df7acb4589091f154214af6d0d49d3", - "sha256:e447dc01619b1e951286f3929be820029d48c75eb25d265c28b92a16548212b8" + "sha256:568c9f16cb204f9decc8d6d24a572eeea27dacbb4cee9e6b03a8025736769751", + "sha256:7952325ffd516c05a8ad0858c74dff2c3343f136fe66a6002b2623dd1d43f223" ], "markers": "python_version >= '3.7'", - "version": "==5.7.1" + "version": "==5.8.0" }, "isort": { "hashes": [ "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" ], - "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", + "markers": "python_version < '4' and python_full_version >= '3.6.1'", "version": "==5.10.1" }, "itsdangerous": { @@ -563,62 +577,58 @@ }, "numpy": { "hashes": [ - "sha256:0791fbd1e43bf74b3502133207e378901272f3c156c4df4954cad833b1380207", - "sha256:1ce7ab2053e36c0a71e7a13a7475bd3b1f54750b4b433adc96313e127b870887", - "sha256:2d487e06ecbf1dc2f18e7efce82ded4f705f4bd0cd02677ffccfb39e5c284c7e", - "sha256:37431a77ceb9307c28382c9773da9f306435135fae6b80b62a11c53cfedd8802", - "sha256:3e1ffa4748168e1cc8d3cde93f006fe92b5421396221a02f2274aab6ac83b077", - "sha256:425b390e4619f58d8526b3dcf656dde069133ae5c240229821f01b5f44ea07af", - "sha256:43a8ca7391b626b4c4fe20aefe79fec683279e31e7c79716863b4b25021e0e74", - "sha256:4c6036521f11a731ce0648f10c18ae66d7143865f19f7299943c985cdc95afb5", - "sha256:59d55e634968b8f77d3fd674a3cf0b96e85147cd6556ec64ade018f27e9479e1", - "sha256:64f56fc53a2d18b1924abd15745e30d82a5782b2cab3429aceecc6875bd5add0", - "sha256:7228ad13744f63575b3a972d7ee4fd61815b2879998e70930d4ccf9ec721dce0", - "sha256:9ce7df0abeabe7fbd8ccbf343dc0db72f68549856b863ae3dd580255d009648e", - "sha256:a911e317e8c826ea632205e63ed8507e0dc877dcdc49744584dfc363df9ca08c", - "sha256:b89bf9b94b3d624e7bb480344e91f68c1c6c75f026ed6755955117de00917a7c", - "sha256:ba9ead61dfb5d971d77b6c131a9dbee62294a932bf6a356e48c75ae684e635b3", - "sha256:c1d937820db6e43bec43e8d016b9b3165dcb42892ea9f106c70fb13d430ffe72", - "sha256:cc7f00008eb7d3f2489fca6f334ec19ca63e31371be28fd5dad955b16ec285bd", - "sha256:d4c5d5eb2ec8da0b4f50c9a843393971f31f1d60be87e0fb0917a49133d257d6", - "sha256:e96d7f3096a36c8754207ab89d4b3282ba7b49ea140e4973591852c77d09eb76", - "sha256:f0725df166cf4785c0bc4cbfb320203182b1ecd30fee6e541c8752a92df6aa32", - "sha256:f3eb268dbd5cfaffd9448113539e44e2dd1c5ca9ce25576f7c04a5453edc26fa", - "sha256:fb7a980c81dd932381f8228a426df8aeb70d59bbcda2af075b627bbc50207cba" - ], - "index": "pypi", - "version": "==1.22.4" + "sha256:092f5e6025813e64ad6d1b52b519165d08c730d099c114a9247c9bb635a2a450", + "sha256:196cd074c3f97c4121601790955f915187736f9cf458d3ee1f1b46aff2b1ade0", + "sha256:1c29b44905af288b3919803aceb6ec7fec77406d8b08aaa2e8b9e63d0fe2f160", + "sha256:2b2da66582f3a69c8ce25ed7921dcd8010d05e59ac8d89d126a299be60421171", + "sha256:5043bcd71fcc458dfb8a0fc5509bbc979da0131b9d08e3d5f50fb0bbb36f169a", + "sha256:58bfd40eb478f54ff7a5710dd61c8097e169bc36cc68333d00a9bcd8def53b38", + "sha256:79a506cacf2be3a74ead5467aee97b81fca00c9c4c8b3ba16dbab488cd99ba10", + "sha256:94b170b4fa0168cd6be4becf37cb5b127bd12a795123984385b8cd4aca9857e5", + "sha256:97a76604d9b0e79f59baeca16593c711fddb44936e40310f78bfef79ee9a835f", + "sha256:98e8e0d8d69ff4d3fa63e6c61e8cfe2d03c29b16b58dbef1f9baa175bbed7860", + "sha256:ac86f407873b952679f5f9e6c0612687e51547af0e14ddea1eedfcb22466babd", + "sha256:ae8adff4172692ce56233db04b7ce5792186f179c415c37d539c25de7298d25d", + "sha256:bd3fa4fe2e38533d5336e1272fc4e765cabbbde144309ccee8675509d5cd7b05", + "sha256:d0d2094e8f4d760500394d77b383a1b06d3663e8892cdf5df3c592f55f3bff66", + "sha256:d54b3b828d618a19779a84c3ad952e96e2c2311b16384e973e671aa5be1f6187", + "sha256:d6ca8dabe696c2785d0c8c9b0d8a9b6e5fdbe4f922bde70d57fa1a2848134f95", + "sha256:d8cc87bed09de55477dba9da370c1679bd534df9baa171dd01accbb09687dac3", + "sha256:f0f18804df7370571fb65db9b98bf1378172bd4e962482b857e612d1fec0f53e", + "sha256:f1d88ef79e0a7fa631bb2c3dda1ea46b32b1fe614e10fedd611d3d5398447f2f", + "sha256:f9c3fc2adf67762c9fe1849c859942d23f8d3e0bee7b5ed3d4a9c3eeb50a2f07", + "sha256:fc431493df245f3c627c0c05c2bd134535e7929dbe2e602b80e42bf52ff760bc", + "sha256:fe8b9683eb26d2c4d5db32cd29b38fdcf8381324ab48313b5b69088e0e355379" + ], + "index": "pypi", + "version": "==1.23.0" }, "onnx": { "hashes": [ - "sha256:0cf47c205b376b3763beef92a6de4152f3b1552d6f640d93044938500baf5958", - "sha256:3403884c482859f8cf2e0c276da84bd9ac2235d266726f4ddc9625d3fd263218", - "sha256:43b32a2f20c94aa98866deae9e4218faf0495144ad05402e918fa279674b6df9", - "sha256:4454906de80a351de6929b0896ad605d106c324c3112c92249240e531f68fbba", - "sha256:4aa899f74acd4c5543f0efed8bfe98a3b701df75c5ffa179212e3088c51971bb", - "sha256:58d4873ec587ac14c44227d8027787edc88cd61596e646e3417f2a826a920898", - "sha256:593ca9e11f15afa26b3aaf2d170bb803d4bd86dbd560aa7be4e5f535d03f83d5", - "sha256:67c6d2654c1c203e5c839a47900b51f588fd0de71bbd497fb193d30a0b3ec1e9", - "sha256:7924d9baa13dbbf335737229f6d068f380d153679f357e495da60007b61cf56d", - "sha256:7a2f5d6998fe79aed80fad9d4522140d02c4d29513047e335d5c5355c1ebda5e", - "sha256:82221a07707b1ccf71fb18c6abb77f2566517a55d5185809775b5ff008bfb35c", - "sha256:89420e5b824d7e182846fe2aa09190ddb41162b261465c6ca928174bc2ac10b7", - "sha256:997d91ffd7b7ae7aee09c6d652a896d906be430d425865c759b51a8de5df9fe0", - "sha256:9b9f58ea01c1b20b057f55f628df4fc0403bbc160b7282a56e3bb4df5c7fb96f", - "sha256:a6e9135f1d02539ca7573f699fb0d31d3c43d10fac1d2d2239a9a1c553506c29", - "sha256:ae74bf8fa343b64e2b7fe205091b7f3728887c018ae061d161dd86ec95eb66a8", - "sha256:b2de0b117ad77689d308824a0c9eb89539ec28a799b4e2e05b3bb977b0da0b45", - "sha256:c3d3503110f2cab2c818f4a7b2bc8abc3bc79649daa39e70d5fb504b208ddb1e", - "sha256:d6581dd2122525549d1d8b431b8bf375298993c77bddb8fd0bf0d92611df76a1", - "sha256:d6ddbe89e32f885db736d36fcb132784e368331a18c3b6168ac9f561eb462057", - "sha256:df85666ab2b88fd9cf9b2504bcb551da39422eab65a143926a8db58f81b09164", - "sha256:ea06dbf57a287657b6dc4e189918e4cb451450308589d482117216194d6f83d6", - "sha256:eb46f31f12bb0bfdcfb68497d10b20447cf8fa6c4f693120c013e052645357b8", - "sha256:eca224c7c2c8ee4072a0743e4898a84a9bdf8297b5e5910a2632e4c4182ffb2a", - "sha256:f335d982b8ed201cf767459b993630acfd20c32b100529f70af9f28a26e72167" - ], - "index": "pypi", - "version": "==1.11.0" + "sha256:13b3e77d27523b9dbf4f30dfc9c959455859d5e34e921c44f712d69b8369eff9", + "sha256:213e73610173f6b2e99f99a4b0636f80b379c417312079d603806e48ada4ca8b", + "sha256:23781594bb8b7ee985de1005b3c601648d5b0568a81e01365c48f91d1f5648e4", + "sha256:2d9a7db54e75529160337232282a4816cc50667dc7dc34be178fd6f6b79d4705", + "sha256:341c7016e23273e9ffa9b6e301eee95b8c37d0f04df7cedbdb169d2c39524c96", + "sha256:3c6e6bcffc3f5c1e148df3837dc667fa4c51999788c1b76b0b8fbba607e02da8", + "sha256:5578b93dc6c918cec4dee7fb7d9dd3b09d338301ee64ca8b4f28bc217ed42dca", + "sha256:56ceb7e094c43882b723cfaa107d85ad673cfdf91faeb28d7dcadacca4f43a07", + "sha256:81a3555fd67be2518bf86096299b48fb9154652596219890abfe90bd43a9ec13", + "sha256:8a7aa61aea339bd28f310f4af4f52ce6c4b876386228760b16308efd58f95059", + "sha256:9fd2f4e23078df197bb76a59b9cd8f5a43a6ad2edc035edb3ecfb9042093e05a", + "sha256:af90427ca04c6b7b8107c2021e1273227a3ef1a7a01f3073039cae7855a59833", + "sha256:b3629e8258db15d4e2c9b7f1be91a3186719dd94661c218c6f5fde3cc7de3d4d", + "sha256:bdbd2578424c70836f4d0f9dda16c21868ddb07cc8192f9e8a176908b43d694b", + "sha256:c11162ffc487167da140f1112f49c4f82d815824f06e58bc3095407699f05863", + "sha256:c39a7a0352c856f1df30dccf527eb6cb4909052e5eaf6fa2772a637324c526aa", + "sha256:c7a9b3ea02c30efc1d2662337e280266aca491a8e86be0d8a657f874b7cccd1e", + "sha256:f66d2996e65f490a57b3ae952e4e9189b53cc9fe3f75e601d50d4db2dc1b1cd9", + "sha256:f8800f28c746ab06e51ef8449fd1215621f4ddba91be3ffc264658937d38a2af", + "sha256:fab13feb4d94342aae6d357d480f2e47d41b9f4e584367542b21ca6defda9e0a", + "sha256:fea5156a03398fe0e23248042d8651c1eaac5f6637d4dd683b4c1f1320b9f7b4" + ], + "index": "pypi", + "version": "==1.12.0" }, "onnxruntime-gpu": { "hashes": [ @@ -635,47 +645,67 @@ }, "pillow": { "hashes": [ - "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f", - "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d", - "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b", - "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c", - "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9", - "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546", - "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578", - "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1", - "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe", - "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098", - "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2", - "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a", - "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45", - "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530", - "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108", - "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1", - "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd", - "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0", - "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6", - "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c", - "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf", - "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4", - "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d", - "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765", - "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602", - "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340", - "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c", - "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b", - "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84", - "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8", - "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92", - "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54", - "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601", - "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a", - "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf", - "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251", - "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a", - "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e" - ], - "index": "pypi", - "version": "==9.1.1" + "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927", + "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14", + "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc", + "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58", + "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60", + "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76", + "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c", + "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac", + "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490", + "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1", + "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f", + "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d", + "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f", + "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069", + "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402", + "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885", + "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e", + "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be", + "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8", + "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff", + "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da", + "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004", + "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f", + "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20", + "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d", + "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c", + "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544", + "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9", + "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3", + "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04", + "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c", + "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5", + "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4", + "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb", + "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4", + "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c", + "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467", + "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e", + "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421", + "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b", + "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8", + "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb", + "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3", + "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf", + "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1", + "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a", + "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28", + "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0", + "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1", + "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8", + "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd", + "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4", + "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8", + "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f", + "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013", + "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59", + "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc", + "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4" + ], + "index": "pypi", + "version": "==9.2.0" }, "platformdirs": { "hashes": [ @@ -803,39 +833,39 @@ }, "pycryptodome": { "hashes": [ - "sha256:028dcbf62d128b4335b61c9fbb7dd8c376594db607ef36d5721ee659719935d5", - "sha256:12ef157eb1e01a157ca43eda275fa68f8db0dd2792bc4fe00479ab8f0e6ae075", - "sha256:2562de213960693b6d657098505fd4493c45f3429304da67efcbeb61f0edfe89", - "sha256:27e92c1293afcb8d2639baf7eb43f4baada86e4de0f1fb22312bfc989b95dae2", - "sha256:36e3242c4792e54ed906c53f5d840712793dc68b726ec6baefd8d978c5282d30", - "sha256:50a5346af703330944bea503106cd50c9c2212174cfcb9939db4deb5305a8367", - "sha256:53dedbd2a6a0b02924718b520a723e88bcf22e37076191eb9b91b79934fb2192", - "sha256:69f05aaa90c99ac2f2af72d8d7f185f729721ad7c4be89e9e3d0ab101b0ee875", - "sha256:75a3a364fee153e77ed889c957f6f94ec6d234b82e7195b117180dcc9fc16f96", - "sha256:766a8e9832128c70012e0c2b263049506cbf334fb21ff7224e2704102b6ef59e", - "sha256:7fb90a5000cc9c9ff34b4d99f7f039e9c3477700e309ff234eafca7b7471afc0", - "sha256:893f32210de74b9f8ac869ed66c97d04e7d351182d6d39ebd3b36d3db8bda65d", - "sha256:8b5c28058102e2974b9868d72ae5144128485d466ba8739abd674b77971454cc", - "sha256:924b6aad5386fb54f2645f22658cb0398b1f25bc1e714a6d1522c75d527deaa5", - "sha256:9924248d6920b59c260adcae3ee231cd5af404ac706ad30aa4cd87051bf09c50", - "sha256:9ec761a35dbac4a99dcbc5cd557e6e57432ddf3e17af8c3c86b44af9da0189c0", - "sha256:a36ab51674b014ba03da7f98b675fcb8eabd709a2d8e18219f784aba2db73b72", - "sha256:aae395f79fa549fb1f6e3dc85cf277f0351e15a22e6547250056c7f0c990d6a5", - "sha256:c880a98376939165b7dc504559f60abe234b99e294523a273847f9e7756f4132", - "sha256:ce7a875694cd6ccd8682017a7c06c6483600f151d8916f2b25cf7a439e600263", - "sha256:d1b7739b68a032ad14c5e51f7e4e1a5f92f3628bba024a2bda1f30c481fc85d8", - "sha256:dcd65355acba9a1d0fc9b923875da35ed50506e339b35436277703d7ace3e222", - "sha256:e04e40a7f8c1669195536a37979dd87da2c32dbdc73d6fe35f0077b0c17c803b", - "sha256:e0c04c41e9ade19fbc0eff6aacea40b831bfcb2c91c266137bcdfd0d7b2f33ba", - "sha256:e24d4ec4b029611359566c52f31af45c5aecde7ef90bf8f31620fd44c438efe7", - "sha256:e64738207a02a83590df35f59d708bf1e7ea0d6adce712a777be2967e5f7043c", - "sha256:ea56a35fd0d13121417d39a83f291017551fa2c62d6daa6b04af6ece7ed30d84", - "sha256:f2772af1c3ef8025c85335f8b828d0193fa1e43256621f613280e2c81bfad423", - "sha256:f403a3e297a59d94121cb3ee4b1cf41f844332940a62d71f9e4a009cc3533493", - "sha256:f572a3ff7b6029dd9b904d6be4e0ce9e309dcb847b03e3ac8698d9d23bb36525" - ], - "index": "pypi", - "version": "==3.14.1" + "sha256:045d75527241d17e6ef13636d845a12e54660aa82e823b3b3341bcf5af03fa79", + "sha256:0926f7cc3735033061ef3cf27ed16faad6544b14666410727b31fea85a5b16eb", + "sha256:092a26e78b73f2530b8bd6b3898e7453ab2f36e42fd85097d705d6aba2ec3e5e", + "sha256:1b22bcd9ec55e9c74927f6b1f69843cb256fb5a465088ce62837f793d9ffea88", + "sha256:2aa55aae81f935a08d5a3c2042eb81741a43e044bd8a81ea7239448ad751f763", + "sha256:2ea63d46157386c5053cfebcdd9bd8e0c8b7b0ac4a0507a027f5174929403884", + "sha256:2ec709b0a58b539a4f9d33fb8508264c3678d7edb33a68b8906ba914f71e8c13", + "sha256:2ffd8b31561455453ca9f62cb4c24e6b8d119d6d531087af5f14b64bee2c23e6", + "sha256:4b52cb18b0ad46087caeb37a15e08040f3b4c2d444d58371b6f5d786d95534c2", + "sha256:4c3ccad74eeb7b001f3538643c4225eac398c77d617ebb3e57571a897943c667", + "sha256:5099c9ca345b2f252f0c28e96904643153bae9258647585e5e6f649bb7a1844a", + "sha256:57f565acd2f0cf6fb3e1ba553d0cb1f33405ec1f9c5ded9b9a0a5320f2c0bd3d", + "sha256:60b4faae330c3624cc5a546ba9cfd7b8273995a15de94ee4538130d74953ec2e", + "sha256:7c9ed8aa31c146bef65d89a1b655f5f4eab5e1120f55fc297713c89c9e56ff0b", + "sha256:7e3a8f6ee405b3bd1c4da371b93c31f7027944b2bcce0697022801db93120d83", + "sha256:9135dddad504592bcc18b0d2d95ce86c3a5ea87ec6447ef25cfedea12d6018b8", + "sha256:9c772c485b27967514d0df1458b56875f4b6d025566bf27399d0c239ff1b369f", + "sha256:9eaadc058106344a566dc51d3d3a758ab07f8edde013712bc8d22032a86b264f", + "sha256:9ee40e2168f1348ae476676a2e938ca80a2f57b14a249d8fe0d3cdf803e5a676", + "sha256:a8f06611e691c2ce45ca09bbf983e2ff2f8f4f87313609d80c125aff9fad6e7f", + "sha256:b9c5b1a1977491533dfd31e01550ee36ae0249d78aae7f632590db833a5012b8", + "sha256:b9cc96e274b253e47ad33ae1fccc36ea386f5251a823ccb50593a935db47fdd2", + "sha256:c3640deff4197fa064295aaac10ab49a0d55ef3d6a54ae1499c40d646655c89f", + "sha256:c77126899c4b9c9827ddf50565e93955cb3996813c18900c16b2ea0474e130e9", + "sha256:d2a39a66057ab191e5c27211a7daf8f0737f23acbf6b3562b25a62df65ffcb7b", + "sha256:e244ab85c422260de91cda6379e8e986405b4f13dc97d2876497178707f87fc1", + "sha256:ecaaef2d21b365d9c5ca8427ffc10cebed9d9102749fd502218c23cb9a05feb5", + "sha256:fd2184aae6ee2a944aaa49113e6f5787cdc5e4db1eb8edb1aea914bd75f33a0c", + "sha256:ff287bcba9fbeb4f1cccc1f2e90a08d691480735a611ee83c80a7d74ad72b9d9", + "sha256:ff7ae90e36c1715a54446e7872b76102baa5c63aa980917f4aa45e8c78d1a3ec" + ], + "index": "pypi", + "version": "==3.15.0" }, "pyflakes": { "hashes": [ @@ -855,101 +885,54 @@ }, "pylint": { "hashes": [ - "sha256:549261e0762c3466cc001024c4419c08252cb8c8d40f5c2c6966fea690e7fe2a", - "sha256:bb71e6d169506de585edea997e48d9ff20c0dc0e2fbc1d166bad6b640120326b" + "sha256:47705453aa9dce520e123a7d51843d5f0032cbfa06870f89f00927aa1f735a4a", + "sha256:89b61867db16eefb7b3c5b84afc94081edaf11544189e2b238154677529ad69f" ], "index": "pypi", - "version": "==2.14.1" + "version": "==2.14.4" }, "pyopencl": { "hashes": [ - "sha256:01030054c201b021715deb3d6f1355844f9795429dfa0591b59b6f8000ec2d38", - "sha256:02997935ac164f519be65c371f9dd2267a2b7532247dc0a2ef43f435cf76cf4b", - "sha256:07482df440e1246cba6dc46ef70d3ebf1a6c8157a3c6456091026c7f9e4d18d2", - "sha256:0b179591c60b4446846fbea035cb3d1acd2685b0226ba91724109882dc59af2c", - "sha256:15ebc3f3eb2df1d196a7dcefd68d0e9ffa11e275f8a6c57a1145a1d0ff36c382", - "sha256:1a5fb7dc32cf24cdeab1205bc075710d7112656720c2bf9972bebe906e28ec4b", - "sha256:1b649637d608e8dabdec0e0f85392f727fdf622463b425cf7587bdd313b4d9eb", - "sha256:22eed49903178bc686287192a8319ce763129b4e5d42a9dfb5d8f763ba5d6bd6", - "sha256:2deef59d73d0bdd11ba40613ab0798c767214a669a1a5a672500787fb7da63d3", - "sha256:3736bfdc946068be66fe4b5c680926c84366b724b3c4b649b2a1940f7bd6afde", - "sha256:3dd0b5ff24d12ad4c13446d8e5439e63914496dfcf9e23a26baeaa65ec3c7039", - "sha256:5430b938e9391309be2ffaefb6269a0a3c016af5d729121cf8a5fce62a5146c2", - "sha256:5e89596e7f18824fc1f84e2cb0ae059fbfe187d1e2e3919ab0cd701cc634eb03", - "sha256:65e406603fbe47ca72298e022a3c3855b2e1732cb9d04ecbb411025050d0bc57", - "sha256:6f9f91594358af6a9728908c31c5ed4bec3fe1a0d25c6292e37e40c92903fe36", - "sha256:77a70b76789aac85566cb0e3ff6b60c4c00729bbd7f0edd24ac4b3b43e4627e2", - "sha256:799355c27463bf801260e3398643c3c9359627fa9e6ac621cfb5dc1d6e77d859", - "sha256:7ae4825562f7c5956b8926cb99882df1631c5e28aa1310d896c22a8471cf8f56", - "sha256:7b17906a4821a30aa1ce7a9d783bba2564230ea6a55ff31eb3f0e2a4aa5b80af", - "sha256:7d4bf4c858554e9e3af9e7f18b06e8d6c39b25d7a80c28db6e5dd412dc457aee", - "sha256:8981a9274796272508158b08a3cb1a5711318cf32b5f0e4829edecb1a9efcf93", - "sha256:8da3ef5a03cfd0a9859a5ebe623f3c43037e9f0dffd2b658e944bbc381beb529", - "sha256:94c744997f4aff86e68fa3a5d383dc8b5f1e529a360156b82c7583a757eddaa5", - "sha256:9a7fb5769bce7ec09a2d264a233ec9c730b15b391c830d04a381df3fc85bdaca", - "sha256:a6ce276a42caedd3a9a7be00031cfe6bf5d1796efdac40e47f1b707846c96d86", - "sha256:a84310ae508f998ed31825b6e3ab888098cb69a2c627bf5970706620a8d4b127", - "sha256:ad08e37cdeda5d38ac3ca9820400da62ce3a67aab76a5eefa5d089ef3a4877c9", - "sha256:b6e426b5fdce61051b112825da20df4cb78429967e491223bfedaf95c025273a", - "sha256:bb363f9993013b04c0b146e269a73b3d5ebef30f78d5fa542f317cc2440e15b6", - "sha256:c58f05b050ae4ac3b0584d97738ae7ac4381e611567b9d67fe7cf4210c0a7b62", - "sha256:c84ef85cf6b83dbcef4e034390fc1ed6bd8eadf5260b5ae89515d3b9744ef207", - "sha256:c9a841b80ef4c332a6133377fc295fe5376f90f8f2e7c63d36903b07b8ea7262", - "sha256:cd5871aff617d3c9d338fd94c9187382390db82452be0868055b8c519a73445d", - "sha256:cf45c232bf818ef54ee831eb41f4edbd5dfe4c67d894b1e65fc17a690a63c81e", - "sha256:e90bd1ed69cca2a750ffabafc70b4f9eb4d109299e986c3c8fdc4c40fee36ef2", - "sha256:ea5b6ef0e4ad23a3ccbdb382f7cccadbb200a47ceb6ff3e965a3c6c46360b4c2", - "sha256:f433ddd7bfd688b591ea95b6971e5e6cb00f8d5f2dc5db833e528e5ede6909d6" - ], - "index": "pypi", - "version": "==2022.1.5" - }, - "pyqt5": { - "hashes": [ - "sha256:213bebd51821ed89b4d5b35bb10dbe67564228b3568f463a351a08e8b1677025", - "sha256:2a69597e0dd11caabe75fae133feca66387819fc9bc050f547e5551bce97e5be", - "sha256:883a549382fc22d29a0568f3ef20b38c8e7ab633a59498ac4eb63a3bf36d3fd3", - "sha256:8c0848ba790a895801d5bfd171da31cad3e551dbcc4e59677a3b622de2ceca98", - "sha256:a88526a271e846e44779bb9ad7a738c6d3c4a9d01e15a128ecfc6dd4696393b7" - ], - "index": "pypi", - "version": "==5.15.4" - }, - "pyqt5-qt5": { - "hashes": [ - "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a", - "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962", - "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154", - "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327" - ], - "version": "==5.15.2" - }, - "pyqt5-sip": { - "hashes": [ - "sha256:055581c6fed44ba4302b70eeb82e979ff70400037358908f251cd85cbb3dbd93", - "sha256:0fc9aefacf502696710b36cdc9fa2a61487f55ee883dbcf2c2a6477e261546f7", - "sha256:42274a501ab4806d2c31659170db14c282b8313d2255458064666d9e70d96206", - "sha256:4347bd81d30c8e3181e553b3734f91658cfbdd8f1a19f254777f906870974e6d", - "sha256:485972daff2fb0311013f471998f8ec8262ea381bded244f9d14edaad5f54271", - "sha256:4f8e05fe01d54275877c59018d8e82dcdd0bc5696053a8b830eecea3ce806121", - "sha256:69a3ad4259172e2b1aa9060de211efac39ddd734a517b1924d9c6c0cc4f55f96", - "sha256:6a8701892a01a5a2a4720872361197cc80fdd5f49c8482d488ddf38c9c84f055", - "sha256:6d5bca2fc222d58e8093ee8a81a6e3437067bb22bc3f86d06ec8be721e15e90a", - "sha256:83c3220b1ca36eb8623ba2eb3766637b19eb0ce9f42336ad8253656d32750c0a", - "sha256:a25b9843c7da6a1608f310879c38e6434331aab1dc2fe6cb65c14f1ecf33780e", - "sha256:ac57d796c78117eb39edd1d1d1aea90354651efac9d3590aac67fa4983f99f1f", - "sha256:b09f4cd36a4831229fb77c424d89635fa937d97765ec90685e2f257e56a2685a", - "sha256:c446971c360a0a1030282a69375a08c78e8a61d568bfd6dab3dcc5cf8817f644", - "sha256:c5216403d4d8d857ec4a61f631d3945e44fa248aa2415e9ee9369ab7c8a4d0c7", - "sha256:d3e4489d7c2b0ece9d203ae66e573939f7f60d4d29e089c9f11daa17cfeaae32", - "sha256:d59af63120d1475b2bf94fe8062610720a9be1e8940ea146c7f42bb449d49067", - "sha256:d85002238b5180bce4b245c13d6face848faa1a7a9e5c6e292025004f2fd619a", - "sha256:d8b2bdff7bbf45bc975c113a03b14fd669dc0c73e1327f02706666a7dd51a197", - "sha256:dd05c768c2b55ffe56a9d49ce6cc77cdf3d53dbfad935258a9e347cbfd9a5850", - "sha256:fc43f2d7c438517ee33e929e8ae77132749c15909afab6aeece5fcf4147ffdb5" - ], - "index": "pypi", - "version": "==12.9.0" + "sha256:069e7eb1a223d88c13eafa54d6ae896fa892e75ba3d56ff2135a26107ef1142b", + "sha256:1490e6cdeaecba42854013c273685d65fd9102ee6dc6bc3bcb814e9e2b8179e5", + "sha256:15f7b3d29c9359e1e440e4f52f70de031f8d0d8d0f8de53a3bc01501b89360c0", + "sha256:1a2029b7fda6709eca077f618f997372c3d6f2780ad45512632b0d056e6305f9", + "sha256:25e87b4ccc0cc53487d445bea07ce9bdb478a335725df16986aead2ff65b68a4", + "sha256:2c9ad1cbc3f540afc52038851be8e06640aacfece051c89408bc3aece605a7ee", + "sha256:2df01c95ea9ae3dd66b277f0df47144cf7535a27b48a8d49fdd98e0583e368ce", + "sha256:316f59d0c40bfce4f6c160dbaf6501883b33880370bb1819f360dad747e52dfe", + "sha256:4836bc4619be967d6c28627adac151223037fdca056c4ab54da16b591f719347", + "sha256:4b53f7f3ed85ab671c8bfc61a0bbc5476725a7a5f51a94bba5512c3962b2d609", + "sha256:5304cb336af7316ae0650abb7467c076032635bfe4710b8df191612d245dca28", + "sha256:55e9302b8f0b1964c87b0fdab7b853aa2b2f10b4188f5b4618782d4380448c11", + "sha256:6032bef8a35f6df727a0b66e3c9faedb3f560318052848b28d2f72622cfbeace", + "sha256:6ec55934057e99461f684ccd293d87db59a452f5834c13ae36b19d31dfe38599", + "sha256:7176f96728be9b43024bd71704f60849cbfcf0fafd20270181b68ea4730ceb2d", + "sha256:730901d409d8251cd6e9dc59e6c518dff5cdb20a3a0b728344bfd2c707f28b64", + "sha256:75be43c7f33fb86f9d18b7b6f8e9081d8bd5b6331a90aec0d2cad3e81e72bc8f", + "sha256:7bef8e8bcfff574b481565390113ea0a37cf33fd2587ade7f2980f15e73f7b08", + "sha256:7ca9597877e1f8bdb4a49810988230f538b2d7aac389c33418a21cf4358f2fd4", + "sha256:814389b3eb9e6930cf43b984283c94a955edf20ec286402da5acfa503d3ae790", + "sha256:8efc3467454ce8c644f09029a3308496f9cb6e93ca5e8c08f6b79e7825da72c5", + "sha256:98bad7035f27b6de5c9268f52c1e10bffe3a2874994e862468a1792b699a4884", + "sha256:9bbfe94bb6e9d0458693183334e73c973e2fcba01568f42db15b453b926fb816", + "sha256:9d112a4426f5b356641c1312bf1004247dc4019e649502589b86333557203c01", + "sha256:a845779f505ed57b83f279307ae6307d886f3e41fb24dcf7889da27daa726118", + "sha256:aca3581f1a7f6b809b8cdc78b0e66587848b38b143bf2983e91ff8fb9a41bc8f", + "sha256:af5664b98140a29966c5fb12e9d29b85b6c6310efa97d82aee58310774917e8f", + "sha256:b85fa5ba1678dd40713587fd437787b6aa940000c2ddffa360884431be21723a", + "sha256:bcabfb5217ca8f8770f9c69298f79576080bb994b1883a99494b4c2668b04836", + "sha256:c00989bed1e7e5b32ad498fec3deb1c93403ab802cd99b7c78b9c692bd0910ef", + "sha256:d0ddc3b74ad1804eb3fe238dfa3b844b997e88b1ca5164a717c16b362b4f34c3", + "sha256:d8bb2eea4e960917e0a6132dedd34c8ec0b7a384f22713f775d50dbce154263a", + "sha256:db833ebb1e756969a8f851f15486598eb9e3fb27b0535c2a8193cc1c71455016", + "sha256:dc2d78cb5da0081ada1c263aaa773fd5479b3da5e2c421547bf7f3258d3239a5", + "sha256:dd2728e59ae088c900ed68f68d953476d0ff07189f182f917b74de2ac7b3972e", + "sha256:ea4eff6b922fa4ad2077ef90b3254d78597d050ada09bfbe74c22dd22d10c6ac", + "sha256:f8887d54e654598f3854472540b2eb228ac56b56a2491b95bdfac8f15be1c943" + ], + "index": "pypi", + "version": "==2022.1.6" }, "pyserial": { "hashes": [ @@ -969,10 +952,10 @@ }, "pytools": { "hashes": [ - "sha256:3393d25029982080e3fb94c47bf627a1e553ccd174fe2edef6c1c5ec723918ff" + "sha256:4d62875e9a2ab2a24e393a9a8b799492f1a721bffa840af3807bfd42871dd1f4" ], "markers": "python_version ~= '3.6'", - "version": "==2022.1.9" + "version": "==2022.1.12" }, "pyyaml": { "hashes": [ @@ -1015,74 +998,74 @@ }, "pyzmq": { "hashes": [ - "sha256:057176dd3f5ccf5aad4abd662d76b6a39bbf799baaf2f39cd4fdaf2eab326e43", - "sha256:05ec90a8da618f2398f9d1aa20b18a9ef332992c6ac23e8c866099faad6ef0d6", - "sha256:154de02b15422af28b53d29a02de72121ba503634955017255573fc1f995143d", - "sha256:16b832adb5d8716f46051da5533c480250bf126984ce86804db6137a3a7f931b", - "sha256:1df26aa854bdd3a8341bf199064dd6aa6e240f2eaa3c9fa8d217e5d8b868c73e", - "sha256:28f9164fb2658b7b414fa0894c75b1a9c61375774cdc1bdb7298beb042a2cd87", - "sha256:2951c29b8649f3672af9dca8ff61d86310d3664d9629788b1c66422fb13b1239", - "sha256:2b08774057ae7ce8a2eb4e7d54db05358234440706ce43a85814500c5d7bd22e", - "sha256:2e2ac40f7a91c740ec68d6db07ae19ea9259c959333c68bee56ab2c799a67d66", - "sha256:312e56799410c34797417a4060a8bd37d4db1f06d1ec0c54f7c8fd81e0d90376", - "sha256:38f778a74e3889392e949326cfd0e9b2eb37dcbb2980d98fad2c51703d523db2", - "sha256:3955dd5bbbe02f454655296ee36a66c334c7102a29b8458223d168c0380edfd5", - "sha256:425ba851a6f9892bde1da2024d82e2fe6796bd77e3391fb96665c50fe9d4c6a5", - "sha256:48bbc2db041ab28eeee4a3e8ada0ed336640946dd5a8e53dbd3805f9dbdcf0dc", - "sha256:4fbcd657cda75574fd1315a4c44bd322bc2e219039fb09f146bbe6f8aef039e9", - "sha256:523ba7fd4d8fe75ad09c1e574a648892b75a97d0cfc8005727681053ac19555b", - "sha256:53b2c1326c2e484d450932d2be739f064b7cb572faabec38386098a28516a529", - "sha256:540d7146c3cdc9bbffab039ea067f494eba24d1abe5bd33eb9f963c01e3305d4", - "sha256:563d4281c4dbdf647d93114420151d33f895afc4c46b7115a67a0aa5347e6624", - "sha256:67a049bcf967a39993858beed873ed3405536019820922d4efacfe35ab3da51a", - "sha256:67ec63ae3c9c1fa2e077fcb42e77035e2121a04f987464bdf9945a28535d30ad", - "sha256:68e22c5d3be451e87d47f956b397a7823bfbde2176341bc902fba30f96831d7e", - "sha256:6ab4b6108e69f63c917cd7ef7217c5727955b1ac90600e44a13ed5312019a014", - "sha256:6bd7f18bd4cf51ea8d7e54825902cf36f9d2f35cc51ef618373988d5398b8dd0", - "sha256:6cd53e861bccc0bdc4620f68fb4a91d5bcfe9f4213cf8e200fa498044d33a6dc", - "sha256:6d346e551fa64b89d57a4ac74b9bc66703413f02f50093e089e861999ec5cccc", - "sha256:6ff8708fabc9f9bc2949f457d39b4088c9656c4c9ac15fbbbbaafce8f6d07833", - "sha256:7626e8384275a7dea6f3d1f749fb5e00299042e9c895fc3dbe24cb154909c242", - "sha256:7e7346b2b33dcd4a2171dd8a9870ae283eec8f6231dcbcf237a0f41e74751a50", - "sha256:81623c67cb71b93b5f7e06c9107f3781738ae86866db830c950223d87af2a235", - "sha256:83f1c76068faf62c32a36dd62dc4db642c2027bbbd960f8f6345b59e9d4dc472", - "sha256:8679bb1dd723ecbea03b1f96c98972815775fd8ec756c440a14f289c436c472e", - "sha256:86fb683cb9a9c0bb7476988b7957393ecdd22777d87d804442c66e62c99197f9", - "sha256:8757c62f7960cd26122f7aaaf86eda1e016fa85734c3777b8054dd334d7dea4d", - "sha256:894be7d17228e7328cc188096c0162697211ec91761f6812fff12790cbe11c66", - "sha256:8a0f240bf43c29be1bd82d77e602a61c798e9de02e5f8bb7bb414cb814f43236", - "sha256:8c3abf7eab5b76ae162c4fbb16d514a947fc57fd995b64e5ea8ef8ba3b888a69", - "sha256:93332c6972e4c91522c4810e907f3aea067424338071161b39cacded022559df", - "sha256:97d6c676dc97d593625d9fc48154f2ffeabb619a1e6fe8d2a5b53f97e3e9bdee", - "sha256:99dd85f0ca1db8d17a01a25c2bbb7784d25a2d39497c6beddbe96bff74194e04", - "sha256:9c7fb691fb07ec7ab99fd173bb0e7e0248d31bf83d484a87b917a342f63812c9", - "sha256:b3bc3cf200aab74f3d758586ac50295214eda496ac6a6636e0c881c5958d9123", - "sha256:bba54f97578943f48f621b4a7afb8eb022370da26a88b88ccc9fee9f3ef7ce45", - "sha256:bd2a13a0f8367e50347cbac87ae230ae1953935443240238f956bf10668bead6", - "sha256:cbc1184349ca6e5112898aa7fc3efa1b1bbae24ab1edc774cfd09cbfd3b091d7", - "sha256:cd82cca9c489e441574804dbda2dd8e114cf3be7935b03de11dade2c9478aea6", - "sha256:ce8ba5ed8b0a7a203922d61cff45ee6001a41a9359f04f00d055a4e988755569", - "sha256:cfee22e072a382b92ee0709dbb8203dabd52d54258051e770d9d2a81b162530b", - "sha256:d977df6f7c4109ed1d96ffb6795f6af77114be606ae4556efbfc9cac725db65d", - "sha256:da72a384a1d7e87490ca71182f3ab469ed21d847adc16b70c34faac5a3b12801", - "sha256:ddf4ad1d651e6c9234945061e1a31fe27a4be0dea21c498b87b186fadf8f5919", - "sha256:eb0ae5dfda83bbce660179d7b41c1c38fd833a54d2e6d9b258c644f3b75ef94d", - "sha256:f4c7d370badc60ac94a554bc571a46d03e39d8aacfba8006b334512e184aed59", - "sha256:f6c378b435a26fda8996579c0e324b108d2ca0d01b4661503a75634e5155559f", - "sha256:f6c9d30888503f2f5f87d6d41f016301352dd98da4a861bd10663c3a2d99d3b5", - "sha256:fab8a7877275060f7b303e1f91c218069a2814a616b6a5ee2d8a3737deb15915", - "sha256:fc32e7d7f98cac3d8d5153ed2cb583158ae3d446a6efb8e28ccb1c54a09f4169" - ], - "index": "pypi", - "version": "==23.1.0" + "sha256:004a431dfa0459123e6f4660d7e3c4ac19217d134ca38bacfffb2e78716fe944", + "sha256:057b154471e096e2dda147f7b057041acc303bb7ca4aa24c3b88c6cecdd78717", + "sha256:0e08671dc202a1880fa522f921f35ca5925ba30da8bc96228d74a8f0643ead9c", + "sha256:1b2a21f595f8cc549abd6c8de1fcd34c83441e35fb24b8a59bf161889c62a486", + "sha256:21552624ce69e69f7924f413b802b1fb554f4c0497f837810e429faa1cd4f163", + "sha256:22ac0243a41798e3eb5d5714b28c2f28e3d10792dffbc8a5fca092f975fdeceb", + "sha256:2b054525c9f7e240562185bf21671ca16d56bde92e9bd0f822c07dec7626b704", + "sha256:30c365e60c39c53f8eea042b37ea28304ffa6558fb7241cf278745095a5757da", + "sha256:3a4d87342c2737fbb9eee5c33c792db27b36b04957b4e6b7edd73a5b239a2a13", + "sha256:420b9abd1a7330687a095373b8280a20cdee04342fbc8ccb3b56d9ec8efd4e62", + "sha256:444f7d615d5f686d0ef508b9edfa8a286e6d89f449a1ba37b60ef69d869220a3", + "sha256:558f5f636e3e65f261b64925e8b190e8689e334911595394572cc7523879006d", + "sha256:5592fb4316f895922b1cacb91b04a0fa09d6f6f19bbab4442b4d0a0825177b93", + "sha256:59928dfebe93cf1e203e3cb0fd5d5dd384da56b99c8305f2e1b0a933751710f6", + "sha256:5cb642e94337b0c76c9c8cb9bfb0f8a78654575847d080d3e1504f312d691fc3", + "sha256:5d57542429df6acff02ff022067aa75b677603cee70e3abb9742787545eec966", + "sha256:5d92e7cbeab7f70b08cc0f27255b0bb2500afc30f31075bca0b1cb87735d186c", + "sha256:602835e5672ca9ca1d78e6c148fb28c4f91b748ebc41fbd2f479d8763d58bc9b", + "sha256:60746a7e8558655420a69441c0a1d47ed225ed3ac355920b96a96d0554ef7e6b", + "sha256:61b97f624da42813f74977425a3a6144d604ea21cf065616d36ea3a866d92c1c", + "sha256:693c96ae4d975eb8efa1639670e9b1fac0c3f98b7845b65c0f369141fb4bb21f", + "sha256:814e5aaf0c3be9991a59066eafb2d6e117aed6b413e3e7e9be45d4e55f5e2748", + "sha256:83005d8928f8a5cebcfb33af3bfb84b1ad65d882b899141a331cc5d07d89f093", + "sha256:831da96ba3f36cc892f0afbb4fb89b28b61b387261676e55d55a682addbd29f7", + "sha256:8355744fdbdeac5cfadfa4f38b82029b5f2b8cab7472a33453a217a7f3a9dce2", + "sha256:8496a2a5efd055c61ac2c6a18116c768a25c644b6747dcfde43e91620ab3453c", + "sha256:859059caf564f0c9398c9005278055ed3d37af4d73de6b1597821193b04ca09b", + "sha256:8c0f4d6f8c985bab83792be26ff3233940ba42e22237610ac50cbcfc10a5c235", + "sha256:8c2d8b69a2bf239ae3d987537bf3fbc2b044a405394cf4c258fc684971dd48b2", + "sha256:984b232802eddf9f0be264a4d57a10b3a1fd7319df14ee6fc7b41c6d155a3e6c", + "sha256:99cedf38eaddf263cf7e2a50e405f12c02cedf6d9df00a0d9c5d7b9417b57f76", + "sha256:a3dc339f7bc185d5fd0fd976242a5baf35de404d467e056484def8a4dd95868b", + "sha256:a51f12a8719aad9dcfb55d456022f16b90abc8dde7d3ca93ce3120b40e3fa169", + "sha256:bbabd1df23bf63ae829e81200034c0e433499275a6ed29ca1a912ea7629426d9", + "sha256:bcc6953e47bcfc9028ddf9ab2a321a3c51d7cc969db65edec092019bb837959f", + "sha256:c0a5f987d73fd9b46c3d180891f829afda714ab6bab30a1218724d4a0a63afd8", + "sha256:c223a13555444707a0a7ebc6f9ee63053147c8c082bd1a31fd1207a03e8b0500", + "sha256:c616893a577e9d6773a3836732fd7e2a729157a108b8fccd31c87512fa01671a", + "sha256:c882f1d4f96fbd807e92c334251d8ebd159a1ef89059ccd386ddea83fdb91bd8", + "sha256:c8dec8a2f3f0bb462e6439df436cd8c7ec37968e90b4209ac621e7fbc0ed3b00", + "sha256:c9638e0057e3f1a8b7c5ce33c7575349d9183a033a19b5676ad55096ae36820b", + "sha256:ce4f71e17fa849de41a06109030d3f6815fcc33338bf98dd0dde6d456d33c929", + "sha256:ced12075cdf3c7332ecc1960f77f7439d5ebb8ea20bbd3c34c8299e694f1b0a1", + "sha256:d11628212fd731b8986f1561d9bb3f8c38d9c15b330c3d8a88963519fbcd553b", + "sha256:d1610260cc672975723fcf7705c69a95f3b88802a594c9867781bedd9b13422c", + "sha256:d4651de7316ec8560afe430fb042c0782ed8ac54c0be43a515944d7c78fddac8", + "sha256:da338e2728410d74ddeb1479ec67cfba73311607037455a40f92b6f5c62bf11d", + "sha256:de727ea906033b30527b4a99498f19aca3f4d1073230a958679a5b726e2784e0", + "sha256:e2e2db5c6ef376e97c912733dfc24406f5949474d03e800d5f07b6aca4d870af", + "sha256:e669913cb2179507628419ec4f0e453e48ce6f924de5884d396f18c31836089c", + "sha256:eb4a573a8499685d62545e806d8fd143c84ac8b3439f925cd92c8763f0ed9bd7", + "sha256:f146648941cadaaaf01254a75651a23c08159d009d36c5af42a7cc200a5e53ec", + "sha256:f3ff6abde52e702397949054cb5b06c1c75b5d6542f6a2ce029e46f71ffbbbf2", + "sha256:f5aa9da520e4bb8cee8189f2f541701405e7690745094ded7a37b425d60527ea", + "sha256:f5fdb00d65ec44b10cc6b9b6318ef1363b81647a4aa3270ca39565eadb2d1201", + "sha256:f685003d836ad0e5d4f08d1e024ee3ac7816eb2f873b2266306eef858f058133", + "sha256:fee86542dc4ee8229e023003e3939b4d58cc2453922cf127778b69505fc9064b" + ], + "index": "pypi", + "version": "==23.2.0" }, "requests": { "hashes": [ - "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f", - "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b" + "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", + "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], "index": "pypi", - "version": "==2.28.0" + "version": "==2.28.1" }, "scons": { "hashes": [ @@ -1094,11 +1077,11 @@ }, "sentry-sdk": { "hashes": [ - "sha256:259535ba66933eacf85ab46524188c84dcb4c39f40348455ce15e2c0aca68863", - "sha256:778b53f0a6c83b1ee43d3b7886318ba86d975e686cb2c7906ccc35b334360be1" + "sha256:b82ad57306d5546713f15d5d70daea0408cf7f998c7566db16e0e6257e51e561", + "sha256:ddbd191b6f4e696b7845b4d87389898ae1207981faf114f968a57363aa6be03c" ], "index": "pypi", - "version": "==1.5.12" + "version": "==1.6.0" }, "setproctitle": { "hashes": [ @@ -1179,11 +1162,11 @@ }, "setuptools": { "hashes": [ - "sha256:5a844ad6e190dccc67d6d7411d119c5152ce01f7c76be4d8a1eaa314501bba77", - "sha256:bf8a748ac98b09d32c9a64a995a6b25921c96cc5743c1efa82763ba80ff54e91" + "sha256:16923d366ced322712c71ccb97164d07472abeecd13f3a6c283f6d5d26722793", + "sha256:db3b8e2f922b2a910a29804776c643ea609badb6a32c4bcc226fd4fd902cce65" ], "markers": "python_version >= '3.7'", - "version": "==62.4.0" + "version": "==63.1.0" }, "six": { "hashes": [ @@ -1227,11 +1210,11 @@ }, "tomlkit": { "hashes": [ - "sha256:0f4050db66fd445b885778900ce4dd9aea8c90c4721141fde0d6ade893820ef1", - "sha256:71ceb10c0eefd8b8f11fe34e8a51ad07812cb1dc3de23247425fbc9ddc47b9dd" + "sha256:1c5bebdf19d5051e2e1de6cf70adfc5948d47221f097fcff7a3ffc91e953eaf5", + "sha256:61901f81ff4017951119cd0d1ed9b7af31c821d6845c8c477587bbdcd5e5854e" ], - "markers": "python_version >= '3.6' and python_version < '4.0'", - "version": "==0.11.0" + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==0.11.1" }, "tqdm": { "hashes": [ @@ -1243,19 +1226,19 @@ }, "typing-extensions": { "hashes": [ - "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708", - "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376" + "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", + "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" ], - "markers": "python_version < '3.10'", - "version": "==4.2.0" + "markers": "python_version >= '3.7'", + "version": "==4.3.0" }, "urllib3": { "hashes": [ - "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", - "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" + "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec", + "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6" ], "index": "pypi", - "version": "==1.26.9" + "version": "==1.26.10" }, "utm": { "hashes": [ @@ -1266,11 +1249,11 @@ }, "websocket-client": { "hashes": [ - "sha256:50b21db0058f7a953d67cc0445be4b948d7fc196ecbeb8083d68d94628e4abf6", - "sha256:722b171be00f2b90e1d4fb2f2b53146a536ca38db1da8ff49c972a4e1365d0ef" + "sha256:5d55652dc1d0b3c734f044337d929aaf83f4f9138816ec680c1aefefb4dc4877", + "sha256:d58c5f284d6a9bf8379dab423259fe8f85b70d5fa5d2916d5791a84594b122b1" ], "index": "pypi", - "version": "==1.3.2" + "version": "==1.3.3" }, "werkzeug": { "hashes": [ @@ -1443,11 +1426,11 @@ }, "babel": { "hashes": [ - "sha256:7aed055f0c04c9e7f51a2f75261e41e1c804efa724cb65b60a970dd4448d469d", - "sha256:81a3beca4d0cd40a9cfb9e2adb2cf39261c2f959b92e7a74750befe5d79afd7b" + "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51", + "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb" ], "markers": "python_version >= '3.6'", - "version": "==2.10.2" + "version": "==2.10.3" }, "bcrypt": { "hashes": [ @@ -1468,11 +1451,11 @@ }, "breathe": { "hashes": [ - "sha256:553aeffb00efc2cf96c4c9ed388d6ee8036ecd6d1bd9bd0c656fc25ca271bd3c", - "sha256:c4b9ff4d5298fd91518d336ede28b6a2d8cacc685d0eae17eb20e760e06bb904" + "sha256:48804dcf0e607a89fb6ad88c729ef12743a42db03ae9489be4ef8f7c4011774a", + "sha256:ac0768a5e84addad3e632028fe67749c567aba2b29088493b64c2c1634bcdba1" ], "index": "pypi", - "version": "==4.33.1" + "version": "==4.34.0" }, "carla": { "hashes": [ @@ -1490,67 +1473,81 @@ }, "certifi": { "hashes": [ - "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7", - "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a" + "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", + "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" ], "markers": "python_version >= '3.6'", - "version": "==2022.5.18.1" + "version": "==2022.6.15" }, "cffi": { "hashes": [ - "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", - "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", - "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", - "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", - "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", - "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", - "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", - "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", - "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", - "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", - "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", - "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", - "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", - "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", - "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", - "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", - "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", - "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", - "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", - "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", - "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", - "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", - "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", - "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", - "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", - "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", - "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", - "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", - "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", - "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", - "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", - "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", - "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", - "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", - "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", - "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", - "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", - "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", - "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", - "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", - "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", - "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", - "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", - "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", - "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", - "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", - "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", - "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", - "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", - "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" - ], - "index": "pypi", - "version": "==1.15.0" + "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", + "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", + "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", + "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", + "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", + "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", + "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", + "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", + "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", + "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", + "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", + "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", + "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", + "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", + "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", + "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", + "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", + "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", + "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", + "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", + "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", + "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", + "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", + "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", + "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", + "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", + "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", + "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", + "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", + "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", + "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", + "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", + "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", + "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", + "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", + "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", + "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", + "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", + "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", + "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", + "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", + "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", + "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", + "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", + "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", + "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", + "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", + "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", + "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", + "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", + "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", + "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", + "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", + "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", + "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", + "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", + "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", + "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", + "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", + "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", + "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", + "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", + "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", + "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" + ], + "index": "pypi", + "version": "==1.15.1" }, "cfgv": { "hashes": [ @@ -1562,11 +1559,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", - "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", + "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" ], - "markers": "python_full_version >= '3.5.0'", - "version": "==2.0.12" + "markers": "python_version >= '3.6'", + "version": "==2.1.0" }, "control": { "hashes": [ @@ -1624,31 +1621,31 @@ }, "cryptography": { "hashes": [ - "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804", - "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178", - "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717", - "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982", - "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004", - "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe", - "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452", - "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336", - "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4", - "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15", - "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d", - "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c", - "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0", - "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06", - "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9", - "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1", - "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023", - "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de", - "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f", - "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181", - "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e", - "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a" - ], - "index": "pypi", - "version": "==37.0.2" + "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", + "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", + "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", + "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", + "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", + "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", + "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", + "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", + "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", + "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", + "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", + "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", + "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", + "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", + "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", + "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", + "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", + "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", + "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", + "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", + "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", + "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" + ], + "index": "pypi", + "version": "==37.0.4" }, "cycler": { "hashes": [ @@ -1742,11 +1739,11 @@ }, "fonttools": { "hashes": [ - "sha256:c0fdcfa8ceebd7c1b2021240bd46ef77aa8e7408cf10434be55df52384865f8e", - "sha256:f829c579a8678fa939a1d9e9894d01941db869de44390adb49ce67055a06cc2a" + "sha256:9a1c52488045cd6c6491fd07711a380f932466e317cb8e016fc4e99dc7eac2f0", + "sha256:d73f25b283cd8033367451122aa868a23de0734757a01984e4b30b18b9050c72" ], "markers": "python_version >= '3.7'", - "version": "==4.33.3" + "version": "==4.34.4" }, "ft4222": { "hashes": [ @@ -1805,24 +1802,24 @@ "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" ], - "markers": "python_full_version >= '3.5.0'", + "markers": "python_version >= '3.5'", "version": "==3.3" }, "imagesize": { "hashes": [ - "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c", - "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d" + "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", + "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.3.0" + "version": "==1.4.1" }, "importlib-metadata": { "hashes": [ - "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700", - "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec" + "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670", + "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" ], "markers": "python_version < '3.10'", - "version": "==4.11.4" + "version": "==4.12.0" }, "iniconfig": { "hashes": [ @@ -2076,38 +2073,39 @@ }, "nodeenv": { "hashes": [ - "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b", - "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7" + "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e", + "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b" ], - "version": "==1.6.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==1.7.0" }, "numpy": { "hashes": [ - "sha256:0791fbd1e43bf74b3502133207e378901272f3c156c4df4954cad833b1380207", - "sha256:1ce7ab2053e36c0a71e7a13a7475bd3b1f54750b4b433adc96313e127b870887", - "sha256:2d487e06ecbf1dc2f18e7efce82ded4f705f4bd0cd02677ffccfb39e5c284c7e", - "sha256:37431a77ceb9307c28382c9773da9f306435135fae6b80b62a11c53cfedd8802", - "sha256:3e1ffa4748168e1cc8d3cde93f006fe92b5421396221a02f2274aab6ac83b077", - "sha256:425b390e4619f58d8526b3dcf656dde069133ae5c240229821f01b5f44ea07af", - "sha256:43a8ca7391b626b4c4fe20aefe79fec683279e31e7c79716863b4b25021e0e74", - "sha256:4c6036521f11a731ce0648f10c18ae66d7143865f19f7299943c985cdc95afb5", - "sha256:59d55e634968b8f77d3fd674a3cf0b96e85147cd6556ec64ade018f27e9479e1", - "sha256:64f56fc53a2d18b1924abd15745e30d82a5782b2cab3429aceecc6875bd5add0", - "sha256:7228ad13744f63575b3a972d7ee4fd61815b2879998e70930d4ccf9ec721dce0", - "sha256:9ce7df0abeabe7fbd8ccbf343dc0db72f68549856b863ae3dd580255d009648e", - "sha256:a911e317e8c826ea632205e63ed8507e0dc877dcdc49744584dfc363df9ca08c", - "sha256:b89bf9b94b3d624e7bb480344e91f68c1c6c75f026ed6755955117de00917a7c", - "sha256:ba9ead61dfb5d971d77b6c131a9dbee62294a932bf6a356e48c75ae684e635b3", - "sha256:c1d937820db6e43bec43e8d016b9b3165dcb42892ea9f106c70fb13d430ffe72", - "sha256:cc7f00008eb7d3f2489fca6f334ec19ca63e31371be28fd5dad955b16ec285bd", - "sha256:d4c5d5eb2ec8da0b4f50c9a843393971f31f1d60be87e0fb0917a49133d257d6", - "sha256:e96d7f3096a36c8754207ab89d4b3282ba7b49ea140e4973591852c77d09eb76", - "sha256:f0725df166cf4785c0bc4cbfb320203182b1ecd30fee6e541c8752a92df6aa32", - "sha256:f3eb268dbd5cfaffd9448113539e44e2dd1c5ca9ce25576f7c04a5453edc26fa", - "sha256:fb7a980c81dd932381f8228a426df8aeb70d59bbcda2af075b627bbc50207cba" - ], - "index": "pypi", - "version": "==1.22.4" + "sha256:092f5e6025813e64ad6d1b52b519165d08c730d099c114a9247c9bb635a2a450", + "sha256:196cd074c3f97c4121601790955f915187736f9cf458d3ee1f1b46aff2b1ade0", + "sha256:1c29b44905af288b3919803aceb6ec7fec77406d8b08aaa2e8b9e63d0fe2f160", + "sha256:2b2da66582f3a69c8ce25ed7921dcd8010d05e59ac8d89d126a299be60421171", + "sha256:5043bcd71fcc458dfb8a0fc5509bbc979da0131b9d08e3d5f50fb0bbb36f169a", + "sha256:58bfd40eb478f54ff7a5710dd61c8097e169bc36cc68333d00a9bcd8def53b38", + "sha256:79a506cacf2be3a74ead5467aee97b81fca00c9c4c8b3ba16dbab488cd99ba10", + "sha256:94b170b4fa0168cd6be4becf37cb5b127bd12a795123984385b8cd4aca9857e5", + "sha256:97a76604d9b0e79f59baeca16593c711fddb44936e40310f78bfef79ee9a835f", + "sha256:98e8e0d8d69ff4d3fa63e6c61e8cfe2d03c29b16b58dbef1f9baa175bbed7860", + "sha256:ac86f407873b952679f5f9e6c0612687e51547af0e14ddea1eedfcb22466babd", + "sha256:ae8adff4172692ce56233db04b7ce5792186f179c415c37d539c25de7298d25d", + "sha256:bd3fa4fe2e38533d5336e1272fc4e765cabbbde144309ccee8675509d5cd7b05", + "sha256:d0d2094e8f4d760500394d77b383a1b06d3663e8892cdf5df3c592f55f3bff66", + "sha256:d54b3b828d618a19779a84c3ad952e96e2c2311b16384e973e671aa5be1f6187", + "sha256:d6ca8dabe696c2785d0c8c9b0d8a9b6e5fdbe4f922bde70d57fa1a2848134f95", + "sha256:d8cc87bed09de55477dba9da370c1679bd534df9baa171dd01accbb09687dac3", + "sha256:f0f18804df7370571fb65db9b98bf1378172bd4e962482b857e612d1fec0f53e", + "sha256:f1d88ef79e0a7fa631bb2c3dda1ea46b32b1fe614e10fedd611d3d5398447f2f", + "sha256:f9c3fc2adf67762c9fe1849c859942d23f8d3e0bee7b5ed3d4a9c3eeb50a2f07", + "sha256:fc431493df245f3c627c0c05c2bd134535e7929dbe2e602b80e42bf52ff760bc", + "sha256:fe8b9683eb26d2c4d5db32cd29b38fdcf8381324ab48313b5b69088e0e355379" + ], + "index": "pypi", + "version": "==1.23.0" }, "opencv-python-headless": { "hashes": [ @@ -2130,6 +2128,33 @@ "markers": "python_version >= '3.6'", "version": "==21.3" }, + "pandas": { + "hashes": [ + "sha256:07238a58d7cbc8a004855ade7b75bbd22c0db4b0ffccc721556bab8a095515f6", + "sha256:0daf876dba6c622154b2e6741f29e87161f844e64f84801554f879d27ba63c0d", + "sha256:16ad23db55efcc93fa878f7837267973b61ea85d244fc5ff0ccbcfa5638706c5", + "sha256:1d9382f72a4f0e93909feece6fef5500e838ce1c355a581b3d8f259839f2ea76", + "sha256:24ea75f47bbd5574675dae21d51779a4948715416413b30614c1e8b480909f81", + "sha256:2893e923472a5e090c2d5e8db83e8f907364ec048572084c7d10ef93546be6d1", + "sha256:2ff7788468e75917574f080cd4681b27e1a7bf36461fe968b49a87b5a54d007c", + "sha256:41fc406e374590a3d492325b889a2686b31e7a7780bec83db2512988550dadbf", + "sha256:48350592665ea3cbcd07efc8c12ff12d89be09cd47231c7925e3b8afada9d50d", + "sha256:605d572126eb4ab2eadf5c59d5d69f0608df2bf7bcad5c5880a47a20a0699e3e", + "sha256:6dfbf16b1ea4f4d0ee11084d9c026340514d1d30270eaa82a9f1297b6c8ecbf0", + "sha256:6f803320c9da732cc79210d7e8cc5c8019aad512589c910c66529eb1b1818230", + "sha256:721a3dd2f06ef942f83a819c0f3f6a648b2830b191a72bbe9451bcd49c3bd42e", + "sha256:755679c49460bd0d2f837ab99f0a26948e68fa0718b7e42afbabd074d945bf84", + "sha256:78b00429161ccb0da252229bcda8010b445c4bf924e721265bec5a6e96a92e92", + "sha256:958a0588149190c22cdebbc0797e01972950c927a11a900fe6c2296f207b1d6f", + "sha256:a3924692160e3d847e18702bb048dc38e0e13411d2b503fecb1adf0fcf950ba4", + "sha256:d51674ed8e2551ef7773820ef5dab9322be0828629f2cbf8d1fc31a0c4fed640", + "sha256:d5ebc990bd34f4ac3c73a2724c2dcc9ee7bf1ce6cf08e87bb25c6ad33507e318", + "sha256:d6c0106415ff1a10c326c49bc5dd9ea8b9897a6ca0c8688eb9c30ddec49535ef", + "sha256:e48fbb64165cda451c06a0f9e4c7a16b534fcabd32546d531b3c240ce2844112" + ], + "index": "pypi", + "version": "==1.4.3" + }, "parameterized": { "hashes": [ "sha256:41bbff37d6186430f77f900d777e5bb6a24928a1c46fb1de692f8b52b8833b5c", @@ -2148,47 +2173,67 @@ }, "pillow": { "hashes": [ - "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f", - "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d", - "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b", - "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c", - "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9", - "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546", - "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578", - "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1", - "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe", - "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098", - "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2", - "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a", - "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45", - "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530", - "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108", - "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1", - "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd", - "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0", - "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6", - "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c", - "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf", - "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4", - "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d", - "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765", - "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602", - "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340", - "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c", - "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b", - "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84", - "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8", - "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92", - "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54", - "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601", - "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a", - "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf", - "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251", - "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a", - "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e" - ], - "index": "pypi", - "version": "==9.1.1" + "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927", + "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14", + "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc", + "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58", + "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60", + "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76", + "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c", + "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac", + "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490", + "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1", + "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f", + "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d", + "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f", + "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069", + "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402", + "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885", + "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e", + "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be", + "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8", + "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff", + "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da", + "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004", + "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f", + "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20", + "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d", + "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c", + "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544", + "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9", + "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3", + "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04", + "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c", + "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5", + "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4", + "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb", + "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4", + "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c", + "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467", + "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e", + "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421", + "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b", + "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8", + "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb", + "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3", + "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf", + "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1", + "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a", + "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28", + "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0", + "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1", + "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8", + "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd", + "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4", + "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8", + "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f", + "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013", + "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59", + "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc", + "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4" + ], + "index": "pypi", + "version": "==9.2.0" }, "platformdirs": { "hashes": [ @@ -2426,11 +2471,11 @@ }, "requests": { "hashes": [ - "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f", - "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b" + "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", + "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], "index": "pypi", - "version": "==2.28.0" + "version": "==2.28.1" }, "reverse-geocoder": { "hashes": [ @@ -2468,6 +2513,14 @@ "index": "pypi", "version": "==1.8.1" }, + "setuptools": { + "hashes": [ + "sha256:16923d366ced322712c71ccb97164d07472abeecd13f3a6c283f6d5d26722793", + "sha256:db3b8e2f922b2a910a29804776c643ea609badb6a32c4bcc226fd4fd902cce65" + ], + "markers": "python_version >= '3.7'", + "version": "==63.1.0" + }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", @@ -2492,11 +2545,11 @@ }, "sphinx": { "hashes": [ - "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6", - "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226" + "sha256:b18e978ea7565720f26019c702cd85c84376e948370f1cd43d60265010e1c7b0", + "sha256:d3e57663eed1d7c5c50895d191fdeda0b54ded6f44d5621b50709466c338d1e8" ], "index": "pypi", - "version": "==4.5.0" + "version": "==5.0.2" }, "sphinx-rtd-theme": { "hashes": [ @@ -2570,6 +2623,15 @@ "index": "pypi", "version": "==3.5.4" }, + "tabulate": { + "hashes": [ + "sha256:0ba055423dbaa164b9e456abe7920c5e8ed33fcc16f6d1b2f2d152c8e1e8b4fc", + "sha256:436f1c768b424654fce8597290d2764def1eea6a77cfa5c33be00b1bc0f4f63d", + "sha256:6c57f3f3dd7ac2782770155f3adb2db0b1a269637e42f27599925e64b114f519" + ], + "index": "pypi", + "version": "==0.8.10" + }, "tenacity": { "hashes": [ "sha256:43242a20e3e73291a28bcbcacfd6e000b02d3857a9a9fff56b297a27afdc932f", @@ -2596,27 +2658,27 @@ }, "typing-extensions": { "hashes": [ - "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708", - "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376" + "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", + "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" ], - "markers": "python_version < '3.10'", - "version": "==4.2.0" + "markers": "python_version >= '3.7'", + "version": "==4.3.0" }, "urllib3": { "hashes": [ - "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", - "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" + "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec", + "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6" ], "index": "pypi", - "version": "==1.26.9" + "version": "==1.26.10" }, "virtualenv": { "hashes": [ - "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a", - "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5" + "sha256:288171134a2ff3bfb1a2f54f119e77cd1b81c29fc1265a2356f3e8d14c7d58c4", + "sha256:b30aefac647e86af6d82bfc944c556f8f1a9c90427b2fb4e3bfbf338cb82becf" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==20.14.1" + "version": "==20.15.1" }, "zipp": { "hashes": [ diff --git a/README.md b/README.md index c426b839d..94837575b 100755 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Table of Contents ======================= * [What is openpilot?](#what-is-openpilot) -* [Running in a car](#running-in-a-car) +* [Running in a car](#running-on-a-dedicated-device-in-a-car) * [Running on PC](#running-on-pc) * [Community and Contributing](#community-and-contributing) * [User Data and comma Account](#user-data-and-comma-account) @@ -105,21 +105,24 @@ Directory Structure ├── third_party # External libraries ├── pyextra # Extra python packages └── system # Generic services + ├── camerad # Driver to capture images from the camera sensors + ├── clocksd # Broadcasts current time + ├── hardware # Hardware abstraction classes ├── logcatd # systemd journal as a service └── proclogd # Logs information from /proc └── selfdrive # Code needed to drive the car ├── assets # Fonts, images, and sounds for UI ├── athena # Allows communication with the app ├── boardd # Daemon to talk to the board - ├── camerad # Driver to capture images from the camera sensors ├── car # Car specific code to read states and control actuators - ├── common # Shared C/C++ code for the daemons ├── controls # Planning and controls ├── debug # Tools to help you debug and do car ports ├── locationd # Precise localization and vehicle parameter estimation ├── loggerd # Logger and uploader of car data + ├── manager # Deamon that starts/stops all other daemons as needed ├── modeld # Driving and monitoring model runners - ├── proclogd # Logs information from proc + ├── monitoring # Daemon to determine driver attention + ├── navd # Turn-by-turn navigation ├── sensord # IMU interface code ├── test # Unit tests, system tests, and a car simulator └── ui # The UI diff --git a/RELEASES.md b/RELEASES.md index 7f365e5ce..bcd6aa7aa 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,20 +1,44 @@ -Version 0.8.15 (2022-XX-XX) +Version 0.8.16 (2022-XX-XX) +======================== +* Chevrolet Bolt EUV 2022-23 support thanks to JasonJShuler! +* Hyundai Ioniq 5 2022 support thanks to sunnyhaibin! +* Hyundai Kona Electric 2022 support thanks to sunnyhaibin! +* Subaru Outback 2020-22 support + +Version 0.8.15 (2022-07-20) ======================== * New driving model + * Path planning uses end-to-end output instead of lane lines at all times + * Reduced ping pong + * Improved lane centering * New lateral controller based on physical wheel torque model - * Much smoother control, consistent across the speed range + * Much smoother control that's consistent across the speed range * Effective feedforward that uses road roll * Simplified tuning, all car-specific parameters can be derived from data + * Used on select Toyota and Hyundai models at first * Significantly improved control on TSS-P Prius * New driver monitoring model - * takes a larger input frame - * outputs a driver state for both driver and passenger - * automatically determines which side the driver is on (soon) -* Display speed limit while navigating + * Bigger model, covering full interior view from driver camera + * Works with a wider variety of mounting angles + * 3x more unique comma three training data than previous +* Navigation improvements + * Speed limits shown while navigating + * Faster position fix by using raw GPS measurements +* UI updates + * Multilanguage support for settings and home screen + * New font + * Refreshed max speed design + * More consistent camera view perspective across cars * Reduced power usage: device runs cooler and fan spins less * AGNOS 5 + * Support VSCode remote SSH target + * Support for delta updates to reduce data usage on future OS updates +* Chrysler ECU firmware fingerprinting thanks to realfast! +* Honda Civic 2022 support * Hyundai Tucson 2021 support thanks to bluesforte! +* Kia EV6 2022 support * Lexus NX Hybrid 2020 support thanks to AlexandreSato! +* Ram 1500 2019-21 support thanks to realfast! Version 0.8.14 (2022-06-01) ======================== diff --git a/SConstruct b/SConstruct index b5c4edc99..49685b19b 100644 --- a/SConstruct +++ b/SConstruct @@ -94,7 +94,7 @@ if arch == "larch64": "/usr/lib/aarch64-linux-gnu" ] cpppath += [ - "#selfdrive/camerad/include", + "#system/camerad/include", ] cflags = ["-DQCOM2", "-mcpu=cortex-a57"] cxxflags = ["-DQCOM2", "-mcpu=cortex-a57"] @@ -356,22 +356,27 @@ Export('cereal', 'messaging', 'visionipc') # Build rednose library and ekf models +rednose_deps = [ + "#selfdrive/locationd/models/constants.py", + "#selfdrive/locationd/models/gnss_helpers.py", +] + rednose_config = { 'generated_folder': '#selfdrive/locationd/models/generated', 'to_build': { - 'gnss': ('#selfdrive/locationd/models/gnss_kf.py', True, []), - 'live': ('#selfdrive/locationd/models/live_kf.py', True, ['live_kf_constants.h']), - 'car': ('#selfdrive/locationd/models/car_kf.py', True, []), + 'gnss': ('#selfdrive/locationd/models/gnss_kf.py', True, [], rednose_deps), + 'live': ('#selfdrive/locationd/models/live_kf.py', True, ['live_kf_constants.h'], rednose_deps), + 'car': ('#selfdrive/locationd/models/car_kf.py', True, [], rednose_deps), }, } if arch != "larch64": rednose_config['to_build'].update({ - 'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, []), - 'pos_computer_4': ('#rednose/helpers/lst_sq_computer.py', False, []), - 'pos_computer_5': ('#rednose/helpers/lst_sq_computer.py', False, []), - 'feature_handler_5': ('#rednose/helpers/feature_handler.py', False, []), - 'lane': ('#xx/pipeline/lib/ekf/lane_kf.py', True, []), + 'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, [], rednose_deps), + 'pos_computer_4': ('#rednose/helpers/lst_sq_computer.py', False, [], []), + 'pos_computer_5': ('#rednose/helpers/lst_sq_computer.py', False, [], []), + 'feature_handler_5': ('#rednose/helpers/feature_handler.py', False, [], []), + 'lane': ('#xx/pipeline/lib/ekf/lane_kf.py', True, [], rednose_deps), }) Export('rednose_config') @@ -379,6 +384,7 @@ SConscript(['rednose/SConscript']) # Build system services SConscript([ + 'system/camerad/SConscript', 'system/clocksd/SConscript', 'system/proclogd/SConscript', ]) @@ -387,16 +393,19 @@ if arch != "Darwin": # Build openpilot -SConscript(['cereal/SConscript']) -SConscript(['panda/board/SConscript']) -SConscript(['opendbc/can/SConscript']) +# build submodules +SConscript([ + 'cereal/SConscript', + 'body/board/SConscript', + 'panda/board/SConscript', + 'opendbc/can/SConscript', +]) SConscript(['third_party/SConscript']) SConscript(['common/kalman/SConscript']) SConscript(['common/transformations/SConscript']) -SConscript(['selfdrive/camerad/SConscript']) SConscript(['selfdrive/modeld/SConscript']) SConscript(['selfdrive/controls/lib/cluster/SConscript']) @@ -410,6 +419,9 @@ SConscript(['selfdrive/loggerd/SConscript']) SConscript(['selfdrive/locationd/SConscript']) SConscript(['selfdrive/sensord/SConscript']) SConscript(['selfdrive/ui/SConscript']) +SConscript(['selfdrive/navd/SConscript']) + +SConscript(['tools/replay/SConscript']) if GetOption('test'): SConscript('panda/tests/safety/SConscript') diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..8b66082bf --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Suspected vulnerabilities can be reported to both `adeeb@comma.ai` and `security@comma.ai`. diff --git a/body b/body new file mode 160000 index 000000000..04aeb30ce --- /dev/null +++ b/body @@ -0,0 +1 @@ +Subproject commit 04aeb30ce0bb14759989cd374158233877e1e151 diff --git a/cereal b/cereal index 6faf34064..5ab197001 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 6faf34064b70ab98c241d4a1cd9006b09ecaadfc +Subproject commit 5ab19700178f01c14f362735532ee2662ab755fc diff --git a/common/modeldata.h b/common/modeldata.h index aee9fdfd8..e13840d53 100644 --- a/common/modeldata.h +++ b/common/modeldata.h @@ -24,12 +24,6 @@ constexpr auto T_IDXS_FLOAT = build_idxs(10.0); constexpr auto X_IDXS = build_idxs(192.0); constexpr auto X_IDXS_FLOAT = build_idxs(192.0); -namespace tici_dm_crop { - const int x_offset = -72; - const int y_offset = -144; - const int width = 954; -}; - const mat3 fcam_intrinsic_matrix = (mat3){{2648.0, 0.0, 1928.0 / 2, 0.0, 2648.0, 1208.0 / 2, 0.0, 0.0, 1.0}}; @@ -40,12 +34,13 @@ const mat3 ecam_intrinsic_matrix = (mat3){{567.0, 0.0, 1928.0 / 2, 0.0, 567.0, 1208.0 / 2, 0.0, 0.0, 1.0}}; -static inline mat3 get_model_yuv_transform(bool bayer = true) { +static inline mat3 get_model_yuv_transform() { float db_s = 1.0; const mat3 transform = (mat3){{ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }}; - return bayer ? transform_scale_buffer(transform, db_s) : transform; + // Can this be removed since scale is 1? + return transform_scale_buffer(transform, db_s); } diff --git a/common/params.cc b/common/params.cc index 8cf1baa6c..11b24fe9c 100644 --- a/common/params.cc +++ b/common/params.cc @@ -94,6 +94,7 @@ std::unordered_map keys = { {"CompletedTrainingVersion", PERSISTENT}, {"ControlsReady", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, + {"DashcamOverride", PERSISTENT}, {"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"DisablePowerDown", PERSISTENT}, {"DisableRadar_Allow", PERSISTENT}, @@ -123,11 +124,13 @@ std::unordered_map keys = { {"IsMetric", PERSISTENT}, {"IsOffroad", CLEAR_ON_MANAGER_START}, {"IsOnroad", PERSISTENT}, - {"IsRHD", PERSISTENT}, + {"IsRhdDetected", PERSISTENT}, {"IsTakingSnapshot", CLEAR_ON_MANAGER_START}, + {"IsTestedBranch", CLEAR_ON_MANAGER_START}, {"IsUpdateAvailable", CLEAR_ON_MANAGER_START}, {"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, - {"LaikadEphemeris", PERSISTENT}, + {"LaikadEphemeris", PERSISTENT | DONT_LOG}, + {"LanguageSetting", PERSISTENT}, {"LastAthenaPingTime", CLEAR_ON_MANAGER_START}, {"LastGPSPosition", PERSISTENT}, {"LastManagerExitReason", CLEAR_ON_MANAGER_START}, @@ -138,6 +141,7 @@ std::unordered_map keys = { {"LiveParameters", PERSISTENT}, {"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"NavSettingTime24h", PERSISTENT}, + {"NavSettingLeftSide", PERSISTENT}, {"NavdRender", PERSISTENT}, {"OpenpilotEnabledToggle", PERSISTENT}, {"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, @@ -151,6 +155,7 @@ std::unordered_map keys = { {"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"SshEnabled", PERSISTENT}, {"SubscriberInfo", PERSISTENT}, + {"SwitchToBranch", CLEAR_ON_MANAGER_START}, {"TermsVersion", PERSISTENT}, {"Timezone", PERSISTENT}, {"TrainingVersion", PERSISTENT}, diff --git a/common/params.py b/common/params.py index 2cfca3712..b6be424d4 100644 --- a/common/params.py +++ b/common/params.py @@ -1,8 +1,9 @@ -from common.params_pyx import Params, ParamKeyType, UnknownKeyName, put_nonblocking # pylint: disable=no-name-in-module, import-error +from common.params_pyx import Params, ParamKeyType, UnknownKeyName, put_nonblocking, put_bool_nonblocking # pylint: disable=no-name-in-module, import-error assert Params assert ParamKeyType assert UnknownKeyName assert put_nonblocking +assert put_bool_nonblocking if __name__ == "__main__": import sys diff --git a/common/params_pyx.pyx b/common/params_pyx.pyx index b3b824852..36c8cf777 100755 --- a/common/params_pyx.pyx +++ b/common/params_pyx.pyx @@ -100,11 +100,7 @@ cdef class Params: return self.p.getParamPath(key_bytes).decode("utf-8") def put_nonblocking(key, val, d=""): - def f(key, val): - params = Params(d) - cdef string k = ensure_bytes(key) - params.put(k, val) - - t = threading.Thread(target=f, args=(key, val)) - t.start() - return t + threading.Thread(target=lambda: Params(d).put(key, val)).start() + +def put_bool_nonblocking(key, bool val, d=""): + threading.Thread(target=lambda: Params(d).put_bool(key, val)).start() diff --git a/common/swaglog.cc b/common/swaglog.cc index 6b0028326..22682dc54 100644 --- a/common/swaglog.cc +++ b/common/swaglog.cc @@ -66,8 +66,9 @@ static void log(int levelnum, const char* filename, int lineno, const char* func char levelnum_c = levelnum; zmq_send(s.sock, (levelnum_c + log_s).c_str(), log_s.length() + 1, ZMQ_NOBLOCK); } + static void cloudlog_common(int levelnum, const char* filename, int lineno, const char* func, - char* msg_buf, json11::Json::object msg_j={}) { + char* msg_buf, const json11::Json::object &msg_j={}) { std::lock_guard lk(s.lock); if (!s.initialized) s.initialize(); diff --git a/common/tests/test_params.py b/common/tests/test_params.py index 906198871..f99ac81a3 100644 --- a/common/tests/test_params.py +++ b/common/tests/test_params.py @@ -4,7 +4,7 @@ import tempfile import shutil import unittest -from common.params import Params, ParamKeyType, UnknownKeyName, put_nonblocking +from common.params import Params, ParamKeyType, UnknownKeyName, put_nonblocking, put_bool_nonblocking class TestParams(unittest.TestCase): def setUp(self): @@ -89,6 +89,15 @@ class TestParams(unittest.TestCase): 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) + def _delayed_writer(): + time.sleep(0.1) + put_bool_nonblocking("CarParams", True, self.tmpdir) + threading.Thread(target=_delayed_writer).start() + assert q.get("CarParams") is None + assert q.get("CarParams", True) == b"1" + if __name__ == "__main__": unittest.main() diff --git a/common/transformations/camera.py b/common/transformations/camera.py index 5d100d104..b20ed5c64 100644 --- a/common/transformations/camera.py +++ b/common/transformations/camera.py @@ -77,6 +77,7 @@ def get_view_frame_from_road_frame(roll, pitch, yaw, height): return np.hstack((view_from_road, [[0], [height], [0]])) + # aka 'extrinsic_matrix' def get_view_frame_from_calib_frame(roll, pitch, yaw, height): device_from_calib= orient.rot_from_euler([roll, pitch, yaw]) @@ -94,12 +95,6 @@ def vp_from_ke(m): return (m[0, 0]/m[2, 0], m[1, 0]/m[2, 0]) -def vp_from_rpy(rpy, intrinsics=fcam_intrinsics): - e = get_view_frame_from_road_frame(rpy[0], rpy[1], rpy[2], 1.22) - ke = np.dot(intrinsics, e) - return vp_from_ke(ke) - - def roll_from_ke(m): # note: different from calibration.h/RollAnglefromKE: i think that one's just wrong return np.arctan2(-(m[1, 0] - m[1, 1] * m[2, 0] / m[2, 1]), @@ -163,11 +158,3 @@ def img_from_device(pt_device): pt_img = pt_view/pt_view[:, 2:3] return pt_img.reshape(input_shape)[:, :2] - -def get_camera_frame_from_calib_frame(camera_frame_from_road_frame, intrinsics=fcam_intrinsics): - camera_frame_from_ground = camera_frame_from_road_frame[:, (0, 1, 3)] - calib_frame_from_ground = np.dot(intrinsics, - get_view_frame_from_road_frame(0, 0, 0, 1.22))[:, (0, 1, 3)] - ground_from_calib_frame = np.linalg.inv(calib_frame_from_ground) - camera_frame_from_calib_frame = np.dot(camera_frame_from_ground, ground_from_calib_frame) - return camera_frame_from_calib_frame diff --git a/common/transformations/model.py b/common/transformations/model.py index 62eb86423..811a17eaf 100644 --- a/common/transformations/model.py +++ b/common/transformations/model.py @@ -1,10 +1,7 @@ import numpy as np from common.transformations.camera import (FULL_FRAME_SIZE, - FOCAL, - get_view_frame_from_road_frame, - get_view_frame_from_calib_frame, - vp_from_ke) + get_view_frame_from_calib_frame) # segnet SEGNET_SIZE = (512, 384) @@ -14,21 +11,6 @@ def get_segnet_frame_from_camera_frame(segnet_size=SEGNET_SIZE, full_frame_size= [0.0, float(segnet_size[1]) / full_frame_size[1]]]) segnet_frame_from_camera_frame = get_segnet_frame_from_camera_frame() # xx -# model -MODEL_INPUT_SIZE = (320, 160) -MODEL_YUV_SIZE = (MODEL_INPUT_SIZE[0], MODEL_INPUT_SIZE[1] * 3 // 2) -MODEL_CX = MODEL_INPUT_SIZE[0] / 2. -MODEL_CY = 21. - -model_fl = 728.0 -model_height = 1.22 - -# canonical model transform -model_intrinsics = np.array([ - [model_fl, 0.0, MODEL_CX], - [0.0, model_fl, MODEL_CY], - [0.0, 0.0, 1.0]]) - # MED model MEDMODEL_INPUT_SIZE = (512, 256) @@ -63,104 +45,73 @@ sbigmodel_intrinsics = np.array([ [0.0, sbigmodel_fl, 0.5 * (256 + MEDMODEL_CY)], [0.0, 0.0, 1.0]]) -model_frame_from_road_frame = np.dot(model_intrinsics, - get_view_frame_from_road_frame(0, 0, 0, model_height)) - -bigmodel_frame_from_road_frame = np.dot(bigmodel_intrinsics, - get_view_frame_from_road_frame(0, 0, 0, model_height)) - bigmodel_frame_from_calib_frame = np.dot(bigmodel_intrinsics, get_view_frame_from_calib_frame(0, 0, 0, 0)) -sbigmodel_frame_from_road_frame = np.dot(sbigmodel_intrinsics, - get_view_frame_from_road_frame(0, 0, 0, model_height)) sbigmodel_frame_from_calib_frame = np.dot(sbigmodel_intrinsics, get_view_frame_from_calib_frame(0, 0, 0, 0)) -medmodel_frame_from_road_frame = np.dot(medmodel_intrinsics, - get_view_frame_from_road_frame(0, 0, 0, model_height)) - medmodel_frame_from_calib_frame = np.dot(medmodel_intrinsics, get_view_frame_from_calib_frame(0, 0, 0, 0)) -model_frame_from_bigmodel_frame = np.dot(model_intrinsics, np.linalg.inv(bigmodel_intrinsics)) medmodel_frame_from_bigmodel_frame = np.dot(medmodel_intrinsics, np.linalg.inv(bigmodel_intrinsics)) -# 'camera from model camera' -def get_model_height_transform(camera_frame_from_road_frame, height): - camera_frame_from_road_ground = np.dot(camera_frame_from_road_frame, np.array([ - [1, 0, 0], - [0, 1, 0], - [0, 0, 0], - [0, 0, 1], - ])) - - camera_frame_from_road_high = np.dot(camera_frame_from_road_frame, np.array([ - [1, 0, 0], - [0, 1, 0], - [0, 0, height - model_height], - [0, 0, 1], - ])) - - road_high_from_camera_frame = np.linalg.inv(camera_frame_from_road_high) - high_camera_from_low_camera = np.dot(camera_frame_from_road_ground, road_high_from_camera_frame) - - return high_camera_from_low_camera - - -# camera_frame_from_model_frame aka 'warp matrix' -# was: calibration.h/CalibrationTransform -def get_camera_frame_from_model_frame(camera_frame_from_road_frame, height=model_height, camera_fl=FOCAL): - vp = vp_from_ke(camera_frame_from_road_frame) - - model_zoom = camera_fl / model_fl - model_camera_from_model_frame = np.array([ - [model_zoom, 0.0, vp[0] - MODEL_CX * model_zoom], - [0.0, model_zoom, vp[1] - MODEL_CY * model_zoom], - [0.0, 0.0, 1.0], - ]) - - # This function is super slow, so skip it if height is very close to canonical - # TODO: speed it up! - if abs(height - model_height) > 0.001: - camera_from_model_camera = get_model_height_transform(camera_frame_from_road_frame, height) - else: - camera_from_model_camera = np.eye(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 - return np.dot(camera_from_model_camera, model_camera_from_model_frame) - - -def get_camera_frame_from_medmodel_frame(camera_frame_from_road_frame): - camera_frame_from_ground = camera_frame_from_road_frame[:, (0, 1, 3)] - medmodel_frame_from_ground = medmodel_frame_from_road_frame[:, (0, 1, 3)] - - ground_from_medmodel_frame = np.linalg.inv(medmodel_frame_from_ground) - camera_frame_from_medmodel_frame = np.dot(camera_frame_from_ground, ground_from_medmodel_frame) + if tici and wide_cam: + intrinsics = tici_ecam_intrinsics + elif tici: + intrinsics = tici_fcam_intrinsics + else: + intrinsics = eon_fcam_intrinsics - return camera_frame_from_medmodel_frame + 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 -def get_camera_frame_from_bigmodel_frame(camera_frame_from_road_frame): - camera_frame_from_ground = camera_frame_from_road_frame[:, (0, 1, 3)] - bigmodel_frame_from_ground = bigmodel_frame_from_road_frame[:, (0, 1, 3)] +### 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 - ground_from_bigmodel_frame = np.linalg.inv(bigmodel_frame_from_ground) - camera_frame_from_bigmodel_frame = np.dot(camera_frame_from_ground, ground_from_bigmodel_frame) - return camera_frame_from_bigmodel_frame + 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 + elif tici: + intrinsics = tici_fcam_intrinsics + else: + intrinsics = eon_fcam_intrinsics -def get_model_frame(snu_full, camera_frame_from_model_frame, size): - idxs = camera_frame_from_model_frame.dot(np.column_stack([np.tile(np.arange(size[0]), size[1]), - np.tile(np.arange(size[1]), (size[0], 1)).T.flatten(), - np.ones(size[0] * size[1])]).T).T.astype(int) - calib_flat = snu_full[idxs[:, 1], idxs[:, 0]] - if len(snu_full.shape) == 3: - calib = calib_flat.reshape((size[1], size[0], 3)) - elif len(snu_full.shape) == 2: - calib = calib_flat.reshape((size[1], size[0])) + 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: - raise ValueError("shape of input img is weird") - return calib + 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) + return warp_matrix diff --git a/common/version.h b/common/version.h index de550d6be..bf1c58df1 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.8.15" +#define COMMA_VERSION "0.8.16" diff --git a/common/watchdog.cc b/common/watchdog.cc index 5a1020782..920df4030 100644 --- a/common/watchdog.cc +++ b/common/watchdog.cc @@ -1,12 +1,9 @@ #include "common/watchdog.h" -#include "common/timing.h" #include "common/util.h" const std::string watchdog_fn_prefix = "/dev/shm/wd_"; // + -bool watchdog_kick() { +bool watchdog_kick(uint64_t ts) { static std::string fn = watchdog_fn_prefix + std::to_string(getpid()); - - uint64_t ts = nanos_since_boot(); return util::write_file(fn.c_str(), &ts, sizeof(ts), O_WRONLY | O_CREAT) > 0; } diff --git a/common/watchdog.h b/common/watchdog.h index 7ed23aa0d..12dd2ca03 100644 --- a/common/watchdog.h +++ b/common/watchdog.h @@ -1,3 +1,5 @@ #pragma once -bool watchdog_kick(); +#include + +bool watchdog_kick(uint64_t ts); diff --git a/docs/CARS.md b/docs/CARS.md index c0bd62eb3..ff539f052 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -1,237 +1,278 @@ + + # Supported Cars A supported vehicle is one that just works when you install a comma device. Every car performs differently with openpilot, but all supported cars should provide a better experience than any stock system. -Cars are organized into three tiers: - -- Gold - The best openpilot experience. Great highway driving and beyond. -- Silver - A solid highway driving experience, but is limited by stock longitudinal. May be upgraded in the future. -- Bronze - A good highway experience, but may have limited performance in traffic and on sharp turns. - -How We Rate The Cars ---- - -### openpilot Adaptive Cruise Control (ACC) -- - openpilot is able to control the gas and brakes. -- - openpilot is able to control the gas and brakes with some restrictions. -- - The gas and brakes are controlled by the car's stock Adaptive Cruise Control (ACC) system. +## How We Rate The Cars ### Stop and Go -- - Adaptive Cruise Control (ACC) operates down to 0 mph. -- - Adaptive Cruise Control (ACC) available only above certain speeds. See your car's manual for the minimum speed. +- [![star](assets/icon-star-full.svg)](##) - openpilot operates down to 0 mph. +- [![star](assets/icon-star-empty.svg)](##) - openpilot operates only above a minimum speed. See your car's manual for the minimum speed. ### Steer to 0 -- - openpilot can control the steering wheel down to 0 mph. -- - No steering control below certain speeds. +- [![star](assets/icon-star-full.svg)](##) - openpilot can control the steering wheel down to 0 mph. +- [![star](assets/icon-star-empty.svg)](##) - No steering control below certain speeds. See your car's manual for the minimum speed. ### Steering Torque -- - Car has enough steering torque to take tighter turns. -- - Car has enough steering torque for comfortable highway driving. -- - Limited ability to make turns. - -### Actively Maintained -- - Mainline software support, harness hardware sold by comma, lots of users, primary development target. -- - Low user count, community maintained, harness hardware not sold by comma. - -**All supported cars can move between the tiers as support changes.** - -# Gold - 28 cars - -|Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained| -|---|---|---|:---:|:---:|:---:|:---:|:---:| -|comma|body|All|||||| -|Genesis|G70 2020|All|||||| -|Hyundai|Palisade 2020-21|All|||||| -|Hyundai|Santa Fe 2019-20|All|||||| -|Hyundai|Sonata 2020-22|All|||||| -|Hyundai|Sonata Hybrid 2020-22|All|||||| -|Kia|Niro Electric 2019-20|All|||||| -|Kia|Niro Electric 2021|All|||||| -|Kia|Niro Electric 2022|All|||||| -|Kia|Telluride 2020|SCC + LKAS|||||| -|Lexus|ES 2019-21|All|||||| -|Lexus|ES Hybrid 2019-22|All|||||| -|Lexus|NX 2020|All|||||| -|Lexus|RX 2020-22|All|||||| -|Lexus|UX Hybrid 2019-21|All|||||| -|Toyota|Avalon 2022|All|||||| -|Toyota|Camry 2021-22|All||[4](#footnotes)|||| -|Toyota|Camry Hybrid 2021-22|All|||||| -|Toyota|Corolla 2020-22|All|||||| -|Toyota|Corolla Hatchback 2019-22|All|||||| -|Toyota|Corolla Hybrid 2020-22|All|||||| -|Toyota|Highlander 2020-22|All|||||| -|Toyota|Highlander Hybrid 2020-22|All|||||| -|Toyota|Mirai 2021|All|||||| -|Toyota|Prius 2021-22|All|||||| -|Toyota|Prius Prime 2021-22|All|||||| -|Toyota|RAV4 2019-21|All|||||| -|Toyota|RAV4 Hybrid 2019-21|All|||||| - -# Silver - 76 cars - -|Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained| -|---|---|---|:---:|:---:|:---:|:---:|:---:| -|Audi|A3 2014-19|ACC + Lane Assist|||||| -|Audi|A3 Sportback e-tron 2017-18|ACC + Lane Assist|||||| -|Audi|Q2 2018|ACC + Lane Assist|||||| -|Audi|RS3 2018|ACC + Lane Assist|||||| -|Audi|S3 2015-17|ACC + Lane Assist|||||| -|Chevrolet|Volt 2017-18[1](#footnotes)|Adaptive Cruise|||||| -|Genesis|G70 2018|All|||||| -|Genesis|G80 2018|All|||||| -|Hyundai|Elantra 2021-22|SCC + LKAS|||||| -|Hyundai|Elantra Hybrid 2021-22|SCC + LKAS|||||| -|Hyundai|Ioniq Electric 2020|SCC + LKAS|||||| -|Hyundai|Ioniq Hybrid 2020-22|SCC + LFA|||||| -|Hyundai|Ioniq Plug-in Hybrid 2020-21|SCC + LKAS|||||| -|Hyundai|Kona 2020|SCC + LKAS|||||| -|Hyundai|Kona Electric 2018-21|SCC + LKAS|||||| -|Hyundai|Kona Hybrid 2020|SCC + LKAS|||||| -|Hyundai|Santa Fe 2021-22|All|||||| -|Hyundai|Santa Fe Hybrid 2022|All|||||| -|Hyundai|Santa Fe Plug-in Hybrid 2022|All|||||| -|Hyundai|Tucson Diesel 2019|SCC + LKAS|||||| -|Kia|Ceed 2019|SCC + LKAS|||||| -|Kia|Forte 2018|SCC + LKAS|||||| -|Kia|Forte 2019-21|SCC + LKAS|||||| -|Kia|K5 2021-22|SCC|||||| -|Kia|Niro Hybrid 2021|SCC + LKAS|||||| -|Kia|Niro Hybrid 2022|SCC + LKAS|||||| -|Kia|Optima 2019|SCC + LKAS|||||| -|Kia|Seltos 2021|SCC + LKAS|||||| -|Kia|Sorento 2018|SCC + LKAS|||||| -|Kia|Sorento 2019|SCC + LKAS|||||| -|Kia|Stinger 2018|SCC + LKAS|||||| -|Lexus|NX 2018-19|All|[3](#footnotes)||||| -|Lexus|NX Hybrid 2018-19|All|[3](#footnotes)||||| -|Lexus|NX Hybrid 2020|All|||||| -|Lexus|RX Hybrid 2020-21|All|||||| -|Mazda|CX-5 2022|All|||||| -|SEAT|Ateca 2018|Driver Assistance|||||| -|SEAT|Leon 2014-20|Driver Assistance|||||| -|Subaru|Crosstrek 2020-21|EyeSight|||||| -|Subaru|Forester 2019-21|All|||||| -|Subaru|Impreza 2020-21|EyeSight|||||| -|Škoda|Kamiq 2021[5](#footnotes)|Driver Assistance|||||| -|Škoda|Karoq 2019|Driver Assistance|||||| -|Škoda|Kodiaq 2018-19|Driver Assistance|||||| -|Škoda|Octavia 2015, 2018-19|Driver Assistance|||||| -|Škoda|Octavia RS 2016|Driver Assistance|||||| -|Škoda|Scala 2020|Driver Assistance|||||| -|Škoda|Superb 2015-18|Driver Assistance|||||| -|Toyota|Alphard 2019-20|All|||||| -|Toyota|Alphard Hybrid 2021|All|||||| -|Toyota|Avalon 2019-21|TSS-P|[3](#footnotes)||||| -|Toyota|Avalon Hybrid 2022|All|||||| -|Toyota|Camry 2018-20|All||[4](#footnotes)|||| -|Toyota|Camry Hybrid 2018-20|All||[4](#footnotes)|||| -|Toyota|Highlander 2017-19|All|[3](#footnotes)||||| -|Toyota|Highlander Hybrid 2017-19|All|[3](#footnotes)||||| -|Toyota|Prius 2016-20|TSS-P|[3](#footnotes)||||| -|Toyota|Prius Prime 2017-20|All|[3](#footnotes)||||| -|Toyota|RAV4 2022|All|||||| -|Toyota|RAV4 Hybrid 2016-18|TSS-P|[3](#footnotes)||||| -|Toyota|RAV4 Hybrid 2022|All|||||| -|Toyota|Sienna 2018-20|All|[3](#footnotes)||||| -|Volkswagen|Atlas 2018-19, 2022[7](#footnotes)|Driver Assistance|||||| -|Volkswagen|e-Golf 2014, 2018-20|Driver Assistance|||||| -|Volkswagen|Golf 2015-20|Driver Assistance|||||| -|Volkswagen|Golf Alltrack 2017-18|Driver Assistance|||||| -|Volkswagen|Golf GTE 2016|Driver Assistance|||||| -|Volkswagen|Golf GTI 2018-21|Driver Assistance|||||| -|Volkswagen|Golf R 2016-19|Driver Assistance|||||| -|Volkswagen|Golf SportsVan 2016|Driver Assistance|||||| -|Volkswagen|Golf SportWagen 2015|Driver Assistance|||||| -|Volkswagen|Polo 2020|Driver Assistance|||||| -|Volkswagen|T-Cross 2021[7](#footnotes)|Driver Assistance|||||| -|Volkswagen|T-Roc 2021[7](#footnotes)|Driver Assistance|||||| -|Volkswagen|Taos 2022[7](#footnotes)|Driver Assistance|||||| -|Volkswagen|Touran 2017|Driver Assistance|||||| - -# Bronze - 69 cars - -|Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained| -|---|---|---|:---:|:---:|:---:|:---:|:---:| -|Acura|ILX 2016-19|AcuraWatch Plus|||||| -|Acura|RDX 2016-18|AcuraWatch Plus|||||| -|Acura|RDX 2019-21|All|||||| -|Audi|Q3 2020-21|ACC + Lane Assist|||||| -|Cadillac|Escalade ESV 2016[1](#footnotes)|ACC + LKAS|||||| -|Chrysler|Pacifica 2017-18|Adaptive Cruise|||||| -|Chrysler|Pacifica 2020|Adaptive Cruise|||||| -|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise|||||| -|Chrysler|Pacifica Hybrid 2019-21|Adaptive Cruise|||||| -|Genesis|G90 2018|All|||||| -|GMC|Acadia 2018[1](#footnotes)|Adaptive Cruise|||||| -|Honda|Accord 2018-21|All|||||| -|Honda|Accord Hybrid 2018-21|All|||||| -|Honda|Civic 2016-18|Honda Sensing|||||| -|Honda|Civic 2019-20|All|||[2](#footnotes)||| -|Honda|Civic Hatchback 2017-21|Honda Sensing|||||| -|Honda|CR-V 2015-16|Touring|||||| -|Honda|CR-V 2017-21|Honda Sensing|||||| -|Honda|CR-V Hybrid 2017-19|Honda Sensing|||||| -|Honda|e 2020|All|||||| -|Honda|Fit 2018-19|Honda Sensing|||||| -|Honda|Freed 2020|Honda Sensing|||||| -|Honda|HR-V 2019-20|Honda Sensing|||||| -|Honda|Insight 2019-21|All|||||| -|Honda|Inspire 2018|All|||||| -|Honda|Odyssey 2018-20|Honda Sensing|||||| -|Honda|Passport 2019-21|All|||||| -|Honda|Pilot 2016-21|Honda Sensing|||||| -|Honda|Ridgeline 2017-22|Honda Sensing|||||| -|Hyundai|Elantra 2017-19|SCC + LKAS|||||| -|Hyundai|Genesis 2015-16|SCC + LKAS|||||| -|Hyundai|Ioniq Electric 2019|SCC + LKAS|||||| -|Hyundai|Ioniq Hybrid 2017-19|SCC + LKAS|||||| -|Hyundai|Ioniq Plug-in Hybrid 2019|SCC + LKAS|||||| -|Hyundai|Sonata 2018-19|SCC + LKAS|||||| -|Hyundai|Tucson 2021|SCC + LKAS|||||| -|Hyundai|Veloster 2019-20|SCC + LKAS|||||| -|Jeep|Grand Cherokee 2016-18|Adaptive Cruise|||||| -|Jeep|Grand Cherokee 2019-20|Adaptive Cruise|||||| -|Kia|Niro Plug-in Hybrid 2019|SCC + LKAS|||||| -|Kia|Optima 2017|SCC + LKAS|||||| -|Lexus|CT Hybrid 2017-18|LSS|[3](#footnotes)||||| -|Lexus|ES Hybrid 2017-18|LSS|[3](#footnotes)||||| -|Lexus|IS 2017-19|All|||||| -|Lexus|RC 2020|All|||||| -|Lexus|RX 2016-18|All|[3](#footnotes)||||| -|Lexus|RX Hybrid 2016-19|All|[3](#footnotes)||||| -|Mazda|CX-9 2021|All|||||| -|Nissan|Altima 2019-20|ProPILOT|||||| -|Nissan|Leaf 2018-22|ProPILOT|||||| -|Nissan|Rogue 2018-20|ProPILOT|||||| -|Nissan|X-Trail 2017|ProPILOT|||||| -|Subaru|Ascent 2019-20|All|||||| -|Subaru|Crosstrek 2018-19|EyeSight|||||| -|Subaru|Impreza 2017-19|EyeSight|||||| -|Toyota|Avalon 2016-18|TSS-P|[3](#footnotes)||||| -|Toyota|Avalon Hybrid 2019-21|TSS-P|[3](#footnotes)||||| -|Toyota|C-HR 2017-21|All|||||| -|Toyota|C-HR Hybrid 2017-19|All|||||| -|Toyota|Corolla 2017-19|All|[3](#footnotes)||||| -|Toyota|Prius v 2017|TSS-P|[3](#footnotes)||||| -|Toyota|RAV4 2016-18|TSS-P|[3](#footnotes)||||| -|Volkswagen|Arteon 2018, 2021[7](#footnotes)|Driver Assistance|||||| -|Volkswagen|California 2021[7](#footnotes)|Driver Assistance|||||| -|Volkswagen|Caravelle 2020[7](#footnotes)|Driver Assistance|||||| -|Volkswagen|Jetta 2018-21|Driver Assistance|||||| -|Volkswagen|Jetta GLI 2021|Driver Assistance|||||| -|Volkswagen|Passat 2015-19[6](#footnotes)|Driver Assistance|||||| -|Volkswagen|Tiguan 2019-22[7](#footnotes)|Driver Assistance|||||| +- [![star](assets/icon-star-full.svg)](##) - Car has enough steering torque to comfortably take most highway turns. +- [![star](assets/icon-star-empty.svg)](##) - Limited ability to make tighter turns. +# 201 Supported Cars + +|Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque| +|---|---|---|:---:|:---:|:---:|:---:| +|Acura|ILX 2016-19|AcuraWatch Plus|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Acura|RDX 2016-18|AcuraWatch Plus|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Acura|RDX 2019-22|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Audi|A3 2014-19|ACC + Lane Assist|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Audi|A3 Sportback e-tron 2017-18|ACC + Lane Assist|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Audi|Q2 2018|ACC + Lane Assist|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Audi|Q3 2020-21|ACC + Lane Assist|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Audi|RS3 2018|ACC + Lane Assist|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Audi|S3 2015-17|ACC + Lane Assist|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Cadillac|Escalade ESV 2016[1](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Chevrolet|Bolt EUV 2022-23|Premier/Premier Redline Trim|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Chevrolet|Volt 2017-18[1](#footnotes)|Adaptive Cruise Control|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Chrysler|Pacifica 2017-18|Adaptive Cruise Control|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Chrysler|Pacifica 2019-20|Adaptive Cruise Control|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Chrysler|Pacifica 2021|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise Control|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|comma|body|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Genesis|G70 2018-19|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Genesis|G70 2020|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Genesis|G80 2017-19|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Genesis|G90 2017-18|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|GMC|Acadia 2018[1](#footnotes)|Adaptive Cruise Control|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Honda|Accord 2018-22|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Accord Hybrid 2018-22|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Civic 2016-18|Honda Sensing|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Civic 2019-21|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)[2](#footnotes)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Civic 2022|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Honda|Civic Hatchback 2017-21|Honda Sensing|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Civic Hatchback 2022|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Honda|CR-V 2015-16|Touring Trim|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|CR-V 2017-22|Honda Sensing|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|CR-V Hybrid 2017-19|Honda Sensing|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|e 2020|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Fit 2018-20|Honda Sensing|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Freed 2020|Honda Sensing|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|HR-V 2019-22|Honda Sensing|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Insight 2019-22|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Inspire 2018|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Odyssey 2018-20|Honda Sensing|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Passport 2019-21|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Pilot 2016-22|Honda Sensing|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Honda|Ridgeline 2017-22|Honda Sensing|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Elantra 2021-22|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Elantra Hybrid 2021-22|Smart Cruise Control (SCC)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Ioniq 5 2022|Highway Driving Assist II|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Ioniq Electric 2020|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC) & LFA|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Ioniq Plug-in Hybrid 2020-21|Smart Cruise Control (SCC)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Kona 2020|Smart Cruise Control (SCC)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Palisade 2020-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Santa Fe 2019-20|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Santa Fe 2021-22|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Santa Fe Hybrid 2022|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Santa Fe Plug-in Hybrid 2022|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Sonata 2020-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Sonata Hybrid 2020-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Ceed 2019|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|EV6 2022|Highway Driving Assist II|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Forte 2018|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Forte 2019-21|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|K5 2021-22|Smart Cruise Control (SCC)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Niro Electric 2019|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Niro Electric 2020|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Niro Electric 2021|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Niro Electric 2022|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Niro Plug-in Hybrid 2018-19|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Optima 2017|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Optima 2019|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Seltos 2021|Smart Cruise Control (SCC)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Sorento 2018|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Sorento 2019|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Stinger 2018-20|Smart Cruise Control (SCC) & LKAS|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Kia|Telluride 2020|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|CT Hybrid 2017-18|Lexus Safety System+|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|ES 2019-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|ES Hybrid 2017-18|Lexus Safety System+|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|ES Hybrid 2019-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|IS 2017-19|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|NX 2018-19|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|NX 2020-21|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|NX Hybrid 2018-19|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|NX Hybrid 2020-21|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|RC 2017-20|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|RX 2016-19|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|RX 2020-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|RX Hybrid 2016-19|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|RX Hybrid 2020-21|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Lexus|UX Hybrid 2019-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Mazda|CX-5 2022|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Mazda|CX-9 2021-22|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Nissan|Altima 2019-20|ProPILOT Assist|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Nissan|Leaf 2018-22|ProPILOT Assist|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Nissan|Rogue 2018-20|ProPILOT Assist|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Nissan|X-Trail 2017|ProPILOT Assist|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Ram|1500 2019-22|Adaptive Cruise Control|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|SEAT|Ateca 2018|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|SEAT|Leon 2014-20|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Subaru|Ascent 2019-21|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Subaru|Crosstrek 2020-21|EyeSight Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Subaru|Forester 2019-21|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Subaru|Impreza 2017-19|EyeSight Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Subaru|Impreza 2020-22|EyeSight Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Subaru|Outback 2020-22|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Subaru|XV 2018-19|EyeSight Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)| +|Subaru|XV 2020-21|EyeSight Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Škoda|Kamiq 2021[5](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Škoda|Karoq 2019-21[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Škoda|Kodiaq 2018-19|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Škoda|Octavia 2015, 2018-19|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Škoda|Octavia RS 2016|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Škoda|Scala 2020|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Škoda|Superb 2015-18|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Alphard 2019-20|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Alphard Hybrid 2021|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Avalon 2016|Toyota Safety Sense P|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Avalon 2017-18|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Avalon 2019-21|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Avalon 2022|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Avalon Hybrid 2019-21|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Avalon Hybrid 2022|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|C-HR 2017-21|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|C-HR Hybrid 2017-19|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Camry 2018-20|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)[4](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Camry 2021-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)[4](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Camry Hybrid 2018-20|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)[4](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Camry Hybrid 2021-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Corolla 2017-19|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Corolla 2020-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Corolla Cross (Non-US only) 2020-21|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Corolla Hatchback 2019-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Corolla Hybrid 2020-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Highlander 2017-19|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Highlander 2020-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Highlander Hybrid 2017-19|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Highlander Hybrid 2020-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Mirai 2021|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Prius 2016|Toyota Safety Sense P|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Prius 2017-20|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Prius 2021-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Prius Prime 2017-20|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Prius Prime 2021-22|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Prius v 2017|Toyota Safety Sense P|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|RAV4 2016|Toyota Safety Sense P|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|RAV4 2017-18|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|RAV4 2019-21|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|RAV4 2022|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|RAV4 Hybrid 2017-18|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|RAV4 Hybrid 2019-21|All|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|RAV4 Hybrid 2022|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Toyota|Sienna 2018-20|All|[![star](assets/icon-star-half.svg)](##)[3](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Arteon 2018-22[7,8](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Arteon eHybrid 2020-22[7,8](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Arteon R 2020-22[7,8](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Atlas 2018-22[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Atlas Cross Sport 2021-22[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|California 2021[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Caravelle 2020[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|CC 2018-22[7,8](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|e-Golf 2014-20|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Golf 2015-20[8](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Golf Alltrack 2015-19|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Golf GTD 2015-20|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Golf GTE 2015-20|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Golf GTI 2015-21|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Golf R 2015-19[8](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Golf SportsVan 2015-20|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Jetta 2018-22[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Jetta GLI 2021-22[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Passat 2015-22[6,7,8](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Passat Alltrack 2015-22[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Passat GTE 2015-22[7,8](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Polo 2020-22[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Polo GTI 2020-22[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|T-Cross 2021[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|T-Roc 2021[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Taos 2022[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Teramont 2018-22[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Teramont Cross Sport 2021-22[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Teramont X 2021-22[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Tiguan 2019-22[7](#footnotes)|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Volkswagen|Touran 2017|Driver Assistance|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| + -1Requires an OBD-II car harness and community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
+1Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
22019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-3When disconnecting the Driver Support Unit (DSU), openpilot Adaptive Cruise Control (ACC) will replace stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
-428mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+3When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
+4openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
5Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-6Not including the USA/China market Passat, which is based on the (currently) unsupported PQ35/NMS platform.
-7Model-years 2021 and beyond may have a new camera harness design, which isn't yet available from the comma store. Before ordering, remove the Lane Assist camera cover and check to see if the connector is black (older design) or light brown (newer design). For the newer design, in the interim, choose "VW J533 Development" from the vehicle drop-down for a harness that integrates at the CAN gateway inside the dashboard.
+6Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+7Model-years 2021 and beyond may have a new camera harness design, which isn't yet available from the comma store. Before ordering, remove the Lane Assist camera cover and check to see if the connector is black (older design) or light brown (newer design). In the interim, if your car has a J533 connector CAN gateway inside the dashboard, choose "VW J533 Development" from the vehicle drop-down for a suitable harness. (Some newer models are also observed to not have a J533 connector.)
+8Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.)
## Community Maintained Cars -Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). \ No newline at end of file +Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). + +# Don't see your car here? + +**openpilot can support many more cars than it currently does.** There are a few reasons your car may not be supported. +If your car doesn't fit into any of the incompatibility criteria here, then there's a good chance it can be supported! We're adding support for new cars all the time. We don't have a roadmap for car support, and in fact, most car support comes from users like you! + +### Which cars are able to be supported? + +openpilot uses the existing steering, gas, and brake interfaces in your car. If your car lacks any one of these interfaces, openpilot will not be able to control the car. If your car has any form of [LKAS](https://en.wikipedia.org/wiki/Automated_Lane_Keeping_Systems)/[LCA](https://en.wikipedia.org/wiki/Lane_centering) and [ACC](https://en.wikipedia.org/wiki/Adaptive_cruise_control), then it almost certainly has these interfaces. These interfaces generally started shipping on cars around 2016. + +If your car has the following packages or features, then it's a good candidate for support. If it does not, then it's unlikely able to be supported. + +| Make | Required Package/Features | +| ---- | ------------------------- | +| Acura | Any car with AcuraWatch Plus will work. AcuraWatch Plus comes standard on many newer models. | +| Honda | Any car with Honda Sensing will work. Honda Sensing comes standard on many newer models. | +| Subaru | Any car with EyeSight will work. EyeSight comes standard on many newer models. | +| Nissan | Any car with ProPILOT will likely work. | +| Toyota & Lexus | Any car that has Toyota/Lexus Safety Sense with "Lane Departure Alert with Steering Assist (LDA w/SA)" and/or "Lane Tracing Assist (LTA)" will work. Note that LDA without Steering Assist will not work. These features come standard on most newer models. | +| Hyundai, Kia, & Genesis | Any car with Smart Cruise Control (SCC) and Lane Following Assist (LFA) or Lane Keeping Assist (LKAS) will work. LKAS/LFA comes standard on most newer models. Any form of SCC will work, such as NSCC. | +| Chrysler, Jeep, & Ram | Any car with LaneSense and Adaptive Cruise Control will likely work. These come standard on many newer models. | + +### FlexRay + +All the cars that openpilot supports use a [CAN bus](https://en.wikipedia.org/wiki/CAN_bus) for communication between all the car's computers, however a CAN bus isn't the only way that the cars in your computer can communicate. Most, if not all, vehicles from the following manufacturers use [FlexRay](https://en.wikipedia.org/wiki/FlexRay) instead of a CAN bus: **BMW, Mercedes, Audi, Land Rover, and some Volvo**. These cars may one day be supported, but we have no immediate plans to support FlexRay. + +### Toyota Security + +Specific new Toyota models are shipping with a new message authentication method that openpilot does not yet support. +So far, this list includes: +* Toyota RAV4 Prime 2021+ +* Toyota Sienna 2021+ +* Toyota Venza 2021+ +* Toyota Sequoia 2023+ +* Toyota Tundra 2022+ +* Toyota Corolla Cross 2022+ (only US model) +* Lexus NX 2022+ +* Toyota bZ4x 2023+ +* Subaru Solterra 2023+ \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile index 02db7db2d..d0aa841c4 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -32,7 +32,7 @@ clean: @echo "Copying docs & config to build folder..." cp -a "$(DOCSDIR)" "$(BUILDDIR)" cd "$(OPENPILOT_ROOT)" && \ - find . -type f \( -name "*.md" -o -name "*.rst" -o -name "*.png" -o -name "*.jpg" \) \ + find . -type f \( -name "*.md" -o -name "*.rst" -o -name "*.png" -o -name "*.jpg" -o -name "*.svg" \) \ -not -path "*/.*" \ -not -path "./build/*" \ -not -path "./docs/*" \ diff --git a/docs/assets/icon-star-empty.svg b/docs/assets/icon-star-empty.svg index b12b42662..5d3c32d67 100644 --- a/docs/assets/icon-star-empty.svg +++ b/docs/assets/icon-star-empty.svg @@ -1,3 +1,56 @@ - - + + + + + + image/svg+xml + + + + + + + diff --git a/docs/assets/icon-star-full.svg b/docs/assets/icon-star-full.svg index 8211fdcc2..294db2b7f 100644 --- a/docs/assets/icon-star-full.svg +++ b/docs/assets/icon-star-full.svg @@ -1,3 +1,56 @@ - - + + + + + + image/svg+xml + + + + + + + diff --git a/docs/assets/icon-star-half.svg b/docs/assets/icon-star-half.svg index 7f64bc87e..ab905fddc 100644 --- a/docs/assets/icon-star-half.svg +++ b/docs/assets/icon-star-half.svg @@ -1,5 +1,66 @@ - - - - + + + + + + image/svg+xml + + + + + + + + + diff --git a/docs/c_docs.rst b/docs/c_docs.rst index 218cd5a7d..94d0adb56 100644 --- a/docs/c_docs.rst +++ b/docs/c_docs.rst @@ -28,11 +28,9 @@ selfdrive camerad ^^^^^^^ .. autodoxygenindex:: - :project: selfdrive_camerad_cameras + :project: system_camerad_cameras .. autodoxygenindex:: - :project: selfdrive_camerad_transforms -.. autodoxygenindex:: - :project: selfdrive_camerad_imgproc + :project: system_camerad_imgproc locationd ^^^^^^^^^ @@ -54,7 +52,7 @@ soundd replay """""" .. autodoxygenindex:: - :project: selfdrive_ui_replay + :project: tools_replay qt "" diff --git a/laika_repo b/laika_repo index 44f048bc1..c8bc1fa01 160000 --- a/laika_repo +++ b/laika_repo @@ -1 +1 @@ -Subproject commit 44f048bc1f58ae9e28dfdeb98e40aea3e0f2b699 +Subproject commit c8bc1fa01be9f22592efb991ee52d3d965d21968 diff --git a/launch_env.sh b/launch_env.sh index 769613bc7..ac84d6dcb 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1 export VECLIB_MAXIMUM_THREADS=1 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="5.1" + export AGNOS_VERSION="5.2" fi if [ -z "$PASSIVE" ]; then diff --git a/opendbc b/opendbc index a7b391cce..c6665ed11 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit a7b391cce88908824f1417f0c7abd35e3ae16f96 +Subproject commit c6665ed11b8d8c998e46f8dbee30e70a7d451e5e diff --git a/panda b/panda index 391b83d0a..0e8854b48 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 391b83d0a73e957e84383a9e04c1cad140ecada2 +Subproject commit 0e8854b48ff81cc61e30e9135091846ab5d9b887 diff --git a/rednose_repo b/rednose_repo index 7663289f1..3b6bd703b 160000 --- a/rednose_repo +++ b/rednose_repo @@ -1 +1 @@ -Subproject commit 7663289f1e68860f53dc34337ef080dde69a2586 +Subproject commit 3b6bd703b7a7667e4f82d0b81ef9a454819b94bd diff --git a/release/build_devel.sh b/release/build_devel.sh index 9d8b06451..f06e3102c 100755 --- a/release/build_devel.sh +++ b/release/build_devel.sh @@ -69,6 +69,15 @@ date: $DATETIME master commit: $GIT_HASH " +# ensure files are within GitHub's limit +BIG_FILES="$(find . -type f -not -path './.git/*' -size +95M)" +if [ ! -z "$BIG_FILES" ]; then + printf '\n\n\n' + echo "Found files exceeding GitHub's 100MB limit:" + echo "$BIG_FILES" + exit 1 +fi + if [ ! -z "$BRANCH" ]; then echo "[-] Pushing to $BRANCH T=$SECONDS" git push -f origin master-ci:$BRANCH diff --git a/release/build_release.sh b/release/build_release.sh index b5e15e05c..1000e607f 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -40,6 +40,7 @@ cp -pR --parents $(cat $FILES_SRC) $BUILD_DIR/ cd $BUILD_DIR rm -f panda/board/obj/panda.bin.signed +rm -f panda/board/obj/panda_h7.bin.signed VERSION=$(cat common/version.h | awk -F[\"-] '{print $2}') echo "#define COMMA_VERSION \"$VERSION-release\"" > common/version.h @@ -53,6 +54,7 @@ git branch --set-upstream-to=origin/$RELEASE_BRANCH pushd panda/ CERT=/data/pandaextra/certs/release RELEASE=1 scons -u . mv board/obj/panda.bin.signed /tmp/panda.bin.signed +mv board/obj/panda_h7.bin.signed /tmp/panda_h7.bin.signed popd # Build @@ -81,6 +83,7 @@ rm selfdrive/modeld/models/supercombo.dlc # Move back signed panda fw mkdir -p panda/board/obj mv /tmp/panda.bin.signed panda/board/obj/panda.bin.signed +mv /tmp/panda_h7.bin.signed panda/board/obj/panda_h7.bin.signed # Restore third_party git checkout third_party/ diff --git a/release/check-dirty.sh b/release/check-dirty.sh new file mode 100755 index 000000000..9c6389f38 --- /dev/null +++ b/release/check-dirty.sh @@ -0,0 +1,11 @@ +#!/usr/bin/bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +if [ ! -z "$(git status --porcelain)" ]; then + echo "Dirty working tree after build:" + git status --porcelain + exit 1 +fi diff --git a/release/files_common b/release/files_common index b6cd4fa79..8bf99f022 100644 --- a/release/files_common +++ b/release/files_common @@ -35,7 +35,6 @@ common/filter_simple.py common/stat_live.py common/spinner.py common/text_window.py -common/SConscript common/kalman/.gitignore common/kalman/* @@ -58,8 +57,11 @@ common/api/__init__.py release/* +tools/__init__.py tools/lib/* tools/joystick/* +tools/replay/*.cc +tools/replay/*.h selfdrive/__init__.py selfdrive/sentry.py @@ -101,10 +103,13 @@ selfdrive/car/interfaces.py selfdrive/car/vin.py selfdrive/car/disable_ecu.py selfdrive/car/fw_versions.py +selfdrive/car/ecu_addrs.py selfdrive/car/isotp_parallel_query.py selfdrive/car/tests/__init__.py selfdrive/car/tests/test_car_interfaces.py -selfdrive/car/torque_data.json +selfdrive/car/torque_data/params.yaml +selfdrive/car/torque_data/substitute.yaml +selfdrive/car/torque_data/override.yaml selfdrive/car/body/*.py selfdrive/car/chrysler/*.py @@ -124,7 +129,15 @@ system/clocksd/.gitignore system/clocksd/SConscript system/clocksd/clocksd.cc -selfdrive/debug/*.py +selfdrive/debug/can_printer.py +selfdrive/debug/check_freq.py +selfdrive/debug/dump.py +selfdrive/debug/filter_log_message.py +selfdrive/debug/get_fingerprint.py +selfdrive/debug/uiview.py + +selfdrive/debug/hyundai_enable_radar_points.py +selfdrive/debug/vw_mqb_config.py common/SConscript common/version.h @@ -182,6 +195,10 @@ selfdrive/controls/lib/longitudinal_mpc_lib/.gitignore selfdrive/controls/lib/lateral_mpc_lib/* selfdrive/controls/lib/longitudinal_mpc_lib/* +selfdrive/hardware + +system/__init__.py + system/hardware/__init__.py system/hardware/base.h system/hardware/base.py @@ -191,6 +208,7 @@ system/hardware/tici/hardware.h system/hardware/tici/hardware.py system/hardware/tici/pins.py system/hardware/tici/agnos.py +system/hardware/tici/casync.py system/hardware/tici/agnos.json system/hardware/tici/amplifier.py system/hardware/tici/updater @@ -211,10 +229,10 @@ selfdrive/locationd/generated/gps.h selfdrive/locationd/laikad.py selfdrive/locationd/laikad_helpers.py -selfdrive/locationd/locationd.cc selfdrive/locationd/locationd.h selfdrive/locationd/locationd.cc selfdrive/locationd/paramsd.py +selfdrive/locationd/models/__init__.py selfdrive/locationd/models/.gitignore selfdrive/locationd/models/car_kf.py selfdrive/locationd/models/gnss_kf.py @@ -226,6 +244,7 @@ selfdrive/locationd/models/gnss_helpers.py selfdrive/locationd/calibrationd.py +system/logcatd/.gitignore system/logcatd/SConscript system/logcatd/logcatd_systemd.cc @@ -234,6 +253,7 @@ system/proclogd/main.cc system/proclogd/proclog.cc system/proclogd/proclog.h +selfdrive/loggerd/.gitignore selfdrive/loggerd/SConscript selfdrive/loggerd/encoder/encoder.cc selfdrive/loggerd/encoder/encoder.h @@ -283,6 +303,8 @@ selfdrive/ui/soundd/*.cc selfdrive/ui/soundd/*.h selfdrive/ui/soundd/soundd selfdrive/ui/soundd/.gitignore +selfdrive/ui/translations/*.ts +selfdrive/ui/translations/languages.json selfdrive/ui/qt/*.cc selfdrive/ui/qt/*.h @@ -291,31 +313,22 @@ selfdrive/ui/qt/offroad/*.h selfdrive/ui/qt/offroad/*.qml selfdrive/ui/qt/widgets/*.cc selfdrive/ui/qt/widgets/*.h - -selfdrive/ui/replay/*.cc -selfdrive/ui/replay/*.h - selfdrive/ui/qt/maps/*.cc selfdrive/ui/qt/maps/*.h -selfdrive/camerad/SConscript -selfdrive/camerad/main.cc +system/camerad/SConscript +system/camerad/main.cc -selfdrive/camerad/snapshot/* -selfdrive/camerad/include/* -selfdrive/camerad/cameras/camera_common.h -selfdrive/camerad/cameras/camera_common.cc -selfdrive/camerad/cameras/sensor2_i2c.h +system/camerad/snapshot/* +system/camerad/include/* +system/camerad/cameras/camera_common.h +system/camerad/cameras/camera_common.cc +system/camerad/cameras/sensor2_i2c.h -selfdrive/camerad/transforms/rgb_to_yuv.cc -selfdrive/camerad/transforms/rgb_to_yuv.h -selfdrive/camerad/transforms/rgb_to_yuv.cl -selfdrive/camerad/transforms/rgb_to_yuv_test.cc - -selfdrive/camerad/imgproc/conv.cl -selfdrive/camerad/imgproc/pool.cl -selfdrive/camerad/imgproc/utils.cc -selfdrive/camerad/imgproc/utils.h +system/camerad/imgproc/conv.cl +system/camerad/imgproc/pool.cl +system/camerad/imgproc/utils.cc +system/camerad/imgproc/utils.h selfdrive/manager/__init__.py selfdrive/manager/build.py @@ -326,6 +339,7 @@ selfdrive/manager/process.py selfdrive/manager/test/__init__.py selfdrive/manager/test/test_manager.py +selfdrive/modeld/__init__.py selfdrive/modeld/SConscript selfdrive/modeld/modeld.cc selfdrive/modeld/dmonitoringmodeld.cc @@ -367,7 +381,9 @@ selfdrive/modeld/runners/run.h selfdrive/monitoring/dmonitoringd.py selfdrive/monitoring/driver_monitor.py -selfdrive/navd/*.py +selfdrive/navd/__init__.py +selfdrive/navd/navd.py +selfdrive/navd/helpers.py selfdrive/assets/.gitignore selfdrive/assets/assets.qrc @@ -406,13 +422,30 @@ third_party/acados/x86_64/** third_party/acados/larch64/** third_party/acados/include/** +third_party/qt5/larch64/bin/** + scripts/update_now.sh scripts/stop_updater.sh pyextra/.gitignore pyextra/acados_template/** +rednose/.gitignore rednose/** +laika/** + +body/.gitignore +body/board/SConscript +body/board/*.h +body/board/*.c +body/board/*.s +body/board/*.ld +body/board/inc/** +body/board/obj/ +body/board/bldc/** +body/board/drivers/** +body/certs/** +body/crypto/** cereal/.gitignore cereal/__init__.py @@ -471,7 +504,9 @@ opendbc/can/parser_pyx.pyx opendbc/comma_body.dbc -opendbc/chrysler_pacifica_2017_hybrid.dbc +opendbc/chrysler_ram_hd_generated.dbc +opendbc/chrysler_ram_dt_generated.dbc +opendbc/chrysler_pacifica_2017_hybrid_generated.dbc opendbc/chrysler_pacifica_2017_hybrid_private_fusion.dbc opendbc/gm_global_a_powertrain_generated.dbc @@ -495,6 +530,7 @@ opendbc/honda_odyssey_exl_2018_generated.dbc opendbc/honda_odyssey_extreme_edition_2018_china_can_generated.dbc opendbc/honda_insight_ex_2019_can_generated.dbc opendbc/acura_ilx_2016_nidec.dbc +opendbc/honda_civic_ex_2022_can_generated.dbc opendbc/kia_ev6.dbc opendbc/hyundai_kia_generic.dbc diff --git a/release/files_tici b/release/files_tici index 485a89879..f8c0a2795 100644 --- a/release/files_tici +++ b/release/files_tici @@ -7,9 +7,9 @@ system/timezoned.py selfdrive/assets/navigation/* selfdrive/assets/training_wide/* -selfdrive/camerad/cameras/camera_qcom2.cc -selfdrive/camerad/cameras/camera_qcom2.h -selfdrive/camerad/cameras/real_debayer.cl +system/camerad/cameras/camera_qcom2.cc +system/camerad/cameras/camera_qcom2.h +system/camerad/cameras/real_debayer.cl selfdrive/ui/qt/spinner_larch64 selfdrive/ui/qt/text_larch64 diff --git a/release/verify.sh b/release/verify.sh index 2ebd50a29..56f21183f 100755 --- a/release/verify.sh +++ b/release/verify.sh @@ -6,7 +6,7 @@ RED="\033[0;31m" GREEN="\033[0;32m" CLEAR="\033[0m" -BRANCHES="devel dashcam dashcam3 release2 release3" +BRANCHES="devel dashcam3 release3" for b in $BRANCHES; do if git diff --quiet origin/$b origin/$b-staging && [ "$(git rev-parse origin/$b)" = "$(git rev-parse origin/$b-staging)" ]; then printf "%-10s $GREEN ok $CLEAR\n" "$b" diff --git a/selfdrive/assets/fonts/opensans_bold.ttf b/selfdrive/assets/fonts/opensans_bold.ttf deleted file mode 100644 index 7b5294560..000000000 Binary files a/selfdrive/assets/fonts/opensans_bold.ttf and /dev/null differ diff --git a/selfdrive/assets/fonts/opensans_regular.ttf b/selfdrive/assets/fonts/opensans_regular.ttf deleted file mode 100644 index 2e31d0242..000000000 Binary files a/selfdrive/assets/fonts/opensans_regular.ttf and /dev/null differ diff --git a/selfdrive/assets/fonts/opensans_semibold.ttf b/selfdrive/assets/fonts/opensans_semibold.ttf deleted file mode 100644 index 99db86aa0..000000000 Binary files a/selfdrive/assets/fonts/opensans_semibold.ttf and /dev/null differ diff --git a/selfdrive/assets/navigation/direction_turn_left_inactive.png b/selfdrive/assets/navigation/direction_turn_left_inactive.png new file mode 100644 index 000000000..2946984ac Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_left_inactive.png differ diff --git a/selfdrive/assets/navigation/direction_turn_right_inactive.png b/selfdrive/assets/navigation/direction_turn_right_inactive.png new file mode 100644 index 000000000..7d327766a Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_right_inactive.png differ diff --git a/selfdrive/assets/navigation/direction_turn_straight_inactive.png b/selfdrive/assets/navigation/direction_turn_straight_inactive.png new file mode 100644 index 000000000..4c567966e Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_straight_inactive.png differ diff --git a/selfdrive/assets/offroad/icon_openpilot_mirrored.png b/selfdrive/assets/offroad/icon_openpilot_mirrored.png deleted file mode 100644 index 23a7d5a55..000000000 Binary files a/selfdrive/assets/offroad/icon_openpilot_mirrored.png and /dev/null differ diff --git a/selfdrive/assets/sounds/prompt.wav b/selfdrive/assets/sounds/prompt.wav index 420e9fabe..1ae77051e 100644 Binary files a/selfdrive/assets/sounds/prompt.wav and b/selfdrive/assets/sounds/prompt.wav differ diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 26220dfa9..6ccd6c3de 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -364,6 +364,11 @@ def uploadFilesToUrls(files_data): failed.append(fn) continue + # Skip item if already in queue + url = file['url'].split('?')[0] + if any(url == item['url'].split('?')[0] for item in listUploadQueue()): + continue + item = UploadItem( path=path, url=file['url'], @@ -493,7 +498,7 @@ def getNetworks(): @dispatcher.add_method def takeSnapshot(): - from selfdrive.camerad.snapshot.snapshot import jpeg_write, snapshot + from system.camerad.snapshot.snapshot import jpeg_write, snapshot ret = snapshot() if ret is not None: def b64jpeg(x): diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 382b549c1..7f511eecf 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -124,7 +124,7 @@ class TestAthenadMethods(unittest.TestCase): fn = os.path.join(athenad.ROOT, 'qlog.bz2') Path(fn).touch() if fn.endswith('.bz2'): - self.assertEqual(athenad.strip_bz2_extension(fn), fn[:-4]) + self.assertEqual(athenad.strip_bz2_extension(fn), fn[:-4]) @with_http_server @@ -142,9 +142,6 @@ class TestAthenadMethods(unittest.TestCase): @with_http_server def test_uploadFileToUrl(self, host): - not_exists_resp = dispatcher["uploadFileToUrl"]("does_not_exist.bz2", "http://localhost:1238", {}) - self.assertEqual(not_exists_resp, {'enqueued': 0, 'items': [], 'failed': ['does_not_exist.bz2']}) - fn = os.path.join(athenad.ROOT, 'qlog.bz2') Path(fn).touch() @@ -155,6 +152,24 @@ class TestAthenadMethods(unittest.TestCase): self.assertIsNotNone(resp['items'][0].get('id')) self.assertEqual(athenad.upload_queue.qsize(), 1) + @with_http_server + def test_uploadFileToUrl_duplicate(self, host): + fn = os.path.join(athenad.ROOT, 'qlog.bz2') + Path(fn).touch() + + url1 = f"{host}/qlog.bz2?sig=sig1" + dispatcher["uploadFileToUrl"]("qlog.bz2", url1, {}) + + # Upload same file again, but with different signature + url2 = f"{host}/qlog.bz2?sig=sig2" + resp = dispatcher["uploadFileToUrl"]("qlog.bz2", url2, {}) + self.assertEqual(resp, {'enqueued': 0, 'items': []}) + + @with_http_server + def test_uploadFileToUrl_does_not_exist(self, host): + not_exists_resp = dispatcher["uploadFileToUrl"]("does_not_exist.bz2", "http://localhost:1238", {}) + self.assertEqual(not_exists_resp, {'enqueued': 0, 'items': [], 'failed': ['does_not_exist.bz2']}) + @with_http_server def test_upload_handler(self, host): fn = os.path.join(athenad.ROOT, 'qlog.bz2') diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index f47b5936b..240935175 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -193,9 +193,11 @@ Panda *usb_connect(std::string serial="", uint32_t index=0) { return nullptr; } + // common panda config if (getenv("BOARDD_LOOPBACK")) { panda->set_loopback(true); } + panda->enable_deepsleep(); // power on charging, only the first time. Panda can also change mode and it causes a brief disconneciton #ifndef __x86_64__ diff --git a/selfdrive/camerad/transforms/rgb_to_yuv.cc b/selfdrive/camerad/transforms/rgb_to_yuv.cc deleted file mode 100644 index 63e032e2d..000000000 --- a/selfdrive/camerad/transforms/rgb_to_yuv.cc +++ /dev/null @@ -1,36 +0,0 @@ -#include "selfdrive/camerad/transforms/rgb_to_yuv.h" - -#include -#include - -Rgb2Yuv::Rgb2Yuv(cl_context ctx, cl_device_id device_id, int width, int height, int rgb_stride) { - assert(width % 2 == 0 && height % 2 == 0); - char args[1024]; - snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " -#ifdef CL_DEBUG - "-DCL_DEBUG " -#endif - "-DWIDTH=%d -DHEIGHT=%d -DUV_WIDTH=%d -DUV_HEIGHT=%d -DRGB_STRIDE=%d -DRGB_SIZE=%d", - width, height, width / 2, height / 2, rgb_stride, width * height); - - cl_program prg = cl_program_from_file(ctx, device_id, "transforms/rgb_to_yuv.cl", args); - krnl = CL_CHECK_ERR(clCreateKernel(prg, "rgb_to_yuv", &err)); - CL_CHECK(clReleaseProgram(prg)); - - work_size[0] = (width + (width % 4 == 0 ? 0 : (4 - width % 4))) / 4; - work_size[1] = (height + (height % 4 == 0 ? 0 : (4 - height % 4))) / 4; -} - -Rgb2Yuv::~Rgb2Yuv() { - CL_CHECK(clReleaseKernel(krnl)); -} - -void Rgb2Yuv::queue(cl_command_queue q, cl_mem rgb_cl, cl_mem yuv_cl) { - CL_CHECK(clSetKernelArg(krnl, 0, sizeof(cl_mem), &rgb_cl)); - CL_CHECK(clSetKernelArg(krnl, 1, sizeof(cl_mem), &yuv_cl)); - cl_event event; - CL_CHECK(clEnqueueNDRangeKernel(q, krnl, 2, NULL, &work_size[0], NULL, 0, 0, &event)); - CL_CHECK(clWaitForEvents(1, &event)); - CL_CHECK(clReleaseEvent(event)); -} diff --git a/selfdrive/camerad/transforms/rgb_to_yuv.h b/selfdrive/camerad/transforms/rgb_to_yuv.h deleted file mode 100644 index e1de180d4..000000000 --- a/selfdrive/camerad/transforms/rgb_to_yuv.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "common/clutil.h" - -class Rgb2Yuv { -public: - Rgb2Yuv(cl_context ctx, cl_device_id device_id, int width, int height, int rgb_stride); - ~Rgb2Yuv(); - void queue(cl_command_queue q, cl_mem rgb_cl, cl_mem yuv_cl); -private: - size_t work_size[2]; - cl_kernel krnl; -}; - diff --git a/selfdrive/camerad/transforms/rgb_to_yuv_test.cc b/selfdrive/camerad/transforms/rgb_to_yuv_test.cc deleted file mode 100644 index c960d168d..000000000 --- a/selfdrive/camerad/transforms/rgb_to_yuv_test.cc +++ /dev/null @@ -1,191 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef ANDROID - -#define MAXE 0 -#include - -#else -// The libyuv implementation on ARM is slightly different than on x86 -// Our implementation matches the ARM version, so accept errors of 1 -#define MAXE 1 - -#endif - -#include - -#include "libyuv.h" -#include "selfdrive/camerad/transforms/rgb_to_yuv.h" -#include "common/clutil.h" - -static inline double millis_since_boot() { - struct timespec t; - clock_gettime(CLOCK_BOOTTIME, &t); - return t.tv_sec * 1000.0 + t.tv_nsec * 1e-6; -} - -void cl_init(cl_device_id &device_id, cl_context &context) { - device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); - context = CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); -} - - -bool compare_results(uint8_t *a, uint8_t *b, int len, int stride, int width, int height, uint8_t *rgb) { - int min_diff = 0., max_diff = 0., max_e = 0.; - int e1 = 0, e0 = 0; - int e0y = 0, e0u = 0, e0v = 0, e1y = 0, e1u = 0, e1v = 0; - int max_e_i = 0; - for (int i = 0;i < len;i++) { - int e = ((int)a[i]) - ((int)b[i]); - if(e < min_diff) { - min_diff = e; - } - if(e > max_diff) { - max_diff = e; - } - int e_abs = std::abs(e); - if(e_abs > max_e) { - max_e = e_abs; - max_e_i = i; - } - if(e_abs < 1) { - e0++; - if(i < stride * height) - e0y++; - else if(i < stride * height + stride * height / 4) - e0u++; - else - e0v++; - } else { - e1++; - if(i < stride * height) - e1y++; - else if(i < stride * height + stride * height / 4) - e1u++; - else - e1v++; - } - } - //printf("max diff : %d, min diff : %d, e < 1: %d, e >= 1: %d\n", max_diff, min_diff, e0, e1); - //printf("Y: e < 1: %d, e >= 1: %d, U: e < 1: %d, e >= 1: %d, V: e < 1: %d, e >= 1: %d\n", e0y, e1y, e0u, e1u, e0v, e1v); - if(max_e <= MAXE) { - return true; - } - int row = max_e_i / stride; - if(row < height) { - printf("max error is Y: %d = (libyuv: %u - cl: %u), row: %d, col: %d\n", max_e, a[max_e_i], b[max_e_i], row, max_e_i % stride); - } else if(row >= height && row < (height + height / 4)) { - printf("max error is U: %d = %u - %u, row: %d, col: %d\n", max_e, a[max_e_i], b[max_e_i], (row - height) / 2, max_e_i % stride / 2); - } else { - printf("max error is V: %d = %u - %u, row: %d, col: %d\n", max_e, a[max_e_i], b[max_e_i], (row - height - height / 4) / 2, max_e_i % stride / 2); - } - return false; -} - -int main(int argc, char** argv) { - srand(1337); - - cl_device_id device_id; - cl_context context; - cl_init(device_id, context) ; - - int err; - const cl_queue_properties props[] = {0}; //CL_QUEUE_PRIORITY_KHR, CL_QUEUE_PRIORITY_HIGH_KHR, 0}; - cl_command_queue q = clCreateCommandQueueWithProperties(context, device_id, props, &err); - if(err != 0) { - std::cout << "clCreateCommandQueueWithProperties error: " << err << std::endl; - } - - int width = 1164; - int height = 874; - - int opt = 0; - while ((opt = getopt(argc, argv, "f")) != -1) - { - switch (opt) - { - case 'f': - std::cout << "Using front camera dimensions" << std::endl; - int width = 1152; - int height = 846; - } - } - - std::cout << "Width: " << width << " Height: " << height << std::endl; - uint8_t *rgb_frame = new uint8_t[width * height * 3]; - - - RGBToYUVState rgb_to_yuv_state; - rgb_to_yuv_init(&rgb_to_yuv_state, context, device_id, width, height, width * 3); - - int frame_yuv_buf_size = width * height * 3 / 2; - cl_mem yuv_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, frame_yuv_buf_size, (void*)NULL, &err)); - uint8_t *frame_yuv_buf = new uint8_t[frame_yuv_buf_size]; - uint8_t *frame_yuv_ptr_y = frame_yuv_buf; - uint8_t *frame_yuv_ptr_u = frame_yuv_buf + (width * height); - uint8_t *frame_yuv_ptr_v = frame_yuv_ptr_u + ((width/2) * (height/2)); - - cl_mem rgb_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, width * height * 3, (void*)NULL, &err)); - int mismatched = 0; - int counter = 0; - srand (time(NULL)); - - for (int i = 0; i < 100; i++) { - for (int i = 0; i < width * height * 3; i++) { - rgb_frame[i] = (uint8_t)rand(); - } - - double t1 = millis_since_boot(); - libyuv::RGB24ToI420((uint8_t*)rgb_frame, width * 3, - frame_yuv_ptr_y, width, - frame_yuv_ptr_u, width/2, - frame_yuv_ptr_v, width/2, - width, height); - double t2 = millis_since_boot(); - //printf("Libyuv: rgb to yuv: %.2fms\n", t2-t1); - - clEnqueueWriteBuffer(q, rgb_cl, CL_TRUE, 0, width * height * 3, (void *)rgb_frame, 0, NULL, NULL); - t1 = millis_since_boot(); - rgb_to_yuv_queue(&rgb_to_yuv_state, q, rgb_cl, yuv_cl); - t2 = millis_since_boot(); - - //printf("OpenCL: rgb to yuv: %.2fms\n", t2-t1); - uint8_t *yyy = (uint8_t *)clEnqueueMapBuffer(q, yuv_cl, CL_TRUE, - CL_MAP_READ, 0, frame_yuv_buf_size, - 0, NULL, NULL, &err); - if(!compare_results(frame_yuv_ptr_y, yyy, frame_yuv_buf_size, width, width, height, (uint8_t*)rgb_frame)) - mismatched++; - clEnqueueUnmapMemObject(q, yuv_cl, yyy, 0, NULL, NULL); - - // std::this_thread::sleep_for(std::chrono::milliseconds(20)); - if(counter++ % 100 == 0) - printf("Matched: %d, Mismatched: %d\n", counter - mismatched, mismatched); - - } - printf("Matched: %d, Mismatched: %d\n", counter - mismatched, mismatched); - - delete[] frame_yuv_buf; - rgb_to_yuv_destroy(&rgb_to_yuv_state); - clReleaseContext(context); - delete[] rgb_frame; - - if (mismatched == 0) - return 0; - else - return -1; -} diff --git a/selfdrive/car/CARS_template.md b/selfdrive/car/CARS_template.md index 891445a55..e448c19d3 100644 --- a/selfdrive/car/CARS_template.md +++ b/selfdrive/car/CARS_template.md @@ -1,20 +1,15 @@ {% set footnote_tag = '[{}](#footnotes)' -%} -{% set star_icon = '' -%} +{% set star_icon = '[![star](assets/icon-star-{}.svg)](##)' -%} + + # Supported Cars A supported vehicle is one that just works when you install a comma device. Every car performs differently with openpilot, but all supported cars should provide a better experience than any stock system. -Cars are organized into three tiers: - -{% for tier in tiers %} -- {{tier.name.title()}} - {{tier.value}} -{% endfor %} - -How We Rate The Cars ---- +## How We Rate The Cars -{% for star_row in star_descriptions.values() %} +{% for star_row in STAR_DESCRIPTIONS.values() %} {% for name, stars in star_row.items() %} ### {{name}} {% for star, description in stars %} @@ -23,20 +18,16 @@ How We Rate The Cars {% endfor %} {% endfor %} -**All supported cars can move between the tiers as support changes.** -{% for tier, cars in tiers.items() %} -# {{tier.name.title()}} - {{cars | length}} cars +# {{all_car_info | length}} Supported Cars |{{Column | map(attribute='value') | join('|')}}| -|---|---|---|:---:|:---:|:---:|:---:|:---:| -{% for car_info in cars %} +|---|---|---|:---:|:---:|:---:|:---:| +{% for car_info in all_car_info %} |{% for column in Column %}{{car_info.get_column(column, star_icon, footnote_tag)}}|{% endfor %} {% endfor %} -{% endfor %} - {% for footnote in footnotes %} {{loop.index}}{{footnote}}
@@ -44,3 +35,42 @@ How We Rate The Cars ## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). + +# Don't see your car here? + +**openpilot can support many more cars than it currently does.** There are a few reasons your car may not be supported. +If your car doesn't fit into any of the incompatibility criteria here, then there's a good chance it can be supported! We're adding support for new cars all the time. We don't have a roadmap for car support, and in fact, most car support comes from users like you! + +### Which cars are able to be supported? + +openpilot uses the existing steering, gas, and brake interfaces in your car. If your car lacks any one of these interfaces, openpilot will not be able to control the car. If your car has any form of [LKAS](https://en.wikipedia.org/wiki/Automated_Lane_Keeping_Systems)/[LCA](https://en.wikipedia.org/wiki/Lane_centering) and [ACC](https://en.wikipedia.org/wiki/Adaptive_cruise_control), then it almost certainly has these interfaces. These interfaces generally started shipping on cars around 2016. + +If your car has the following packages or features, then it's a good candidate for support. If it does not, then it's unlikely able to be supported. + +| Make | Required Package/Features | +| ---- | ------------------------- | +| Acura | Any car with AcuraWatch Plus will work. AcuraWatch Plus comes standard on many newer models. | +| Honda | Any car with Honda Sensing will work. Honda Sensing comes standard on many newer models. | +| Subaru | Any car with EyeSight will work. EyeSight comes standard on many newer models. | +| Nissan | Any car with ProPILOT will likely work. | +| Toyota & Lexus | Any car that has Toyota/Lexus Safety Sense with "Lane Departure Alert with Steering Assist (LDA w/SA)" and/or "Lane Tracing Assist (LTA)" will work. Note that LDA without Steering Assist will not work. These features come standard on most newer models. | +| Hyundai, Kia, & Genesis | Any car with Smart Cruise Control (SCC) and Lane Following Assist (LFA) or Lane Keeping Assist (LKAS) will work. LKAS/LFA comes standard on most newer models. Any form of SCC will work, such as NSCC. | +| Chrysler, Jeep, & Ram | Any car with LaneSense and Adaptive Cruise Control will likely work. These come standard on many newer models. | + +### FlexRay + +All the cars that openpilot supports use a [CAN bus](https://en.wikipedia.org/wiki/CAN_bus) for communication between all the car's computers, however a CAN bus isn't the only way that the cars in your computer can communicate. Most, if not all, vehicles from the following manufacturers use [FlexRay](https://en.wikipedia.org/wiki/FlexRay) instead of a CAN bus: **BMW, Mercedes, Audi, Land Rover, and some Volvo**. These cars may one day be supported, but we have no immediate plans to support FlexRay. + +### Toyota Security + +Specific new Toyota models are shipping with a new message authentication method that openpilot does not yet support. +So far, this list includes: +* Toyota RAV4 Prime 2021+ +* Toyota Sienna 2021+ +* Toyota Venza 2021+ +* Toyota Sequoia 2023+ +* Toyota Tundra 2022+ +* Toyota Corolla Cross 2022+ (only US model) +* Lexus NX 2022+ +* Toyota bZ4x 2023+ +* Subaru Solterra 2023+ diff --git a/selfdrive/car/body/bodycan.py b/selfdrive/car/body/bodycan.py index cc448d53d..580e5025a 100644 --- a/selfdrive/car/body/bodycan.py +++ b/selfdrive/car/body/bodycan.py @@ -1,9 +1,7 @@ -def create_control(packer, torque_l, torque_r, idx): - can_bus = 0 - +def create_control(packer, torque_l, torque_r): values = { "TORQUE_L": torque_l, "TORQUE_R": torque_r, } - return packer.make_can_msg("TORQUE_CMD", can_bus, values, idx) + return packer.make_can_msg("TORQUE_CMD", 0, values) diff --git a/selfdrive/car/body/carcontroller.py b/selfdrive/car/body/carcontroller.py index 5714445f6..0d5d780bd 100644 --- a/selfdrive/car/body/carcontroller.py +++ b/selfdrive/car/body/carcontroller.py @@ -1,8 +1,8 @@ import numpy as np from common.realtime import DT_CTRL -from selfdrive.car.body import bodycan 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 @@ -14,7 +14,7 @@ MAX_POS_INTEGRATOR = 0.2 # meters MAX_TURN_INTEGRATOR = 0.1 # meters -class CarController(): +class CarController: def __init__(self, dbc_name, CP, VM): self.frame = 0 self.packer = CANPacker(dbc_name) @@ -61,8 +61,8 @@ class CarController(): speed_diff_measured = SPEED_FROM_RPM * (CS.out.wheelSpeeds.fl - CS.out.wheelSpeeds.fr) turn_error = speed_diff_measured - speed_diff_desired - freeze_integrator = ((turn_error < 0 and self.turn_pid.error_integral <= -MAX_POS_INTEGRATOR) or - (turn_error > 0 and self.turn_pid.error_integral >= MAX_POS_INTEGRATOR)) + freeze_integrator = ((turn_error < 0 and self.turn_pid.error_integral <= -MAX_TURN_INTEGRATOR) or + (turn_error > 0 and self.turn_pid.error_integral >= MAX_TURN_INTEGRATOR)) torque_diff = self.turn_pid.update(turn_error, freeze_integrator=freeze_integrator) # Combine 2 PIDs outputs @@ -80,7 +80,7 @@ class CarController(): torque_l = int(np.clip(self.torque_l_filtered, -MAX_TORQUE, MAX_TORQUE)) can_sends = [] - can_sends.append(bodycan.create_control(self.packer, torque_l, torque_r, self.frame // 2)) + can_sends.append(bodycan.create_control(self.packer, torque_l, torque_r)) new_actuators = CC.actuators.copy() new_actuators.accel = torque_l diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index f98a8878e..da28c3b9e 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -7,8 +7,8 @@ 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, VIN_UNKNOWN -from selfdrive.car.fw_versions import get_fw_versions, match_fw_to_car +from selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN +from selfdrive.car.fw_versions import get_fw_versions_ordered, match_fw_to_car, get_present_ecus from system.swaglog import cloudlog import cereal.messaging as messaging from selfdrive.car import gen_empty_fingerprint @@ -79,6 +79,7 @@ interfaces = load_interfaces(interface_names) def fingerprint(logcan, sendcan): fixed_fingerprint = os.environ.get('FINGERPRINT', "") skip_fw_query = os.environ.get('SKIP_FW_QUERY', False) + ecu_rx_addrs = set() if not fixed_fingerprint and not skip_fw_query: # Vin query only reliably works thorugh OBDII @@ -92,19 +93,20 @@ def fingerprint(logcan, sendcan): if False and cached_params is not None and len(cached_params.carFw) > 0 and cached_params.carVin is not VIN_UNKNOWN: cloudlog.warning("Using cached CarParams") - vin = cached_params.carVin + vin, vin_rx_addr = cached_params.carVin, 0 car_fw = list(cached_params.carFw) else: cloudlog.warning("Getting VIN & FW versions") - _, vin = get_vin(logcan, sendcan, bus) - car_fw = get_fw_versions(logcan, sendcan) + _, vin_rx_addr, vin = get_vin(logcan, sendcan, bus) + ecu_rx_addrs = get_present_ecus(logcan, sendcan) + car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs) exact_fw_match, fw_candidates = match_fw_to_car(car_fw) else: - vin = VIN_UNKNOWN + vin, vin_rx_addr = VIN_UNKNOWN, 0 exact_fw_match, fw_candidates, car_fw = True, set(), [] - if len(vin) != 17: + if not is_valid_vin(vin): cloudlog.event("Malformed VIN", vin=vin, error=True) vin = VIN_UNKNOWN cloudlog.warning("VIN %s", vin) @@ -113,7 +115,7 @@ def fingerprint(logcan, sendcan): finger = gen_empty_fingerprint() candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1 frame = 0 - frame_fingerprint = 25 # 0.25s + frame_fingerprint = 100 # 1s car_fingerprint = None done = False @@ -163,8 +165,8 @@ def fingerprint(logcan, sendcan): car_fingerprint = fixed_fingerprint source = car.CarParams.FingerprintSource.fixed - cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, - source=source, fuzzy=not exact_match, fw_count=len(car_fw)) + cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, source=source, fuzzy=not exact_match, + fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), vin_rx_addr=vin_rx_addr, 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 3d6295d77..22abf1b67 100644 --- a/selfdrive/car/chrysler/carcontroller.py +++ b/selfdrive/car/chrysler/carcontroller.py @@ -1,8 +1,8 @@ -from cereal import car from opendbc.can.packer import CANPacker +from common.realtime import DT_CTRL from selfdrive.car import apply_toyota_steer_torque_limits -from selfdrive.car.chrysler.chryslercan import create_lkas_hud, create_lkas_command, create_wheel_buttons -from selfdrive.car.chrysler.values import CAR, CarControllerParams +from selfdrive.car.chrysler.chryslercan import create_lkas_hud, create_lkas_command, create_cruise_buttons +from selfdrive.car.chrysler.values import CAR, RAM_CARS, CarControllerParams class CarController: @@ -10,61 +10,72 @@ class CarController: self.CP = CP self.apply_steer_last = 0 self.frame = 0 - self.prev_lkas_frame = -1 + self.hud_count = 0 - self.car_fingerprint = CP.carFingerprint - self.gone_fast_yet = False - self.steer_rate_limited = False + self.last_lkas_falling_edge = 0 + self.lkas_control_bit_prev = False + self.last_button_frame = 0 self.packer = CANPacker(dbc_name) + self.params = CarControllerParams(CP) def update(self, CC, CS): - # this seems needed to avoid steering faults and to force the sync with the EPS counter - if self.prev_lkas_frame == CS.lkas_counter: - return car.CarControl.Actuators.new_message(), [] - - actuators = CC.actuators - - # steer torque - new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX)) - apply_steer = apply_toyota_steer_torque_limits(new_steer, self.apply_steer_last, - CS.out.steeringTorqueEps, CarControllerParams) - self.steer_rate_limited = new_steer != apply_steer - - moving_fast = CS.out.vEgo > self.CP.minSteerSpeed # for status message - if CS.out.vEgo > (self.CP.minSteerSpeed - 0.5): # for command high bit - self.gone_fast_yet = True - elif self.car_fingerprint in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019): + can_sends = [] + + # TODO: can we make this more sane? why is it different for all the cars? + lkas_control_bit = self.lkas_control_bit_prev + if CS.out.vEgo > self.CP.minSteerSpeed: + lkas_control_bit = True + elif self.CP.carFingerprint in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019): if CS.out.vEgo < (self.CP.minSteerSpeed - 3.0): - self.gone_fast_yet = False # < 14.5m/s stock turns off this bit, but fine down to 13.5 - lkas_active = moving_fast and CC.enabled + lkas_control_bit = False + elif self.CP.carFingerprint in RAM_CARS: + if CS.out.vEgo < (self.CP.minSteerSpeed - 0.5): + lkas_control_bit = False - if not lkas_active: - apply_steer = 0 + # EPS faults if LKAS re-enables too quickly + lkas_control_bit = lkas_control_bit and (self.frame - self.last_lkas_falling_edge > 200) + lkas_active = CC.latActive and self.lkas_control_bit_prev - self.apply_steer_last = apply_steer + # *** control msgs *** - can_sends = [] + # cruise buttons + if (self.frame - self.last_button_frame)*DT_CTRL > 0.05: + das_bus = 2 if self.CP.carFingerprint in RAM_CARS else 0 - # *** control msgs *** + # 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)) - if CC.cruiseControl.cancel: - can_sends.append(create_wheel_buttons(self.packer, CS.button_counter + 1, 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)) - # LKAS_HEARTBIT is forwarded by Panda so no need to send it here. - # frame is 100Hz (0.01s period) - if self.frame % 25 == 0: # 0.25s period + # HUD alerts + if self.frame % 25 == 0: if CS.lkas_car_model != -1: - can_sends.append(create_lkas_hud(self.packer, CS.out.gearShifter, lkas_active, - CC.hudControl.visualAlert, self.hud_count, CS.lkas_car_model)) + 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)) self.hud_count += 1 - can_sends.append(create_lkas_command(self.packer, int(apply_steer), self.gone_fast_yet, CS.lkas_counter)) + # steering + if self.frame % 2 == 0: + # steer torque + new_steer = int(round(CC.actuators.steer * self.params.STEER_MAX)) + apply_steer = apply_toyota_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorqueEps, self.params) + if not lkas_active: + 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)) self.frame += 1 - self.prev_lkas_frame = CS.lkas_counter + if not lkas_control_bit and self.lkas_control_bit_prev: + self.last_lkas_falling_edge = self.frame + self.lkas_control_bit_prev = lkas_control_bit - new_actuators = actuators.copy() - new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + new_actuators = CC.actuators.copy() + new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX return new_actuators, can_sends diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index 8ddf1c868..fefd351a2 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -3,150 +3,203 @@ from 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 +from selfdrive.car.chrysler.values import DBC, STEER_THRESHOLD, RAM_CARS class CarState(CarStateBase): def __init__(self, CP): super().__init__(CP) + self.CP = CP can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) - self.shifter_values = can_define.dv["GEAR"]["PRNDL"] + + self.auto_high_beam = 0 + self.button_counter = 0 + self.lkas_car_model = -1 + + if CP.carFingerprint in RAM_CARS: + self.shifter_values = can_define.dv["Transmission_Status"]["Gear_State"] + else: + self.shifter_values = can_define.dv["GEAR"]["PRNDL"] def update(self, cp, cp_cam): ret = car.CarState.new_message() - self.frame = int(cp.vl["EPS_STATUS"]["COUNTER"]) + # lock info + ret.doorOpen = any([cp.vl["BCM_1"]["DOOR_OPEN_FL"], + cp.vl["BCM_1"]["DOOR_OPEN_FR"], + cp.vl["BCM_1"]["DOOR_OPEN_RL"], + cp.vl["BCM_1"]["DOOR_OPEN_RR"]]) + ret.seatbeltUnlatched = cp.vl["ORC_1"]["SEATBELT_DRIVER_UNLATCHED"] == 1 - ret.doorOpen = any([cp.vl["DOORS"]["DOOR_OPEN_FL"], - cp.vl["DOORS"]["DOOR_OPEN_FR"], - cp.vl["DOORS"]["DOOR_OPEN_RL"], - cp.vl["DOORS"]["DOOR_OPEN_RR"]]) - ret.seatbeltUnlatched = cp.vl["SEATBELT_STATUS"]["SEATBELT_DRIVER_UNLATCHED"] == 1 - - ret.brakePressed = cp.vl["BRAKE_2"]["BRAKE_PRESSED_2"] == 5 # human-only + # brake pedal ret.brake = 0 - ret.gas = cp.vl["ACCEL_GAS_134"]["ACCEL_134"] - ret.gasPressed = ret.gas > 1e-5 + ret.brakePressed = cp.vl["ESP_1"]['Brake_Pedal_State'] == 1 # Physical brake pedal switch - ret.espDisabled = (cp.vl["TRACTION_BUTTON"]["TRACTION_OFF"] == 1) + # gas pedal + ret.gas = cp.vl["ECM_5"]["Accelerator_Position"] + ret.gasPressed = ret.gas > 1e-5 + # car speed + if self.CP.carFingerprint in RAM_CARS: + ret.vEgoRaw = cp.vl["ESP_8"]["Vehicle_Speed"] * CV.KPH_TO_MS + ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(cp.vl["Transmission_Status"]["Gear_State"], None)) + else: + ret.vEgoRaw = (cp.vl["SPEED_1"]["SPEED_LEFT"] + cp.vl["SPEED_1"]["SPEED_RIGHT"]) / 2. + ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(cp.vl["GEAR"]["PRNDL"], None)) + ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) + ret.standstill = not ret.vEgoRaw > 0.001 ret.wheelSpeeds = self.get_wheel_speeds( - cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"], - cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"], - cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"], - cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"], + cp.vl["ESP_6"]["WHEEL_SPEED_FL"], + cp.vl["ESP_6"]["WHEEL_SPEED_FR"], + cp.vl["ESP_6"]["WHEEL_SPEED_RL"], + cp.vl["ESP_6"]["WHEEL_SPEED_RR"], unit=1, ) - ret.vEgoRaw = (cp.vl["SPEED_1"]["SPEED_LEFT"] + cp.vl["SPEED_1"]["SPEED_RIGHT"]) / 2. - ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) - ret.standstill = not ret.vEgoRaw > 0.001 + # button presses ret.leftBlinker = cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 1 ret.rightBlinker = cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 2 - ret.steeringAngleDeg = cp.vl["STEERING"]["STEER_ANGLE"] + ret.genericToggle = cp.vl["STEERING_LEVERS"]["HIGH_BEAM_PRESSED"] == 1 + + # steering wheel + ret.steeringAngleDeg = cp.vl["STEERING"]["STEERING_ANGLE"] + cp.vl["STEERING"]["STEERING_ANGLE_HP"] ret.steeringRateDeg = cp.vl["STEERING"]["STEERING_RATE"] - ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(cp.vl["GEAR"]["PRNDL"], None)) + ret.steeringTorque = cp.vl["EPS_2"]["COLUMN_TORQUE"] + ret.steeringTorqueEps = cp.vl["EPS_2"]["EPS_TORQUE_MOTOR"] + ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD - ret.cruiseState.enabled = cp.vl["ACC_2"]["ACC_STATUS_2"] == 7 # ACC is green. - ret.cruiseState.available = ret.cruiseState.enabled # FIXME: for now same as enabled - ret.cruiseState.speed = cp.vl["DASHBOARD"]["ACC_SPEED_CONFIG_KPH"] * CV.KPH_TO_MS - # CRUISE_STATE is a three bit msg, 0 is off, 1 and 2 are Non-ACC mode, 3 and 4 are ACC mode, find if there are other states too - ret.cruiseState.nonAdaptive = cp.vl["DASHBOARD"]["CRUISE_STATE"] in (1, 2) - ret.accFaulted = cp.vl["ACC_2"]["ACC_FAULTED"] != 0 + # cruise state + cp_cruise = cp_cam if self.CP.carFingerprint in RAM_CARS else cp - ret.steeringTorque = cp.vl["EPS_STATUS"]["TORQUE_DRIVER"] - ret.steeringTorqueEps = cp.vl["EPS_STATUS"]["TORQUE_MOTOR"] - ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD - steer_state = cp.vl["EPS_STATUS"]["LKAS_STATE"] - ret.steerFaultPermanent = steer_state == 4 or (steer_state == 0 and ret.vEgo > self.CP.minSteerSpeed) + ret.cruiseState.available = cp_cruise.vl["DAS_3"]["ACC_AVAILABLE"] == 1 + ret.cruiseState.enabled = cp_cruise.vl["DAS_3"]["ACC_ACTIVE"] == 1 + ret.cruiseState.speed = cp_cruise.vl["DAS_4"]["ACC_SET_SPEED_KPH"] * CV.KPH_TO_MS + ret.cruiseState.nonAdaptive = cp_cruise.vl["DAS_4"]["ACC_STATE"] in (1, 2) # 1 NormalCCOn and 2 NormalCCSet + ret.cruiseState.standstill = cp_cruise.vl["DAS_3"]["ACC_STANDSTILL"] == 1 + ret.accFaulted = cp_cruise.vl["DAS_3"]["ACC_FAULTED"] != 0 - ret.genericToggle = bool(cp.vl["STEERING_LEVERS"]["HIGH_BEAM_FLASH"]) + if self.CP.carFingerprint in RAM_CARS: + self.auto_high_beam = cp_cam.vl["DAS_6"]['AUTO_HIGH_BEAM_ON'] # Auto High Beam isn't Located in this message on chrysler or jeep currently located in 729 message + ret.steerFaultTemporary = cp.vl["EPS_3"]["DASM_FAULT"] == 1 + else: + ret.steerFaultPermanent = cp.vl["EPS_2"]["LKAS_STATE"] == 4 + # blindspot sensors if self.CP.enableBsm: - ret.leftBlindspot = cp.vl["BLIND_SPOT_WARNINGS"]["BLIND_SPOT_LEFT"] == 1 - ret.rightBlindspot = cp.vl["BLIND_SPOT_WARNINGS"]["BLIND_SPOT_RIGHT"] == 1 + ret.leftBlindspot = cp.vl["BSM_1"]["LEFT_STATUS"] == 1 + ret.rightBlindspot = cp.vl["BSM_1"]["RIGHT_STATUS"] == 1 - self.lkas_counter = cp_cam.vl["LKAS_COMMAND"]["COUNTER"] - self.lkas_car_model = cp_cam.vl["LKAS_HUD"]["CAR_MODEL"] - self.lkas_status_ok = cp_cam.vl["LKAS_HEARTBIT"]["LKAS_STATUS_OK"] - self.button_counter = cp.vl["WHEEL_BUTTONS"]["COUNTER"] + self.lkas_car_model = cp_cam.vl["DAS_6"]["CAR_MODEL"] + self.button_counter = cp.vl["CRUISE_BUTTONS"]["COUNTER"] return ret + @staticmethod + def get_cruise_signals(): + signals = [ + ("ACC_AVAILABLE", "DAS_3"), + ("ACC_ACTIVE", "DAS_3"), + ("ACC_FAULTED", "DAS_3"), + ("ACC_STANDSTILL", "DAS_3"), + ("COUNTER", "DAS_3"), + ("ACC_SET_SPEED_KPH", "DAS_4"), + ("ACC_STATE", "DAS_4"), + ] + checks = [ + ("DAS_3", 50), + ("DAS_4", 50), + ] + return signals, checks + @staticmethod def get_can_parser(CP): signals = [ # sig_name, sig_address - ("PRNDL", "GEAR"), - ("DOOR_OPEN_FL", "DOORS"), - ("DOOR_OPEN_FR", "DOORS"), - ("DOOR_OPEN_RL", "DOORS"), - ("DOOR_OPEN_RR", "DOORS"), - ("BRAKE_PRESSED_2", "BRAKE_2"), - ("ACCEL_134", "ACCEL_GAS_134"), - ("SPEED_LEFT", "SPEED_1"), - ("SPEED_RIGHT", "SPEED_1"), - ("WHEEL_SPEED_FL", "WHEEL_SPEEDS"), - ("WHEEL_SPEED_RR", "WHEEL_SPEEDS"), - ("WHEEL_SPEED_RL", "WHEEL_SPEEDS"), - ("WHEEL_SPEED_FR", "WHEEL_SPEEDS"), - ("STEER_ANGLE", "STEERING"), + ("DOOR_OPEN_FL", "BCM_1"), + ("DOOR_OPEN_FR", "BCM_1"), + ("DOOR_OPEN_RL", "BCM_1"), + ("DOOR_OPEN_RR", "BCM_1"), + ("Brake_Pedal_State", "ESP_1"), + ("Accelerator_Position", "ECM_5"), + ("WHEEL_SPEED_FL", "ESP_6"), + ("WHEEL_SPEED_RR", "ESP_6"), + ("WHEEL_SPEED_RL", "ESP_6"), + ("WHEEL_SPEED_FR", "ESP_6"), + ("STEERING_ANGLE", "STEERING"), + ("STEERING_ANGLE_HP", "STEERING"), ("STEERING_RATE", "STEERING"), ("TURN_SIGNALS", "STEERING_LEVERS"), - ("ACC_STATUS_2", "ACC_2"), - ("ACC_FAULTED", "ACC_2"), - ("HIGH_BEAM_FLASH", "STEERING_LEVERS"), - ("ACC_SPEED_CONFIG_KPH", "DASHBOARD"), - ("CRUISE_STATE", "DASHBOARD"), - ("TORQUE_DRIVER", "EPS_STATUS"), - ("TORQUE_MOTOR", "EPS_STATUS"), - ("LKAS_STATE", "EPS_STATUS"), - ("COUNTER", "EPS_STATUS",), - ("TRACTION_OFF", "TRACTION_BUTTON"), - ("SEATBELT_DRIVER_UNLATCHED", "SEATBELT_STATUS"), - ("COUNTER", "WHEEL_BUTTONS"), + ("HIGH_BEAM_PRESSED", "STEERING_LEVERS"), + ("SEATBELT_DRIVER_UNLATCHED", "ORC_1"), + ("COUNTER", "EPS_2",), + ("COLUMN_TORQUE", "EPS_2"), + ("EPS_TORQUE_MOTOR", "EPS_2"), + ("LKAS_STATE", "EPS_2"), + ("COUNTER", "CRUISE_BUTTONS"), ] checks = [ # sig_address, frequency - ("BRAKE_2", 50), - ("EPS_STATUS", 100), - ("SPEED_1", 100), - ("WHEEL_SPEEDS", 50), + ("ESP_1", 50), + ("EPS_2", 100), + ("ESP_6", 50), ("STEERING", 100), - ("ACC_2", 50), - ("GEAR", 50), - ("ACCEL_GAS_134", 50), - ("WHEEL_BUTTONS", 50), - ("DASHBOARD", 15), + ("ECM_5", 50), + ("CRUISE_BUTTONS", 50), ("STEERING_LEVERS", 10), - ("SEATBELT_STATUS", 2), - ("DOORS", 1), - ("TRACTION_BUTTON", 1), + ("ORC_1", 2), + ("BCM_1", 1), ] if CP.enableBsm: signals += [ - ("BLIND_SPOT_RIGHT", "BLIND_SPOT_WARNINGS"), - ("BLIND_SPOT_LEFT", "BLIND_SPOT_WARNINGS"), + ("RIGHT_STATUS", "BSM_1"), + ("LEFT_STATUS", "BSM_1"), + ] + checks.append(("BSM_1", 2)) + + if CP.carFingerprint in RAM_CARS: + signals += [ + ("DASM_FAULT", "EPS_3"), + ("Vehicle_Speed", "ESP_8"), + ("Gear_State", "Transmission_Status"), + ] + checks += [ + ("ESP_8", 50), + ("EPS_3", 50), + ("Transmission_Status", 50), + ] + else: + signals += [ + ("PRNDL", "GEAR"), + ("SPEED_LEFT", "SPEED_1"), + ("SPEED_RIGHT", "SPEED_1"), ] - checks.append(("BLIND_SPOT_WARNINGS", 2)) + checks += [ + ("GEAR", 50), + ("SPEED_1", 100), + ] + signals += CarState.get_cruise_signals()[0] + checks += CarState.get_cruise_signals()[1] return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 0) @staticmethod def get_cam_can_parser(CP): signals = [ - # sig_name, sig_address - ("COUNTER", "LKAS_COMMAND"), - ("CAR_MODEL", "LKAS_HUD"), - ("LKAS_STATUS_OK", "LKAS_HEARTBIT") + # sig_name, sig_address, default + ("CAR_MODEL", "DAS_6"), ] checks = [ - ("LKAS_COMMAND", 100), - ("LKAS_HEARTBIT", 10), - ("LKAS_HUD", 4), + ("DAS_6", 4), ] + if CP.carFingerprint in RAM_CARS: + signals += [ + ("AUTO_HIGH_BEAM_ON", "DAS_6"), + ] + signals += CarState.get_cruise_signals()[0] + checks += CarState.get_cruise_signals()[1] + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2) diff --git a/selfdrive/car/chrysler/chryslercan.py b/selfdrive/car/chrysler/chryslercan.py index 896a6b15d..10ed73e9f 100644 --- a/selfdrive/car/chrysler/chryslercan.py +++ b/selfdrive/car/chrysler/chryslercan.py @@ -1,57 +1,71 @@ from cereal import car -from selfdrive.car import make_can_msg - +from selfdrive.car.chrysler.values import RAM_CARS GearShifter = car.CarState.GearShifter VisualAlert = car.CarControl.HUDControl.VisualAlert -def create_lkas_hud(packer, gear, lkas_active, hud_alert, hud_count, lkas_car_model): - # LKAS_HUD 0x2a6 (678) Controls what lane-keeping icon is displayed. +def create_lkas_hud(packer, CP, lkas_active, hud_alert, hud_count, car_model, auto_high_beam): + # LKAS_HUD - Controls what lane-keeping icon is displayed + + # == Color == + # 0 hidden? + # 1 white + # 2 green + # 3 ldw - if hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw): - msg = b'\x00\x00\x00\x03\x00\x00\x00\x00' - return make_can_msg(0x2a6, msg, 0) + # == Lines == + # 03 white Lines + # 04 grey lines + # 09 left lane close + # 0A right lane close + # 0B left Lane very close + # 0C right Lane very close + # 0D left cross cross + # 0E right lane cross - color = 1 # default values are for park or neutral in 2017 are 0 0, but trying 1 1 for 2019 - lines = 1 - alerts = 0 + # == Alerts == + # 7 Normal + # 6 lane departure place hands on wheel + + color = 2 if lkas_active else 1 + lines = 3 if lkas_active else 0 + alerts = 7 if lkas_active else 0 if hud_count < (1 * 4): # first 3 seconds, 4Hz alerts = 1 - # CAR.PACIFICA_2018_HYBRID and CAR.PACIFICA_2019_HYBRID - # had color = 1 and lines = 1 but trying 2017 hybrid style for now. - if gear in (GearShifter.drive, GearShifter.reverse, GearShifter.low): - if lkas_active: - color = 2 # control active, display green. - lines = 6 - else: - color = 1 # control off, display white. - lines = 1 + + if hud_alert in (VisualAlert.ldw, VisualAlert.steerRequired): + color = 4 + lines = 0 + alerts = 6 values = { - "LKAS_ICON_COLOR": color, # byte 0, last 2 bits - "CAR_MODEL": lkas_car_model, # byte 1 - "LKAS_LANE_LINES": lines, # byte 2, last 4 bits - "LKAS_ALERTS": alerts, # byte 3, last 4 bits - } + "LKAS_ICON_COLOR": color, + "CAR_MODEL": car_model, + "LKAS_LANE_LINES": lines, + "LKAS_ALERTS": alerts, + } + + if CP.carFingerprint in RAM_CARS: + values['AUTO_HIGH_BEAM_ON'] = auto_high_beam - return packer.make_can_msg("LKAS_HUD", 0, values) # 0x2a6 + return packer.make_can_msg("DAS_6", 0, values) -def create_lkas_command(packer, apply_steer, moving_fast, frame): - # LKAS_COMMAND 0x292 (658) Lane-keeping signal to turn the wheel. +def create_lkas_command(packer, CP, apply_steer, lkas_control_bit): + # LKAS_COMMAND Lane-keeping signal to turn the wheel + enabled_val = 2 if CP.carFingerprint in RAM_CARS else 1 values = { - "LKAS_STEERING_TORQUE": apply_steer, - "LKAS_HIGH_TORQUE": int(moving_fast), - "COUNTER": frame % 0x10, + "STEERING_TORQUE": apply_steer, + "LKAS_CONTROL_BIT": enabled_val if lkas_control_bit else 0, } return packer.make_can_msg("LKAS_COMMAND", 0, values) -def create_wheel_buttons(packer, frame, cancel=False): - # WHEEL_BUTTONS (571) Message sent to cancel ACC. +def create_cruise_buttons(packer, frame, bus, cancel=False, resume=False): values = { - "ACC_CANCEL": cancel, - "COUNTER": frame % 0x10 + "ACC_Cancel": cancel, + "ACC_Resume": resume, + "COUNTER": frame % 0x10, } - return packer.make_can_msg("WHEEL_BUTTONS", 0, values) + return packer.make_can_msg("CRUISE_BUTTONS", bus, values) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 817a5b387..9bb22b607 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 from cereal import car -from selfdrive.car.chrysler.values import CAR +from panda import Panda from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car.chrysler.values import CAR, DBC, RAM_HD, RAM_DT from selfdrive.car.interfaces import CarInterfaceBase @@ -10,30 +11,71 @@ class CarInterface(CarInterfaceBase): def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, disable_radar=False): ret = CarInterfaceBase.get_std_params(candidate, fingerprint) ret.carName = "chrysler" - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.chrysler)] - # Speed conversion: 20, 45 mph - ret.wheelbase = 3.089 # in meters for Pacifica Hybrid 2017 - ret.steerRatio = 16.2 # Pacifica Hybrid 2017 - ret.mass = 2242. + STD_CARGO_KG # kg curb weight Pacifica Hybrid 2017 - ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] - ret.lateralTuning.pid.kf = 0.00006 # full torque for 10 deg at 80mph means 0.00007818594 + ret.dashcamOnly = candidate in RAM_HD + + ret.radarOffCan = DBC[candidate]['radar'] is None + ret.steerActuatorDelay = 0.1 ret.steerLimitTimer = 0.4 - if candidate in (CAR.JEEP_CHEROKEE, CAR.JEEP_CHEROKEE_2019): - ret.wheelbase = 2.91 # in meters - ret.steerRatio = 12.7 - ret.steerActuatorDelay = 0.2 # in seconds - - ret.centerToFront = ret.wheelbase * 0.44 + # safety config + ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.chrysler)] + if candidate in RAM_HD: + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_CHRYSLER_RAM_HD + elif candidate in RAM_DT: + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_CHRYSLER_RAM_DT ret.minSteerSpeed = 3.8 # m/s if candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019): - # TODO allow 2019 cars to steer down to 13 m/s if already engaged. + # TODO: allow 2019 cars to steer down to 13 m/s if already engaged. ret.minSteerSpeed = 17.5 # m/s 17 on the way up, 13 on the way down once engaged. + # Chrysler + if candidate in (CAR.PACIFICA_2017_HYBRID, CAR.PACIFICA_2018, CAR.PACIFICA_2018_HYBRID, CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020): + ret.mass = 2242. + STD_CARGO_KG + ret.wheelbase = 3.089 + ret.steerRatio = 16.2 # Pacifica Hybrid 2017 + ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] + ret.lateralTuning.pid.kf = 0.00006 + + # Jeep + elif candidate in (CAR.JEEP_CHEROKEE, CAR.JEEP_CHEROKEE_2019): + ret.mass = 1778 + STD_CARGO_KG + ret.wheelbase = 2.71 + ret.steerRatio = 16.7 + ret.steerActuatorDelay = 0.2 + ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] + ret.lateralTuning.pid.kf = 0.00006 + + # Ram + elif candidate == CAR.RAM_1500: + ret.steerActuatorDelay = 0.2 + ret.wheelbase = 3.88 + ret.steerRatio = 16.3 + ret.mass = 2493. + STD_CARGO_KG + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + ret.minSteerSpeed = 14.5 + if car_fw is not None: + for fw in car_fw: + if fw.ecu == 'eps' and fw.fwVersion in (b"68312176AE", b"68312176AG", b"68273275AG"): + ret.minSteerSpeed = 0. + + elif candidate == CAR.RAM_HD: + ret.steerActuatorDelay = 0.2 + ret.wheelbase = 3.785 + ret.steerRatio = 15.61 + ret.mass = 3405. + STD_CARGO_KG + ret.minSteerSpeed = 16 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, 1.0, False) + + else: + raise ValueError(f"Unsupported car: {candidate}") + + ret.centerToFront = ret.wheelbase * 0.44 + # starting with reasonable value for civic and scaling by mass and wheelbase ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) @@ -45,23 +87,23 @@ class CarInterface(CarInterfaceBase): return ret - # returns a car.CarState def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) - ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False - # events events = self.create_common_events(ret, extra_gears=[car.CarState.GearShifter.low]) - if ret.vEgo < self.CP.minSteerSpeed: + # Low speed steer alert hysteresis logic + if self.CP.minSteerSpeed > 0. and ret.vEgo < (self.CP.minSteerSpeed + 0.5): + self.low_speed_alert = True + elif ret.vEgo > (self.CP.minSteerSpeed + 1.): + self.low_speed_alert = False + if self.low_speed_alert: events.add(car.CarEvent.EventName.belowSteerSpeed) ret.events = events.to_msg() return ret - # pass in a car.CarControl - # to be called @ 100hz def apply(self, c): return self.CC.update(c, self.CS) diff --git a/selfdrive/car/chrysler/radar_interface.py b/selfdrive/car/chrysler/radar_interface.py index 8882dc2d9..348e3c363 100755 --- a/selfdrive/car/chrysler/radar_interface.py +++ b/selfdrive/car/chrysler/radar_interface.py @@ -10,6 +10,10 @@ LAST_MSG = max(RADAR_MSGS_C + RADAR_MSGS_D) NUMBER_MSGS = len(RADAR_MSGS_C) + len(RADAR_MSGS_D) def _create_radar_can_parser(car_fingerprint): + dbc = DBC[car_fingerprint]['radar'] + if dbc is None: + return None + msg_n = len(RADAR_MSGS_C) # list of [(signal name, message name or number), (...)] # [('RADAR_STATE', 1024), @@ -46,6 +50,9 @@ class RadarInterface(RadarInterfaceBase): self.trigger_msg = LAST_MSG def update(self, can_strings): + if self.rcp is None: + return super().update(None) + vls = self.rcp.update_strings(can_strings) self.updated_messages.update(vls) @@ -81,4 +88,4 @@ class RadarInterface(RadarInterfaceBase): ret.points = [x for x in self.pts.values() if x.dRel != 0] self.updated_messages.clear() - return ret + return ret \ No newline at end of file diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index b2757aafc..10478efa8 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -2,43 +2,72 @@ from dataclasses import dataclass from enum import Enum from typing import Dict, List, Optional, Union +from cereal import car from selfdrive.car import dbc_dict from selfdrive.car.docs_definitions import CarInfo, Harness -from cereal import car Ecu = car.CarParams.Ecu -class CarControllerParams: - STEER_MAX = 261 # 262 faults - STEER_DELTA_UP = 3 # 3 is stock. 100 is fine. 200 is too much it seems - STEER_DELTA_DOWN = 3 # no faults on the way down it seems - STEER_ERROR_MAX = 80 - - class CAR: + # Chrysler PACIFICA_2017_HYBRID = "CHRYSLER PACIFICA HYBRID 2017" PACIFICA_2018_HYBRID = "CHRYSLER PACIFICA HYBRID 2018" PACIFICA_2019_HYBRID = "CHRYSLER PACIFICA HYBRID 2019" - PACIFICA_2018 = "CHRYSLER PACIFICA 2018" # includes 2017 Pacifica + PACIFICA_2018 = "CHRYSLER PACIFICA 2018" PACIFICA_2020 = "CHRYSLER PACIFICA 2020" - JEEP_CHEROKEE = "JEEP GRAND CHEROKEE V6 2018" # includes 2017 Trailhawk - JEEP_CHEROKEE_2019 = "JEEP GRAND CHEROKEE 2019" # includes 2020 Trailhawk + # Jeep + JEEP_CHEROKEE = "JEEP GRAND CHEROKEE V6 2018" # includes 2017 Trailhawk + JEEP_CHEROKEE_2019 = "JEEP GRAND CHEROKEE 2019" # includes 2020 Trailhawk + + # Ram + RAM_1500 = "RAM 1500 5TH GEN" + RAM_HD = "RAM HD 5TH GEN" + + +class CarControllerParams: + def __init__(self, CP): + self.STEER_ERROR_MAX = 80 + if CP.carFingerprint in RAM_HD: + self.STEER_DELTA_UP = 14 + self.STEER_DELTA_DOWN = 14 + self.STEER_MAX = 361 # higher than this faults the EPS + elif CP.carFingerprint in RAM_DT: + self.STEER_DELTA_UP = 6 + self.STEER_DELTA_DOWN = 6 + self.STEER_MAX = 261 # EPS allows more, up to 350? + else: + self.STEER_DELTA_UP = 3 + self.STEER_DELTA_DOWN = 3 + self.STEER_MAX = 261 # higher than this faults the EPS + +STEER_THRESHOLD = 120 + +RAM_DT = {CAR.RAM_1500, } +RAM_HD = {CAR.RAM_HD, } +RAM_CARS = RAM_DT | RAM_HD @dataclass class ChryslerCarInfo(CarInfo): - package: str = "Adaptive Cruise" + package: str = "Adaptive Cruise Control" harness: Enum = Harness.fca - CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { CAR.PACIFICA_2017_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2017-18"), CAR.PACIFICA_2018_HYBRID: None, # same platforms - CAR.PACIFICA_2019_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2019-21"), + CAR.PACIFICA_2019_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2019-22"), CAR.PACIFICA_2018: ChryslerCarInfo("Chrysler Pacifica 2017-18"), - CAR.PACIFICA_2020: ChryslerCarInfo("Chrysler Pacifica 2020"), + CAR.PACIFICA_2020: [ + ChryslerCarInfo("Chrysler Pacifica 2019-20"), + ChryslerCarInfo("Chrysler Pacifica 2021", package="All"), + ], CAR.JEEP_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"), - CAR.JEEP_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-20", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), + CAR.JEEP_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), + CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-22", harness=Harness.ram), + CAR.RAM_HD: [ + ChryslerCarInfo("Ram 2500 2020-22", harness=Harness.ram), + ChryslerCarInfo("Ram 3500 2020-22", harness=Harness.ram), + ], } # Unique CAN messages: @@ -85,10 +114,14 @@ FINGERPRINTS = { }, # Based on "8190c7275a24557b|2020-02-24--09-57-23" { - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2015: 8, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8 + 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1536: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2015: 8, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8 }], CAR.JEEP_CHEROKEE: [{ 55: 8, 168: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 788: 3, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 975: 8, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1543: 8, 1562: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 + }, + # Based on c88f65eeaee4003a|2022-08-04--15-37-16 + { + 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 658: 6, 660: 8, 671: 8, 672: 8, 678: 8, 680: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 783: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 844: 5, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1235: 8, 1242: 8, 1252: 8, 1792: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 8, 1892: 8, 1927: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }], CAR.JEEP_CHEROKEE_2019: [{ # Jeep Grand Cherokee 2019, including most 2020 models @@ -96,15 +129,110 @@ FINGERPRINTS = { }], } +FW_VERSIONS = { + CAR.RAM_1500: { + (Ecu.combinationMeter, 0x742, None): [ + b'68294063AH', + b'68294063AG', + b'68434860AC', + b'68527375AD', + b'68453503AC', + ], + (Ecu.srs, 0x744, None): [ + b'68441329AB', + b'68490898AA', + b'68428609AB', + b'68500728AA', + ], + (Ecu.esp, 0x747, None): [ + b'68432418AD', + b'68432418AB', + b'68436004AE', + b'68438454AD', + b'68436004AD', + b'68535469AB', + b'68438454AC', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'68320950AL', + b'68320950AJ', + b'68454268AB', + b'68475160AG', + b'04672892AB', + b'68475160AE', + ], + (Ecu.eps, 0x75A, None): [ + b'68273275AG', + b'68469901AA', + b'68552788AA', + ], + (Ecu.engine, 0x7e0, None): [ + b'68448163AJ', + b'68500630AD', + b'68539650AD', + ], + (Ecu.transmission, 0x7e1, None): [ + b'68360078AL', + b'68384328AD', + b'68360085AL', + b'68360081AM', + b'68502994AD', + b'68445533AB', + b'68540431AB', + b'68484467AC', + ], + (Ecu.gateway, 0x18DACBF1, None): [ + b'68402660AB', + b'68445283AB', + b'68533631AB', + b'68500483AB', + ], + }, + + CAR.RAM_HD: { + (Ecu.combinationMeter, 0x742, None): [ + b'68361606AH', + b'68492693AD', + ], + (Ecu.srs, 0x744, None): [ + b'68399794AC', + b'68428503AA', + b'68428505AA', + ], + (Ecu.esp, 0x747, None): [ + b'68334977AH', + b'68504022AB', + b'68530686AB', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'04672895AB', + b'56029827AG', + b'68484694AE', + ], + (Ecu.eps, 0x761, None): [ + b'68421036AC', + b'68507906AB', + ], + (Ecu.engine, 0x7e0, None): [ + b'52421132AF', + b'M2370131MB', + b'M2421132MB', + ], + (Ecu.gateway, 0x18DACBF1, None): [ + b'68488419AB', + b'68535476AB', + ], + }, +} DBC = { - CAR.PACIFICA_2017_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'), - CAR.PACIFICA_2018: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'), - CAR.PACIFICA_2020: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'), - CAR.PACIFICA_2018_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'), - CAR.PACIFICA_2019_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'), - CAR.JEEP_CHEROKEE: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'), - CAR.JEEP_CHEROKEE_2019: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'), + CAR.PACIFICA_2017_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), + CAR.PACIFICA_2018: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), + CAR.PACIFICA_2020: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), + CAR.PACIFICA_2018_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), + CAR.PACIFICA_2019_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), + CAR.JEEP_CHEROKEE: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), + CAR.JEEP_CHEROKEE_2019: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), + CAR.RAM_1500: dbc_dict('chrysler_ram_dt_generated', None), + CAR.RAM_HD: dbc_dict('chrysler_ram_hd_generated', None), } - -STEER_THRESHOLD = 120 diff --git a/selfdrive/car/disable_ecu.py b/selfdrive/car/disable_ecu.py old mode 100644 new mode 100755 index ac5c6c9f8..cd3e93fa8 --- a/selfdrive/car/disable_ecu.py +++ b/selfdrive/car/disable_ecu.py @@ -6,6 +6,7 @@ EXT_DIAG_RESPONSE = b'\x50\x03' COM_CONT_RESPONSE = b'' + def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, com_cont_req=b'\x28\x83\x01', timeout=0.1, retry=10, debug=False): """Silence an ECU by disabling sending and receiving messages using UDS 0x28. The ECU will stay silent as long as openpilot keeps sending Tester Present. @@ -26,9 +27,22 @@ def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, com_cont_req=b'\x28\x83\x01' cloudlog.warning("ecu disabled") return True + except Exception: cloudlog.exception("ecu disable exception") print(f"ecu disable retry ({i+1}) ...") cloudlog.warning("ecu disable failed") return False + + +if __name__ == "__main__": + import time + import cereal.messaging as messaging + sendcan = messaging.pub_sock('sendcan') + logcan = messaging.sub_sock('can') + time.sleep(1) + + # honda bosch radar disable + disabled = disable_ecu(logcan, sendcan, bus=1, addr=0x18DAB0F1, com_cont_req=b'\x28\x83\x03', timeout=0.5, debug=False) + print(f"disabled: {disabled}") diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py index 860503dbd..5793596a4 100755 --- a/selfdrive/car/docs.py +++ b/selfdrive/car/docs.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import argparse +from collections import defaultdict import jinja2 import os from enum import Enum @@ -7,26 +8,26 @@ from natsort import natsorted from typing import Dict, List from common.basedir import BASEDIR -from selfdrive.car.docs_definitions import STAR_DESCRIPTIONS, CarInfo, Column, Star, Tier +from selfdrive.car.docs_definitions import STAR_DESCRIPTIONS, StarColumns, TierColumns, CarInfo, Column, Star from selfdrive.car.car_helpers import interfaces, get_interface_attr from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR as HKG_RADAR_START_ADDR -from selfdrive.car.tests.routes import non_tested_cars -def get_all_footnotes() -> Dict[Enum, int]: +def get_all_footnotes(only_tier_cols: bool = False) -> Dict[Enum, int]: all_footnotes = [] + hide_cols = set(StarColumns) - set(TierColumns) if only_tier_cols else [] for footnotes in get_interface_attr("Footnote", ignore_none=True).values(): - all_footnotes += footnotes + all_footnotes.extend([fn for fn in footnotes if fn.value.column not in hide_cols]) return {fn: idx + 1 for idx, fn in enumerate(all_footnotes)} -ALL_FOOTNOTES: Dict[Enum, int] = get_all_footnotes() CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS.md") CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md") -def get_all_car_info() -> List[CarInfo]: +def get_all_car_info(only_tier_cols: bool = False) -> List[CarInfo]: all_car_info: List[CarInfo] = [] + footnotes = get_all_footnotes(only_tier_cols) for model, car_info in get_interface_attr("CAR_INFO", combine_brands=True).items(): # Hyundai exception: those with radar have openpilot longitudinal fingerprint = {0: {}, 1: {HKG_RADAR_START_ADDR: 8}, 2: {}, 3: {}} @@ -40,32 +41,37 @@ def get_all_car_info() -> List[CarInfo]: car_info = (car_info,) for _car_info in car_info: - all_car_info.append(_car_info.init(CP, non_tested_cars, ALL_FOOTNOTES)) + if not hasattr(_car_info, "row"): + _car_info.init(CP, footnotes) + all_car_info.append(_car_info) # Sort cars by make and model + year - sorted_cars: List[CarInfo] = natsorted(all_car_info, key=lambda car: (car.make + car.model).lower()) + sorted_cars: List[CarInfo] = natsorted(all_car_info, key=lambda car: car.name.lower()) return sorted_cars -def sort_by_tier(all_car_info: List[CarInfo]) -> Dict[Tier, List[CarInfo]]: - tier_car_info: Dict[Tier, List[CarInfo]] = {tier: [] for tier in Tier} +def group_by_make(all_car_info: List[CarInfo]) -> Dict[str, List[CarInfo]]: + sorted_car_info = defaultdict(list) for car_info in all_car_info: - tier_car_info[car_info.tier].append(car_info) - - # Sort cars by make and model + year - for tier, cars in tier_car_info.items(): - tier_car_info[tier] = natsorted(cars, key=lambda car: (car.make + car.model).lower()) - - return tier_car_info + sorted_car_info[car_info.make].append(car_info) + return dict(sorted_car_info) -def generate_cars_md(all_car_info: List[CarInfo], template_fn: str) -> str: +def generate_cars_md(all_car_info: List[CarInfo], template_fn: str, only_tier_cols: bool) -> str: with open(template_fn, "r") as f: template = jinja2.Template(f.read(), trim_blocks=True, lstrip_blocks=True) - footnotes = [fn.value.text for fn in ALL_FOOTNOTES] - cars_md: str = template.render(tiers=sort_by_tier(all_car_info), all_car_info=all_car_info, - footnotes=footnotes, Star=Star, Column=Column, star_descriptions=STAR_DESCRIPTIONS) + cols = list(Column) + if only_tier_cols: + hide_cols = set(StarColumns) - set(TierColumns) + cols = [c for c in cols if c not in hide_cols] + for car in all_car_info: + for c in hide_cols: + del car.row[c] + + footnotes = [fn.value.text for fn in get_all_footnotes(only_tier_cols)] + cars_md: str = template.render(all_car_info=all_car_info, group_by_make=group_by_make, + footnotes=footnotes, Star=Star, Column=cols, STAR_DESCRIPTIONS=STAR_DESCRIPTIONS) return cars_md @@ -73,10 +79,11 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description="Auto generates supported cars documentation", formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("--tier-columns", action="store_true", help="Include only columns that count in the tier") parser.add_argument("--template", default=CARS_MD_TEMPLATE, help="Override default template filename") parser.add_argument("--out", default=CARS_MD_OUT, help="Override default generated filename") args = parser.parse_args() with open(args.out, 'w') as f: - f.write(generate_cars_md(get_all_car_info(), args.template)) + f.write(generate_cars_md(get_all_car_info(args.tier_columns), args.template, args.tier_columns)) print(f"Generated and written to {args.out}") diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index f09dad786..65dbe4c62 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -1,20 +1,24 @@ -import math - -from cereal import car +import re from collections import namedtuple -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum -from typing import Dict, List, Optional, Union, no_type_check +from typing import Dict, List, Optional, Tuple, Union, no_type_check + +from cereal import car +from common.conversions import Conversions as CV -TACO_TORQUE_THRESHOLD = 2.5 # m/s^2 -GREAT_TORQUE_THRESHOLD = 1.4 # m/s^2 GOOD_TORQUE_THRESHOLD = 1.0 # m/s^2 +MODEL_YEARS_RE = r"(?<= )((\d{4}-\d{2})|(\d{4}))(,|$)" + +# Makes that lack auto-resume with stock long, and auto resume in any configuration +NO_AUTO_RESUME_STOCK_LONG = {"toyota", "gm"} +NO_AUTO_RESUME = NO_AUTO_RESUME_STOCK_LONG | {"nissan", "subaru"} class Tier(Enum): - GOLD = "The best openpilot experience. Great highway driving and beyond." - SILVER = "A solid highway driving experience, but is limited by stock longitudinal. May be upgraded in the future." - BRONZE = "A good highway experience, but may have limited performance in traffic and on sharp turns." + GOLD = 0 + SILVER = 1 + BRONZE = 2 class Column(Enum): @@ -25,7 +29,6 @@ class Column(Enum): FSR_LONGITUDINAL = "Stop and Go" FSR_STEERING = "Steer to 0" STEERING_TORQUE = "Steering Torque" - MAINTAINED = "Actively Maintained" class Star(Enum): @@ -35,16 +38,41 @@ class Star(Enum): StarColumns = list(Column)[3:] +TierColumns = (Column.FSR_LONGITUDINAL, Column.FSR_STEERING, Column.STEERING_TORQUE) CarFootnote = namedtuple("CarFootnote", ["text", "column", "star"], defaults=[None]) -def get_footnote(footnotes: Optional[List[Enum]], column: Column) -> Optional[Enum]: - # Returns applicable footnote given current column - if footnotes is not None: - for fn in footnotes: - if fn.value.column == column: - return fn - return None +def get_footnotes(footnotes: List[Enum], column: Column) -> List[Enum]: + # Returns applicable footnotes given current column + return [fn for fn in footnotes if fn.value.column == column] + + +# TODO: store years as a list +def get_year_list(years): + years_list = [] + if len(years) == 0: + return years_list + + for year in years.split(','): + year = year.strip() + if len(year) == 4: + years_list.append(str(year)) + elif "-" in year and len(year) == 7: + start, end = year.split("-") + years_list.extend(map(str, range(int(start), int(f"20{end}") + 1))) + else: + raise Exception(f"Malformed year string: {years}") + return years_list + + +def split_name(name: str) -> Tuple[str, str, str]: + make, model = name.split(" ", 1) + years = "" + match = re.search(MODEL_YEARS_RE, model) + if match is not None: + years = model[match.start():] + model = model[:match.start() - 1] + return make, model, years @dataclass @@ -52,47 +80,40 @@ class CarInfo: name: str package: str video_link: Optional[str] = None - footnotes: Optional[List[Enum]] = None + footnotes: List[Enum] = field(default_factory=list) min_steer_speed: Optional[float] = None min_enable_speed: Optional[float] = None - good_torque: bool = False harness: Optional[Enum] = None - def init(self, CP: car.CarParams, non_tested_cars: List[str], all_footnotes: Dict[Enum, int]): + def init(self, CP: car.CarParams, all_footnotes: Dict[Enum, int]): # TODO: set all the min steer speeds in carParams and remove this - min_steer_speed = CP.minSteerSpeed if self.min_steer_speed is not None: - min_steer_speed = self.min_steer_speed assert CP.minSteerSpeed == 0, f"{CP.carFingerprint}: Minimum steer speed set in both CarInfo and CarParams" - - assert self.harness is not None, f"{CP.carFingerprint}: Need to specify car harness" + else: + self.min_steer_speed = CP.minSteerSpeed # TODO: set all the min enable speeds in carParams correctly and remove this - min_enable_speed = CP.minEnableSpeed - if self.min_enable_speed is not None: - min_enable_speed = self.min_enable_speed + if self.min_enable_speed is None: + self.min_enable_speed = CP.minEnableSpeed self.car_name = CP.carName - self.make, self.model = self.name.split(' ', 1) + self.car_fingerprint = CP.carFingerprint + self.make, self.model, self.years = split_name(self.name) self.row = { Column.MAKE: self.make, Column.MODEL: self.model, Column.PACKAGE: self.package, # StarColumns Column.LONGITUDINAL: Star.FULL if CP.openpilotLongitudinalControl and not CP.radarOffCan else Star.EMPTY, - Column.FSR_LONGITUDINAL: Star.FULL if min_enable_speed <= 0. else Star.EMPTY, - Column.FSR_STEERING: Star.FULL if min_steer_speed <= 0. else Star.EMPTY, - Column.STEERING_TORQUE: Star.FULL if self.good_torque else Star.EMPTY, # TODO: remove hardcoding and use maxLateralAccel - Column.MAINTAINED: Star.FULL if CP.carFingerprint not in non_tested_cars and self.harness is not Harness.none else Star.EMPTY, + Column.FSR_LONGITUDINAL: Star.FULL if self.min_enable_speed <= 0. else Star.EMPTY, + Column.FSR_STEERING: Star.FULL if self.min_steer_speed <= 0. else Star.EMPTY, + Column.STEERING_TORQUE: Star.EMPTY, } - if not math.isnan(CP.maxLateralAccel): - if CP.maxLateralAccel >= GREAT_TORQUE_THRESHOLD: - self.row[Column.STEERING_TORQUE] = Star.FULL - elif CP.maxLateralAccel >= GOOD_TORQUE_THRESHOLD: - self.row[Column.STEERING_TORQUE] = Star.HALF - else: - self.row[Column.STEERING_TORQUE] = Star.EMPTY + # Set steering torque star from max lateral acceleration + assert CP.maxLateralAccel > 0.1 + if CP.maxLateralAccel >= GOOD_TORQUE_THRESHOLD: + self.row[Column.STEERING_TORQUE] = Star.FULL if CP.notCar: for col in StarColumns: @@ -101,32 +122,76 @@ class CarInfo: self.all_footnotes = all_footnotes for column in StarColumns: # Demote if footnote specifies a star - footnote = get_footnote(self.footnotes, column) - if footnote is not None and footnote.value.star is not None: - self.row[column] = footnote.value.star + for fn in get_footnotes(self.footnotes, column): + if fn.value.star is not None: + self.row[column] = fn.value.star + + # openpilot ACC star doesn't count for tiers + full_stars = [s for col, s in self.row.items() if col in TierColumns].count(Star.FULL) + if full_stars == len(TierColumns): + self.tier = Tier.GOLD + elif full_stars == len(TierColumns) - 1: + self.tier = Tier.SILVER + else: + self.tier = Tier.BRONZE + + self.year_list = get_year_list(self.years) + self.detail_sentence = self.get_detail_sentence(CP) - self.tier = {5: Tier.GOLD, 4: Tier.SILVER}.get(list(self.row.values()).count(Star.FULL), Tier.BRONZE) return self + def get_detail_sentence(self, CP): + if not CP.notCar: + sentence_builder = "openpilot upgrades your {car_model} with automated lane centering{alc} and adaptive cruise control{acc}." + + if self.min_steer_speed > self.min_enable_speed: + alc = f" above {self.min_steer_speed * CV.MS_TO_MPH:.0f} mph," if self.min_steer_speed > 0 else " at all speeds," + else: + alc = "" + + # Exception for cars which do not auto-resume yet + acc = "" + if self.min_enable_speed > 0: + acc = f" while driving above {self.min_enable_speed * CV.MS_TO_MPH:.0f} mph" + elif CP.carName not in NO_AUTO_RESUME or (CP.carName in NO_AUTO_RESUME_STOCK_LONG and CP.openpilotLongitudinalControl): + acc = " that automatically resumes from a stop" + + if self.row[Column.STEERING_TORQUE] != Star.FULL: + sentence_builder += " This car may not be able to take tight turns on its own." + + return sentence_builder.format(car_model=f"{self.make} {self.model}", alc=alc, acc=acc) + + else: + if CP.carFingerprint == "COMMA BODY": + return "The body is a robotics dev kit that can run openpilot. Learn more." + else: + raise Exception(f"This notCar does not have a detail sentence: {CP.carFingerprint}") + @no_type_check def get_column(self, column: Column, star_icon: str, footnote_tag: str) -> str: item: Union[str, Star] = self.row[column] if column in StarColumns: item = star_icon.format(item.value) + elif column == Column.MODEL and len(self.years): + item += f" {self.years}" - footnote = get_footnote(self.footnotes, column) - if footnote is not None: - item += footnote_tag.format(self.all_footnotes[footnote]) + footnotes = get_footnotes(self.footnotes, column) + if len(footnotes): + sups = sorted([self.all_footnotes[fn] for fn in footnotes]) + item += footnote_tag.format(f'{",".join(map(str, sups))}') return item class Harness(Enum): nidec = "Honda Nidec" - bosch = "Honda Bosch A" + bosch_a = "Honda Bosch A" + bosch_b = "Honda Bosch B" toyota = "Toyota" - subaru = "Subaru" + subaru_a = "Subaru A" + subaru_b = "Subaru B" fca = "FCA" + ram = "Ram" vw = "VW" j533 = "J533" hyundai_a = "Hyundai A" @@ -144,8 +209,11 @@ class Harness(Enum): hyundai_m = "Hyundai M" hyundai_n = "Hyundai N" hyundai_o = "Hyundai O" + hyundai_p = "Hyundai P" + hyundai_q = "Hyundai Q" custom = "Developer" obd_ii = "OBD-II" + gm = "GM" nissan_a = "Nissan A" nissan_b = "Nissan B" mazda = "Mazda" @@ -154,31 +222,19 @@ class Harness(Enum): STAR_DESCRIPTIONS = { "Gas & Brakes": { # icon and row name - "openpilot Adaptive Cruise Control (ACC)": [ # star column - [Star.FULL.value, "openpilot is able to control the gas and brakes."], - [Star.HALF.value, "openpilot is able to control the gas and brakes with some restrictions."], - [Star.EMPTY.value, "The gas and brakes are controlled by the car's stock Adaptive Cruise Control (ACC) system."], - ], Column.FSR_LONGITUDINAL.value: [ - [Star.FULL.value, "Adaptive Cruise Control (ACC) operates down to 0 mph."], - [Star.EMPTY.value, "Adaptive Cruise Control (ACC) available only above certain speeds. See your car's manual for the minimum speed."], + [Star.FULL.value, "openpilot operates down to 0 mph."], + [Star.EMPTY.value, "openpilot operates only above a minimum speed. See your car's manual for the minimum speed."], ], }, "Steering": { Column.FSR_STEERING.value: [ [Star.FULL.value, "openpilot can control the steering wheel down to 0 mph."], - [Star.EMPTY.value, "No steering control below certain speeds."], + [Star.EMPTY.value, "No steering control below certain speeds. See your car's manual for the minimum speed."], ], Column.STEERING_TORQUE.value: [ - [Star.FULL.value, "Car has enough steering torque to take tighter turns."], - [Star.HALF.value, "Car has enough steering torque for comfortable highway driving."], - [Star.EMPTY.value, "Limited ability to make turns."], - ], - }, - "Support": { - Column.MAINTAINED.value: [ - [Star.FULL.value, "Mainline software support, harness hardware sold by comma, lots of users, primary development target."], - [Star.EMPTY.value, "Low user count, community maintained, harness hardware not sold by comma."], + [Star.FULL.value, "Car has enough steering torque to comfortably take most highway turns."], + [Star.EMPTY.value, "Limited ability to make tighter turns."], ], }, } diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py new file mode 100755 index 000000000..267701509 --- /dev/null +++ b/selfdrive/car/ecu_addrs.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +import capnp +import time +import traceback +from typing import Optional, Set, Tuple + +import cereal.messaging as messaging +from panda.python.uds import SERVICE_TYPE +from selfdrive.car import make_can_msg +from selfdrive.boardd.boardd import can_list_to_can_capnp +from system.swaglog import cloudlog + + +def make_tester_present_msg(addr, bus, subaddr=None): + dat = [0x02, SERVICE_TYPE.TESTER_PRESENT, 0x0] + if subaddr is not None: + dat.insert(0, subaddr) + + dat.extend([0x0] * (8 - len(dat))) + return make_can_msg(addr, bytes(dat), bus) + + +def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subaddr: Optional[int] = None) -> bool: + # ISO-TP messages are always padded to 8 bytes + # tester present response is always a single frame + dat_offset = 1 if subaddr is not None else 0 + if len(msg.dat) == 8 and 1 <= msg.dat[dat_offset] <= 7: + # success response + if msg.dat[dat_offset + 1] == (SERVICE_TYPE.TESTER_PRESENT + 0x40): + return True + # error response + if msg.dat[dat_offset + 1] == 0x7F and msg.dat[dat_offset + 2] == SERVICE_TYPE.TESTER_PRESENT: + return True + return False + + +def get_all_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, bus: int, timeout: float = 1, debug: bool = True) -> Set[Tuple[int, Optional[int], int]]: + addr_list = [0x700 + i for i in range(256)] + [0x18da00f1 + (i << 8) for i in range(256)] + queries: Set[Tuple[int, Optional[int], int]] = {(addr, None, bus) for addr in addr_list} + responses = queries + return get_ecu_addrs(logcan, sendcan, queries, responses, timeout=timeout, debug=debug) + + +def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, queries: Set[Tuple[int, Optional[int], int]], + responses: Set[Tuple[int, Optional[int], int]], timeout: float = 1, debug: bool = False) -> Set[Tuple[int, Optional[int], int]]: + ecu_responses: Set[Tuple[int, Optional[int], int]] = set() # set((addr, subaddr, bus),) + try: + msgs = [make_tester_present_msg(addr, bus, subaddr) for addr, subaddr, bus in queries] + + messaging.drain_sock_raw(logcan) + sendcan.send(can_list_to_can_capnp(msgs, msgtype='sendcan')) + start_time = time.monotonic() + while time.monotonic() - start_time < timeout: + can_packets = messaging.drain_sock(logcan, wait_for_one=True) + for packet in can_packets: + for msg in packet.can: + subaddr = None if (msg.address, None, msg.src) in responses else msg.dat[0] + if (msg.address, subaddr, msg.src) in responses and is_tester_present_response(msg, subaddr): + if debug: + print(f"CAN-RX: {hex(msg.address)} - 0x{bytes.hex(msg.dat)}") + if (msg.address, subaddr, msg.src) in ecu_responses: + print(f"Duplicate ECU address: {hex(msg.address)}") + ecu_responses.add((msg.address, subaddr, msg.src)) + except Exception: + cloudlog.warning(f"ECU addr scan exception: {traceback.format_exc()}") + return ecu_responses + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description='Get addresses of all ECUs') + parser.add_argument('--debug', action='store_true') + args = parser.parse_args() + + logcan = messaging.sub_sock('can') + sendcan = messaging.pub_sock('sendcan') + + time.sleep(1.0) + + print("Getting ECU addresses ...") + ecu_addrs = get_all_ecu_addrs(logcan, sendcan, 1, debug=args.debug) + + print() + print("Found ECUs on addresses:") + for addr, subaddr, bus in ecu_addrs: + msg = f" 0x{hex(addr)}" + if subaddr is not None: + msg += f" (sub-address: 0x{hex(subaddr)})" + print(msg) diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index c2f87a480..11d9bb3c7 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -1,8 +1,8 @@ from cereal import car from common.numpy_fast import clip, interp +from opendbc.can.packer import CANPacker from selfdrive.car.ford import fordcan from selfdrive.car.ford.values import CarControllerParams -from opendbc.can.packer import CANPacker VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -17,19 +17,19 @@ def apply_ford_steer_angle_limits(apply_steer, apply_steer_last, vEgo): return apply_steer -class CarController(): +class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP self.VM = VM self.packer = CANPacker(dbc_name) + self.frame = 0 self.apply_steer_last = 0 - self.steer_rate_limited = False self.main_on_last = False self.lkas_enabled_last = False self.steer_alert_last = False - def update(self, CC, CS, frame): + def update(self, CC, CS): can_sends = [] actuators = CC.actuators @@ -45,10 +45,9 @@ class CarController(): # apply rate limits new_steer = actuators.steeringAngleDeg apply_steer = apply_ford_steer_angle_limits(new_steer, self.apply_steer_last, CS.out.vEgo) - self.steer_rate_limited = new_steer != apply_steer # send steering commands at 20Hz - if (frame % CarControllerParams.LKAS_STEER_STEP) == 0: + if (self.frame % CarControllerParams.LKAS_STEER_STEP) == 0: lca_rq = 1 if CC.latActive else 0 # use LatCtlPath_An_Actl to actuate steering for now until curvature control is implemented @@ -69,15 +68,14 @@ class CarController(): can_sends.append(fordcan.create_tja_command(self.packer, lca_rq, ramp_type, precision, path_offset, path_angle, curvature_rate, curvature)) - send_ui = (self.main_on_last != main_on) or (self.lkas_enabled_last != CC.latActive) or (self.steer_alert_last != steer_alert) # send lkas ui command at 1Hz or if ui state changes - if (frame % CarControllerParams.LKAS_UI_STEP) == 0 or send_ui: + if (self.frame % CarControllerParams.LKAS_UI_STEP) == 0 or send_ui: can_sends.append(fordcan.create_lkas_ui_command(self.packer, main_on, CC.latActive, steer_alert, CS.lkas_status_stock_values)) # send acc ui command at 20Hz or if ui state changes - if (frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui: + if (self.frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui: can_sends.append(fordcan.create_acc_ui_command(self.packer, main_on, CC.latActive, CS.acc_tja_status_stock_values)) self.main_on_last = main_on @@ -87,4 +85,5 @@ class CarController(): new_actuators = actuators.copy() new_actuators.steeringAngleDeg = apply_steer + self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 67a72fd67..aaa3af612 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -1,5 +1,3 @@ -from typing import Dict - from cereal import car from common.conversions import Conversions as CV from opendbc.can.can_define import CANDefine @@ -51,8 +49,8 @@ class CarState(CarStateBase): # gear if self.CP.transmissionType == TransmissionType.automatic: - gear = int(cp.vl["Gear_Shift_by_Wire_FD1"]["TrnGear_D_RqDrv"]) - ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear, None)) + gear = self.shifter_values.get(cp.vl["Gear_Shift_by_Wire_FD1"]["TrnGear_D_RqDrv"], None) + ret.gearShifter = self.parse_gear_shifter(gear) elif self.CP.transmissionType == TransmissionType.manual: ret.clutchPressed = cp.vl["Engine_Clutch_Data"]["CluPdlPos_Pc_Meas"] > 0 if bool(cp.vl["BCM_Lamp_Stat_FD1"]["RvrseLghtOn_B_Stat"]): @@ -85,14 +83,6 @@ class CarState(CarStateBase): return ret - @staticmethod - def parse_gear_shifter(gear: str) -> car.CarState.GearShifter: - d: Dict[str, car.CarState.GearShifter] = { - 'Park': GearShifter.park, 'Reverse': GearShifter.reverse, 'Neutral': GearShifter.neutral, - 'Manual': GearShifter.manumatic, 'Drive': GearShifter.drive, - } - return d.get(gear, GearShifter.unknown) - @staticmethod def get_can_parser(CP): signals = [ diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index c608cc08d..405579fa6 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -2,7 +2,7 @@ from cereal import car from common.conversions import Conversions as CV from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint -from selfdrive.car.ford.values import TransmissionType, CAR +from selfdrive.car.ford.values import CAR, TransmissionType, GearShifter from selfdrive.car.interfaces import CarInterfaceBase @@ -70,14 +70,10 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) - ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False - - events = self.create_common_events(ret) + events = self.create_common_events(ret, extra_gears=[GearShifter.manumatic]) ret.events = events.to_msg() return ret def apply(self, c): - ret = self.CC.update(c, self.CS, self.frame) - self.frame += 1 - return ret + return self.CC.update(c, self.CS) diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index aae011ad3..6b8396cf0 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -7,6 +7,7 @@ from selfdrive.car.docs_definitions import CarInfo Ecu = car.CarParams.Ecu TransmissionType = car.CarParams.TransmissionType +GearShifter = car.CarState.GearShifter AngleRateLimit = namedtuple('AngleRateLimit', ['speed_points', 'max_angle_diff_points']) diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 81883ceca..9d2fd11b4 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -3,11 +3,12 @@ import struct import traceback from collections import defaultdict from dataclasses import dataclass, field -from typing import Any, List +from typing import Any, List, Optional, Set, Tuple from tqdm import tqdm import panda.python.uds as uds from cereal import car +from selfdrive.car.ecu_addrs import get_ecu_addrs from selfdrive.car.interfaces import get_interface_attr from selfdrive.car.fingerprints import FW_VERSIONS from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery @@ -15,6 +16,7 @@ from selfdrive.car.toyota.values import CAR as TOYOTA from system.swaglog import cloudlog Ecu = car.CarParams.Ecu +ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.esp, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] def p16(val): @@ -90,6 +92,18 @@ SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) +CHRYSLER_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(0xf132) +CHRYSLER_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ + p16(0xf132) + +CHRYSLER_RX_OFFSET = -0x280 + +FORD_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER) +FORD_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER) + @dataclass class Request: @@ -130,16 +144,19 @@ REQUESTS: List[Request] = [ "toyota", [SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST], [SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE], + bus=0, ), Request( "toyota", [SHORT_TESTER_PRESENT_REQUEST, OBD_VERSION_REQUEST], [SHORT_TESTER_PRESENT_RESPONSE, OBD_VERSION_RESPONSE], + bus=0, ), Request( "toyota", [TESTER_PRESENT_REQUEST, DEFAULT_DIAGNOSTIC_REQUEST, EXTENDED_DIAGNOSTIC_REQUEST, UDS_VERSION_REQUEST], [TESTER_PRESENT_RESPONSE, DEFAULT_DIAGNOSTIC_RESPONSE, EXTENDED_DIAGNOSTIC_RESPONSE, UDS_VERSION_RESPONSE], + bus=0, ), # Volkswagen Request( @@ -186,6 +203,32 @@ REQUESTS: List[Request] = [ [TESTER_PRESENT_RESPONSE, UDS_VERSION_RESPONSE], bus=0, ), + # Chrysler / FCA / Stellantis + Request( + "chrysler", + [CHRYSLER_VERSION_REQUEST], + [CHRYSLER_VERSION_RESPONSE], + rx_offset=CHRYSLER_RX_OFFSET, + ), + Request( + "chrysler", + [CHRYSLER_VERSION_REQUEST], + [CHRYSLER_VERSION_RESPONSE], + ), + # Ford + Request( + "ford", + [TESTER_PRESENT_REQUEST, FORD_VERSION_REQUEST], + [TESTER_PRESENT_RESPONSE, FORD_VERSION_RESPONSE], + whitelist_ecus=[Ecu.engine], + ), + Request( + "ford", + [TESTER_PRESENT_REQUEST, FORD_VERSION_REQUEST], + [TESTER_PRESENT_RESPONSE, FORD_VERSION_RESPONSE], + bus=0, + whitelist_ecus=[Ecu.eps, Ecu.esp, Ecu.fwdRadar, Ecu.fwdCamera], + ), ] @@ -194,13 +237,23 @@ def chunks(l, n=128): yield l[i:i + n] -def build_fw_dict(fw_versions): - fw_versions_dict = {} +def build_fw_dict(fw_versions, filter_brand=None): + fw_versions_dict = defaultdict(set) for fw in fw_versions: - addr = fw.address - sub_addr = fw.subAddress if fw.subAddress != 0 else None - fw_versions_dict[(addr, sub_addr)] = fw.fwVersion - return fw_versions_dict + if filter_brand is None or fw.brand == filter_brand: + addr = fw.address + sub_addr = fw.subAddress if fw.subAddress != 0 else None + fw_versions_dict[(addr, sub_addr)].add(fw.fwVersion) + return dict(fw_versions_dict) + + +def get_brand_addrs(): + versions = get_interface_attr('FW_VERSIONS', ignore_none=True) + brand_addrs = defaultdict(set) + for brand, cars in versions.items(): + for fw in cars.values(): + brand_addrs[brand] |= {(addr, sub_addr) for _, addr, sub_addr in fw.keys()} + return brand_addrs def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): @@ -214,7 +267,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): # time and only one is in our database. exclude_types = [Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps, Ecu.debug] - # Build lookup table from (addr, subaddr, fw) to list of candidate cars + # Build lookup table from (addr, sub_addr, fw) to list of candidate cars all_fw_versions = defaultdict(list) for candidate, fw_by_addr in FW_VERSIONS.items(): if candidate == exclude: @@ -228,17 +281,18 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): match_count = 0 candidate = None - for addr, version in fw_versions_dict.items(): - # All cars that have this FW response on the specified address - candidates = all_fw_versions[(addr[0], addr[1], version)] - - if len(candidates) == 1: - match_count += 1 - if candidate is None: - candidate = candidates[0] - # We uniquely matched two different cars. No fuzzy match possible - elif candidate != candidates[0]: - return set() + for addr, versions in fw_versions_dict.items(): + for version in versions: + # All cars that have this FW response on the specified address + candidates = all_fw_versions[(addr[0], addr[1], version)] + + if len(candidates) == 1: + match_count += 1 + if candidate is None: + candidate = candidates[0] + # We uniquely matched two different cars. No fuzzy match possible + elif candidate != candidates[0]: + return set() if match_count >= 2: if log: @@ -260,63 +314,140 @@ def match_fw_to_car_exact(fw_versions_dict): for ecu, expected_versions in fws.items(): ecu_type = ecu[0] addr = ecu[1:] - found_version = fw_versions_dict.get(addr, None) - ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.esp, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] - if ecu_type == Ecu.esp and candidate in (TOYOTA.RAV4, TOYOTA.COROLLA, TOYOTA.HIGHLANDER, TOYOTA.SIENNA, TOYOTA.LEXUS_IS) and found_version is None: + found_versions = fw_versions_dict.get(addr, set()) + if ecu_type == Ecu.esp and candidate in (TOYOTA.RAV4, TOYOTA.COROLLA, TOYOTA.HIGHLANDER, TOYOTA.SIENNA, TOYOTA.LEXUS_IS) and not len(found_versions): continue # On some Toyota models, the engine can show on two different addresses - if ecu_type == Ecu.engine and candidate in (TOYOTA.CAMRY, TOYOTA.COROLLA_TSS2, TOYOTA.CHR, TOYOTA.LEXUS_IS) and found_version is None: + if ecu_type == Ecu.engine and candidate in (TOYOTA.CAMRY, TOYOTA.COROLLA_TSS2, TOYOTA.CHR, TOYOTA.LEXUS_IS) and not len(found_versions): continue # Ignore non essential ecus - if ecu_type not in ESSENTIAL_ECUS and found_version is None: + if ecu_type not in ESSENTIAL_ECUS and not len(found_versions): continue # Virtual debug ecu doesn't need to match the database if ecu_type == Ecu.debug: continue - if found_version not in expected_versions: + if not any([found_version in expected_versions for found_version in found_versions]): invalid.append(candidate) break return set(candidates.keys()) - set(invalid) -def match_fw_to_car(fw_versions, allow_fuzzy=True): - fw_versions_dict = build_fw_dict(fw_versions) - matches = match_fw_to_car_exact(fw_versions_dict) +def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True): + # Try exact matching first + exact_matches = [] + if allow_exact: + exact_matches = [(True, match_fw_to_car_exact)] + if allow_fuzzy: + exact_matches.append((False, match_fw_to_car_fuzzy)) - exact_match = True - if allow_fuzzy and len(matches) == 0: - matches = match_fw_to_car_fuzzy(fw_versions_dict) + brands = get_interface_attr('FW_VERSIONS', ignore_none=True).keys() + for exact_match, match_func in exact_matches: + # For each brand, attempt to fingerprint using all FW returned from its queries + matches = set() + for brand in brands: + fw_versions_dict = build_fw_dict(fw_versions, filter_brand=brand) + matches |= match_func(fw_versions_dict) - # Fuzzy match found - if len(matches) == 1: - exact_match = False + if len(matches): + return exact_match, matches - return exact_match, matches + return True, set() -def get_fw_versions(logcan, sendcan, extra=None, timeout=0.1, debug=False, progress=False): - ecu_types = {} +def get_present_ecus(logcan, sendcan): + queries = list() + parallel_queries = list() + responses = set() + versions = get_interface_attr('FW_VERSIONS', ignore_none=True) - # Extract ECU addresses to query from fingerprints - # ECUs using a subadress need be queried one by one, the rest can be done in parallel - addrs = [] - parallel_addrs = [] + for r in REQUESTS: + if r.brand not in versions: + continue + + for brand_versions in versions[r.brand].values(): + for ecu_type, addr, sub_addr in brand_versions: + # Only query ecus in whitelist if whitelist is not empty + if len(r.whitelist_ecus) == 0 or ecu_type in r.whitelist_ecus: + a = (addr, sub_addr, r.bus) + # Build set of queries + if sub_addr is None: + if a not in parallel_queries: + parallel_queries.append(a) + else: # subaddresses must be queried one by one + if [a] not in queries: + queries.append([a]) + # Build set of expected responses to filter + response_addr = uds.get_rx_addr_for_tx_addr(addr, r.rx_offset) + responses.add((response_addr, sub_addr, r.bus)) + + queries.insert(0, parallel_queries) + + ecu_responses: Set[Tuple[int, Optional[int], int]] = set() + for query in queries: + ecu_responses.update(get_ecu_addrs(logcan, sendcan, set(query), responses, timeout=0.1)) + return ecu_responses + + +def get_brand_ecu_matches(ecu_rx_addrs): + """Returns dictionary of brands and matches with ECUs in their FW versions""" + + brand_addrs = get_brand_addrs() + brand_matches = {r.brand: set() for r in REQUESTS} + + brand_rx_offsets = set((r.brand, r.rx_offset) for r in REQUESTS) + for addr, sub_addr, _ in ecu_rx_addrs: + # Since we can't know what request an ecu responded to, add matches for all possible rx offsets + for brand, rx_offset in brand_rx_offsets: + a = (uds.get_rx_addr_for_tx_addr(addr, -rx_offset), sub_addr) + if a in brand_addrs[brand]: + brand_matches[brand].add(a) + + return brand_matches + + +def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, debug=False, progress=False): + """Queries for FW versions ordering brands by likelihood, breaks when exact match is found""" + + all_car_fw = [] + brand_matches = get_brand_ecu_matches(ecu_rx_addrs) + + for brand in sorted(brand_matches, key=lambda b: len(brand_matches[b]), reverse=True): + car_fw = get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, debug=debug, progress=progress) + all_car_fw.extend(car_fw) + # Try to match using FW returned from this brand only + matches = match_fw_to_car_exact(build_fw_dict(car_fw)) + if len(matches) == 1: + break + + return all_car_fw + + +def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, debug=False, progress=False): versions = get_interface_attr('FW_VERSIONS', ignore_none=True) + if query_brand is not None: + versions = {query_brand: versions[query_brand]} + if extra is not None: versions.update(extra) + # Extract ECU addresses to query from fingerprints + # ECUs using a subaddress need be queried one by one, the rest can be done in parallel + addrs = [] + parallel_addrs = [] + ecu_types = {} + for brand, brand_versions in versions.items(): for c in brand_versions.values(): for ecu_type, addr, sub_addr in c.keys(): a = (brand, addr, sub_addr) if a not in ecu_types: - ecu_types[(addr, sub_addr)] = ecu_type + ecu_types[a] = ecu_type if sub_addr is None: if a not in parallel_addrs: @@ -327,36 +458,35 @@ def get_fw_versions(logcan, sendcan, extra=None, timeout=0.1, debug=False, progr addrs.insert(0, parallel_addrs) - fw_versions = {} - for i, addr in enumerate(tqdm(addrs, disable=not progress)): + # Get versions and build capnp list to put into CarParams + car_fw = [] + requests = [r for r in REQUESTS if query_brand is None or r.brand == query_brand] + for addr in tqdm(addrs, disable=not progress): for addr_chunk in chunks(addr): - for r in REQUESTS: + for r in requests: try: addrs = [(a, s) for (b, a, s) in addr_chunk if b in (r.brand, 'any') and - (len(r.whitelist_ecus) == 0 or ecu_types[(a, s)] in r.whitelist_ecus)] + (len(r.whitelist_ecus) == 0 or ecu_types[(b, a, s)] in r.whitelist_ecus)] if addrs: query = IsoTpParallelQuery(sendcan, logcan, r.bus, addrs, r.request, r.response, r.rx_offset, debug=debug) - t = 2 * timeout if i == 0 else timeout - fw_versions.update({addr: (version, r.request, r.rx_offset) for addr, version in query.get_data(t).items()}) - except Exception: - cloudlog.warning(f"FW query exception: {traceback.format_exc()}") + for (addr, rx_addr), version in query.get_data(timeout).items(): + f = car.CarParams.CarFw.new_message() - # Build capnp list to put into CarParams - car_fw = [] - for addr, (version, request, rx_offset) in fw_versions.items(): - f = car.CarParams.CarFw.new_message() + f.ecu = ecu_types.get((r.brand, addr[0], addr[1]), Ecu.unknown) + f.fwVersion = version + f.address = addr[0] + f.responseAddress = rx_addr + f.request = r.request + f.brand = r.brand + f.bus = r.bus - f.ecu = ecu_types[addr] - f.fwVersion = version - f.address = addr[0] - f.responseAddress = addr[0] + rx_offset - f.request = request + if addr[1] is not None: + f.subAddress = addr[1] - if addr[1] is not None: - f.subAddress = addr[1] - - car_fw.append(f) + car_fw.append(f) + except Exception: + cloudlog.warning(f"FW query exception: {traceback.format_exc()}") return car_fw @@ -370,6 +500,7 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description='Get firmware version of ECUs') parser.add_argument('--scan', action='store_true') parser.add_argument('--debug', action='store_true') + parser.add_argument('--brand', help='Only query addresses/with requests for this brand') args = parser.parse_args() logcan = messaging.sub_sock('can') @@ -389,21 +520,22 @@ if __name__ == "__main__": t = time.time() print("Getting vin...") - addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug) - print(f"VIN: {vin}") + addr, vin_rx_addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug) + print(f'TX: {hex(addr)}, RX: {hex(vin_rx_addr)}, VIN: {vin}') print(f"Getting VIN took {time.time() - t:.3f} s") print() t = time.time() - fw_vers = get_fw_versions(logcan, sendcan, extra=extra, debug=args.debug, progress=True) + fw_vers = get_fw_versions(logcan, sendcan, query_brand=args.brand, extra=extra, debug=args.debug, progress=True) _, candidates = match_fw_to_car(fw_vers) print() print("Found FW versions") print("{") + padding = max([len(fw.brand) for fw in fw_vers] or [0]) for version in fw_vers: subaddr = None if version.subAddress == 0 else hex(version.subAddress) - print(f" (Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}]") + print(f" Brand: {version.brand:{padding}}, bus: {version.bus} - (Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}]") print("}") print() diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index ae2a188e3..10e602797 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -1,32 +1,34 @@ from cereal import car from common.conversions import Conversions as CV -from common.realtime import DT_CTRL from common.numpy_fast import interp +from common.realtime import DT_CTRL from opendbc.can.packer import CANPacker from selfdrive.car import apply_std_steer_torque_limits from selfdrive.car.gm import gmcan -from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams +from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons, EV_CAR VisualAlert = car.CarControl.HUDControl.VisualAlert +NetworkLocation = car.CarParams.NetworkLocation class CarController: def __init__(self, dbc_name, CP, VM): + self.CP = CP self.start_time = 0. self.apply_steer_last = 0 self.apply_gas = 0 self.apply_brake = 0 self.frame = 0 + self.last_button_frame = 0 self.lka_steering_cmd_counter_last = -1 self.lka_icon_status_last = (False, False) - self.steer_rate_limited = False self.params = CarControllerParams() - self.packer_pt = CANPacker(DBC[CP.carFingerprint]['pt']) - self.packer_obj = CANPacker(DBC[CP.carFingerprint]['radar']) - self.packer_ch = CANPacker(DBC[CP.carFingerprint]['chassis']) + self.packer_pt = CANPacker(DBC[self.CP.carFingerprint]['pt']) + self.packer_obj = CANPacker(DBC[self.CP.carFingerprint]['radar']) + self.packer_ch = CANPacker(DBC[self.CP.carFingerprint]['chassis']) def update(self, CC, CS): actuators = CC.actuators @@ -45,11 +47,9 @@ class CarController: if CS.lka_steering_cmd_counter != self.lka_steering_cmd_counter_last: self.lka_steering_cmd_counter_last = CS.lka_steering_cmd_counter elif (self.frame % self.params.STEER_STEP) == 0: - lkas_enabled = CC.latActive and CS.out.vEgo > self.params.MIN_STEER_SPEED - if lkas_enabled: + if CC.latActive: new_steer = int(round(actuators.steer * self.params.STEER_MAX)) apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) - self.steer_rate_limited = new_steer != apply_steer else: apply_steer = 0 @@ -58,50 +58,61 @@ class CarController: # moment of disengaging, increment the counter based on the last message known to pass Panda safety checks. idx = (CS.lka_steering_cmd_counter + 1) % 4 - can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, lkas_enabled)) - - # Gas/regen and brakes - all at 25Hz - if (self.frame % 4) == 0: - if not CC.longActive: - # Stock ECU sends max regen when not enabled - self.apply_gas = self.params.MAX_ACC_REGEN - self.apply_brake = 0 - else: - self.apply_gas = int(round(interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) - self.apply_brake = int(round(interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) - - idx = (self.frame // 4) % 4 - - at_full_stop = CC.longActive and CS.out.standstill - near_stop = CC.longActive and (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE) - # GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation - can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, CC.enabled, at_full_stop)) - can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, CanBus.CHASSIS, self.apply_brake, idx, near_stop, at_full_stop)) - - # Send dashboard UI commands (ACC status), 25hz - if (self.frame % 4) == 0: - send_fcw = hud_alert == VisualAlert.fcw - can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, CC.enabled, - hud_v_cruise * CV.MS_TO_KPH, hud_control.leadVisible, send_fcw)) - - # Radar needs to know current speed and yaw rate (50hz), - # and that ADAS is alive (10hz) - time_and_headlights_step = 10 - tt = self.frame * DT_CTRL - - if self.frame % time_and_headlights_step == 0: - idx = (self.frame // time_and_headlights_step) % 4 - can_sends.append(gmcan.create_adas_time_status(CanBus.OBSTACLE, int((tt - self.start_time) * 60), idx)) - can_sends.append(gmcan.create_adas_headlights_status(self.packer_obj, CanBus.OBSTACLE)) - - speed_and_accelerometer_step = 2 - if self.frame % speed_and_accelerometer_step == 0: - idx = (self.frame // speed_and_accelerometer_step) % 4 - can_sends.append(gmcan.create_adas_steering_status(CanBus.OBSTACLE, idx)) - can_sends.append(gmcan.create_adas_accelerometer_speed_status(CanBus.OBSTACLE, CS.out.vEgo, idx)) - - if self.frame % self.params.ADAS_KEEPALIVE_STEP == 0: - can_sends += gmcan.create_adas_keepalive(CanBus.POWERTRAIN) + can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, CC.latActive)) + + if self.CP.openpilotLongitudinalControl: + # Gas/regen, brakes, and UI commands - all at 25Hz + if self.frame % 4 == 0: + if not CC.longActive: + # Stock ECU sends max regen when not enabled + self.apply_gas = self.params.MAX_ACC_REGEN + self.apply_brake = 0 + else: + if self.CP.carFingerprint in EV_CAR: + self.apply_gas = int(round(interp(actuators.accel, self.params.EV_GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) + self.apply_brake = int(round(interp(actuators.accel, self.params.EV_BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) + else: + self.apply_gas = int(round(interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) + self.apply_brake = int(round(interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) + + idx = (self.frame // 4) % 4 + + at_full_stop = CC.longActive and CS.out.standstill + near_stop = CC.longActive and (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE) + # GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation + can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, CC.enabled, at_full_stop)) + can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, CanBus.CHASSIS, self.apply_brake, idx, near_stop, at_full_stop)) + + # Send dashboard UI commands (ACC status) + send_fcw = hud_alert == VisualAlert.fcw + can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, CC.enabled, + hud_v_cruise * CV.MS_TO_KPH, hud_control.leadVisible, send_fcw)) + + # Radar needs to know current speed and yaw rate (50hz), + # and that ADAS is alive (10hz) + if not self.CP.radarOffCan: + tt = self.frame * DT_CTRL + time_and_headlights_step = 10 + if self.frame % time_and_headlights_step == 0: + idx = (self.frame // time_and_headlights_step) % 4 + can_sends.append(gmcan.create_adas_time_status(CanBus.OBSTACLE, int((tt - self.start_time) * 60), idx)) + can_sends.append(gmcan.create_adas_headlights_status(self.packer_obj, CanBus.OBSTACLE)) + + speed_and_accelerometer_step = 2 + if self.frame % speed_and_accelerometer_step == 0: + idx = (self.frame // speed_and_accelerometer_step) % 4 + can_sends.append(gmcan.create_adas_steering_status(CanBus.OBSTACLE, idx)) + can_sends.append(gmcan.create_adas_accelerometer_speed_status(CanBus.OBSTACLE, CS.out.vEgo, idx)) + + if self.CP.networkLocation == NetworkLocation.gateway and self.frame % self.params.ADAS_KEEPALIVE_STEP == 0: + can_sends += gmcan.create_adas_keepalive(CanBus.POWERTRAIN) + + else: + # Stock longitudinal, integrated at camera + if (self.frame - self.last_button_frame) * DT_CTRL > 0.1: + if CC.cruiseControl.cancel: + self.last_button_frame = self.frame + can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, CruiseButtons.CANCEL)) # Show green icon when LKA torque is applied, and # alarming orange icon when approaching torque limit. @@ -110,7 +121,9 @@ class CarController: lka_active = CS.lkas_status == 1 lka_critical = lka_active and abs(actuators.steer) > 0.9 lka_icon_status = (lka_active, lka_critical) - if self.frame % self.params.CAMERA_KEEPALIVE_STEP == 0 or lka_icon_status != self.lka_icon_status_last: + + # SW_GMLAN not yet on cam harness, no HUD alerts + if self.CP.networkLocation != NetworkLocation.fwdCamera and (self.frame % self.params.CAMERA_KEEPALIVE_STEP == 0 or lka_icon_status != self.lka_icon_status_last): steer_alert = hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw) can_sends.append(gmcan.create_lka_icon_command(CanBus.SW_GMLAN, lka_active, lka_critical, steer_alert)) self.lka_icon_status_last = lka_icon_status diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 48b9f2544..0bda7edad 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -1,19 +1,23 @@ from cereal import car +from common.conversions import Conversions as CV from 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, CAR, AccState, CanBus, STEER_THRESHOLD +from selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD + +TransmissionType = car.CarParams.TransmissionType +NetworkLocation = car.CarParams.NetworkLocation class CarState(CarStateBase): def __init__(self, CP): super().__init__(CP) can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) - self.shifter_values = can_define.dv["ECMPRDNL"]["PRNDL"] + self.shifter_values = can_define.dv["ECMPRDNL2"]["PRNDL2"] self.lka_steering_cmd_counter = 0 - def update(self, pt_cp, loopback_cp): + def update(self, pt_cp, cam_cp, loopback_cp): ret = car.CarState.new_message() self.prev_cruise_buttons = self.cruise_buttons @@ -28,14 +32,18 @@ class CarState(CarStateBase): ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr]) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.standstill = ret.vEgoRaw < 0.01 - ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL"]["PRNDL"], None)) + + if pt_cp.vl["ECMPRDNL2"]["ManualMode"] == 1: + ret.gearShifter = self.parse_gear_shifter("T") + else: + ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None)) # Brake pedal's potentiometer returns near-zero reading even when pedal is not pressed. ret.brake = pt_cp.vl["EBCMBrakePedalPosition"]["BrakePedalPosition"] / 0xd0 ret.brakePressed = pt_cp.vl["EBCMBrakePedalPosition"]["BrakePedalPosition"] >= 10 # Regen braking is braking - if self.car_fingerprint == CAR.VOLT: + if self.CP.transmissionType == TransmissionType.direct: ret.brakePressed = ret.brakePressed or pt_cp.vl["EBCMRegenPaddle"]["RegenPaddle"] != 0 ret.gas = pt_cp.vl["AcceleratorPedal2"]["AcceleratorPedal2"] / 254. @@ -71,9 +79,21 @@ class CarState(CarStateBase): ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL + if self.CP.networkLocation == NetworkLocation.fwdCamera: + ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS return ret + @staticmethod + def get_cam_can_parser(CP): + signals = [] + checks = [] + if CP.networkLocation == NetworkLocation.fwdCamera: + signals.append(("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus")) + checks.append(("ASCMActiveCruiseControlStatus", 25)) + + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.CAMERA) + @staticmethod def get_can_parser(CP): signals = [ @@ -95,7 +115,8 @@ class CarState(CarStateBase): ("FRWheelSpd", "EBCMWheelSpdFront"), ("RLWheelSpd", "EBCMWheelSpdRear"), ("RRWheelSpd", "EBCMWheelSpdRear"), - ("PRNDL", "ECMPRDNL"), + ("PRNDL2", "ECMPRDNL2"), + ("ManualMode", "ECMPRDNL2"), ("LKADriverAppldTrq", "PSCMStatus"), ("LKATorqueDelivered", "PSCMStatus"), ("LKATorqueDeliveredStatus", "PSCMStatus"), @@ -106,7 +127,7 @@ class CarState(CarStateBase): checks = [ ("BCMTurnSignals", 1), - ("ECMPRDNL", 10), + ("ECMPRDNL2", 10), ("PSCMStatus", 10), ("ESPStatus", 10), ("BCMDoorBeltStatus", 10), @@ -120,7 +141,7 @@ class CarState(CarStateBase): ("EBCMBrakePedalPosition", 100), ] - if CP.carFingerprint == CAR.VOLT: + if CP.transmissionType == TransmissionType.direct: signals.append(("RegenPaddle", "EBCMRegenPaddle")) checks.append(("EBCMRegenPaddle", 50)) @@ -133,7 +154,9 @@ class CarState(CarStateBase): ] checks = [ - ("ASCMLKASteeringCmd", 50), + ("ASCMLKASteeringCmd", 10), # 10 Hz is the stock inactive rate (every 100ms). + # While active 50 Hz (every 20 ms) is normal + # EPS will tolerate around 200ms when active before faulting ] return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.LOOPBACK) diff --git a/selfdrive/car/gm/gmcan.py b/selfdrive/car/gm/gmcan.py index 47e6090a0..3a10c4d9d 100644 --- a/selfdrive/car/gm/gmcan.py +++ b/selfdrive/car/gm/gmcan.py @@ -1,5 +1,9 @@ from selfdrive.car import make_can_msg +def create_buttons(packer, bus, button): + values = {"ACCButtons": button} + return packer.make_can_msg("ASCMSteeringButton", bus, values) + def create_steering_control(packer, bus, apply_steer, idx, lkas_active): values = { @@ -59,8 +63,7 @@ def create_friction_brake_command(packer, bus, apply_brake, idx, near_stop, at_f return packer.make_can_msg("EBCMFrictionBrakeCmd", bus, values) def create_acc_dashboard_command(packer, bus, acc_engaged, target_speed_kph, lead_car_in_sight, fcw): - # Not a bit shift, dash can round up based on low 4 bits. - target_speed = int(target_speed_kph * 16) & 0xfff + target_speed = min(target_speed_kph, 255) values = { "ACCAlwaysOne" : 1, diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 9c1744dfb..f3f1e587c 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -1,14 +1,18 @@ #!/usr/bin/env python3 from cereal import car from math import fabs +from panda import Panda from common.conversions import Conversions as CV from selfdrive.car import STD_CARGO_KG, create_button_enable_events, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config -from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams +from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR from selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName +GearShifter = car.CarState.GearShifter +TransmissionType = car.CarParams.TransmissionType +NetworkLocation = car.CarParams.NetworkLocation BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise, CruiseButtons.MAIN: ButtonType.altButton3, CruiseButtons.CANCEL: ButtonType.cancel} @@ -45,25 +49,36 @@ class CarInterface(CarInterfaceBase): ret = CarInterfaceBase.get_std_params(candidate, fingerprint) ret.carName = "gm" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.gm)] - ret.pcmCruise = False # stock cruise control is kept off + + if candidate in EV_CAR: + ret.transmissionType = TransmissionType.direct + else: + ret.transmissionType = TransmissionType.automatic + + if candidate in CAMERA_ACC_CAR: + ret.openpilotLongitudinalControl = False + ret.networkLocation = NetworkLocation.fwdCamera + ret.radarOffCan = True # no radar + ret.pcmCruise = True + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM + else: # ASCM, OBD-II harness + ret.openpilotLongitudinalControl = True + ret.networkLocation = NetworkLocation.gateway + ret.radarOffCan = False + ret.pcmCruise = False # stock non-adaptive cruise control is kept off # These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars likely still work fine. Once a user confirms each car works and a test route is # added to selfdrive/car/tests/routes.py, we can remove it from this list. ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL} - # Presence of a camera on the object bus is ok. - # Have to go to read_only if ASCM is online (ACC-enabled cars), - # or camera is on powertrain bus (LKA cars without ACC). - ret.openpilotLongitudinalControl = True - tire_stiffness_factor = 0.444 # not optimized yet - # Start with a baseline tuning for all GM vehicles. Override tuning as needed in each model section below. ret.minSteerSpeed = 7 * CV.MPH_TO_MS ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.00]] ret.lateralTuning.pid.kf = 0.00004 # full torque for 20 deg at 80mph means 0.00007818594 ret.steerActuatorDelay = 0.1 # Default delay, not measured yet + tire_stiffness_factor = 0.444 # not optimized yet ret.longitudinalTuning.kpBP = [5., 35.] ret.longitudinalTuning.kpV = [2.4, 1.5] @@ -80,14 +95,14 @@ class CarInterface(CarInterfaceBase): ret.mass = 1607. + STD_CARGO_KG ret.wheelbase = 2.69 ret.steerRatio = 17.7 # Stock 15.7, LiveParameters - tire_stiffness_factor = 0.469 # Stock Michelin Energy Saver A/S, LiveParameters - ret.centerToFront = ret.wheelbase * 0.45 # Volt Gen 1, TODO corner weigh + tire_stiffness_factor = 0.469 # Stock Michelin Energy Saver A/S, LiveParameters + ret.centerToFront = ret.wheelbase * 0.45 # Volt Gen 1, TODO corner weigh ret.lateralTuning.pid.kpBP = [0., 40.] ret.lateralTuning.pid.kpV = [0., 0.17] ret.lateralTuning.pid.kiBP = [0.] ret.lateralTuning.pid.kiV = [0.] - ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_volt() + ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_volt() ret.steerActuatorDelay = 0.2 elif candidate == CAR.MALIBU: @@ -110,6 +125,7 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 14.4 # end to end is 13.46 ret.centerToFront = ret.wheelbase * 0.4 ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_acadia() + ret.longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking elif candidate == CAR.BUICK_REGAL: ret.mass = 3779. * CV.LB_TO_KG + STD_CARGO_KG # (3849+3708)/2 @@ -134,6 +150,16 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 0.000045 tire_stiffness_factor = 1.0 + elif candidate == CAR.BOLT_EUV: + ret.minEnableSpeed = -1 + ret.mass = 1669. + STD_CARGO_KG + ret.wheelbase = 2.675 + ret.steerRatio = 16.8 + ret.centerToFront = ret.wheelbase * 0.4 + tire_stiffness_factor = 1.0 + ret.steerActuatorDelay = 0.2 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + # TODO: get actual value, for now starting with reasonable value for # civic and scaling by mass and wheelbase ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) @@ -147,9 +173,7 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): - ret = self.CS.update(self.cp, self.cp_loopback) - - ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False + ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback) if self.CS.cruise_buttons != self.CS.prev_cruise_buttons and self.CS.prev_cruise_buttons != CruiseButtons.INIT: be = create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS) @@ -160,7 +184,9 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [be] - events = self.create_common_events(ret, pcm_enable=False) + events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low, + GearShifter.eco, GearShifter.manumatic], + pcm_enable=self.CP.pcmCruise) if ret.vEgo < self.CP.minEnableSpeed: events.add(EventName.belowEngageSpeed) @@ -170,12 +196,11 @@ class CarInterface(CarInterfaceBase): events.add(car.CarEvent.EventName.belowSteerSpeed) # handle button presses - events.events.extend(create_button_enable_events(ret.buttonEvents)) + events.events.extend(create_button_enable_events(ret.buttonEvents, pcm_cruise=self.CP.pcmCruise)) ret.events = events.to_msg() return ret def apply(self, c): - ret = self.CC.update(c, self.CS) - return ret + return self.CC.update(c, self.CS) diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 8ef33e829..cb9aff172 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -1,4 +1,5 @@ -from dataclasses import dataclass +from collections import defaultdict +from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Union @@ -9,11 +10,10 @@ Ecu = car.CarParams.Ecu class CarControllerParams: - STEER_MAX = 300 # Safety limit, not LKA max. Trucks use 600. - STEER_STEP = 2 # control frames per command - STEER_DELTA_UP = 7 + STEER_MAX = 300 # GM limit is 3Nm. Used by carcontroller to generate LKA output + STEER_STEP = 2 # Control frames per command (50hz) + STEER_DELTA_UP = 7 # Delta rates require review due to observed EPS weakness STEER_DELTA_DOWN = 17 - MIN_STEER_SPEED = 3. # m/s STEER_DRIVER_ALLOWANCE = 50 STEER_DRIVER_MULTIPLIER = 4 STEER_DRIVER_FACTOR = 100 @@ -24,26 +24,31 @@ class CarControllerParams: CAMERA_KEEPALIVE_STEP = 100 # Volt gasbrake lookups - MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill. - ZERO_GAS = 2048 # Coasting - MAX_BRAKE = 350 # ~ -3.5 m/s^2 with regen + # TODO: These values should be confirmed on non-Volt vehicles + MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill. + ZERO_GAS = 2048 # Coasting + MAX_BRAKE = 350 # ~ -3.5 m/s^2 with regen + MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen # Allow small margin below -3.5 m/s^2 from ISO 15622:2018 since we # perform the closed loop control, and might need some # to apply some more braking if we're on a downhill slope. # Our controller should still keep the 2 second average above # -3.5 m/s^2 as per planner limits - ACCEL_MAX = 2. # m/s^2 - ACCEL_MIN = -4. # m/s^2 + ACCEL_MAX = 2. # m/s^2 + ACCEL_MIN = -4. # m/s^2 + + EV_GAS_LOOKUP_BP = [-1., 0., ACCEL_MAX] + EV_BRAKE_LOOKUP_BP = [ACCEL_MIN, -1.] + + # ICE has much less engine braking force compared to regen in EVs, + # lower threshold removes some braking deadzone + GAS_LOOKUP_BP = [-0.1, 0., ACCEL_MAX] + BRAKE_LOOKUP_BP = [ACCEL_MIN, -0.1] - MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen - GAS_LOOKUP_BP = [-1., 0., ACCEL_MAX] GAS_LOOKUP_V = [MAX_ACC_REGEN, ZERO_GAS, MAX_GAS] - BRAKE_LOOKUP_BP = [ACCEL_MIN, -1.] BRAKE_LOOKUP_V = [MAX_BRAKE, 0.] -STEER_THRESHOLD = 1.0 - class CAR: HOLDEN_ASTRA = "HOLDEN ASTRA RS-V BK 2017" @@ -53,30 +58,32 @@ class CAR: ACADIA = "GMC ACADIA DENALI 2018" BUICK_REGAL = "BUICK REGAL ESSENCE 2018" ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016" + BOLT_EUV = "CHEVROLET BOLT EUV 2022" class Footnote(Enum): OBD_II = CarFootnote( - 'Requires an OBD-II car harness and ' + - 'community built ASCM harness. ' + + 'Requires a community built ASCM harness. ' + 'NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).', Column.MODEL) @dataclass class GMCarInfo(CarInfo): - package: str = "Adaptive Cruise" - harness: Enum = Harness.none + package: str = "Adaptive Cruise Control" + harness: Enum = Harness.obd_ii + footnotes: List[Enum] = field(default_factory=lambda: [Footnote.OBD_II]) CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { - CAR.HOLDEN_ASTRA: GMCarInfo("Holden Astra 2017", harness=Harness.custom), - CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", footnotes=[Footnote.OBD_II], min_enable_speed=0, harness=Harness.custom), + CAR.HOLDEN_ASTRA: GMCarInfo("Holden Astra 2017"), + CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0), CAR.CADILLAC_ATS: GMCarInfo("Cadillac ATS Premium Performance 2018"), - CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017", harness=Harness.custom), - CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo", footnotes=[Footnote.OBD_II]), + CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"), + CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"), - CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "ACC + LKAS", footnotes=[Footnote.OBD_II]), + CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"), + CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier/Premier Redline Trim", video_link="https://youtu.be/xvwzGMUA210", footnotes=[], harness=Harness.gm), } @@ -97,13 +104,16 @@ class AccState: class CanBus: POWERTRAIN = 0 OBSTACLE = 1 + CAMERA = 2 CHASSIS = 2 SW_GMLAN = 3 LOOPBACK = 128 + DROPPED = 192 FINGERPRINTS = { + CAR.HOLDEN_ASTRA: [ # Astra BK MY17, ASCM unplugged - CAR.HOLDEN_ASTRA: [{ + { 190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 401: 8, 413: 8, 417: 8, 419: 8, 422: 1, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 458: 5, 479: 8, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 647: 5, 707: 8, 715: 8, 723: 8, 753: 5, 761: 7, 806: 1, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1009: 8, 1011: 6, 1017: 8, 1019: 3, 1020: 8, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 8, 1280: 4, 1300: 8, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1908: 7, 1912: 7, 1919: 7, }], CAR.VOLT: [ @@ -143,14 +153,17 @@ FINGERPRINTS = { { 309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8 }], + CAR.BOLT_EUV: [ + { + 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7 + }], } -DBC = { - CAR.HOLDEN_ASTRA: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'), - CAR.VOLT: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'), - CAR.MALIBU: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'), - CAR.ACADIA: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'), - CAR.CADILLAC_ATS: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'), - CAR.BUICK_REGAL: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'), - CAR.ESCALADE_ESV: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'), -} +DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis')) + +EV_CAR = {CAR.VOLT, CAR.BOLT_EUV} + +# We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness) +CAMERA_ACC_CAR = {CAR.BOLT_EUV} + +STEER_THRESHOLD = 1.0 diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index a6aa84adf..5fa475fe0 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -7,7 +7,7 @@ from 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_NIDEC_ALT_PCM_ACCEL, CarControllerParams +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 VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -155,9 +155,8 @@ class CarController: can_sends.append((0x18DAB0F1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 1)) # Send steering command. - idx = self.frame % 4 can_sends.append(hondacan.create_steering_control(self.packer, apply_steer, CC.latActive, self.CP.carFingerprint, - idx, CS.CP.openpilotLongitudinalControl)) + CS.CP.openpilotLongitudinalControl)) # wind brake from air resistance decel at high speed wind_brake = interp(CS.out.vEgo, [0.0, 2.3, 35.0], [0.001, 0.002, 0.15]) @@ -189,19 +188,17 @@ class CarController: pcm_accel = int(clip((accel / 1.44) / max_accel, 0.0, 1.0) * 0xc6) if not self.CP.openpilotLongitudinalControl: - if self.frame % 2 == 0: - idx = self.frame // 2 - can_sends.append(hondacan.create_bosch_supplemental_1(self.packer, self.CP.carFingerprint, idx)) + if self.frame % 2 == 0 and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: # radarless cars don't have supplemental message + can_sends.append(hondacan.create_bosch_supplemental_1(self.packer, self.CP.carFingerprint)) # If using stock ACC, spam cancel command to kill gas when OP disengages. if pcm_cancel_cmd: - can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.CANCEL, idx, self.CP.carFingerprint)) - elif CS.out.cruiseState.standstill: - can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.RES_ACCEL, idx, self.CP.carFingerprint)) + can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.CANCEL, self.CP.carFingerprint)) + elif CC.cruiseControl.resume: + can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.RES_ACCEL, self.CP.carFingerprint)) else: # Send gas and brake commands. if self.frame % 2 == 0: - idx = self.frame // 2 ts = self.frame * DT_CTRL if self.CP.carFingerprint in HONDA_BOSCH: @@ -210,7 +207,7 @@ class CarController: stopping = actuators.longControlState == LongCtrlState.stopping can_sends.extend(hondacan.create_acc_commands(self.packer, CC.enabled, CC.longActive, self.accel, self.gas, - idx, stopping, self.CP.carFingerprint)) + stopping, self.CP.carFingerprint)) else: apply_brake = clip(self.brake_last - wind_brake, 0.0, 1.0) apply_brake = int(clip(apply_brake * self.params.NIDEC_BRAKE_MAX, 0, self.params.NIDEC_BRAKE_MAX - 1)) @@ -218,7 +215,7 @@ class CarController: pcm_override = True can_sends.append(hondacan.create_brake_command(self.packer, apply_brake, pump_on, - pcm_override, pcm_cancel_cmd, fcw_display, idx, + pcm_override, pcm_cancel_cmd, fcw_display, self.CP.carFingerprint, CS.stock_brake)) self.apply_brake_last = apply_brake self.brake = apply_brake / self.params.NIDEC_BRAKE_MAX @@ -234,14 +231,13 @@ class CarController: self.gas = clip(gas_mult * (gas - brake + wind_brake * 3 / 4), 0., 1.) else: self.gas = 0.0 - can_sends.append(create_gas_interceptor_command(self.packer, self.gas, idx)) + can_sends.append(create_gas_interceptor_command(self.packer, self.gas, self.frame // 2)) # Send dashboard UI commands. if self.frame % 10 == 0: - idx = (self.frame // 10) % 4 hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_control.leadVisible, hud_control.lanesVisible, fcw_display, acc_alert, steer_required) - can_sends.extend(hondacan.create_ui_commands(self.packer, self.CP, CC.enabled, pcm_speed, hud, CS.is_metric, idx, CS.stock_hud)) + can_sends.extend(hondacan.create_ui_commands(self.packer, self.CP, CC.enabled, pcm_speed, hud, CS.is_metric, CS.acc_hud, CS.lkas_hud)) if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint not in HONDA_BOSCH: self.speed = pcm_speed diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index f5cdc838c..4696bec82 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -5,8 +5,9 @@ from common.conversions import Conversions as CV from 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_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 selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL TransmissionType = car.CarParams.TransmissionType @@ -80,7 +81,8 @@ def get_can_signals(CP, gearbox_msg, main_on_sig_msg): checks.append(("EPB_STATUS", 50)) if CP.carFingerprint in HONDA_BOSCH: - if not CP.openpilotLongitudinalControl: + # these messages are on camera bus on radarless cars + if not CP.openpilotLongitudinalControl and CP.carFingerprint not in HONDA_BOSCH_RADARLESS: signals += [ ("CRUISE_CONTROL_LABEL", "ACC_HUD"), ("CRUISE_SPEED", "ACC_HUD"), @@ -100,23 +102,16 @@ def get_can_signals(CP, gearbox_msg, main_on_sig_msg): else: checks.append(("CRUISE_PARAMS", 50)) - if CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): + if CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022): signals.append(("DRIVERS_DOOR_OPEN", "SCM_FEEDBACK")) - elif CP.carFingerprint == CAR.ODYSSEY_CHN: + elif CP.carFingerprint in (CAR.ODYSSEY_CHN, CAR.FREED, CAR.HRV): signals.append(("DRIVERS_DOOR_OPEN", "SCM_BUTTONS")) - elif CP.carFingerprint in (CAR.FREED, CAR.HRV): - signals += [("DRIVERS_DOOR_OPEN", "SCM_BUTTONS"), - ("WHEELS_MOVING", "STANDSTILL")] else: signals += [("DOOR_OPEN_FL", "DOORS_STATUS"), ("DOOR_OPEN_FR", "DOORS_STATUS"), ("DOOR_OPEN_RL", "DOORS_STATUS"), - ("DOOR_OPEN_RR", "DOORS_STATUS"), - ("WHEELS_MOVING", "STANDSTILL")] - checks += [ - ("DOORS_STATUS", 3), - ("STANDSTILL", 50), - ] + ("DOOR_OPEN_RR", "DOORS_STATUS")] + checks.append(("DOORS_STATUS", 3)) # add gas interceptor reading if we are using it if CP.enableGasInterceptor: @@ -175,7 +170,8 @@ class CarState(CarStateBase): # STANDSTILL->WHEELS_MOVING bit can be noisy around zero, so use XMISSION_SPEED # panda checks if the signal is non-zero ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 1e-5 - if self.CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): + # TODO: find a common signal across all cars + if self.CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022): ret.doorOpen = bool(cp.vl["SCM_FEEDBACK"]["DRIVERS_DOOR_OPEN"]) elif self.CP.carFingerprint in (CAR.ODYSSEY_CHN, CAR.FREED, CAR.HRV): ret.doorOpen = bool(cp.vl["SCM_BUTTONS"]["DRIVERS_DOOR_OPEN"]) @@ -235,11 +231,15 @@ class CarState(CarStateBase): if self.CP.carFingerprint in HONDA_BOSCH: if not self.CP.openpilotLongitudinalControl: - ret.cruiseState.nonAdaptive = cp.vl["ACC_HUD"]["CRUISE_CONTROL_LABEL"] != 0 - ret.cruiseState.standstill = cp.vl["ACC_HUD"]["CRUISE_SPEED"] == 252. + # ACC_HUD is on camera bus on radarless cars + acc_hud = cp_cam.vl["ACC_HUD"] if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS else cp.vl["ACC_HUD"] + ret.cruiseState.nonAdaptive = acc_hud["CRUISE_CONTROL_LABEL"] != 0 + ret.cruiseState.standstill = acc_hud["CRUISE_SPEED"] == 252. + # on certain cars, CRUISE_SPEED changes to imperial with car's unit setting + conversion_factor = CV.MPH_TO_MS if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS and not self.is_metric else CV.KPH_TO_MS # On set, cruise set speed pulses between 254~255 and the set speed prev is set to avoid this. - ret.cruiseState.speed = self.v_cruise_pcm_prev if cp.vl["ACC_HUD"]["CRUISE_SPEED"] > 160.0 else cp.vl["ACC_HUD"]["CRUISE_SPEED"] * CV.KPH_TO_MS + ret.cruiseState.speed = self.v_cruise_pcm_prev if acc_hud["CRUISE_SPEED"] > 160.0 else acc_hud["CRUISE_SPEED"] * conversion_factor self.v_cruise_pcm_prev = ret.cruiseState.speed else: ret.cruiseState.speed = cp.vl["CRUISE"]["CRUISE_SPEED_PCM"] * CV.KPH_TO_MS @@ -269,17 +269,20 @@ class CarState(CarStateBase): ret.brakePressed = True if self.CP.carFingerprint in HONDA_BOSCH: - ret.stockAeb = (not self.CP.openpilotLongitudinalControl) and bool(cp.vl["ACC_CONTROL"]["AEB_STATUS"] and cp.vl["ACC_CONTROL"]["ACCEL_COMMAND"] < -1e-5) + # TODO: find the radarless AEB_STATUS bit and make sure ACCEL_COMMAND is correct to enable AEB alerts + if self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: + ret.stockAeb = (not self.CP.openpilotLongitudinalControl) and bool(cp.vl["ACC_CONTROL"]["AEB_STATUS"] and cp.vl["ACC_CONTROL"]["ACCEL_COMMAND"] < -1e-5) else: ret.stockAeb = bool(cp_cam.vl["BRAKE_COMMAND"]["AEB_REQ_1"] and cp_cam.vl["BRAKE_COMMAND"]["COMPUTER_BRAKE"] > 1e-5) - if self.CP.carFingerprint in HONDA_BOSCH: - self.stock_hud = False - ret.stockFcw = False - else: + self.acc_hud = False + self.lkas_hud = False + if self.CP.carFingerprint not in HONDA_BOSCH: ret.stockFcw = cp_cam.vl["BRAKE_COMMAND"]["FCW"] != 0 - self.stock_hud = cp_cam.vl["ACC_HUD"] + self.acc_hud = cp_cam.vl["ACC_HUD"] self.stock_brake = cp_cam.vl["BRAKE_COMMAND"] + if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS: + self.lkas_hud = cp_cam.vl["LKAS_HUD"] if self.CP.enableBsm and self.CP.carFingerprint in (CAR.CRV_5G, ): # BSM messages are on B-CAN, requires a panda forwarding B-CAN messages to CAN 0 @@ -291,8 +294,7 @@ class CarState(CarStateBase): def get_can_parser(self, CP): signals, checks = get_can_signals(CP, self.gearbox_msg, self.main_on_sig_msg) - bus_pt = 1 if CP.carFingerprint in HONDA_BOSCH else 0 - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, bus_pt) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, get_pt_bus(CP.carFingerprint)) @staticmethod def get_cam_can_parser(CP): @@ -301,7 +303,17 @@ class CarState(CarStateBase): ("STEERING_CONTROL", 100), ] - if CP.carFingerprint not in HONDA_BOSCH: + if CP.carFingerprint in HONDA_BOSCH_RADARLESS: + signals.append(("LKAS_PROBLEM", "LKAS_HUD")) + checks.append(("LKAS_HUD", 10)) + if not CP.openpilotLongitudinalControl: + signals += [ + ("CRUISE_SPEED", "ACC_HUD"), + ("CRUISE_CONTROL_LABEL", "ACC_HUD"), + ] + checks.append(("ACC_HUD", 10)) + + elif CP.carFingerprint not in HONDA_BOSCH: signals += [("COMPUTER_BRAKE", "BRAKE_COMMAND"), ("AEB_REQ_1", "BRAKE_COMMAND"), ("FCW", "BRAKE_COMMAND"), diff --git a/selfdrive/car/honda/hondacan.py b/selfdrive/car/honda/hondacan.py index 3d8c79c80..87f8e6c5d 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, CAR, CarControllerParams +from selfdrive.car.honda.values import HondaFlags, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, CAR, CarControllerParams # CAN bus layout with relay # 0 = ACC-CAN - radar side @@ -7,8 +7,9 @@ from selfdrive.car.honda.values import HondaFlags, HONDA_BOSCH, CAR, CarControll # 2 = ACC-CAN - camera side # 3 = F-CAN A - OBDII port + def get_pt_bus(car_fingerprint): - return 1 if car_fingerprint in HONDA_BOSCH else 0 + return 1 if car_fingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) else 0 def get_lkas_cmd_bus(car_fingerprint, radar_disabled=False): @@ -18,7 +19,8 @@ def get_lkas_cmd_bus(car_fingerprint, radar_disabled=False): # normally steering commands are sent to radar, which forwards them to powertrain bus return 0 -def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw, idx, car_fingerprint, stock_brake): + +def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw, car_fingerprint, stock_brake): # TODO: do we loose pressure if we keep pump off for long? brakelights = apply_brake > 0 brake_rq = apply_brake > 0 @@ -40,10 +42,10 @@ def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_ "AEB_STATUS": 0, } bus = get_pt_bus(car_fingerprint) - return packer.make_can_msg("BRAKE_COMMAND", bus, values, idx) + return packer.make_can_msg("BRAKE_COMMAND", bus, values) -def create_acc_commands(packer, enabled, active, accel, gas, idx, stopping, car_fingerprint): +def create_acc_commands(packer, enabled, active, accel, gas, stopping, car_fingerprint): commands = [] bus = get_pt_bus(car_fingerprint) min_gas_accel = CarControllerParams.BOSCH_GAS_LOOKUP_BP[0] @@ -65,7 +67,7 @@ def create_acc_commands(packer, enabled, active, accel, gas, idx, stopping, car_ "STANDSTILL": standstill, "STANDSTILL_RELEASE": standstill_release, } - commands.append(packer.make_can_msg("ACC_CONTROL", bus, acc_control_values, idx)) + commands.append(packer.make_can_msg("ACC_CONTROL", bus, acc_control_values)) acc_control_on_values = { "SET_TO_3": 0x03, @@ -74,20 +76,21 @@ def create_acc_commands(packer, enabled, active, accel, gas, idx, stopping, car_ "SET_TO_75": 0x75, "SET_TO_30": 0x30, } - commands.append(packer.make_can_msg("ACC_CONTROL_ON", bus, acc_control_on_values, idx)) + commands.append(packer.make_can_msg("ACC_CONTROL_ON", bus, acc_control_on_values)) return commands -def create_steering_control(packer, apply_steer, lkas_active, car_fingerprint, idx, radar_disabled): + +def create_steering_control(packer, apply_steer, lkas_active, car_fingerprint, radar_disabled): values = { "STEER_TORQUE": apply_steer if lkas_active else 0, "STEER_TORQUE_REQUEST": lkas_active, } bus = get_lkas_cmd_bus(car_fingerprint, radar_disabled) - return packer.make_can_msg("STEERING_CONTROL", bus, values, idx) + return packer.make_can_msg("STEERING_CONTROL", bus, values) -def create_bosch_supplemental_1(packer, car_fingerprint, idx): +def create_bosch_supplemental_1(packer, car_fingerprint): # non-active params values = { "SET_ME_X04": 0x04, @@ -95,10 +98,10 @@ def create_bosch_supplemental_1(packer, car_fingerprint, idx): "SET_ME_X10": 0x10, } bus = get_lkas_cmd_bus(car_fingerprint) - return packer.make_can_msg("BOSCH_SUPPLEMENTAL_1", bus, values, idx) + return packer.make_can_msg("BOSCH_SUPPLEMENTAL_1", bus, values) -def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, idx, stock_hud): +def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, lkas_hud): commands = [] bus_pt = get_pt_bus(CP.carFingerprint) radar_disabled = CP.carFingerprint in HONDA_BOSCH and CP.openpilotLongitudinalControl @@ -108,7 +111,7 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, idx, stoc acc_hud_values = { 'CRUISE_SPEED': hud.v_cruise, 'ENABLE_MINI_CAR': 1, - 'HUD_DISTANCE': 3, # max distance setting on display + 'HUD_DISTANCE': 0, # max distance setting on display 'IMPERIAL_UNIT': int(not is_metric), 'HUD_LEAD': 2 if enabled and hud.lead_visible else 1 if enabled else 0, 'SET_ME_X01_2': 1, @@ -122,11 +125,11 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, idx, stoc acc_hud_values['PCM_SPEED'] = pcm_speed * CV.MS_TO_KPH acc_hud_values['PCM_GAS'] = hud.pcm_accel acc_hud_values['SET_ME_X01'] = 1 - acc_hud_values['FCM_OFF'] = stock_hud['FCM_OFF'] - acc_hud_values['FCM_OFF_2'] = stock_hud['FCM_OFF_2'] - acc_hud_values['FCM_PROBLEM'] = stock_hud['FCM_PROBLEM'] - acc_hud_values['ICONS'] = stock_hud['ICONS'] - commands.append(packer.make_can_msg("ACC_HUD", bus_pt, acc_hud_values, idx)) + acc_hud_values['FCM_OFF'] = acc_hud['FCM_OFF'] + acc_hud_values['FCM_OFF_2'] = acc_hud['FCM_OFF_2'] + acc_hud_values['FCM_PROBLEM'] = acc_hud['FCM_PROBLEM'] + acc_hud_values['ICONS'] = acc_hud['ICONS'] + commands.append(packer.make_can_msg("ACC_HUD", bus_pt, acc_hud_values)) lkas_hud_values = { 'SET_ME_X41': 0x41, @@ -135,32 +138,39 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, idx, stoc 'BEEP': 0, } + if CP.carFingerprint in HONDA_BOSCH_RADARLESS: + lkas_hud_values['LANE_LINES'] = 3 + lkas_hud_values['DASHED_LANES'] = hud.lanes_visible + # car likely needs to see LKAS_PROBLEM fall within a specific time frame, so forward from camera + lkas_hud_values['LKAS_PROBLEM'] = lkas_hud['LKAS_PROBLEM'] + if not (CP.flags & HondaFlags.BOSCH_EXT_HUD): lkas_hud_values['SET_ME_X48'] = 0x48 if CP.flags & HondaFlags.BOSCH_EXT_HUD and not CP.openpilotLongitudinalControl: - commands.append(packer.make_can_msg('LKAS_HUD_A', bus_lkas, lkas_hud_values, idx)) - commands.append(packer.make_can_msg('LKAS_HUD_B', bus_lkas, lkas_hud_values, idx)) + commands.append(packer.make_can_msg('LKAS_HUD_A', bus_lkas, lkas_hud_values)) + commands.append(packer.make_can_msg('LKAS_HUD_B', bus_lkas, lkas_hud_values)) else: - commands.append(packer.make_can_msg('LKAS_HUD', bus_lkas, lkas_hud_values, idx)) + commands.append(packer.make_can_msg('LKAS_HUD', bus_lkas, lkas_hud_values)) if radar_disabled and CP.carFingerprint in HONDA_BOSCH: radar_hud_values = { 'CMBS_OFF': 0x01, 'SET_TO_1': 0x01, } - commands.append(packer.make_can_msg('RADAR_HUD', bus_pt, radar_hud_values, idx)) + commands.append(packer.make_can_msg('RADAR_HUD', bus_pt, radar_hud_values)) if CP.carFingerprint == CAR.CIVIC_BOSCH: - commands.append(packer.make_can_msg("LEGACY_BRAKE_COMMAND", bus_pt, {}, idx)) + commands.append(packer.make_can_msg("LEGACY_BRAKE_COMMAND", bus_pt, {})) return commands -def spam_buttons_command(packer, button_val, idx, car_fingerprint): +def spam_buttons_command(packer, button_val, car_fingerprint): values = { 'CRUISE_BUTTONS': button_val, 'CRUISE_SETTING': 0, } - bus = get_pt_bus(car_fingerprint) - return packer.make_can_msg("SCM_BUTTONS", bus, values, idx) + # send buttons to camera on radarless cars + bus = 2 if car_fingerprint in HONDA_BOSCH_RADARLESS else get_pt_bus(car_fingerprint) + return packer.make_can_msg("SCM_BUTTONS", bus, values) diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 994152608..a78189b69 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -3,7 +3,7 @@ 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, HONDA_BOSCH_ALT_BRAKE_SIGNAL +from selfdrive.car.honda.values import CarControllerParams, CruiseButtons, HondaFlags, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL, HONDA_BOSCH_RADARLESS from selfdrive.car import STD_CARGO_KG, CivicParams, create_button_enable_events, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.disable_ecu import disable_ecu @@ -37,9 +37,10 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaBosch)] ret.radarOffCan = True - # Disable the radar and let openpilot control longitudinal - # WARNING: THIS DISABLES AEB! - ret.openpilotLongitudinalControl = disable_radar + if candidate not in HONDA_BOSCH_RADARLESS: + # Disable the radar and let openpilot control longitudinal + # WARNING: THIS DISABLES AEB! + ret.openpilotLongitudinalControl = disable_radar ret.pcmCruise = not ret.openpilotLongitudinalControl else: @@ -104,7 +105,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[1.1], [0.33]] tire_stiffness_factor = 1. - elif candidate in (CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL): + elif candidate in (CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CIVIC_2022): stop_and_go = True ret.mass = CivicParams.MASS ret.wheelbase = CivicParams.WHEELBASE @@ -304,6 +305,9 @@ class CarInterface(CarInterfaceBase): if ret.openpilotLongitudinalControl and candidate in HONDA_BOSCH: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_BOSCH_LONG + if candidate in HONDA_BOSCH_RADARLESS: + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_RADARLESS + # min speed to enable ACC. if car can do stop and go, then set enabling speed # to a negative value, so it won't matter. Otherwise, add 0.5 mph margin to not # conflict with PCM acc @@ -325,7 +329,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def init(CP, logcan, sendcan): - if CP.carFingerprint in HONDA_BOSCH and CP.openpilotLongitudinalControl: + if CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and CP.openpilotLongitudinalControl: disable_ecu(logcan, sendcan, bus=1, addr=0x18DAB0F1, com_cont_req=b'\x28\x83\x03') # returns a car.CarState diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 152f71e98..ab1cc9a6c 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -75,6 +75,7 @@ class CAR: CIVIC = "HONDA CIVIC 2016" CIVIC_BOSCH = "HONDA CIVIC (BOSCH) 2019" CIVIC_BOSCH_DIESEL = "HONDA CIVIC SEDAN 1.6 DIESEL 2019" + CIVIC_2022 = "HONDA CIVIC 2022" ACURA_ILX = "ACURA ILX 2016" CRV = "HONDA CR-V 2016" CRV_5G = "HONDA CR-V 2017" @@ -108,33 +109,37 @@ class HondaCarInfo(CarInfo): CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = { CAR.ACCORD: [ - HondaCarInfo("Honda Accord 2018-21", "All", video_link="https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch), - HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch), + HondaCarInfo("Honda Accord 2018-22", "All", video_link="https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), + HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), ], - CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-21", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch), + CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", harness=Harness.nidec), CAR.CIVIC_BOSCH: [ - HondaCarInfo("Honda Civic 2019-20", "All", video_link="https://www.youtube.com/watch?v=4Iz1Mz5LGF8", footnotes=[Footnote.CIVIC_DIESEL], min_steer_speed=2. * CV.MPH_TO_MS, harness=Harness.bosch), - HondaCarInfo("Honda Civic Hatchback 2017-21", harness=Harness.bosch), + HondaCarInfo("Honda Civic 2019-21", "All", video_link="https://www.youtube.com/watch?v=4Iz1Mz5LGF8", footnotes=[Footnote.CIVIC_DIESEL], min_steer_speed=2. * CV.MPH_TO_MS, harness=Harness.bosch_a), + HondaCarInfo("Honda Civic Hatchback 2017-21", harness=Harness.bosch_a), ], CAR.CIVIC_BOSCH_DIESEL: None, # same platform + CAR.CIVIC_2022: [ + HondaCarInfo("Honda Civic 2022", "All", min_steer_speed=0., harness=Harness.bosch_b), + HondaCarInfo("Honda Civic Hatchback 2022", "All", min_steer_speed=0., harness=Harness.bosch_b), + ], CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring", harness=Harness.nidec), - CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-21", harness=Harness.bosch), + CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", harness=Harness.nidec), + CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", harness=Harness.bosch_a), CAR.CRV_EU: None, # HondaCarInfo("Honda CR-V EU", "Touring"), # Euro version of CRV Touring - CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", harness=Harness.bosch), - CAR.FIT: HondaCarInfo("Honda Fit 2018-19", harness=Harness.nidec), + CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", harness=Harness.bosch_a), + CAR.FIT: HondaCarInfo("Honda Fit 2018-20", harness=Harness.nidec), CAR.FREED: HondaCarInfo("Honda Freed 2020", harness=Harness.nidec), - CAR.HRV: HondaCarInfo("Honda HR-V 2019-20", harness=Harness.nidec), + CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", harness=Harness.nidec), CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20", min_steer_speed=0., harness=Harness.nidec), CAR.ODYSSEY_CHN: None, # Chinese version of Odyssey CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", harness=Harness.nidec), - CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-21", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch), - CAR.PILOT: HondaCarInfo("Honda Pilot 2016-21", harness=Harness.nidec), + CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), + CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", harness=Harness.nidec), CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", harness=Harness.nidec), CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", harness=Harness.nidec), - CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-21", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch), - CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch), + CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), + CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), } @@ -1298,6 +1303,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TXM-A070\x00\x00', + b'36802-TXM-A080\x00\x00', ], (Ecu.fwdCamera, 0x18dab5f1, None): [ b'36161-TXM-A050\x00\x00', @@ -1336,6 +1342,7 @@ FW_VERSIONS = { b'36161-T7A-A140\x00\x00', b'36161-T7A-A240\x00\x00', b'36161-T7A-C440\x00\x00', + b'36161-T7A-A040\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-T7A-A230\x00\x00', @@ -1346,6 +1353,7 @@ FW_VERSIONS = { b'78109-THX-A210\x00\x00', b'78109-THX-A220\x00\x00', b'78109-THX-C220\x00\x00', + b'78109-THW-A110\x00\x00', ], }, CAR.ACURA_ILX: { @@ -1392,6 +1400,48 @@ FW_VERSIONS = { b'57114-TYF-E030\x00\x00' ], }, + CAR.CIVIC_2022: { + (Ecu.eps, 0x18DA30F1, None): [ + b'39990-T39-A130\x00\x00', + b'39990-T43-J020\x00\x00', + ], + (Ecu.gateway, 0x18DAEFF1, None): [ + b'38897-T20-A020\x00\x00', + b'38897-T20-A510\x00\x00', + b'38897-T21-A010\x00\x00', + b'38897-T20-A210\x00\x00', + b'38897-T20-A310\x00\x00', + ], + (Ecu.srs, 0x18DA53F1, None): [ + b'77959-T20-A970\x00\x00', + b'77959-T47-A940\x00\x00', + b'77959-T47-A950\x00\x00', + ], + (Ecu.combinationMeter, 0x18DA60F1, None): [ + b'78108-T21-A220\x00\x00', + b'78108-T21-A620\x00\x00', + b'78108-T23-A110\x00\x00', + b'78108-T21-A230\x00\x00', + b'78108-T22-A020\x00\x00', + ], + (Ecu.vsa, 0x18DA28F1, None): [ + b'57114-T20-AB40\x00\x00', + b'57114-T43-JB30\x00\x00', + ], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28101-65D-A020\x00\x00', + b'28101-65D-A120\x00\x00', + b'28101-65H-A020\x00\x00', + b'28101-65H-A120\x00\x00', + ], + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-64L-A540\x00\x00', + b'37805-64S-A540\x00\x00', + b'37805-64S-A720\x00\x00', + b'37805-64A-A540\x00\x00', + b'37805-64A-A620\x00\x00', + ], + }, } DBC = { @@ -1417,6 +1467,7 @@ DBC = { CAR.RIDGELINE: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), CAR.INSIGHT: dbc_dict('honda_insight_ex_2019_can_generated', None), CAR.HONDA_E: dbc_dict('acura_rdx_2020_can_generated', None), + CAR.CIVIC_2022: dbc_dict('honda_civic_ex_2022_can_generated', None), } STEER_THRESHOLD = { @@ -1429,5 +1480,6 @@ HONDA_NIDEC_ALT_PCM_ACCEL = {CAR.ODYSSEY} HONDA_NIDEC_ALT_SCM_MESSAGES = {CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_EU, CAR.FIT, CAR.FREED, CAR.HRV, CAR.ODYSSEY_CHN, CAR.PILOT, CAR.PASSPORT, CAR.RIDGELINE} HONDA_BOSCH = {CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_5G, - CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E} + CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022} HONDA_BOSCH_ALT_BRAKE_SIGNAL = {CAR.ACCORD, CAR.CRV_5G, CAR.ACURA_RDX_3G} +HONDA_BOSCH_RADARLESS = {CAR.CIVIC_2022} diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index fb3579464..60fb46340 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -1,11 +1,11 @@ from cereal import car -from common.realtime import DT_CTRL -from common.numpy_fast import clip, interp from common.conversions import Conversions as CV -from selfdrive.car import apply_std_steer_torque_limits -from selfdrive.car.hyundai import hda2can, hyundaican -from selfdrive.car.hyundai.values import Buttons, CarControllerParams, HDA2_CAR, CAR +from common.numpy_fast import clip, interp +from common.realtime import DT_CTRL from opendbc.can.packer import CANPacker +from selfdrive.car import apply_std_steer_torque_limits +from selfdrive.car.hyundai import hyundaicanfd, hyundaican +from selfdrive.car.hyundai.values import Buttons, CarControllerParams, CANFD_CAR, CAR VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState @@ -43,7 +43,6 @@ class CarController: self.apply_steer_last = 0 self.car_fingerprint = CP.carFingerprint - self.steer_rate_limited = False self.last_button_frame = 0 self.accel = 0 @@ -52,9 +51,13 @@ class CarController: hud_control = CC.hudControl # Steering Torque - new_steer = int(round(actuators.steer * self.params.STEER_MAX)) + + # These cars have significantly more torque than most HKG. Limit to 70% of max. + steer = actuators.steer + if self.CP.carFingerprint in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): + steer = clip(steer, -0.7, 0.7) + new_steer = int(round(steer * self.params.STEER_MAX)) apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) - self.steer_rate_limited = new_steer != apply_steer if not CC.latActive: apply_steer = 0 @@ -66,20 +69,23 @@ class CarController: can_sends = [] - if self.CP.carFingerprint in HDA2_CAR: + if self.CP.carFingerprint in CANFD_CAR: # steering control - can_sends.append(hda2can.create_lkas(self.packer, CC.enabled, self.frame, CC.latActive, apply_steer)) + can_sends.append(hyundaicanfd.create_lkas(self.packer, CC.enabled, CC.latActive, apply_steer)) + + if self.frame % 5 == 0: + can_sends.append(hyundaicanfd.create_cam_0x2a4(self.packer, CS.cam_0x2a4)) # cruise cancel if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: if CC.cruiseControl.cancel: for _ in range(20): - can_sends.append(hda2can.create_buttons(self.packer, CS.buttons_counter+1, True, False)) + can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.CANCEL)) self.last_button_frame = self.frame # cruise standstill resume - elif CC.enabled and CS.out.cruiseState.standstill: - can_sends.append(hda2can.create_buttons(self.packer, CS.buttons_counter+1, False, True)) + elif CC.cruiseControl.resume: + can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.RES_ACCEL)) self.last_button_frame = self.frame else: @@ -95,12 +101,12 @@ class CarController: if not self.CP.openpilotLongitudinalControl: if CC.cruiseControl.cancel: - can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL)) - elif CS.out.cruiseState.standstill: + 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)] * 25) + can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP.carFingerprint)] * 25) self.last_button_frame = self.frame if self.frame % 2 == 0 and self.CP.openpilotLongitudinalControl: @@ -122,7 +128,7 @@ class CarController: # 20 Hz LFA MFA message if self.frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, - CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, + CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, CAR.KONA_EV_2022, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022): can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled)) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 9d864faac..9105f1278 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -5,10 +5,10 @@ from cereal import car from common.conversions import Conversions as CV from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine -from selfdrive.car.hyundai.values import DBC, STEER_THRESHOLD, FEATURES, HDA2_CAR, EV_CAR, HYBRID_CAR, Buttons +from selfdrive.car.hyundai.values import DBC, FEATURES, CAMERA_SCC_CAR, CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams from selfdrive.car.interfaces import CarStateBase -PREV_BUTTON_SAMPLES = 4 +PREV_BUTTON_SAMPLES = 8 class CarState(CarStateBase): @@ -19,7 +19,7 @@ class CarState(CarStateBase): self.cruise_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) self.main_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) - if CP.carFingerprint in HDA2_CAR: + if CP.carFingerprint in CANFD_CAR: self.shifter_values = can_define.dv["ACCELERATOR"]["GEAR"] elif self.CP.carFingerprint in FEATURES["use_cluster_gears"]: self.shifter_values = can_define.dv["CLU15"]["CF_Clu_Gear"] @@ -32,12 +32,16 @@ class CarState(CarStateBase): self.park_brake = False self.buttons_counter = 0 + self.params = CarControllerParams(CP) + def update(self, cp, cp_cam): - if self.CP.carFingerprint in HDA2_CAR: - return self.update_hda2(cp, cp_cam) + if self.CP.carFingerprint in CANFD_CAR: + return self.update_canfd(cp, cp_cam) ret = car.CarState.new_message() + cp_cruise = cp_cam if self.CP.carFingerprint in CAMERA_SCC_CAR else cp + ret.doorOpen = any([cp.vl["CGW1"]["CF_Gway_DrvDrSw"], cp.vl["CGW1"]["CF_Gway_AstDrSw"], cp.vl["CGW2"]["CF_Gway_RLDrSw"], cp.vl["CGW2"]["CF_Gway_RRDrSw"]]) @@ -61,7 +65,7 @@ class CarState(CarStateBase): 50, cp.vl["CGW1"]["CF_Gway_TurnSigLh"], cp.vl["CGW1"]["CF_Gway_TurnSigRh"]) ret.steeringTorque = cp.vl["MDPS12"]["CR_Mdps_StrColTq"] ret.steeringTorqueEps = cp.vl["MDPS12"]["CR_Mdps_OutTq"] - ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD + ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD ret.steerFaultTemporary = cp.vl["MDPS12"]["CF_Mdps_ToiUnavail"] != 0 or cp.vl["MDPS12"]["CF_Mdps_ToiFlt"] != 0 # cruise state @@ -71,11 +75,11 @@ class CarState(CarStateBase): ret.cruiseState.enabled = cp.vl["TCS13"]["ACC_REQ"] == 1 ret.cruiseState.standstill = False else: - ret.cruiseState.available = cp.vl["SCC11"]["MainMode_ACC"] == 1 - ret.cruiseState.enabled = cp.vl["SCC12"]["ACCMode"] != 0 - ret.cruiseState.standstill = cp.vl["SCC11"]["SCCInfoDisplay"] == 4. + 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. speed_conv = CV.MPH_TO_MS if cp.vl["CLU11"]["CF_Clu_SPEED_UNIT"] else CV.KPH_TO_MS - ret.cruiseState.speed = cp.vl["SCC11"]["VSetDis"] * speed_conv + ret.cruiseState.speed = cp_cruise.vl["SCC11"]["VSetDis"] * speed_conv # TODO: Find brake pressure ret.brake = 0 @@ -108,11 +112,11 @@ class CarState(CarStateBase): if not self.CP.openpilotLongitudinalControl: if self.CP.carFingerprint in FEATURES["use_fca"]: - ret.stockAeb = cp.vl["FCA11"]["FCA_CmdAct"] != 0 - ret.stockFcw = cp.vl["FCA11"]["CF_VSM_Warn"] == 2 + ret.stockAeb = cp_cruise.vl["FCA11"]["FCA_CmdAct"] != 0 + ret.stockFcw = cp_cruise.vl["FCA11"]["CF_VSM_Warn"] == 2 else: - ret.stockAeb = cp.vl["SCC12"]["AEB_CmdAct"] != 0 - ret.stockFcw = cp.vl["SCC12"]["CF_VSM_Warn"] == 2 + ret.stockAeb = cp_cruise.vl["SCC12"]["AEB_CmdAct"] != 0 + ret.stockFcw = cp_cruise.vl["SCC12"]["CF_VSM_Warn"] == 2 if self.CP.enableBsm: ret.leftBlindspot = cp.vl["LCA11"]["CF_Lca_IndLeft"] != 0 @@ -129,7 +133,7 @@ class CarState(CarStateBase): return ret - def update_hda2(self, cp, cp_cam): + def update_canfd(self, cp, cp_cam): ret = car.CarState.new_message() ret.gas = cp.vl["ACCELERATOR"]["ACCELERATOR_PEDAL"] / 255. @@ -157,7 +161,7 @@ class CarState(CarStateBase): ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEERING_ANGLE"] * -1 ret.steeringTorque = cp.vl["MDPS"]["STEERING_COL_TORQUE"] ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"] - ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD + ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"]["LEFT_LAMP"], cp.vl["BLINKERS"]["RIGHT_LAMP"]) @@ -169,14 +173,18 @@ class CarState(CarStateBase): speed_factor = CV.MPH_TO_MS if cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] == 1 else CV.KPH_TO_MS ret.cruiseState.speed = cp.vl["CRUISE_INFO"]["SET_SPEED"] * speed_factor - self.buttons_counter = cp.vl["CRUISE_BUTTONS"]["_COUNTER"] + self.cruise_buttons.extend(cp.vl_all["CRUISE_BUTTONS"]["CRUISE_BUTTONS"]) + self.main_buttons.extend(cp.vl_all["CRUISE_BUTTONS"]["ADAPTIVE_CRUISE_MAIN_BTN"]) + self.buttons_counter = cp.vl["CRUISE_BUTTONS"]["COUNTER"] + + self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"]) return ret @staticmethod def get_can_parser(CP): - if CP.carFingerprint in HDA2_CAR: - return CarState.get_can_parser_hda2(CP) + if CP.carFingerprint in CANFD_CAR: + return CarState.get_can_parser_canfd(CP) signals = [ # signal_name, signal_address @@ -245,7 +253,7 @@ class CarState(CarStateBase): ("SAS11", 100), ] - if not CP.openpilotLongitudinalControl: + if not CP.openpilotLongitudinalControl and CP.carFingerprint not in CAMERA_SCC_CAR: signals += [ ("MainMode_ACC", "SCC11"), ("VSetDis", "SCC11"), @@ -310,8 +318,10 @@ class CarState(CarStateBase): @staticmethod def get_cam_can_parser(CP): - if CP.carFingerprint in HDA2_CAR: - return None + if CP.carFingerprint in CANFD_CAR: + signals = [(f"BYTE{i}", "CAM_0x2a4") for i in range(3, 24)] + checks = [("CAM_0x2a4", 20)] + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 6) signals = [ # signal_name, signal_address @@ -335,10 +345,35 @@ class CarState(CarStateBase): ("LKAS11", 100) ] + if not CP.openpilotLongitudinalControl and CP.carFingerprint in CAMERA_SCC_CAR: + signals += [ + ("MainMode_ACC", "SCC11"), + ("VSetDis", "SCC11"), + ("SCCInfoDisplay", "SCC11"), + ("ACC_ObjDist", "SCC11"), + ("ACCMode", "SCC12"), + ] + checks += [ + ("SCC11", 50), + ("SCC12", 50), + ] + + if CP.carFingerprint in FEATURES["use_fca"]: + signals += [ + ("FCA_CmdAct", "FCA11"), + ("CF_VSM_Warn", "FCA11"), + ] + checks.append(("FCA11", 50)) + else: + signals += [ + ("AEB_CmdAct", "SCC12"), + ("CF_VSM_Warn", "SCC12"), + ] + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2) @staticmethod - def get_can_parser_hda2(CP): + def get_can_parser_canfd(CP): signals = [ ("WHEEL_SPEED_1", "WHEEL_SPEEDS"), ("WHEEL_SPEED_2", "WHEEL_SPEEDS"), @@ -357,7 +392,9 @@ class CarState(CarStateBase): ("CRUISE_ACTIVE", "SCC1"), ("SET_SPEED", "CRUISE_INFO"), ("CRUISE_STANDSTILL", "CRUISE_INFO"), - ("_COUNTER", "CRUISE_BUTTONS"), + ("COUNTER", "CRUISE_BUTTONS"), + ("CRUISE_BUTTONS", "CRUISE_BUTTONS"), + ("ADAPTIVE_CRUISE_MAIN_BTN", "CRUISE_BUTTONS"), ("DISTANCE_UNIT", "CLUSTER_INFO"), diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index 53499053e..8a5e33f11 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 +from selfdrive.car.hyundai.values import CAR, CHECKSUM, CAMERA_SCC_CAR hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf) @@ -18,8 +18,8 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, if car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020, - CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.SANTA_FE_2022, - CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): + 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): values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsOpt_USM"] = 2 @@ -62,11 +62,13 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, return packer.make_can_msg("LKAS11", 0, values) -def create_clu11(packer, frame, clu11, button): +def create_clu11(packer, frame, clu11, button, car_fingerprint): values = clu11 values["CF_Clu_CruiseSwState"] = button values["CF_Clu_AliveCnt1"] = frame % 0x10 - return packer.make_can_msg("CLU11", 0, values) + # send buttons to camera on camera-scc based cars + bus = 2 if car_fingerprint in CAMERA_SCC_CAR else 0 + return packer.make_can_msg("CLU11", bus, values) def create_lfahda_mfc(packer, enabled, hda_set_speed=0): diff --git a/selfdrive/car/hyundai/hda2can.py b/selfdrive/car/hyundai/hyundaicanfd.py similarity index 50% rename from selfdrive/car/hyundai/hda2can.py rename to selfdrive/car/hyundai/hyundaicanfd.py index e4c658c1a..36207aa11 100644 --- a/selfdrive/car/hyundai/hda2can.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -1,4 +1,4 @@ -def create_lkas(packer, enabled, frame, lat_active, apply_steer): +def create_lkas(packer, enabled, lat_active, apply_steer): values = { "LKA_MODE": 2, "LKA_ICON": 2 if enabled else 1, @@ -10,14 +10,18 @@ def create_lkas(packer, enabled, frame, lat_active, apply_steer): "NEW_SIGNAL_1": 0, "NEW_SIGNAL_2": 0, } - return packer.make_can_msg("LKAS", 4, values, frame % 255) + return packer.make_can_msg("LKAS", 4, values) +def create_cam_0x2a4(packer, camera_values): + camera_values.update({ + "BYTE7": 0, + }) + return packer.make_can_msg("CAM_0x2a4", 4, camera_values) -def create_buttons(packer, cnt, cancel, resume): +def create_buttons(packer, cnt, btn): values = { - "_COUNTER": cnt % 0xf, + "COUNTER": cnt, "SET_ME_1": 1, - "DISTANCE_BTN": 1 if resume else 0, - "PAUSE_RESUME_BTN": 1 if cancel else 0, + "CRUISE_BUTTONS": btn, } return packer.make_can_msg("CRUISE_BUTTONS", 5, values) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 497d6b3f3..d3dc1a2c6 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 -import os from cereal import car from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car.hyundai.values import CAR, DBC, HDA2_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams +from selfdrive.car.hyundai.values import CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR from selfdrive.car import STD_CARGO_KG, create_button_enable_events, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.disable_ecu import disable_ecu -from selfdrive.controls.lib.latcontrol_torque import set_torque_tune ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -27,11 +25,10 @@ class CarInterface(CarInterfaceBase): ret = CarInterfaceBase.get_std_params(candidate, fingerprint) ret.carName = "hyundai" - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundai, 0)] ret.radarOffCan = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None # WARNING: disabling radar also disables AEB (and we show the same warning on the instrument cluster as if you manually disabled AEB) - ret.openpilotLongitudinalControl = disable_radar and (candidate not in LEGACY_SAFETY_MODE_CAR) + ret.openpilotLongitudinalControl = disable_radar and (candidate not in (LEGACY_SAFETY_MODE_CAR | CAMERA_SCC_CAR)) ret.pcmCruise = not ret.openpilotLongitudinalControl @@ -39,8 +36,6 @@ class CarInterface(CarInterfaceBase): # These cars likely still work fine. Once a user confirms each car works and a test route is # added to selfdrive/car/tests/routes.py, we can remove it from this list. ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, CAR.ELANTRA_GT_I30} - if candidate in HDA2_CAR: - ret.dashcamOnly = not os.path.exists('/data/enable-ev6') ret.steerActuatorDelay = 0.1 # Default delay ret.steerLimitTimer = 0.4 @@ -54,7 +49,6 @@ class CarInterface(CarInterfaceBase): ret.stopAccel = 0.0 ret.longitudinalActuatorDelayUpperBound = 1.0 # s - torque_params = CarInterfaceBase.get_torque_params(candidate) if candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): ret.lateralTuning.pid.kf = 0.00005 ret.mass = 3982. * CV.LB_TO_KG + STD_CARGO_KG @@ -69,7 +63,7 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.84 ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable tire_stiffness_factor = 0.65 - set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION']) + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.SONATA_LF: ret.lateralTuning.pid.kf = 0.00005 ret.mass = 4497. * CV.LB_TO_KG @@ -83,8 +77,7 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.90 ret.steerRatio = 15.6 * 1.15 tire_stiffness_factor = 0.63 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.05]] + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate in (CAR.ELANTRA, CAR.ELANTRA_GT_I30): ret.lateralTuning.pid.kf = 0.00006 ret.mass = 1275. + STD_CARGO_KG @@ -99,13 +92,13 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.72 ret.steerRatio = 12.9 tire_stiffness_factor = 0.65 - set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION']) + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.ELANTRA_HEV_2021: ret.mass = (3017. * CV.LB_TO_KG) + STD_CARGO_KG ret.wheelbase = 2.72 ret.steerRatio = 12.9 tire_stiffness_factor = 0.65 - set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION']) + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.HYUNDAI_GENESIS: ret.lateralTuning.pid.kf = 0.00005 ret.mass = 2060. + STD_CARGO_KG @@ -121,9 +114,9 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.indi.actuatorEffectivenessBP = [0.] ret.lateralTuning.indi.actuatorEffectivenessV = [2.3] ret.minSteerSpeed = 60 * CV.KPH_TO_MS - elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV): + elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): ret.lateralTuning.pid.kf = 0.00005 - ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425.}.get(candidate, 1275.) + STD_CARGO_KG + ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425., CAR.KONA_EV_2022: 1743.}.get(candidate, 1275.) + STD_CARGO_KG ret.wheelbase = 2.6 ret.steerRatio = 13.42 # Spec tire_stiffness_factor = 0.385 @@ -207,8 +200,7 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.80 ret.steerRatio = 13.75 tire_stiffness_factor = 0.5 - torque_params = CarInterfaceBase.get_torque_params(CAR.KIA_OPTIMA) - set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION']) + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.KIA_STINGER: ret.lateralTuning.pid.kf = 0.00005 ret.mass = 1825. + STD_CARGO_KG @@ -245,13 +237,14 @@ class CarInterface(CarInterfaceBase): ret.mass = 2055 + STD_CARGO_KG ret.wheelbase = 2.9 ret.steerRatio = 16. - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput), - get_safety_config(car.CarParams.SafetyModel.hyundaiHDA2)] tire_stiffness_factor = 0.65 - - ret.maxLateralAccel = 2. - # TODO override until there is more data - set_torque_tune(ret.lateralTuning, 2.0, 0.05) + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + elif candidate == CAR.IONIQ_5: + ret.mass = 2012 + STD_CARGO_KG + ret.wheelbase = 3.0 + ret.steerRatio = 16. + tire_stiffness_factor = 0.65 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) # Genesis elif candidate == CAR.GENESIS_G70: @@ -289,15 +282,28 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.01]] - # these cars require a special panda safety mode due to missing counters and checksums in the messages - if candidate in LEGACY_SAFETY_MODE_CAR: - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundaiLegacy)] - - # set appropriate safety param for gas signal - if candidate in HYBRID_CAR: - ret.safetyConfigs[0].safetyParam = 2 - elif candidate in EV_CAR: - ret.safetyConfigs[0].safetyParam = 1 + # panda safety config + if candidate in CANFD_CAR: + ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput), + get_safety_config(car.CarParams.SafetyModel.hyundaiCanfd)] + else: + if candidate in LEGACY_SAFETY_MODE_CAR: + # these cars require a special panda safety mode due to missing counters and checksums in the messages + ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundaiLegacy)] + else: + ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundai, 0)] + + # set appropriate safety param for gas signal + if candidate in HYBRID_CAR: + ret.safetyConfigs[0].safetyParam = 2 + elif candidate in EV_CAR: + ret.safetyConfigs[0].safetyParam = 1 + + if ret.openpilotLongitudinalControl: + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_LONG + + if candidate in CAMERA_SCC_CAR: + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC ret.centerToFront = ret.wheelbase * 0.4 @@ -312,9 +318,6 @@ class CarInterface(CarInterfaceBase): ret.enableBsm = 0x58b in fingerprint[0] - if ret.openpilotLongitudinalControl: - ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_LONG - return ret @staticmethod @@ -324,14 +327,12 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) - ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False # 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 # Main button also can trigger an engagement on these cars allow_enable = any(btn in ENABLE_BUTTONS for btn in self.CS.cruise_buttons) or any(self.CS.main_buttons) - allow_enable = allow_enable or self.CP.carFingerprint in HDA2_CAR - events = self.create_common_events(ret, pcm_enable=self.CS.CP.pcmCruise, allow_enable=allow_enable or True) + events = self.create_common_events(ret, pcm_enable=self.CS.CP.pcmCruise, allow_enable=allow_enable) if self.CS.brake_error: events.add(EventName.brakeUnavailable) @@ -358,5 +359,4 @@ class CarInterface(CarInterfaceBase): return ret def apply(self, c): - ret = self.CC.update(c, self.CS) - return ret + return self.CC.update(c, self.CS) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index df7ce1e6f..625b2630f 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -13,21 +13,29 @@ class CarControllerParams: ACCEL_MAX = 2.0 # m/s def __init__(self, CP): + self.STEER_DELTA_UP = 3 + self.STEER_DELTA_DOWN = 7 + self.STEER_DRIVER_ALLOWANCE = 50 + self.STEER_DRIVER_MULTIPLIER = 2 + self.STEER_DRIVER_FACTOR = 1 + self.STEER_THRESHOLD = 150 + + if CP.carFingerprint in CANFD_CAR: + self.STEER_MAX = 270 + self.STEER_DRIVER_ALLOWANCE = 250 + self.STEER_DRIVER_MULTIPLIER = 2 + self.STEER_THRESHOLD = 250 + # To determine the limit for your car, find the maximum value that the stock LKAS will request. # If the max stock LKAS request is <384, add your car to this list. - if CP.carFingerprint in HDA2_CAR: - self.STEER_MAX = 150 elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.ELANTRA_GT_I30, CAR.IONIQ, - CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_HEV, - CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO, CAR.KIA_STINGER): + CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_HEV, + CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO, CAR.KIA_STINGER): self.STEER_MAX = 255 + + # Default for most HKG else: self.STEER_MAX = 384 - self.STEER_DELTA_UP = 3 - self.STEER_DELTA_DOWN = 7 - self.STEER_DRIVER_ALLOWANCE = 50 - self.STEER_DRIVER_MULTIPLIER = 2 - self.STEER_DRIVER_FACTOR = 1 class CAR: @@ -45,6 +53,7 @@ class CAR: IONIQ_PHEV = "HYUNDAI IONIQ PHEV 2020" KONA = "HYUNDAI KONA 2020" KONA_EV = "HYUNDAI KONA ELECTRIC 2019" + KONA_EV_2022 = "HYUNDAI KONA ELECTRIC 2022" KONA_HEV = "HYUNDAI KONA HYBRID 2020" SANTA_FE = "HYUNDAI SANTA FE 2019" SANTA_FE_2022 = "HYUNDAI SANTA FE 2022" @@ -56,6 +65,7 @@ class CAR: PALISADE = "HYUNDAI PALISADE 2020" VELOSTER = "HYUNDAI VELOSTER 2019" SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021" + IONIQ_5 = "HYUNDAI IONIQ 5 2022" # Kia KIA_FORTE = "KIA FORTE E 2018 & GT 2021" @@ -82,25 +92,25 @@ class CAR: class HyundaiCarInfo(CarInfo): # TODO: we can probably remove LKAS. LKAS is standard on many # HKG and for others, it's likely packaged together with SCC - package: str = "SCC + LKAS" - good_torque: bool = True + package: str = "Smart Cruise Control (SCC) & LKAS" CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.ELANTRA: HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_b), CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), - CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), + CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-22", "Smart Cruise Control (SCC)", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), CAR.ELANTRA_GT_I30: None, # dashcamOnly and same platform as CAR.ELANTRA CAR.HYUNDAI_GENESIS: HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), CAR.IONIQ: HyundaiCarInfo("Hyundai Ioniq Hybrid 2017-19", harness=Harness.hyundai_c), - CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", "SCC + LFA", harness=Harness.hyundai_h), + CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", "Smart Cruise Control (SCC) & LFA", harness=Harness.hyundai_h), CAR.IONIQ_EV_LTD: HyundaiCarInfo("Hyundai Ioniq Electric 2019", harness=Harness.hyundai_c), CAR.IONIQ_EV_2020: HyundaiCarInfo("Hyundai Ioniq Electric 2020", harness=Harness.hyundai_h), CAR.IONIQ_PHEV_2019: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2019", harness=Harness.hyundai_c), - CAR.IONIQ_PHEV: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2020-21", harness=Harness.hyundai_h), - CAR.KONA: HyundaiCarInfo("Hyundai Kona 2020", harness=Harness.hyundai_b), + CAR.IONIQ_PHEV: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2020-21", "Smart Cruise Control (SCC)", harness=Harness.hyundai_h), + CAR.KONA: HyundaiCarInfo("Hyundai Kona 2020", "Smart Cruise Control (SCC)", harness=Harness.hyundai_b), CAR.KONA_EV: HyundaiCarInfo("Hyundai Kona Electric 2018-21", harness=Harness.hyundai_g), - CAR.KONA_HEV: HyundaiCarInfo("Hyundai Kona Hybrid 2020", harness=Harness.hyundai_i), + CAR.KONA_EV_2022: HyundaiCarInfo("Hyundai Kona Electric 2022", "Smart Cruise Control (SCC)", harness=Harness.hyundai_o), + CAR.KONA_HEV: HyundaiCarInfo("Hyundai Kona Hybrid 2020", video_link="https://youtu.be/0dwpAHiZgFo", harness=Harness.hyundai_i), CAR.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", harness=Harness.hyundai_d), CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-22", "All", harness=Harness.hyundai_l), CAR.SANTA_FE_HEV_2022: HyundaiCarInfo("Hyundai Santa Fe Hybrid 2022", "All", harness=Harness.hyundai_l), @@ -108,28 +118,30 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-22", "All", video_link="https://www.youtube.com/watch?v=ix63r9kE3Fw", harness=Harness.hyundai_a), CAR.SONATA_LF: HyundaiCarInfo("Hyundai Sonata 2018-19", harness=Harness.hyundai_e), CAR.TUCSON: [ - HyundaiCarInfo("Hyundai Tucson 2021", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_l), - HyundaiCarInfo("Hyundai Tucson Diesel 2019", harness=Harness.hyundai_l), + HyundaiCarInfo("Hyundai Tucson 2021", "Smart Cruise Control (SCC)", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_l), + HyundaiCarInfo("Hyundai Tucson Diesel 2019", "Smart Cruise Control (SCC)", harness=Harness.hyundai_l), ], CAR.PALISADE: [ - HyundaiCarInfo("Hyundai Palisade 2020-21", "All", video_link="https://youtu.be/TAnDqjF4fDY?t=456", harness=Harness.hyundai_h), - HyundaiCarInfo("Kia Telluride 2020", harness=Harness.hyundai_h), + HyundaiCarInfo("Hyundai Palisade 2020-22", "All", video_link="https://youtu.be/TAnDqjF4fDY?t=456", harness=Harness.hyundai_h), + HyundaiCarInfo("Kia Telluride 2020", "All", harness=Harness.hyundai_h), ], - CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e), + CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", "Smart Cruise Control (SCC)", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e), CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a), + CAR.IONIQ_5: HyundaiCarInfo("Hyundai Ioniq 5 2022", "Highway Driving Assist II", harness=Harness.hyundai_q), # Kia CAR.KIA_FORTE: [ HyundaiCarInfo("Kia Forte 2018", harness=Harness.hyundai_b), - HyundaiCarInfo("Kia Forte 2019-21", harness=Harness.hyundai_g), + HyundaiCarInfo("Kia Forte 2019-21", "All", harness=Harness.hyundai_g), ], - CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", "SCC", harness=Harness.hyundai_a), + CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", "Smart Cruise Control (SCC)", harness=Harness.hyundai_a), CAR.KIA_NIRO_EV: [ - HyundaiCarInfo("Kia Niro Electric 2019-20", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_f), + HyundaiCarInfo("Kia Niro Electric 2019", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), + HyundaiCarInfo("Kia Niro Electric 2020", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_f), HyundaiCarInfo("Kia Niro Electric 2021", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Niro Electric 2022", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), ], - CAR.KIA_NIRO_HEV: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2019", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c), + CAR.KIA_NIRO_HEV: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c), CAR.KIA_NIRO_HEV_2021: [ HyundaiCarInfo("Kia Niro Hybrid 2021", harness=Harness.hyundai_f), # TODO: could be hyundai_d, verify HyundaiCarInfo("Kia Niro Hybrid 2022", harness=Harness.hyundai_h), @@ -138,21 +150,21 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Optima 2017", min_steer_speed=32. * CV.MPH_TO_MS, harness=Harness.hyundai_b), HyundaiCarInfo("Kia Optima 2019", harness=Harness.hyundai_g), ], - CAR.KIA_OPTIMA_H: HyundaiCarInfo("Kia Optima 2017, 2019"), # TODO: info may be incorrect - CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", harness=Harness.hyundai_a), + CAR.KIA_OPTIMA_H: HyundaiCarInfo("Kia Optima Hybrid 2017, 2019"), # TODO: info may be incorrect + CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", "Smart Cruise Control (SCC)", harness=Harness.hyundai_a), CAR.KIA_SORENTO: [ HyundaiCarInfo("Kia Sorento 2018", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e), ], - CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), + CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", harness=Harness.hyundai_e), - CAR.KIA_EV6: HyundaiCarInfo("Kia EV6 2022", "All", harness=Harness.none), + CAR.KIA_EV6: HyundaiCarInfo("Kia EV6 2022", "Highway Driving Assist II", harness=Harness.hyundai_p), # Genesis - CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018", "All", harness=Harness.hyundai_f), + CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f), CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f), - CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2018", "All", harness=Harness.hyundai_h), - CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2018", "All", harness=Harness.hyundai_c), + CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h), + CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c), } class Buttons: @@ -160,7 +172,7 @@ class Buttons: RES_ACCEL = 1 SET_DECEL = 2 GAP_DIST = 3 - CANCEL = 4 + CANCEL = 4 # on newer models, this is a pause/resume button FINGERPRINTS = { CAR.ELANTRA: [{ @@ -235,6 +247,9 @@ FINGERPRINTS = { CAR.KONA_EV: [{ 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 549: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1307: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1378: 4, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 1157: 4, 1193: 8, 1379: 8, 1988: 8, 1996: 8 }], + CAR.KONA_EV_2022: [{ + 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1069: 8, 1078: 4, 1136: 8, 1145: 8, 1151: 8, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 8, 1173: 8, 1183: 8, 1188: 8, 1191: 2, 1193: 8, 1225: 8, 1227: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1339: 8, 1342: 8, 1343: 8, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1446: 8, 1456: 4, 1470: 8, 1473: 8, 1485: 8, 1507: 8, 1535: 8, 1990: 8, 1998: 8 + }], CAR.KIA_NIRO_EV: [{ 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 516: 8, 544: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1193: 8, 1225: 8, 1260: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 1990: 8, 1998: 8, 1996: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 2015: 8 }], @@ -376,7 +391,7 @@ FW_VERSIONS = { b'\xf1\x8799110L0000\xf1\x00DN8_ SCC FHCUP 1.00 1.00 99110-L0000 ', ], (Ecu.esp, 0x7d1, None): [ - b'\xf1\x00DN ESC \a 106 \a\x01 58910-L0100', + b'\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', b'\xf1\x00DN ESC \x01 102\x19\x04\x13 58910-L1300', b'\xf1\x00DN ESC \x03 100 \x08\x01 58910-L0300', b'\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100', @@ -406,8 +421,8 @@ FW_VERSIONS = { b'\xf1\x8739110-2S278\xf1\x82DNDVD5GMCCXXXL5B', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware - b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware + b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware + b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware b'\xf1\x00DN8 MDPS C 1.00 1.01 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4DNAC101', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', @@ -486,6 +501,9 @@ FW_VERSIONS = { b'\xf1\x87SAMFBA9283024GJ2wwwwEUuWwwgwwwwwwwww\x87/\xfb\xff\x98w\x8f\xff<\xd3\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SAMFBA9708354GJ2wwwwVf\x86h\x88wx\x87xww\x87\x88\x88\x88\x88w/\xfa\xff\x97w\x8f\xff\x86\xa0\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SANDB45316691GC6\x99\x99\x99\x99\x88\x88\xa8\x8avfwfwwww\x87wxwT\x9f\xfd\xff\x88wo\xff\x1c\xfa\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB3\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALFBA7460044GJ2gx\x87\x88Vf\x86hx\x88\x87\x88wwwwgw\x86wd?\xfa\xff\x86U_\xff\xaf\x1f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x87SAMFBA8105254GJ2wx\x87\x88Vf\x86hx\x88\x87\x88wwwwwwww\x86O\xfa\xff\x99\x88\x7f\xffZG\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x87SANFB45889451GC7wx\x87\x88gw\x87x\x88\x88x\x88\x87wxw\x87wxw\x87\x8f\xfc\xffeU\x8f\xff+Q\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', ], }, CAR.SONATA_LF: { @@ -610,6 +628,7 @@ FW_VERSIONS = { b'\xf1\x82TACVN5GSI3XXXH0A', b'\xf1\x82TMCFD5MMCXXXXG0A', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82TMDWN5TMD3TXXJ1A', + b'\xf1\x81HM6M2_0a0_G00', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00TM MDPS C 1.00 1.02 56370-S2AA0 0B19', @@ -628,6 +647,7 @@ FW_VERSIONS = { b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7', b'\xf1\x87KMMYBU034207SB72x\x89\x88\x98h\x88\x98\x89\x87fhvvfWf33_\xff\x87\xff\x8f\xfa\x81\xe5\xf1\x89HT6TAF00A1\xf1\x82STM0M25GS1\x00\x00\x00\x00\x00\x00', b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6', + b'\xf1\x00HT6TA290BLHT6TAF00A1STM0M25GS1\x00\x00\x00\x00\x00\x006\xd8\x97\x15', ], }, CAR.SANTA_FE_HEV_2022: { @@ -700,7 +720,7 @@ FW_VERSIONS = { }, CAR.PALISADE: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\000LX2_ SCC F-CUP 1.00 1.05 99110-S8100 ', + b'\xf1\x00LX2_ SCC F-CUP 1.00 1.05 99110-S8100 ', b'\xf1\x00LX2 SCC FHCUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCU- 1.00 1.05 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCUP 1.00 1.00 99110-S8110 ', @@ -710,7 +730,7 @@ FW_VERSIONS = { ], (Ecu.esp, 0x7d1, None): [ b'\xf1\x00LX ESC \x01 103\x19\t\x10 58910-S8360', - b'\xf1\x00LX ESC \x01 103\x31\t\020 58910-S8360', + b'\xf1\x00LX ESC \x01 1031\t\x10 58910-S8360', b'\xf1\x00LX ESC \x0b 101\x19\x03\x17 58910-S8330', b'\xf1\x00LX ESC \x0b 102\x19\x05\x07 58910-S8330', b'\xf1\x00LX ESC \x0b 103\x19\t\x07 58910-S8330', @@ -718,6 +738,7 @@ FW_VERSIONS = { b'\xf1\x00LX ESC \x0b 104 \x10\x16 58910-S8360', b'\xf1\x00ON ESC \x0b 100\x18\x12\x18 58910-S9360', b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360', + b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -725,7 +746,7 @@ FW_VERSIONS = { b'\xf1\x81640S1051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00LX2 MDPS C 1,00 1,03 56310-S8020 4LXDC103', # modified firmware + b'\xf1\x00LX2 MDPS C 1,00 1,03 56310-S8020 4LXDC103', b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8020 4LXDC103', b'\xf1\x00LX2 MDPS C 1.00 1.04 56310-S8020 4LXDC104', b'\xf1\x00ON MDPS C 1.00 1.00 56340-S9000 8B13', @@ -738,6 +759,7 @@ FW_VERSIONS = { b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.08 99211-S8100 200903', b'\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 181105', b'\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 200720', + b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.00 99211-S8110 210226', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', @@ -745,7 +767,7 @@ FW_VERSIONS = { b'\xf1\x87LBLUFN591307KF25vgvw\x97wwwy\x99\xa7\x99\x99\xaa\xa9\x9af\x88\x96h\x95o\xf7\xff\x99f/\xff\xe4c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB2\xd7\xc1/\xd1', b'\xf1\x87LBLUFN650868KF36\xa9\x98\x89\x88\xa8\x88\x88\x88h\x99\xa6\x89fw\x86gw\x88\x97x\xaa\x7f\xf6\xff\xbb\xbb\x8f\xff+\x82\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', b'\xf1\x87LBLUFN655162KF36\x98\x88\x88\x88\x98\x88\x88\x88x\x99\xa7\x89x\x99\xa7\x89x\x99\x97\x89g\x7f\xf7\xffwU_\xff\xe9!\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', - b'\xf1\x87LBLUFN731381KF36\xb9\x99\x89\x98\x98\x88\x88\x88\x89\x99\xa8\x99\x88\x99\xa8\x89\x88\x88\x98\x88V\177\xf6\xff\x99w\x8f\xff\xad\xd8\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\000bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', + b'\xf1\x87LBLUFN731381KF36\xb9\x99\x89\x98\x98\x88\x88\x88\x89\x99\xa8\x99\x88\x99\xa8\x89\x88\x88\x98\x88V\x7f\xf6\xff\x99w\x8f\xff\xad\xd8\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', b'\xf1\x87LDKVAA0028604HH1\xa8\x88x\x87vgvw\x88\x99\xa8\x89gw\x86ww\x88\x97x\x97o\xf9\xff\x97w\x7f\xffo\x02\xf1\x81U872\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', b'\xf1\x87LDKVAA3068374HH1wwww\x87xw\x87y\x99\xa7\x99w\x88\x87xw\x88\x97x\x85\xaf\xfa\xffvU/\xffU\xdc\xf1\x81U872\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', b'\xf1\x87LDKVBN382172KF26\x98\x88\x88\x88\xa8\x88\x88\x88x\x99\xa7\x89\x87\x88\x98x\x98\x99\xa9\x89\xa5_\xf6\xffDDO\xff\xcd\x16\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', @@ -786,6 +808,8 @@ FW_VERSIONS = { b'\xf1\x87LDMVBN895969KF37vefV\x87vgfx\x99\xa7\x89\x99\x99\xb9\x99f\x88\x96he_\xf7\xffxwo\xff\x14\xf9\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', b'\xf1\x87LDMVBN899222KF37\xa8\x88x\x87\x97www\x98\x99\x99\x89\x88\x99\x98\x89f\x88\x96hdo\xf7\xff\xbb\xaa\x9f\xff\xe2U\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', b"\xf1\x87LBLUFN622950KF36\xa8\x88\x88\x88\x87w\x87xh\x99\x96\x89\x88\x99\x98\x89\x88\x99\x98\x89\x87o\xf6\xff\x98\x88o\xffx'\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8", + b'\xf1\x87LDMVBN950669KF37\x97www\x96fffy\x99\xa7\x99\xa9\x99\xaa\x99g\x88\x96x\xb8\x8f\xf9\xffTD/\xff\xa7\xcb\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDLVAA4478824HH1\x87wwwvfvg\x89\x99\xa8\x99w\x88\x87x\x89\x99\xa8\x99\xa6o\xfa\xfffU/\xffu\x92\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', ], }, CAR.VELOSTER: { @@ -846,6 +870,12 @@ FW_VERSIONS = { b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', ], }, + CAR.GENESIS_G90: { + (Ecu.transmission, 0x7e1, None): [b'\xf1\x87VDGMD15866192DD3x\x88x\x89wuFvvfUf\x88vWwgwwwvfVgx\x87o\xff\xbc^\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7'], + (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00HI__ SCC F-CUP 1.00 1.01 96400-D2100 '], + (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00HI LKAS AT USA LHD 1.00 1.00 95895-D2020 160302'], + (Ecu.engine, 0x7e0, None): [b'\xf1\x810000000000\x00'], + }, CAR.KONA: { (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00OS__ SCC F-CUP 1.00 1.00 95655-J9200 ', ], (Ecu.esp, 0x7d1, None): [b'\xf1\x816V5RAK00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', ], @@ -951,9 +981,33 @@ FW_VERSIONS = { b'\xf1\x00OSev SCC FNCUP 1.00 1.01 99110-K4000 ', ], }, + CAR.KONA_EV_2022: { + (Ecu.esp, 0x7D1, None): [ + b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010', + b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010', + b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', + # 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', + ], + (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', + ], + (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', + ], + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00', + ], + }, CAR.KIA_NIRO_EV: { (Ecu.fwdRadar, 0x7D0, None): [ b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', + b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4000 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4100 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.03 96400-Q4100 ', b'\xf1\x8799110Q4000\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', @@ -966,6 +1020,7 @@ FW_VERSIONS = { (Ecu.eps, 0x7D4, None): [ b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4000\x00 4DEEC105', b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4100\x00 4DEEC105', + b'\xf1\x00DE MDPS C 1.00 1.04 56310Q4100\x00 4DEEC104', ], (Ecu.fwdCamera, 0x7C4, None): [ b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4100 200706', @@ -973,24 +1028,29 @@ FW_VERSIONS = { b'\xf1\x00DEE MFC AT USA LHD 1.00 1.00 99211-Q4000 191211', b'\xf1\x00DEE MFC AT USA LHD 1.00 1.03 95740-Q4000 180821', b'\xf1\x00DEE MFC AT USA LHD 1.00 1.01 99211-Q4500 210428', + b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.03 95740-Q4000 180821', ], }, CAR.KIA_NIRO_HEV: { (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6F4051\000\000\000\000\000\000\000\000', + b'\xf1\x816H6F4051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ - b"\xf1\x816U3J2051\000\000\xf1\0006U3H0_C2\000\0006U3J2051\000\000PDE0G16NS2\xf4\'\\\x91", - b'\xf1\x816U3J2051\000\000\xf1\0006U3H0_C2\000\0006U3J2051\000\000PDE0G16NS2\000\000\000\000', + b"\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\xf4'\\\x91", + b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\x00\x00\x00\x00', + b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x00\x00\x00\x00', + b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x13\xcd\x88\x92', ], (Ecu.eps, 0x7D4, None): [ - b'\xf1\000DE MDPS C 1.00 1.09 56310G5301\000 4DEHC109', + b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109', ], (Ecu.fwdCamera, 0x7C4, None): [ - b'\xf1\000DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424', + b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424', + b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 95740-G5010 170117', ], (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\000DEhe SCC H-CUP 1.01 1.02 96400-G5100 ', + b'\xf1\x00DEhe SCC H-CUP 1.01 1.02 96400-G5100 ', ], }, CAR.KIA_NIRO_HEV_2021: { @@ -1039,18 +1099,27 @@ FW_VERSIONS = { ], }, CAR.KIA_OPTIMA: { - (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4110 '], - (Ecu.esp, 0x7d1, None): [b'\xf1\x00JF ESC \v 11 \x18\x030 58920-D5180',], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4110 ', + ], + (Ecu.esp, 0x7d1, None): [ + b'\xf1\x00JF ESC \x0b 11 \x18\x030 58920-D5180', + ], (Ecu.engine, 0x7e0, None): [ - b'\x01TJFAJNU06F201H03', b'\xf1\x89F1JF600AISEIU702\xf1\x82F1JF600AISEIU702', ], - (Ecu.eps, 0x7d4, None): [b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409'], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409', + ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5001 h32', b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.02 95895-D5000 h31', ], - (Ecu.transmission, 0x7e1, None): [b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW'], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW', + b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.', + b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', + ], }, CAR.ELANTRA_2021: { (Ecu.fwdRadar, 0x7d0, None): [ @@ -1140,9 +1209,6 @@ FW_VERSIONS = { b'\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', b'\xf1\x8799110L5000\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', ], - (Ecu.esp, 0x7b0, None): [ - b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81\x00\x00\x00\x00\x00\x00\x00\x00', - ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x8756310-L5500\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102', b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102', @@ -1199,6 +1265,23 @@ FW_VERSIONS = { b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027', ], }, + CAR.IONIQ_5: { + (Ecu.esp, 0x7d1, None): [ + b'\xf1\x00NE1 IEB \x07 106!\x11) 58520-GI010', + b'\xf1\x8758520GI010\xf1\x00NE1 IEB \x07 106!\x11) 58520-GI010', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00NE MDPS R 1.00 1.06 57700GI000 4NEDR106', + b'\xf1\x8757700GI000 \xf1\x00NE MDPS R 1.00 1.06 57700GI000 4NEDR106', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', + b'\xf1\x8799110GI000\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', + ], + }, } CHECKSUM = { @@ -1210,16 +1293,19 @@ FEATURES = { # which message has the gear "use_cluster_gears": {CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA}, "use_tcu_gears": {CAR.KIA_OPTIMA, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, - "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019}, + "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022}, # these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 - "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.ELANTRA_GT_I30, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON}, + "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.ELANTRA_GT_I30, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022}, } -HDA2_CAR = {CAR.KIA_EV6, } +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5} + +# The camera does SCC on these cars, rather than the radar +CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_HEV, 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} # these cars use a different gas signal -EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV} +EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022} # 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, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022} @@ -1254,6 +1340,7 @@ DBC = { CAR.KIA_STINGER: dbc_dict('hyundai_kia_generic', None), CAR.KONA: dbc_dict('hyundai_kia_generic', None), CAR.KONA_EV: dbc_dict('hyundai_kia_generic', None), + CAR.KONA_EV_2022: dbc_dict('hyundai_kia_generic', None), CAR.KONA_HEV: dbc_dict('hyundai_kia_generic', None), CAR.SANTA_FE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.SANTA_FE_2022: dbc_dict('hyundai_kia_generic', None), @@ -1267,6 +1354,5 @@ DBC = { CAR.KIA_CEED: dbc_dict('hyundai_kia_generic', None), CAR.KIA_EV6: dbc_dict('kia_ev6', None), CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), + CAR.IONIQ_5: dbc_dict('kia_ev6', None), } - -STEER_THRESHOLD = 150 diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 9220aee52..1e55e72ac 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -1,8 +1,8 @@ -import json +import yaml import os import time from abc import abstractmethod, ABC -from typing import Any, Dict, Tuple, List +from typing import Any, Dict, Optional, Tuple, List from cereal import car from common.basedir import BASEDIR @@ -20,7 +20,34 @@ EventName = car.CarEvent.EventName MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS ACCEL_MAX = 2.0 ACCEL_MIN = -3.5 -TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data.json') + +TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.yaml') +TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml') +TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/substitute.yaml') + + +def get_torque_params(candidate): + with open(TORQUE_SUBSTITUTE_PATH) as f: + sub = yaml.load(f, Loader=yaml.CSafeLoader) + if candidate in sub: + candidate = sub[candidate] + + with open(TORQUE_PARAMS_PATH) as f: + params = yaml.load(f, Loader=yaml.CSafeLoader) + with open(TORQUE_OVERRIDE_PATH) as f: + override = yaml.load(f, Loader=yaml.CSafeLoader) + + # Ensure no overlap + if sum([candidate in x for x in [sub, params, override]]) > 1: + raise RuntimeError(f'{candidate} is defined twice in torque config') + + if candidate in override: + out = override[candidate] + elif candidate in params: + out = params[candidate] + else: + raise NotImplementedError(f"Did not find torque params for {candidate}") + return {key: out[i] for i, key in enumerate(params['legend'])} # generic car and radar interfaces @@ -34,6 +61,7 @@ class CarInterfaceBase(ABC): self.steering_unpressed = 0 self.low_speed_alert = False self.silent_steer_warning = True + self.v_ego_cluster_seen = False self.CS = None self.can_parsers = [] @@ -83,7 +111,7 @@ class CarInterfaceBase(ABC): ret.steerControlType = car.CarParams.SteerControlType.torque ret.minSteerSpeed = 0. ret.wheelSpeedFactor = 1.0 - ret.maxLateralAccel = CarInterfaceBase.get_torque_params(candidate)['MAX_LAT_ACCEL_MEASURED'] + ret.maxLateralAccel = get_torque_params(candidate)['MAX_LAT_ACCEL_MEASURED'] ret.pcmCruise = True # openpilot's state is tied to the PCM's cruise state on most cars ret.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this @@ -108,10 +136,16 @@ class CarInterfaceBase(ABC): return ret @staticmethod - def get_torque_params(candidate, default=float('NaN')): - with open(TORQUE_PARAMS_PATH) as f: - data = json.load(f) - return {key: data[key].get(candidate, default) for key in data} + def configure_torque_tune(candidate, tune, steering_angle_deadzone_deg=0.0, use_steering_angle=True): + params = get_torque_params(candidate) + + tune.init('torque') + tune.torque.useSteeringAngle = use_steering_angle + tune.torque.kp = 1.0 / params['LAT_ACCEL_FACTOR'] + tune.torque.kf = 1.0 / params['LAT_ACCEL_FACTOR'] + tune.torque.ki = 0.1 / params['LAT_ACCEL_FACTOR'] + tune.torque.friction = params['FRICTION'] + tune.torque.steeringAngleDeadzoneDeg = steering_angle_deadzone_deg @abstractmethod def _update(self, c: car.CarControl) -> car.CarState: @@ -129,6 +163,14 @@ class CarInterfaceBase(ABC): ret.canValid = all(cp.can_valid for cp in self.can_parsers if cp is not None) ret.canTimeout = any(cp.bus_timeout for cp in self.can_parsers if cp is not None) + if ret.vEgoCluster == 0.0 and not self.v_ego_cluster_seen: + ret.vEgoCluster = ret.vEgo + else: + self.v_ego_cluster_seen = True + + if ret.cruiseState.speedCluster == 0: + ret.cruiseState.speedCluster = ret.cruiseState.speed + # copy back for next iteration reader = ret.as_reader() if self.CS is not None: @@ -279,13 +321,22 @@ class CarStateBase(ABC): return bool(left_blinker_stalk or self.left_blinker_cnt > 0), bool(right_blinker_stalk or self.right_blinker_cnt > 0) @staticmethod - def parse_gear_shifter(gear: str) -> car.CarState.GearShifter: + def parse_gear_shifter(gear: Optional[str]) -> car.CarState.GearShifter: + if gear is None: + return GearShifter.unknown + d: Dict[str, car.CarState.GearShifter] = { - 'P': GearShifter.park, 'R': GearShifter.reverse, 'N': GearShifter.neutral, - 'E': GearShifter.eco, 'T': GearShifter.manumatic, 'D': GearShifter.drive, - 'S': GearShifter.sport, 'L': GearShifter.low, 'B': GearShifter.brake + 'P': GearShifter.park, 'PARK': GearShifter.park, + 'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse, + 'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral, + 'E': GearShifter.eco, 'ECO': GearShifter.eco, + 'T': GearShifter.manumatic, 'MANUAL': GearShifter.manumatic, + 'D': GearShifter.drive, 'DRIVE': GearShifter.drive, + 'S': GearShifter.sport, 'SPORT': GearShifter.sport, + 'L': GearShifter.low, 'LOW': GearShifter.low, + 'B': GearShifter.brake, 'BRAKE': GearShifter.brake, } - return d.get(gear, GearShifter.unknown) + return d.get(gear.upper(), GearShifter.unknown) @staticmethod def get_cam_can_parser(CP): diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 0e807512c..65122ab89 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -126,7 +126,7 @@ class IsoTpParallelQuery: msg.send(self.request[counter + 1]) request_counter[tx_addr] += 1 else: - results[tx_addr] = dat[len(expected_response):] + results[(tx_addr, msg._can_client.rx_addr)] = dat[len(expected_response):] request_done[tx_addr] = True else: error_code = dat[2] if len(dat) > 2 else -1 @@ -135,6 +135,7 @@ class IsoTpParallelQuery: if self.debug: cloudlog.warning(f"iso-tp query response pending: {tx_addr}") else: + response_timeouts[tx_addr] = 0 request_done[tx_addr] = True cloudlog.warning(f"iso-tp query bad response: {tx_addr} - 0x{dat.hex()}") diff --git a/selfdrive/car/mazda/carcontroller.py b/selfdrive/car/mazda/carcontroller.py index d003079d6..2add59ccb 100644 --- a/selfdrive/car/mazda/carcontroller.py +++ b/selfdrive/car/mazda/carcontroller.py @@ -1,67 +1,64 @@ from cereal import car from opendbc.can.packer import CANPacker +from selfdrive.car import apply_std_steer_torque_limits from selfdrive.car.mazda import mazdacan from selfdrive.car.mazda.values import CarControllerParams, Buttons -from selfdrive.car import apply_std_steer_torque_limits VisualAlert = car.CarControl.HUDControl.VisualAlert -class CarController(): + +class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP self.apply_steer_last = 0 self.packer = CANPacker(dbc_name) - self.steer_rate_limited = False self.brake_counter = 0 + self.frame = 0 - def update(self, c, CS, frame): + def update(self, CC, CS): can_sends = [] apply_steer = 0 - self.steer_rate_limited = False - if c.latActive: + if CC.latActive: # calculate steer and also set limits due to driver torque - new_steer = int(round(c.actuators.steer * CarControllerParams.STEER_MAX)) + new_steer = int(round(CC.actuators.steer * CarControllerParams.STEER_MAX)) apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, CarControllerParams) - self.steer_rate_limited = new_steer != apply_steer - - if c.enabled: - if CS.out.standstill and frame % 5 == 0: - # Mazda Stop and Go requires a RES button (or gas) press if the car stops more than 3 seconds - # Send Resume button at 20hz if we're engaged at standstill to support full stop and go! - # TODO: improve the resume trigger logic by looking at actual radar data - can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP.carFingerprint, CS.crz_btns_counter, Buttons.RESUME)) - if c.cruiseControl.cancel: + if CC.cruiseControl.cancel: # If brake is pressed, let us wait >70ms before trying to disable crz to avoid # a race condition with the stock system, where the second cancel from openpilot # will disable the crz 'main on'. crz ctrl msg runs at 50hz. 70ms allows us to # read 3 messages and most likely sync state before we attempt cancel. self.brake_counter = self.brake_counter + 1 - if frame % 10 == 0 and not (CS.out.brakePressed and self.brake_counter < 7): + if self.frame % 10 == 0 and not (CS.out.brakePressed and self.brake_counter < 7): # Cancel Stock ACC if it's enabled while OP is disengaged # Send at a rate of 10hz until we sync with stock ACC state can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP.carFingerprint, CS.crz_btns_counter, Buttons.CANCEL)) else: self.brake_counter = 0 + if CC.cruiseControl.resume and self.frame % 5 == 0: + # Mazda Stop and Go requires a RES button (or gas) press if the car stops more than 3 seconds + # Send Resume button when planner wants car to move + can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP.carFingerprint, CS.crz_btns_counter, Buttons.RESUME)) self.apply_steer_last = apply_steer # send HUD alerts - if frame % 50 == 0: - ldw = c.hudControl.visualAlert == VisualAlert.ldw - steer_required = c.hudControl.visualAlert == VisualAlert.steerRequired + if self.frame % 50 == 0: + ldw = CC.hudControl.visualAlert == VisualAlert.ldw + steer_required = CC.hudControl.visualAlert == VisualAlert.steerRequired # TODO: find a way to silence audible warnings so we can add more hud alerts steer_required = steer_required and CS.lkas_allowed_speed can_sends.append(mazdacan.create_alert_command(self.packer, CS.cam_laneinfo, ldw, steer_required)) # send steering command can_sends.append(mazdacan.create_steering_control(self.packer, self.CP.carFingerprint, - frame, apply_steer, CS.cam_lkas)) + self.frame, apply_steer, CS.cam_lkas)) - new_actuators = c.actuators.copy() + new_actuators = CC.actuators.copy() new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py index 6e1ad3e48..944d79809 100644 --- a/selfdrive/car/mazda/carstate.py +++ b/selfdrive/car/mazda/carstate.py @@ -79,6 +79,7 @@ class CarState(CarStateBase): # it should be used for carState.cruiseState.nonAdaptive instead ret.cruiseState.available = cp.vl["CRZ_CTRL"]["CRZ_AVAILABLE"] == 1 ret.cruiseState.enabled = cp.vl["CRZ_CTRL"]["CRZ_ACTIVE"] == 1 + ret.cruiseState.standstill = cp.vl["PEDALS"]["STANDSTILL"] == 1 ret.cruiseState.speed = cp.vl["CRZ_EVENTS"]["CRZ_SPEED"] * CV.KPH_TO_MS if ret.cruiseState.enabled: diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index cbeb910de..864090344 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -28,34 +28,24 @@ class CarInterface(CarInterfaceBase): ret.steerLimitTimer = 0.8 tire_stiffness_factor = 0.70 # not optimized yet + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + if candidate in (CAR.CX5, CAR.CX5_2022): ret.mass = 3655 * CV.LB_TO_KG + STD_CARGO_KG ret.wheelbase = 2.7 ret.steerRatio = 15.5 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.19], [0.019]] - ret.lateralTuning.pid.kf = 0.00006 elif candidate in (CAR.CX9, CAR.CX9_2021): ret.mass = 4217 * CV.LB_TO_KG + STD_CARGO_KG ret.wheelbase = 3.1 ret.steerRatio = 17.6 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.19], [0.019]] - ret.lateralTuning.pid.kf = 0.00006 elif candidate == CAR.MAZDA3: ret.mass = 2875 * CV.LB_TO_KG + STD_CARGO_KG ret.wheelbase = 2.7 ret.steerRatio = 14.0 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.19], [0.019]] - ret.lateralTuning.pid.kf = 0.00006 elif candidate == CAR.MAZDA6: ret.mass = 3443 * CV.LB_TO_KG + STD_CARGO_KG ret.wheelbase = 2.83 ret.steerRatio = 15.5 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.19], [0.019]] - ret.lateralTuning.pid.kf = 0.00006 if candidate not in (CAR.CX5_2022, ): ret.minSteerSpeed = LKAS_LIMITS.DISABLE_SPEED * CV.KPH_TO_MS @@ -90,6 +80,4 @@ class CarInterface(CarInterfaceBase): return ret def apply(self, c): - ret = self.CC.update(c, self.CS, self.frame) - self.frame += 1 - return ret + return self.CC.update(c, self.CS) diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index 5c80303db..7e99cccec 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -36,12 +36,12 @@ class MazdaCarInfo(CarInfo): CAR_INFO: Dict[str, Union[MazdaCarInfo, List[MazdaCarInfo]]] = { - CAR.CX5: MazdaCarInfo("Mazda CX-5 2017, 2019"), # TODO: verify years and torque for first 4 - CAR.CX9: MazdaCarInfo("Mazda CX-9 2016-17"), - CAR.MAZDA3: MazdaCarInfo("Mazda 3 2017"), - CAR.MAZDA6: MazdaCarInfo("Mazda 6 2017"), - CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021", good_torque=True), - CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022", good_torque=True), + CAR.CX5: MazdaCarInfo("Mazda CX-5 2017-21"), + CAR.CX9: MazdaCarInfo("Mazda CX-9 2016-20"), + CAR.MAZDA3: MazdaCarInfo("Mazda 3 2017-18"), + CAR.MAZDA6: MazdaCarInfo("Mazda 6 2017-20"), + CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021-22"), + CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022"), } @@ -65,6 +65,9 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7e0, None): [ b'PX2G-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2H-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'SH54-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXFG-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -77,6 +80,8 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'SH51-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXFG-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.CX5: { @@ -264,6 +269,7 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'PXM4-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXM6-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -276,9 +282,11 @@ FW_VERSIONS = { b'GSH7-67XK2-M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXM6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], } } diff --git a/selfdrive/car/nissan/carcontroller.py b/selfdrive/car/nissan/carcontroller.py index 4ceb142d4..dbc2b33c6 100644 --- a/selfdrive/car/nissan/carcontroller.py +++ b/selfdrive/car/nissan/carcontroller.py @@ -1,25 +1,27 @@ from cereal import car from common.numpy_fast import clip, interp -from selfdrive.car.nissan import nissancan from opendbc.can.packer import CANPacker +from selfdrive.car.nissan import nissancan from selfdrive.car.nissan.values import CAR, CarControllerParams - VisualAlert = car.CarControl.HUDControl.VisualAlert -class CarController(): +class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP self.car_fingerprint = CP.carFingerprint + self.frame = 0 self.lkas_max_torque = 0 self.last_angle = 0 self.packer = CANPacker(dbc_name) - def update(self, c, CS, frame, actuators, cruise_cancel, hud_alert, - left_line, right_line, left_lane_depart, right_lane_depart): + def update(self, CC, CS): + actuators = CC.actuators + hud_control = CC.hudControl + pcm_cancel_cmd = CC.cruiseControl.cancel can_sends = [] @@ -28,10 +30,10 @@ class CarController(): lkas_hud_info_msg = CS.lkas_hud_info_msg apply_angle = actuators.steeringAngleDeg - steer_hud_alert = 1 if hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw) else 0 + steer_hud_alert = 1 if hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw) else 0 - if c.latActive: - # # windup slower + if CC.latActive: + # windup slower if self.last_angle * apply_angle > 0. and abs(apply_angle) > abs(self.last_angle): angle_rate_lim = interp(CS.out.vEgo, CarControllerParams.ANGLE_DELTA_BP, CarControllerParams.ANGLE_DELTA_V) else: @@ -57,25 +59,25 @@ class CarController(): self.last_angle = apply_angle - if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA) and cruise_cancel: - can_sends.append(nissancan.create_acc_cancel_cmd(self.packer, self.car_fingerprint, CS.cruise_throttle_msg, frame)) + if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA) and pcm_cancel_cmd: + can_sends.append(nissancan.create_acc_cancel_cmd(self.packer, self.car_fingerprint, CS.cruise_throttle_msg)) # TODO: Find better way to cancel! # For some reason spamming the cancel button is unreliable on the Leaf # We now cancel by making propilot think the seatbelt is unlatched, # this generates a beep and a warning message every time you disengage - if self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC) and frame % 2 == 0: - can_sends.append(nissancan.create_cancel_msg(self.packer, CS.cancel_msg, cruise_cancel)) + if self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC) and self.frame % 2 == 0: + can_sends.append(nissancan.create_cancel_msg(self.packer, CS.cancel_msg, pcm_cancel_cmd)) can_sends.append(nissancan.create_steering_control( - self.packer, apply_angle, frame, c.enabled, self.lkas_max_torque)) + self.packer, apply_angle, self.frame, CC.enabled, self.lkas_max_torque)) if lkas_hud_msg and lkas_hud_info_msg: - if frame % 2 == 0: + if self.frame % 2 == 0: can_sends.append(nissancan.create_lkas_hud_msg( - self.packer, lkas_hud_msg, c.enabled, left_line, right_line, left_lane_depart, right_lane_depart)) + self.packer, lkas_hud_msg, CC.enabled, hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart)) - if frame % 50 == 0: + if self.frame % 50 == 0: can_sends.append(nissancan.create_lkas_hud_info_msg( self.packer, lkas_hud_info_msg, steer_hud_alert )) @@ -83,4 +85,5 @@ class CarController(): new_actuators = actuators.copy() new_actuators.steeringAngleDeg = apply_angle + self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/nissan/carstate.py b/selfdrive/car/nissan/carstate.py index 3c5d7dc24..a5ca23711 100644 --- a/selfdrive/car/nissan/carstate.py +++ b/selfdrive/car/nissan/carstate.py @@ -74,8 +74,8 @@ class CarState(CarStateBase): conversion = CV.MPH_TO_MS if cp.vl["HUD_SETTINGS"]["SPEED_MPH"] else CV.KPH_TO_MS else: conversion = CV.MPH_TO_MS if cp.vl["HUD"]["SPEED_MPH"] else CV.KPH_TO_MS - speed -= 1 # Speed on HUD is always 1 lower than actually sent on can bus ret.cruiseState.speed = speed * conversion + ret.cruiseState.speedCluster = (speed - 1) * conversion # Speed on HUD is always 1 lower than actually sent on can bus if self.CP.carFingerprint == CAR.ALTIMA: ret.steeringTorque = cp_cam.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_DRIVER"] diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 436cef68b..9c04d975f 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 from cereal import car -from selfdrive.car.nissan.values import CAR from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase +from selfdrive.car.nissan.values import CAR + class CarInterface(CarInterfaceBase): @@ -67,10 +68,4 @@ class CarInterface(CarInterfaceBase): return ret def apply(self, c): - hud_control = c.hudControl - ret = self.CC.update(c, self.CS, self.frame, c.actuators, - c.cruiseControl.cancel, hud_control.visualAlert, - hud_control.leftLaneVisible, hud_control.rightLaneVisible, - hud_control.leftLaneDepart, hud_control.rightLaneDepart) - self.frame += 1 - return ret + return self.CC.update(c, self.CS) diff --git a/selfdrive/car/nissan/nissancan.py b/selfdrive/car/nissan/nissancan.py index ceace5088..f59714b8d 100644 --- a/selfdrive/car/nissan/nissancan.py +++ b/selfdrive/car/nissan/nissancan.py @@ -2,17 +2,17 @@ import copy import crcmod from selfdrive.car.nissan.values import CAR +# TODO: add this checksum to the CANPacker nissan_checksum = crcmod.mkCrcFun(0x11d, initCrc=0x00, rev=False, xorOut=0xff) def create_steering_control(packer, apply_steer, frame, steer_on, lkas_max_torque): - idx = (frame % 16) values = { + "COUNTER": frame % 0x10, "DESIRED_ANGLE": apply_steer, "SET_0x80_2": 0x80, "SET_0x80": 0x80, "MAX_TORQUE": lkas_max_torque if steer_on else 0, - "COUNTER": idx, "LKA_ACTIVE": steer_on, } @@ -22,7 +22,7 @@ def create_steering_control(packer, apply_steer, frame, steer_on, lkas_max_torqu return packer.make_can_msg("LKAS", 0, values) -def create_acc_cancel_cmd(packer, car_fingerprint, cruise_throttle_msg, frame): +def create_acc_cancel_cmd(packer, car_fingerprint, cruise_throttle_msg): values = copy.copy(cruise_throttle_msg) can_bus = 2 @@ -35,7 +35,6 @@ def create_acc_cancel_cmd(packer, car_fingerprint, cruise_throttle_msg, frame): values["SET_BUTTON"] = 0 values["RES_BUTTON"] = 0 values["FOLLOW_DISTANCE_BUTTON"] = 0 - values["COUNTER"] = (frame % 4) return packer.make_can_msg("CRUISE_THROTTLE", can_bus, values) diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index 5ffef691d..2126e550e 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -28,7 +28,7 @@ class CAR: @dataclass class NissanCarInfo(CarInfo): - package: str = "ProPILOT" + package: str = "ProPILOT Assist" harness: Enum = Harness.nissan_a diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py index d37b82eed..b5429daef 100644 --- a/selfdrive/car/subaru/carcontroller.py +++ b/selfdrive/car/subaru/carcontroller.py @@ -1,27 +1,33 @@ +from opendbc.can.packer import CANPacker from selfdrive.car import apply_std_steer_torque_limits from selfdrive.car.subaru import subarucan -from selfdrive.car.subaru.values import DBC, PREGLOBAL_CARS, CarControllerParams -from opendbc.can.packer import CANPacker +from selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, CarControllerParams -class CarController(): +class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP self.apply_steer_last = 0 - self.es_distance_cnt = -1 + self.frame = 0 + self.es_lkas_cnt = -1 + self.es_distance_cnt = -1 + self.es_dashstatus_cnt = -1 self.cruise_button_prev = 0 - self.steer_rate_limited = False + self.last_cancel_frame = 0 self.p = CarControllerParams(CP) self.packer = CANPacker(DBC[CP.carFingerprint]['pt']) - def update(self, c, CS, frame, actuators, pcm_cancel_cmd, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart): + def update(self, CC, CS): + actuators = CC.actuators + hud_control = CC.hudControl + pcm_cancel_cmd = CC.cruiseControl.cancel can_sends = [] # *** steering *** - if (frame % self.p.STEER_STEP) == 0: + if (self.frame % self.p.STEER_STEP) == 0: apply_steer = int(round(actuators.steer * self.p.STEER_MAX)) @@ -29,15 +35,14 @@ class CarController(): new_steer = int(round(apply_steer)) apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.p) - self.steer_rate_limited = new_steer != apply_steer - if not c.latActive: + if not CC.latActive: apply_steer = 0 if self.CP.carFingerprint in PREGLOBAL_CARS: - can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer, frame, self.p.STEER_STEP)) + can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer)) else: - can_sends.append(subarucan.create_steering_control(self.packer, apply_steer, frame, self.p.STEER_STEP)) + can_sends.append(subarucan.create_steering_control(self.packer, apply_steer)) self.apply_steer_last = apply_steer @@ -45,7 +50,7 @@ class CarController(): # *** alerts and pcm cancel *** if self.CP.carFingerprint in PREGLOBAL_CARS: - if self.es_distance_cnt != CS.es_distance_msg["Counter"]: + if self.es_distance_cnt != CS.es_distance_msg["COUNTER"]: # 1 = main, 2 = set shallow, 3 = set deep, 4 = resume shallow, 5 = resume deep # disengage ACC when OP is disengaged if pcm_cancel_cmd: @@ -62,18 +67,26 @@ class CarController(): self.cruise_button_prev = cruise_button can_sends.append(subarucan.create_preglobal_es_distance(self.packer, cruise_button, CS.es_distance_msg)) - self.es_distance_cnt = CS.es_distance_msg["Counter"] + self.es_distance_cnt = CS.es_distance_msg["COUNTER"] else: - if self.es_distance_cnt != CS.es_distance_msg["Counter"]: - can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg, pcm_cancel_cmd)) - self.es_distance_cnt = CS.es_distance_msg["Counter"] + if pcm_cancel_cmd and (self.frame - self.last_cancel_frame) > 0.2: + bus = 1 if self.CP.carFingerprint in GLOBAL_GEN2 else 0 + can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg, bus, pcm_cancel_cmd)) + self.last_cancel_frame = self.frame + + if self.es_dashstatus_cnt != CS.es_dashstatus_msg["COUNTER"]: + can_sends.append(subarucan.create_es_dashstatus(self.packer, CS.es_dashstatus_msg)) + self.es_dashstatus_cnt = CS.es_dashstatus_msg["COUNTER"] - if self.es_lkas_cnt != CS.es_lkas_msg["Counter"]: - can_sends.append(subarucan.create_es_lkas(self.packer, CS.es_lkas_msg, c.enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart)) - self.es_lkas_cnt = CS.es_lkas_msg["Counter"] + if self.es_lkas_cnt != CS.es_lkas_msg["COUNTER"]: + can_sends.append(subarucan.create_es_lkas(self.packer, CS.es_lkas_msg, CC.enabled, hud_control.visualAlert, + hud_control.leftLaneVisible, hud_control.rightLaneVisible, + hud_control.leftLaneDepart, hud_control.rightLaneDepart)) + self.es_lkas_cnt = CS.es_lkas_msg["COUNTER"] new_actuators = actuators.copy() new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX + self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index 45ea66fb2..de5e62cb7 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -4,7 +4,7 @@ from opendbc.can.can_define import CANDefine from common.conversions import Conversions as CV from selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser -from selfdrive.car.subaru.values import DBC, STEER_THRESHOLD, CAR, PREGLOBAL_CARS +from selfdrive.car.subaru.values import DBC, STEER_THRESHOLD, CAR, GLOBAL_GEN2, PREGLOBAL_CARS class CarState(CarStateBase): @@ -13,7 +13,7 @@ class CarState(CarStateBase): can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) self.shifter_values = can_define.dv["Transmission"]["Gear"] - def update(self, cp, cp_cam): + def update(self, cp, cp_cam, cp_body): ret = car.CarState.new_message() ret.gas = cp.vl["Throttle"]["Throttle_Pedal"] / 255. @@ -21,13 +21,15 @@ class CarState(CarStateBase): if self.car_fingerprint in PREGLOBAL_CARS: ret.brakePressed = cp.vl["Brake_Pedal"]["Brake_Pedal"] > 2 else: - ret.brakePressed = cp.vl["Brake_Status"]["Brake"] == 1 + cp_brakes = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp + ret.brakePressed = cp_brakes.vl["Brake_Status"]["Brake"] == 1 + cp_wheels = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp ret.wheelSpeeds = self.get_wheel_speeds( - cp.vl["Wheel_Speeds"]["FL"], - cp.vl["Wheel_Speeds"]["FR"], - cp.vl["Wheel_Speeds"]["RL"], - cp.vl["Wheel_Speeds"]["RR"], + cp_wheels.vl["Wheel_Speeds"]["FL"], + cp_wheels.vl["Wheel_Speeds"]["FR"], + cp_wheels.vl["Wheel_Speeds"]["RL"], + cp_wheels.vl["Wheel_Speeds"]["RR"], ) ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. # Kalman filter, even though Subaru raw wheel speed is heaviliy filtered by default @@ -35,8 +37,8 @@ class CarState(CarStateBase): ret.standstill = ret.vEgoRaw < 0.01 # continuous blinker signals for assisted lane change - ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp( - 50, cp.vl["Dashlights"]["LEFT_BLINKER"], cp.vl["Dashlights"]["RIGHT_BLINKER"]) + ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["Dashlights"]["LEFT_BLINKER"], + cp.vl["Dashlights"]["RIGHT_BLINKER"]) if self.CP.enableBsm: ret.leftBlindspot = (cp.vl["BSD_RCTA"]["L_ADJACENT"] == 1) or (cp.vl["BSD_RCTA"]["L_APPROACHING"] == 1) @@ -47,12 +49,17 @@ class CarState(CarStateBase): ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"] ret.steeringTorque = cp.vl["Steering_Torque"]["Steer_Torque_Sensor"] + ret.steeringTorqueEps = cp.vl["Steering_Torque"]["Steer_Torque_Output"] ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD[self.car_fingerprint] - ret.cruiseState.enabled = cp.vl["CruiseControl"]["Cruise_Activated"] != 0 - ret.cruiseState.available = cp.vl["CruiseControl"]["Cruise_On"] != 0 + 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 ret.cruiseState.speed = cp_cam.vl["ES_DashStatus"]["Cruise_Set_Speed"] * CV.KPH_TO_MS + if self.car_fingerprint not in PREGLOBAL_CARS: + ret.cruiseState.standstill = cp_cam.vl["ES_DashStatus"]["Cruise_State"] == 3 + if (self.car_fingerprint in PREGLOBAL_CARS and cp.vl["Dash_State2"]["UNITS"] == 1) or \ (self.car_fingerprint not in PREGLOBAL_CARS and cp.vl["Dashlights"]["UNITS"] == 1): ret.cruiseState.speed *= CV.MPH_TO_KPH @@ -71,28 +78,74 @@ class CarState(CarStateBase): ret.steerFaultTemporary = cp.vl["Steering_Torque"]["Steer_Warning"] == 1 ret.cruiseState.nonAdaptive = cp_cam.vl["ES_DashStatus"]["Conventional_Cruise"] == 1 self.es_lkas_msg = copy.copy(cp_cam.vl["ES_LKAS_State"]) - self.es_distance_msg = copy.copy(cp_cam.vl["ES_Distance"]) + + cp_es_distance = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam + self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"]) + self.es_dashstatus_msg = copy.copy(cp_cam.vl["ES_DashStatus"]) return ret + @staticmethod + def get_common_global_signals(): + signals = [ + ("Cruise_On", "CruiseControl"), + ("Cruise_Activated", "CruiseControl"), + ("FL", "Wheel_Speeds"), + ("FR", "Wheel_Speeds"), + ("RL", "Wheel_Speeds"), + ("RR", "Wheel_Speeds"), + ("Brake", "Brake_Status"), + ] + checks = [ + ("CruiseControl", 20), + ("Wheel_Speeds", 50), + ("Brake_Status", 50), + ] + + return signals, checks + + @staticmethod + def get_global_es_distance_signals(): + signals = [ + ("COUNTER", "ES_Distance"), + ("Signal1", "ES_Distance"), + ("Cruise_Fault", "ES_Distance"), + ("Cruise_Throttle", "ES_Distance"), + ("Signal2", "ES_Distance"), + ("Car_Follow", "ES_Distance"), + ("Signal3", "ES_Distance"), + ("Cruise_Soft_Disable", "ES_Distance"), + ("Signal7", "ES_Distance"), + ("Cruise_Brake_Active", "ES_Distance"), + ("Distance_Swap", "ES_Distance"), + ("Cruise_EPB", "ES_Distance"), + ("Signal4", "ES_Distance"), + ("Close_Distance", "ES_Distance"), + ("Signal5", "ES_Distance"), + ("Cruise_Cancel", "ES_Distance"), + ("Cruise_Set", "ES_Distance"), + ("Cruise_Resume", "ES_Distance"), + ("Signal6", "ES_Distance"), + ] + checks = [ + ("ES_Distance", 20), + ] + + return signals, checks + @staticmethod def get_can_parser(CP): signals = [ # sig_name, sig_address ("Steer_Torque_Sensor", "Steering_Torque"), + ("Steer_Torque_Output", "Steering_Torque"), ("Steering_Angle", "Steering_Torque"), ("Steer_Error_1", "Steering_Torque"), - ("Cruise_On", "CruiseControl"), - ("Cruise_Activated", "CruiseControl"), ("Brake_Pedal", "Brake_Pedal"), ("Throttle_Pedal", "Throttle"), ("LEFT_BLINKER", "Dashlights"), ("RIGHT_BLINKER", "Dashlights"), ("SEATBELT_FL", "Dashlights"), - ("FL", "Wheel_Speeds"), - ("FR", "Wheel_Speeds"), - ("RL", "Wheel_Speeds"), - ("RR", "Wheel_Speeds"), ("DOOR_OPEN_FR", "BodyInfo"), ("DOOR_OPEN_FL", "BodyInfo"), ("DOOR_OPEN_RR", "BodyInfo"), @@ -105,7 +158,6 @@ class CarState(CarStateBase): ("Throttle", 100), ("Dashlights", 10), ("Brake_Pedal", 50), - ("Wheel_Speeds", 50), ("Transmission", 100), ("Steering_Torque", 50), ("BodyInfo", 1), @@ -121,36 +173,47 @@ class CarState(CarStateBase): checks.append(("BSD_RCTA", 17)) if CP.carFingerprint not in PREGLOBAL_CARS: + if CP.carFingerprint not in GLOBAL_GEN2: + signals += CarState.get_common_global_signals()[0] + checks += CarState.get_common_global_signals()[1] + signals += [ ("Steer_Warning", "Steering_Torque"), - ("Brake", "Brake_Status"), ("UNITS", "Dashlights"), ] checks += [ ("Dashlights", 10), ("BodyInfo", 10), - ("Brake_Status", 50), - ("CruiseControl", 20), ] else: - signals.append(("UNITS", "Dash_State2")) - - checks.append(("Dash_State2", 1)) - - if CP.carFingerprint == CAR.FORESTER_PREGLOBAL: - checks += [ - ("Dashlights", 20), - ("BodyInfo", 1), - ("CruiseControl", 50), + signals += [ + ("FL", "Wheel_Speeds"), + ("FR", "Wheel_Speeds"), + ("RL", "Wheel_Speeds"), + ("RR", "Wheel_Speeds"), + ("UNITS", "Dash_State2"), + ("Cruise_On", "CruiseControl"), + ("Cruise_Activated", "CruiseControl"), ] - - if CP.carFingerprint in (CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018): checks += [ - ("Dashlights", 10), - ("CruiseControl", 50), + ("Wheel_Speeds", 50), + ("Dash_State2", 1), ] + if CP.carFingerprint == CAR.FORESTER_PREGLOBAL: + checks += [ + ("Dashlights", 20), + ("BodyInfo", 1), + ("CruiseControl", 50), + ] + + if CP.carFingerprint in (CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018): + checks += [ + ("Dashlights", 10), + ("CruiseControl", 50), + ] + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 0) @staticmethod @@ -173,7 +236,7 @@ class CarState(CarStateBase): ("Standstill_2", "ES_Distance"), ("Cruise_Fault", "ES_Distance"), ("Signal5", "ES_Distance"), - ("Counter", "ES_Distance"), + ("COUNTER", "ES_Distance"), ("Signal6", "ES_Distance"), ("Cruise_Button", "ES_Distance"), ("Signal7", "ES_Distance"), @@ -185,28 +248,34 @@ class CarState(CarStateBase): ] else: signals = [ - ("Cruise_Set_Speed", "ES_DashStatus"), + ("Counter", "ES_DashStatus"), + ("PCB_Off", "ES_DashStatus"), + ("LDW_Off", "ES_DashStatus"), + ("Signal1", "ES_DashStatus"), + ("Cruise_State_Msg", "ES_DashStatus"), + ("LKAS_State_Msg", "ES_DashStatus"), + ("Signal2", "ES_DashStatus"), + ("Cruise_Soft_Disable", "ES_DashStatus"), + ("EyeSight_Status_Msg", "ES_DashStatus"), + ("Signal3", "ES_DashStatus"), + ("Cruise_Distance", "ES_DashStatus"), + ("Signal4", "ES_DashStatus"), ("Conventional_Cruise", "ES_DashStatus"), + ("Signal5", "ES_DashStatus"), + ("Cruise_Disengaged", "ES_DashStatus"), + ("Cruise_Activated", "ES_DashStatus"), + ("Signal6", "ES_DashStatus"), + ("Cruise_Set_Speed", "ES_DashStatus"), + ("Cruise_Fault", "ES_DashStatus"), + ("Cruise_On", "ES_DashStatus"), + ("Display_Own_Car", "ES_DashStatus"), + ("Brake_Lights", "ES_DashStatus"), + ("Car_Follow", "ES_DashStatus"), + ("Signal7", "ES_DashStatus"), + ("Far_Distance", "ES_DashStatus"), + ("Cruise_State", "ES_DashStatus"), - ("Counter", "ES_Distance"), - ("Signal1", "ES_Distance"), - ("Cruise_Fault", "ES_Distance"), - ("Cruise_Throttle", "ES_Distance"), - ("Signal2", "ES_Distance"), - ("Car_Follow", "ES_Distance"), - ("Signal3", "ES_Distance"), - ("Cruise_Brake_Active", "ES_Distance"), - ("Distance_Swap", "ES_Distance"), - ("Cruise_EPB", "ES_Distance"), - ("Signal4", "ES_Distance"), - ("Close_Distance", "ES_Distance"), - ("Signal5", "ES_Distance"), - ("Cruise_Cancel", "ES_Distance"), - ("Cruise_Set", "ES_Distance"), - ("Cruise_Resume", "ES_Distance"), - ("Signal6", "ES_Distance"), - - ("Counter", "ES_LKAS_State"), + ("COUNTER", "ES_LKAS_State"), ("LKAS_Alert_Msg", "ES_LKAS_State"), ("Signal1", "ES_LKAS_State"), ("LKAS_ACTIVE", "ES_LKAS_State"), @@ -225,8 +294,21 @@ class CarState(CarStateBase): checks = [ ("ES_DashStatus", 10), - ("ES_Distance", 20), ("ES_LKAS_State", 10), ] + if CP.carFingerprint not in GLOBAL_GEN2: + signals += CarState.get_global_es_distance_signals()[0] + checks += CarState.get_global_es_distance_signals()[1] + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2) + + @staticmethod + def get_body_can_parser(CP): + if CP.carFingerprint in GLOBAL_GEN2: + signals, checks = CarState.get_common_global_signals() + signals += CarState.get_global_es_distance_signals()[0] + checks += CarState.get_global_es_distance_signals()[1] + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 1) + + return None \ No newline at end of file diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 8c5cab86e..049da10a1 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 from cereal import car -from selfdrive.car.subaru.values import CAR, PREGLOBAL_CARS +from panda import Panda from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase +from selfdrive.car.subaru.values import CAR, GLOBAL_GEN2, PREGLOBAL_CARS + class CarInterface(CarInterfaceBase): @@ -12,17 +14,20 @@ class CarInterface(CarInterfaceBase): ret.carName = "subaru" ret.radarOffCan = True + ret.dashcamOnly = candidate in PREGLOBAL_CARS if candidate in PREGLOBAL_CARS: - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaruLegacy)] ret.enableBsm = 0x25c in fingerprint[0] + ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaruLegacy)] else: - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaru)] ret.enableBsm = 0x228 in fingerprint[0] - - ret.dashcamOnly = candidate in PREGLOBAL_CARS + ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaru)] + if candidate in GLOBAL_GEN2: + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_SUBARU_GEN2 ret.steerLimitTimer = 0.4 + ret.steerActuatorDelay = 0.1 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) if candidate == CAR.ASCENT: ret.mass = 2031. + STD_CARGO_KG @@ -30,70 +35,72 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.5 ret.steerRatio = 13.5 ret.steerActuatorDelay = 0.3 # end-to-end angle controller + ret.lateralTuning.init('pid') ret.lateralTuning.pid.kf = 0.00003 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.0025, 0.1], [0.00025, 0.01]] - if candidate == CAR.IMPREZA: + elif candidate == CAR.IMPREZA: ret.mass = 1568. + STD_CARGO_KG ret.wheelbase = 2.67 ret.centerToFront = ret.wheelbase * 0.5 ret.steerRatio = 15 ret.steerActuatorDelay = 0.4 # end-to-end angle controller + ret.lateralTuning.init('pid') ret.lateralTuning.pid.kf = 0.00005 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.3], [0.02, 0.03]] - if candidate == CAR.IMPREZA_2020: + elif candidate == CAR.IMPREZA_2020: ret.mass = 1480. + STD_CARGO_KG ret.wheelbase = 2.67 ret.centerToFront = ret.wheelbase * 0.5 ret.steerRatio = 17 # learned, 14 stock - ret.steerActuatorDelay = 0.1 + ret.lateralTuning.init('pid') ret.lateralTuning.pid.kf = 0.00005 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.045, 0.042, 0.20], [0.04, 0.035, 0.045]] - if candidate == CAR.FORESTER: + elif candidate == CAR.FORESTER: ret.mass = 1568. + STD_CARGO_KG ret.wheelbase = 2.67 ret.centerToFront = ret.wheelbase * 0.5 ret.steerRatio = 17 # learned, 14 stock - ret.steerActuatorDelay = 0.1 + ret.lateralTuning.init('pid') ret.lateralTuning.pid.kf = 0.000038 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.065, 0.2], [0.001, 0.015, 0.025]] - if candidate in (CAR.FORESTER_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018): + elif candidate == CAR.OUTBACK: + ret.mass = 1568. + STD_CARGO_KG + ret.wheelbase = 2.67 + ret.centerToFront = ret.wheelbase * 0.5 + ret.steerRatio = 17 + ret.steerActuatorDelay = 0.1 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + + elif candidate in (CAR.FORESTER_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018): ret.safetyConfigs[0].safetyParam = 1 # Outback 2018-2019 and Forester have reversed driver torque signal ret.mass = 1568 + STD_CARGO_KG ret.wheelbase = 2.67 ret.centerToFront = ret.wheelbase * 0.5 ret.steerRatio = 20 # learned, 14 stock - ret.steerActuatorDelay = 0.1 - ret.lateralTuning.pid.kf = 0.000039 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 10., 20.], [0., 10., 20.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.05, 0.2], [0.003, 0.018, 0.025]] - if candidate == CAR.LEGACY_PREGLOBAL: + elif candidate == CAR.LEGACY_PREGLOBAL: ret.mass = 1568 + STD_CARGO_KG ret.wheelbase = 2.67 ret.centerToFront = ret.wheelbase * 0.5 ret.steerRatio = 12.5 # 14.5 stock ret.steerActuatorDelay = 0.15 - ret.lateralTuning.pid.kf = 0.00005 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.1, 0.2], [0.01, 0.02]] - if candidate == CAR.OUTBACK_PREGLOBAL: + elif candidate == CAR.OUTBACK_PREGLOBAL: ret.mass = 1568 + STD_CARGO_KG ret.wheelbase = 2.67 ret.centerToFront = ret.wheelbase * 0.5 ret.steerRatio = 20 # learned, 14 stock - ret.steerActuatorDelay = 0.1 - ret.lateralTuning.pid.kf = 0.000039 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 10., 20.], [0., 10., 20.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.05, 0.2], [0.003, 0.018, 0.025]] + + else: + raise ValueError(f"unknown car: {candidate}") # TODO: get actual value, for now starting with reasonable value for # civic and scaling by mass and wheelbase @@ -108,18 +115,11 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): - ret = self.CS.update(self.cp, self.cp_cam) - - ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False + ret = self.CS.update(self.cp, self.cp_cam, self.cp_body) ret.events = self.create_common_events(ret).to_msg() return ret def apply(self, c): - hud_control = c.hudControl - ret = self.CC.update(c, self.CS, self.frame, c.actuators, - c.cruiseControl.cancel, hud_control.visualAlert, - hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart) - self.frame += 1 - return ret + return self.CC.update(c, self.CS) diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py index 63511d183..d83b639a4 100644 --- a/selfdrive/car/subaru/subarucan.py +++ b/selfdrive/car/subaru/subarucan.py @@ -3,29 +3,23 @@ from cereal import car VisualAlert = car.CarControl.HUDControl.VisualAlert -def create_steering_control(packer, apply_steer, frame, steer_step): - - idx = (frame / steer_step) % 16 - +def create_steering_control(packer, apply_steer): values = { - "Counter": idx, "LKAS_Output": apply_steer, "LKAS_Request": 1 if apply_steer != 0 else 0, "SET_1": 1 } - return packer.make_can_msg("ES_LKAS", 0, values) -def create_steering_status(packer, apply_steer, frame, steer_step): +def create_steering_status(packer): return packer.make_can_msg("ES_LKAS_State", 0, {}) -def create_es_distance(packer, es_distance_msg, pcm_cancel_cmd): - +def create_es_distance(packer, es_distance_msg, bus, pcm_cancel_cmd): values = copy.copy(es_distance_msg) + values["COUNTER"] = (values["COUNTER"] + 1) % 0x10 if pcm_cancel_cmd: values["Cruise_Cancel"] = 1 - - return packer.make_can_msg("ES_Distance", 0, values) + return packer.make_can_msg("ES_Distance", bus, values) def create_es_lkas(packer, es_lkas_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart): @@ -39,6 +33,18 @@ def create_es_lkas(packer, es_lkas_msg, enabled, visual_alert, left_line, right_ if values["LKAS_Alert"] == 27: values["LKAS_Alert"] = 0 + # Filter the stock LKAS sending an audible alert when "Keep hands on wheel" alert is active (2020+ models) + if values["LKAS_Alert"] == 28 and values["LKAS_Alert_Msg"] == 7: + values["LKAS_Alert"] = 0 + + # Filter the stock LKAS sending an audible alert when "Keep hands on wheel OFF" alert is active (2020+ models) + if values["LKAS_Alert"] == 30: + values["LKAS_Alert"] = 0 + + # Filter the stock LKAS sending "Keep hands on wheel OFF" alert (2020+ models) + if values["LKAS_Alert_Msg"] == 7: + values["LKAS_Alert_Msg"] = 0 + # Show Keep hands on wheel alert for openpilot steerRequired alert if visual_alert == VisualAlert.steerRequired: values["LKAS_Alert_Msg"] = 1 @@ -61,18 +67,23 @@ def create_es_lkas(packer, es_lkas_msg, enabled, visual_alert, left_line, right_ return packer.make_can_msg("ES_LKAS_State", 0, values) +def create_es_dashstatus(packer, dashstatus_msg): + values = copy.copy(dashstatus_msg) + + # Filter stock LKAS disabled and Keep hands on steering wheel OFF alerts + if values["LKAS_State_Msg"] in [2, 3]: + values["LKAS_State_Msg"] = 0 + + return packer.make_can_msg("ES_DashStatus", 0, values) + # *** Subaru Pre-global *** def subaru_preglobal_checksum(packer, values, addr): dat = packer.make_can_msg(addr, 0, values)[2] return (sum(dat[:7])) % 256 -def create_preglobal_steering_control(packer, apply_steer, frame, steer_step): - - idx = (frame / steer_step) % 8 - +def create_preglobal_steering_control(packer, apply_steer): values = { - "Counter": idx, "LKAS_Command": apply_steer, "LKAS_Active": 1 if apply_steer != 0 else 0 } diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 45358eb3a..691a4c915 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -11,23 +11,32 @@ Ecu = car.CarParams.Ecu class CarControllerParams: def __init__(self, CP): - if CP.carFingerprint == CAR.IMPREZA_2020: - self.STEER_MAX = 1439 - else: - self.STEER_MAX = 2047 self.STEER_STEP = 2 # how often we update the steer cmd self.STEER_DELTA_UP = 50 # torque increase per refresh, 0.8s to max self.STEER_DELTA_DOWN = 70 # torque decrease per refresh self.STEER_DRIVER_ALLOWANCE = 60 # allowed driver torque before start limiting - self.STEER_DRIVER_MULTIPLIER = 10 # weight driver torque heavily + self.STEER_DRIVER_MULTIPLIER = 50 # weight driver torque heavily self.STEER_DRIVER_FACTOR = 1 # from dbc + if CP.carFingerprint in GLOBAL_GEN2: + self.STEER_MAX = 1000 + self.STEER_DELTA_UP = 40 + self.STEER_DELTA_DOWN = 40 + elif CP.carFingerprint == CAR.IMPREZA_2020: + self.STEER_MAX = 1439 + else: + self.STEER_MAX = 2047 + class CAR: + # Global platform ASCENT = "SUBARU ASCENT LIMITED 2019" IMPREZA = "SUBARU IMPREZA LIMITED 2019" IMPREZA_2020 = "SUBARU IMPREZA SPORT 2020" FORESTER = "SUBARU FORESTER 2019" + OUTBACK = "SUBARU OUTBACK 6TH GEN" + + # Pre-global FORESTER_PREGLOBAL = "SUBARU FORESTER 2017 - 2018" LEGACY_PREGLOBAL = "SUBARU LEGACY 2015 - 2018" OUTBACK_PREGLOBAL = "SUBARU OUTBACK 2015 - 2017" @@ -36,19 +45,22 @@ class CAR: @dataclass class SubaruCarInfo(CarInfo): - package: str = "EyeSight" - harness: Enum = Harness.subaru + package: str = "EyeSight Driver Assistance" + harness: Enum = Harness.subaru_a CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { - CAR.ASCENT: SubaruCarInfo("Subaru Ascent 2019-20", "All"), + CAR.ASCENT: SubaruCarInfo("Subaru Ascent 2019-21", "All"), + CAR.OUTBACK: SubaruCarInfo("Subaru Outback 2020-22", "All", harness=Harness.subaru_b), CAR.IMPREZA: [ SubaruCarInfo("Subaru Impreza 2017-19"), SubaruCarInfo("Subaru Crosstrek 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"), + SubaruCarInfo("Subaru XV 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"), ], CAR.IMPREZA_2020: [ - SubaruCarInfo("Subaru Impreza 2020-21"), + SubaruCarInfo("Subaru Impreza 2020-22"), SubaruCarInfo("Subaru Crosstrek 2020-21"), + SubaruCarInfo("Subaru XV 2020-21"), ], CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-21", "All"), CAR.FORESTER_PREGLOBAL: SubaruCarInfo("Subaru Forester 2017-18"), @@ -87,7 +99,6 @@ FW_VERSIONS = { b'\x00\xfe\xf7\x00\x00', b'\001\xfe\xf9\000\000', b'\x01\xfe\xf7\x00\x00', - b'\xf1\x00\xa4\x10@', ], }, CAR.IMPREZA: { @@ -104,6 +115,7 @@ FW_VERSIONS = { b'z\x94\x08\x90\x00', b'z\x84\x19\x90\x00', b'\xf1\x00\xb2\x06\x04', + b'z\x94\x0c\x90\x00', ], (Ecu.eps, 0x746, None): [ b'\x7a\xc0\x0c\x00', @@ -112,6 +124,7 @@ FW_VERSIONS = { b'z\xc0\x04\x00', b'z\xc0\x00\x00', b'\x8a\xc0\x10\x00', + b'z\xc0\n\x00', ], (Ecu.fwdCamera, 0x787, None): [ b'\x00\x00\x64\xb5\x1f\x40\x20\x0e', @@ -126,6 +139,7 @@ FW_VERSIONS = { b'\x00\x00c\xf4\x00\x00\x00\x00', b'\x00\x00d\xdc\x00\x00\x00\x00', b'\x00\x00dd\x00\x00\x00\x00', + b'\x00\x00c\xf4\x1f@ \x07', ], (Ecu.engine, 0x7e0, None): [ b'\xaa\x61\x66\x73\x07', @@ -135,7 +149,6 @@ FW_VERSIONS = { b'\xaa!`u\a', b'\xaa!dq\a', b'\xaa!dt\a', - b'\xf1\x00\xa2\x10\t', b'\xc5!ar\a', b'\xbe!as\a', b'\xc5!ds\a', @@ -145,6 +158,7 @@ FW_VERSIONS = { b'\xaa\x00Bu\x07', b'\xc5!dr\x07', b'\xaa!aw\x07', + b'\xaa!av\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xe3\xe5\x46\x31\x00', @@ -160,7 +174,6 @@ FW_VERSIONS = { b'\xe4\xf5\002\000\000', b'\xe3\xd0\x081\x00', b'\xe3\xf5\x06\x00\x00', - b'\xf1\x00\xa4\x10@', ], }, CAR.IMPREZA_2020: { @@ -189,7 +202,6 @@ FW_VERSIONS = { b'\xca!`0\a', b'\xcc\"f0\a', b'\xcc!fp\a', - b'\xf1\x00\xa2\x10\t', b'\xca!f@\x07', b'\xca!fp\x07', ], @@ -227,13 +239,13 @@ FW_VERSIONS = { b'\xcb\"`@\a', b'\xcb\"`p\a', b'\xf1\x00\xa2\x10\n', + b'\xcf"`p\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\032\xf6B0\000', b'\x1a\xf6F`\x00', b'\032\xf6b`\000', b'\x1a\xf6B`\x00', - b'\xf1\x00\xa4\x10@', b'\x1a\xf6b0\x00', ], }, @@ -393,6 +405,47 @@ FW_VERSIONS = { b'\xbb\xfb\xe0`\000', ], }, + CAR.OUTBACK: { + (Ecu.esp, 0x7b0, None): [ + b'\xa1 \x06\x01', + b'\xa1 \a\x00', + b'\xa1 \b\001', + b'\xa1 \x06\x00', + b'\xa1 "\t\x01', + b'\xa1 \x08\x02', + b'\xa1 \x06\x02', + b'\xa1 \x08\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x9b\xc0\x10\x00', + b'\x9b\xc0\x20\x00', + b'\x1b\xc0\x10\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00eJ\x00\x1f@ \x19\x00', + b'\000\000e\x80\000\037@ \031\000', + b'\x00\x00e\x9a\x00\x1f@ 1\x00', + b'\x00\x00eJ\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xbc,\xa0q\x07', + b'\xbc\"`@\a', + b'\xde"`0\a', + b'\xf1\x82\xbc,\xa0q\a', + b'\xf1\x82\xe3,\xa0@\x07', + b'\xe2"`p\x07', + b'\xf1\x82\xe2,\xa0@\x07', + b'\xbc"`q\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xa5\xfe\xf7@\x00', + b'\xa5\xf6D@\x00', + b'\xa5\xfe\xf6@\x00', + b'\xa7\x8e\xf40\x00', + b'\xf1\x82\xa7\xf6D@\x00', + b'\xa7\xfe\xf4@\x00', + ], + }, } STEER_THRESHOLD = { @@ -400,6 +453,7 @@ STEER_THRESHOLD = { CAR.IMPREZA: 80, CAR.IMPREZA_2020: 80, CAR.FORESTER: 80, + CAR.OUTBACK: 80, CAR.FORESTER_PREGLOBAL: 75, CAR.LEGACY_PREGLOBAL: 75, CAR.OUTBACK_PREGLOBAL: 75, @@ -411,10 +465,12 @@ DBC = { 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.OUTBACK: dbc_dict('subaru_global_2017_generated', None), CAR.FORESTER_PREGLOBAL: dbc_dict('subaru_forester_2017_generated', None), CAR.LEGACY_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None), CAR.OUTBACK_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None), CAR.OUTBACK_PREGLOBAL_2018: dbc_dict('subaru_outback_2019_generated', None), } -PREGLOBAL_CARS = [CAR.FORESTER_PREGLOBAL, CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018] +GLOBAL_GEN2 = (CAR.OUTBACK, ) +PREGLOBAL_CARS = (CAR.FORESTER_PREGLOBAL, CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018) diff --git a/selfdrive/car/tesla/interface.py b/selfdrive/car/tesla/interface.py index d4eda38e6..5e78c72b6 100755 --- a/selfdrive/car/tesla/interface.py +++ b/selfdrive/car/tesla/interface.py @@ -64,5 +64,4 @@ class CarInterface(CarInterfaceBase): return ret def apply(self, c): - ret = self.CC.update(c, self.CS) - return ret + return self.CC.update(c, self.CS) diff --git a/selfdrive/car/tesla/values.py b/selfdrive/car/tesla/values.py index 2ed4c963e..296169587 100644 --- a/selfdrive/car/tesla/values.py +++ b/selfdrive/car/tesla/values.py @@ -22,7 +22,7 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { FINGERPRINTS = { CAR.AP2_MODELS: [ { - 1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 277: 6, 280: 6, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 518: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 538: 8, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 576: 3, 577: 8, 582: 5, 583: 8, 584: 4, 585: 8, 590: 8, 601: 8, 606: 8, 608: 1, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 692: 8, 693: 8, 695: 8, 696: 8, 697: 8, 699: 8, 700: 8, 701: 8, 702: 8, 703: 8, 704: 8, 708: 8, 709: 8, 710: 8, 711: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 811: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 845: 8, 846: 5, 848: 8, 852: 8, 853: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 876: 8, 877: 8, 879: 8, 880: 8, 882: 8, 884: 8, 888: 8, 893: 8, 894: 8, 901: 6, 904: 3, 905: 8, 906: 8, 908: 2, 909: 8, 910: 8, 912: 8, 920: 8, 921: 8, 925: 4, 926: 6, 936: 8, 941: 8, 949: 8, 952: 8, 953: 6, 968: 8, 969: 6, 970: 8, 971: 8, 977: 8, 984: 8, 987: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1007: 8, 1008: 8, 1010: 6, 1014: 1, 1015: 8, 1016: 8, 1017: 8, 1018: 8, 1020: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1049: 8, 1061: 8, 1064: 8, 1065: 8, 1070: 8, 1080: 8, 1081: 8, 1097: 8, 1113: 8, 1129: 8, 1145: 8, 1160: 4, 1177: 8, 1281: 8, 1328: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1353: 8, 1368: 8, 1412: 8, 1436: 8, 1476: 8, 1481: 8, 1497: 8, 1513: 8, 1519: 8, 1601: 8, 1605: 8, 1617: 8, 1621: 8, 1625: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1824: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2015: 8, 2043: 5, 2045: 4 + 1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 277: 6, 280: 6, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 518: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 538: 8, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 576: 3, 577: 8, 582: 5, 583: 8, 584: 4, 585: 8, 590: 8, 601: 8, 606: 8, 608: 1, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 692: 8, 693: 8, 695: 8, 696: 8, 697: 8, 699: 8, 700: 8, 701: 8, 702: 8, 703: 8, 704: 8, 708: 8, 709: 8, 710: 8, 711: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 811: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 845: 8, 846: 5, 848: 8, 852: 8, 853: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 876: 8, 877: 8, 879: 8, 880: 8, 882: 8, 884: 8, 888: 8, 893: 8, 894: 8, 901: 6, 904: 3, 905: 8, 906: 8, 908: 2, 909: 8, 910: 8, 912: 8, 920: 8, 921: 8, 925: 4, 926: 6, 936: 8, 941: 8, 949: 8, 952: 8, 953: 6, 968: 8, 969: 6, 970: 8, 971: 8, 977: 8, 984: 8, 987: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1007: 8, 1008: 8, 1010: 6, 1014: 1, 1015: 8, 1016: 8, 1017: 8, 1018: 8, 1020: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1049: 8, 1061: 8, 1064: 8, 1065: 8, 1070: 8, 1080: 8, 1081: 8, 1097: 8, 1113: 8, 1129: 8, 1145: 8, 1160: 4, 1177: 8, 1281: 8, 1328: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1353: 8, 1368: 8, 1412: 8, 1436: 8, 1476: 8, 1481: 8, 1497: 8, 1513: 8, 1519: 8, 1601: 8, 1605: 8, 1617: 8, 1621: 8, 1625: 8, 1665: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1824: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2015: 8, 2043: 5, 2045: 4 }, ], CAR.AP1_MODELS: [ diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index a9d36f9ea..0b1726b01 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -38,6 +38,8 @@ routes = [ TestRoute("378472f830ee7395|2021-05-28--07-38-43", CHRYSLER.PACIFICA_2018_HYBRID), TestRoute("8190c7275a24557b|2020-01-29--08-33-58", CHRYSLER.PACIFICA_2019_HYBRID), TestRoute("3d84727705fecd04|2021-05-25--08-38-56", CHRYSLER.PACIFICA_2020), + TestRoute("221c253375af4ee9|2022-06-15--18-38-24", CHRYSLER.RAM_1500), + TestRoute("8fb5eabf914632ae|2022-08-04--17-28-53", CHRYSLER.RAM_HD, segment=6), #TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION), @@ -45,6 +47,7 @@ routes = [ TestRoute("aa20e335f61ba898|2019-02-05--16-59-04", GM.BUICK_REGAL), TestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV), TestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT), + TestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV), TestRoute("0e7a2ba168465df5|2020-10-18--14-14-22", HONDA.ACURA_RDX_3G), TestRoute("a74b011b32b51b56|2020-07-26--17-09-36", HONDA.CIVIC), @@ -71,6 +74,7 @@ routes = [ TestRoute("f34a60d68d83b1e5|2020-10-06--14-35-55", HONDA.ACURA_RDX), TestRoute("54fd8451b3974762|2021-04-01--14-50-10", HONDA.RIDGELINE), TestRoute("2d5808fae0b38ac6|2021-09-01--17-14-11", HONDA.HONDA_E), + TestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.CIVIC_2022), TestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS), TestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70), @@ -87,6 +91,7 @@ routes = [ TestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON), TestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO), TestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.PALISADE), + TestRoute("22de8111a8c5463c|2022-07-29--13-34-49", HYUNDAI.IONIQ_5), TestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.IONIQ_PHEV_2019), TestRoute("fa8db5869167f821|2021-06-10--22-50-10", HYUNDAI.IONIQ_PHEV), TestRoute("2c5cf2dd6102e5da|2020-12-17--16-06-44", HYUNDAI.IONIQ_EV_2020), @@ -95,6 +100,7 @@ routes = [ TestRoute("ab59fe909f626921|2021-10-18--18-34-28", HYUNDAI.IONIQ_HEV_2022), TestRoute("22d955b2cd499c22|2020-08-10--19-58-21", HYUNDAI.KONA), TestRoute("efc48acf44b1e64d|2021-05-28--21-05-04", HYUNDAI.KONA_EV), + TestRoute("ff973b941a69366f|2022-07-28--22-01-19", HYUNDAI.KONA_EV_2022, segment=11), TestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV), TestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER), TestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.VELOSTER), @@ -185,13 +191,11 @@ routes = [ TestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER), TestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA), TestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020), - # Dashcam + TestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.OUTBACK, segment=3), + # Pre-global, dashcam TestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL), - # Dashcam TestRoute("df5ca7660000fba8|2020-06-16--17-37-19", SUBARU.LEGACY_PREGLOBAL), - # Dashcam TestRoute("5ab784f361e19b78|2020-06-08--16-30-41", SUBARU.OUTBACK_PREGLOBAL), - # Dashcam TestRoute("e19eb5d5353b1ac1|2020-08-09--14-37-56", SUBARU.OUTBACK_PREGLOBAL_2018), TestRoute("fbbfa6af821552b9|2020-03-03--08-09-43", NISSAN.XTRAIL), diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 15df1aafe..412874c81 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -33,6 +33,7 @@ class TestCarInterfaces(unittest.TestCase): assert car_interface self.assertGreater(car_params.mass, 1) + self.assertGreater(car_params.maxLateralAccel, 0) if car_params.steerControlType != car.CarParams.SteerControlType.angle: tuning = car_params.lateralTuning.which() diff --git a/selfdrive/car/tests/test_docs.py b/selfdrive/car/tests/test_docs.py index b4bc14ef0..84c6b6e52 100755 --- a/selfdrive/car/tests/test_docs.py +++ b/selfdrive/car/tests/test_docs.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 +import re import unittest 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 Column, Star +from selfdrive.car.docs_definitions import Column, Harness, Star +from selfdrive.car.honda.values import CAR as HONDA class TestCarDocs(unittest.TestCase): @@ -11,37 +13,56 @@ class TestCarDocs(unittest.TestCase): self.all_cars = get_all_car_info() def test_generator(self): - generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE) + generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE, False) with open(CARS_MD_OUT, "r") as f: current_cars_md = f.read() self.assertEqual(generated_cars_md, current_cars_md, - "Run selfdrive/car/docs.py to generate new supported cars documentation") + "Run selfdrive/car/docs.py to update the compatibility documentation") def test_missing_car_info(self): all_car_info_platforms = get_interface_attr("CAR_INFO", combine_brands=True).keys() for platform in sorted(interfaces.keys()): - if platform not in all_car_info_platforms: - self.fail("Platform: {} doesn't exist in CarInfo".format(platform)) + with self.subTest(platform=platform): + self.assertTrue(platform in all_car_info_platforms, "Platform: {} doesn't exist in CarInfo".format(platform)) def test_naming_conventions(self): - # Asserts market-standard car naming conventions by make + # Asserts market-standard car naming conventions by brand for car in self.all_cars: - tokens = car.model.lower().split(" ") - if car.car_name == "hyundai": - self.assertNotIn("phev", tokens, "Use `Plug-in Hybrid`") - self.assertNotIn("hev", tokens, "Use `Hybrid`") - self.assertNotIn("ev", tokens, "Use `Electric`") - if "plug-in hybrid" in car.model.lower(): - self.assertIn("Plug-in Hybrid", car.model, "Use correct capitalization") - elif car.car_name == "toyota": - if "rav4" in tokens: - self.assertIn("RAV4", car.model, "Use correct capitalization") + with self.subTest(car=car): + tokens = car.model.lower().split(" ") + if car.car_name == "hyundai": + self.assertNotIn("phev", tokens, "Use `Plug-in Hybrid`") + self.assertNotIn("hev", tokens, "Use `Hybrid`") + self.assertNotIn("ev", tokens, "Use `Electric`") + if "plug-in hybrid" in car.model.lower(): + self.assertIn("Plug-in Hybrid", car.model, "Use correct capitalization") + elif car.car_name == "toyota": + if "rav4" in tokens: + self.assertIn("RAV4", car.model, "Use correct capitalization") def test_torque_star(self): + # Asserts brand-specific assumptions around steering torque star for car in self.all_cars: - if car.car_name == "honda": - self.assertTrue(car.row[Column.STEERING_TORQUE] in (Star.EMPTY, Star.HALF), f"{car.name} has full torque star") + with self.subTest(car=car): + # honda sanity check, it's the definition of a no torque star + if car.car_fingerprint in (HONDA.ACCORD, HONDA.CIVIC, HONDA.CRV, HONDA.ODYSSEY, HONDA.PILOT): + self.assertEqual(car.row[Column.STEERING_TORQUE], Star.EMPTY, f"{car.name} has full torque star") + elif car.car_name in ("toyota", "hyundai"): + self.assertNotEqual(car.row[Column.STEERING_TORQUE], Star.EMPTY, f"{car.name} has no torque star") + + def test_year_format(self): + for car in self.all_cars: + with self.subTest(car=car): + self.assertIsNone(re.search(r"\d{4}-\d{4}", car.name), f"Format years correctly: {car.name}") + + def test_harnesses(self): + for car in self.all_cars: + with self.subTest(car=car): + if car.name == "comma body": + raise unittest.SkipTest + + self.assertNotIn(car.harness, [None, Harness.none], f"Need to specify car harness: {car.name}") if __name__ == "__main__": diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index ed7e420e1..cda241c73 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -12,6 +12,7 @@ CarFw = car.CarParams.CarFw Ecu = car.CarParams.Ecu ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} +VERSIONS = get_interface_attr("FW_VERSIONS", ignore_none=True) class TestFwFingerprint(unittest.TestCase): @@ -20,14 +21,16 @@ class TestFwFingerprint(unittest.TestCase): self.assertEqual(len(candidates), 1, f"got more than one candidate: {candidates}") self.assertEqual(candidates[0], expected) - @parameterized.expand([(k, v) for k, v in FW_VERSIONS.items()]) - def test_fw_fingerprint(self, car_model, ecus): + @parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e]) + def test_fw_fingerprint(self, brand, car_model, ecus): CP = car.CarParams.new_message() for _ in range(200): fw = [] for ecu, fw_versions in ecus.items(): + if not len(fw_versions): + raise unittest.SkipTest("Car model has no FW versions") ecu_name, addr, sub_addr = ecu - fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), + fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand, "address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) CP.carFw = fw _, matches = match_fw_to_car(CP.carFw) @@ -60,10 +63,9 @@ class TestFwFingerprint(unittest.TestCase): def test_fw_request_ecu_whitelist(self): passed = True brands = set(r.brand for r in REQUESTS) - versions = get_interface_attr('FW_VERSIONS') for brand in brands: whitelisted_ecus = [ecu for r in REQUESTS for ecu in r.whitelist_ecus if r.brand == brand] - brand_ecus = set([fw[0] for car_fw in versions[brand].values() for fw in car_fw]) + brand_ecus = set([fw[0] for car_fw in VERSIONS[brand].values() for fw in car_fw]) # each ecu in brand's fw versions needs to be whitelisted at least once ecus_not_whitelisted = set(brand_ecus) - set(whitelisted_ecus) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 27dc4ba77..4a1f1a61e 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -57,6 +57,10 @@ class TestCarModelBase(unittest.TestCase): if cls.__name__ == 'TestCarModel' or cls.__name__.endswith('Base'): raise unittest.SkipTest + if 'FILTER' in os.environ: + if not cls.car_model.startswith(tuple(os.environ.get('FILTER').split(','))): + raise unittest.SkipTest + if cls.test_route is None: if cls.car_model in non_tested_cars: print(f"Skipping tests for {cls.car_model}: missing route") @@ -214,6 +218,8 @@ class TestCarModelBase(unittest.TestCase): for can in self.can_msgs: CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) for msg in can_capnp_to_can_list(can.can, src_filter=range(64)): + msg = list(msg) + msg[3] %= 4 to_send = package_can_msg(msg) ret = self.safety.safety_rx_hook(to_send) self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}") @@ -221,6 +227,7 @@ class TestCarModelBase(unittest.TestCase): # TODO: check rest of panda's carstate (steering, ACC main on, etc.) checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev() + checks['cruiseState'] += CS.cruiseState.enabled and not CS.cruiseState.available # TODO: remove this exception once this mismatch is resolved brake_pressed = CS.brakePressed diff --git a/selfdrive/car/torque_data.json b/selfdrive/car/torque_data.json deleted file mode 100644 index 63ee0e9bc..000000000 --- a/selfdrive/car/torque_data.json +++ /dev/null @@ -1 +0,0 @@ -{"LAT_ACCEL_FACTOR": {"HONDA PILOT 2017": 1.682289482065265, "HONDA CIVIC 2016": 1.5248128495527884, "TOYOTA CAMRY 2018": 2.1115709806216447, "TOYOTA COROLLA HYBRID TSS2 2019": 2.3250600977240077, "TOYOTA RAV4 2019": 2.625504029066767, "HYUNDAI PALISADE 2020": 2.5250855675875634, "TOYOTA SIENNA 2018": 1.8254254785341577, "ACURA RDX 2020": 1.3998101622214894, "TOYOTA RAV4 2017": 1.948190869577896, "HONDA RIDGELINE 2017": 1.4158181862793415, "TOYOTA PRIUS 2017": 1.9142926195557595, "TOYOTA HIGHLANDER HYBRID 2020": 2.1097056247344392, "HYUNDAI SONATA 2020": 3.2488989629905944, "KIA STINGER GT2 2018": 2.7592622336517834, "TOYOTA HIGHLANDER 2020": 2.0408544157877055, "HONDA ACCORD 2018": 1.6374118241564064, "TOYOTA PRIUS TSS2 2021": 2.3207270770298365, "NISSAN LEAF 2018": NaN, "CHRYSLER PACIFICA HYBRID 2019": 1.46050785084946, "LEXUS NX 2020": 2.29533657249232, "TOYOTA RAV4 HYBRID 2019": 2.4003012079562085, "HONDA CIVIC (BOSCH) 2019": 1.6523031416671652, "KIA NIRO HYBRID 2021": 2.743464625803003, "HONDA ACCORD HYBRID 2018": 1.5904016830979033, "LEXUS NX HYBRID 2018": 2.398678119681945, "TOYOTA COROLLA TSS2 2019": 2.3859244449846466, "VOLKSWAGEN ARTEON 1ST GEN": 1.4249208219414902, "TOYOTA CAMRY HYBRID 2021": 2.5434553806317055, "VOLKSWAGEN JETTA 7TH GEN": 1.2228130240634283, "HONDA INSIGHT 2019": 1.468352089969897, "SUBARU FORESTER 2019": 3.6185035528523546, "HYUNDAI ELANTRA 2021": 3.5294999663335185, "HYUNDAI IONIQ ELECTRIC LIMITED 2019": 2.2179616966432905, "HYUNDAI KONA HYBRID 2020": 4.493208192966529, "HONDA ODYSSEY 2018": 1.8838175399087222, "LEXUS RX 2016": 1.3912132245094184, "TOYOTA COROLLA 2017": 3.0143547548384735, "LEXUS ES 2019": 2.012201253045193, "HYUNDAI SANTA FE 2019": 3.039728566484244, "TOYOTA AVALON 2022": 2.4619858654670885, "JEEP GRAND CHEROKEE V6 2018": 1.8411674990629987, "CHEVROLET VOLT PREMIER 2017": 1.5943438675127841, "TOYOTA RAV4 HYBRID 2017": 1.9803053616868995, "LEXUS RX 2020": 1.664616846377383, "TOYOTA HIGHLANDER HYBRID 2018": 1.8866764457400844, "TOYOTA CAMRY HYBRID 2018": 2.014213351947917, "TESLA AP2 MODEL S": NaN, "VOLKSWAGEN GOLF 7TH GEN": 1.4428896585442685, "TOYOTA MIRAI 2021": 2.7217623852898853, "LEXUS IS 2018": 3.5624668608596837, "TOYOTA HIGHLANDER 2017": 1.9199133105823853, "HYUNDAI SONATA HYBRID 2021": 2.7313907441569554, "VOLKSWAGEN ATLAS 1ST GEN": 1.4483948408160645, "LEXUS ES HYBRID 2019": 2.4138026617523547, "HYUNDAI GENESIS 2015-2016": 1.7636839808044658, "JEEP GRAND CHEROKEE 2019": 1.787264083939164, "SUBARU ASCENT LIMITED 2019": 3.0494069339774565, "HONDA CR-V 2017": 1.9828470679233807, "HONDA FIT 2018": 1.594940026552055, "TOYOTA CAMRY 2021": 2.5057990840460342, "AUDI Q3 2ND GEN": 1.4558300885316715, "AUDI A3 3RD GEN": 1.5304173783542625, "LEXUS RX HYBRID 2017": 1.577216425446677, "HONDA CIVIC 2022": 2.69252285552613, "GENESIS G70 2018": 3.866842361627636, "CHRYSLER PACIFICA HYBRID 2018": 1.5771851419640903, "VOLKSWAGEN PASSAT 8TH GEN": 1.2985597059739313, "HONDA CR-V 2016": 0.7745984062630755, "HYUNDAI IONIQ PHEV 2020": 2.5696218908589383, "GMC ACADIA DENALI 2018": 1.3310088601868082, "HYUNDAI SONATA 2019": 1.9736552675022665, "TOYOTA AVALON 2019": 1.7245149905226294, "TOYOTA C-HR 2018": 1.5895016960662856, "HONDA CR-V HYBRID 2019": 2.0687746810729193, "CHRYSLER PACIFICA 2020": 1.40536880000744, "HYUNDAI IONIQ ELECTRIC 2020": 3.3220838625838667, "VOLKSWAGEN TIGUAN 2ND GEN": NaN, "LEXUS NX 2018": 1.7753192756242595, "KIA OPTIMA SX 2019 & 2016": 3.12625562280304, "TOYOTA AVALON HYBRID 2019": 1.7681286449373381, "TOYOTA RAV4 HYBRID 2022": 2.5518187542231816, "HONDA PASSPORT 2021": 1.5174924139130355, "KIA K5 2021": 2.482916204106975, "ACURA ILX 2016": 1.5237423964720282, "HYUNDAI IONIQ HYBRID 2017-2019": 2.3723887901632645, "KIA NIRO EV 2020": 2.924651969180446, "SUBARU IMPREZA SPORT 2020": 2.5317689549587694, "CHRYSLER PACIFICA HYBRID 2017": 1.167126725149114, "HYUNDAI KONA ELECTRIC 2019": 4.201092987427836, "HYUNDAI ELANTRA HYBRID 2021": 3.7153193626001926, "HYUNDAI SANTA FE HYBRID 2022": 3.3049230586030545, "CHRYSLER PACIFICA 2018": 1.524867383058782, "NISSAN ROGUE 2019": NaN, "KIA SORENTO GT LINE 2018": 2.5970979517766213, "COMMA BODY": NaN, "NISSAN LEAF 2018 Instrument Cluster": NaN, "LEXUS RX HYBRID 2020": 1.5460982690267173, "MAZDA CX-9 2021": 1.9514800984278198, "HYUNDAI SANTA FE 2022": 3.5354982200434524, "HYUNDAI SANTA FE PlUG-IN HYBRID 2022": 1.8902492836532216, "HONDA HRV 2019": 2.1262957371020352, "TOYOTA AVALON HYBRID 2022": 2.4142150048378683, "SUBARU IMPREZA LIMITED 2019": 1.2203463907025918, "GENESIS G80 2017": 2.4086794443413906, "VOLKSWAGEN TAOS 1ST GEN": 2.0031666974545947, "KIA FORTE E 2018 & GT 2021": 2.022553820222557, "CADILLAC ESCALADE ESV 2016": 1.5522339636408988, "TOYOTA C-HR 2021": 1.6519334844316687, "TOYOTA C-HR HYBRID 2018": 1.3193315010905482}, "MAX_LAT_ACCEL_MEASURED": {"HONDA PILOT 2017": 0.9069354290994807, "HONDA CIVIC 2016": 0.4030275472529351, "TOYOTA CAMRY 2018": 1.686123168195758, "TOYOTA COROLLA HYBRID TSS2 2019": 1.9139332621491167, "TOYOTA RAV4 2019": 2.234047196286479, "HYUNDAI PALISADE 2020": 1.8303582523301922, "TOYOTA SIENNA 2018": 1.4752503435300715, "ACURA RDX 2020": 0.40911581320000334, "TOYOTA RAV4 2017": 1.6622227720995595, "HONDA RIDGELINE 2017": 0.8224685813281227, "TOYOTA PRIUS 2017": 1.4548827870876067, "TOYOTA HIGHLANDER HYBRID 2020": 2.0649784271823037, "HYUNDAI SONATA 2020": 2.243989856570093, "KIA STINGER GT2 2018": 1.9531287107084392, "TOYOTA HIGHLANDER 2020": 1.659381392090836, "HONDA ACCORD 2018": 0.40486739531686267, "TOYOTA PRIUS TSS2 2021": 1.861541601048098, "NISSAN LEAF 2018": NaN, "CHRYSLER PACIFICA HYBRID 2019": 1.1930739374812243, "LEXUS NX 2020": 1.565268724838564, "TOYOTA RAV4 HYBRID 2019": 2.0915384047218426, "HONDA CIVIC (BOSCH) 2019": 0.4062886118517984, "KIA NIRO HYBRID 2021": NaN, "HONDA ACCORD HYBRID 2018": 0.35128914564548286, "LEXUS NX HYBRID 2018": 1.81821359787186, "TOYOTA COROLLA TSS2 2019": 1.911280958056631, "VOLKSWAGEN ARTEON 1ST GEN": 1.2587939472578302, "TOYOTA CAMRY HYBRID 2021": 2.312510643730013, "VOLKSWAGEN JETTA 7TH GEN": 1.232161945396623, "HONDA INSIGHT 2019": 0.5174836462945298, "SUBARU FORESTER 2019": 2.29255993930968, "HYUNDAI ELANTRA 2021": NaN, "HYUNDAI IONIQ ELECTRIC LIMITED 2019": 2.133978602602408, "HYUNDAI KONA HYBRID 2020": NaN, "HONDA ODYSSEY 2018": 0.8254773781363679, "LEXUS RX 2016": 1.0954776820595344, "TOYOTA COROLLA 2017": 2.2012870528168964, "LEXUS ES 2019": 2.069508805495439, "HYUNDAI SANTA FE 2019": 2.3763720477660253, "TOYOTA AVALON 2022": 2.531962323786023, "JEEP GRAND CHEROKEE V6 2018": 1.4193323242487865, "CHEVROLET VOLT PREMIER 2017": 1.8576430337666092, "TOYOTA RAV4 HYBRID 2017": 1.7425797219020926, "LEXUS RX 2020": 1.5118835180227874, "TOYOTA HIGHLANDER HYBRID 2018": 1.6872527654528833, "TOYOTA CAMRY HYBRID 2018": 1.6793468378089895, "TESLA AP2 MODEL S": NaN, "VOLKSWAGEN GOLF 7TH GEN": 1.5614447712441282, "TOYOTA MIRAI 2021": 2.271146483563897, "LEXUS IS 2018": NaN, "TOYOTA HIGHLANDER 2017": 1.6573774863189379, "HYUNDAI SONATA HYBRID 2021": 1.9464120717803253, "VOLKSWAGEN ATLAS 1ST GEN": 1.6867005451451638, "LEXUS ES HYBRID 2019": 1.956450687999482, "HYUNDAI GENESIS 2015-2016": 1.5359761378898085, "JEEP GRAND CHEROKEE 2019": 1.2418961305308847, "SUBARU ASCENT LIMITED 2019": NaN, "HONDA CR-V 2017": 0.2642062271814174, "HONDA FIT 2018": 0.5896345937094754, "TOYOTA CAMRY 2021": 2.1783533980215166, "AUDI Q3 2ND GEN": 1.1582239457022647, "AUDI A3 3RD GEN": 1.598699116126939, "LEXUS RX HYBRID 2017": 1.319771127672888, "HONDA CIVIC 2022": 1.1806949852580793, "GENESIS G70 2018": 2.2496820850331134, "CHRYSLER PACIFICA HYBRID 2018": 1.294798200968084, "VOLKSWAGEN PASSAT 8TH GEN": 1.247540921731637, "HONDA CR-V 2016": 0.6991119250342539, "HYUNDAI IONIQ PHEV 2020": 1.9062392690595655, "GMC ACADIA DENALI 2018": 1.2986994230652662, "HYUNDAI SONATA 2019": 1.257445187146704, "TOYOTA AVALON 2019": 1.664577368475227, "TOYOTA C-HR 2018": 1.308490445144888, "HONDA CR-V HYBRID 2019": 0.4693072746041504, "CHRYSLER PACIFICA 2020": 1.1712413003138664, "HYUNDAI IONIQ ELECTRIC 2020": NaN, "VOLKSWAGEN TIGUAN 2ND GEN": 1.1573057001955744, "LEXUS NX 2018": 1.9457312007432144, "KIA OPTIMA SX 2019 & 2016": 2.0928228595938845, "TOYOTA AVALON HYBRID 2019": NaN, "TOYOTA RAV4 HYBRID 2022": 1.7647290773049569, "HONDA PASSPORT 2021": 0.8248357750132685, "KIA K5 2021": 1.4628018983720577, "ACURA ILX 2016": 0.6330753921140401, "HYUNDAI IONIQ HYBRID 2017-2019": NaN, "KIA NIRO EV 2020": 2.020186575503497, "SUBARU IMPREZA SPORT 2020": 2.136786720514988, "CHRYSLER PACIFICA HYBRID 2017": 1.0642918033307907, "HYUNDAI KONA ELECTRIC 2019": NaN, "HYUNDAI ELANTRA HYBRID 2021": NaN, "HYUNDAI SANTA FE HYBRID 2022": NaN, "CHRYSLER PACIFICA 2018": 1.3654603720349934, "NISSAN ROGUE 2019": NaN, "KIA SORENTO GT LINE 2018": NaN, "COMMA BODY": NaN, "NISSAN LEAF 2018 Instrument Cluster": NaN, "LEXUS RX HYBRID 2020": 1.255230465866663, "MAZDA CX-9 2021": NaN, "HYUNDAI SANTA FE 2022": 3.3823387508235827, "HYUNDAI SANTA FE PlUG-IN HYBRID 2022": 1.544104124172169, "HONDA HRV 2019": 0.7492792210307291, "TOYOTA AVALON HYBRID 2022": NaN, "SUBARU IMPREZA LIMITED 2019": 0.8127509604734238, "GENESIS G80 2017": NaN, "VOLKSWAGEN TAOS 1ST GEN": 1.6590543949912684, "KIA FORTE E 2018 & GT 2021": 2.3970573789339786, "CADILLAC ESCALADE ESV 2016": NaN, "TOYOTA C-HR 2021": 1.3559230155096402, "TOYOTA C-HR HYBRID 2018": 0.8910235787356033}, "FRICTION": {"HONDA PILOT 2017": 0.2168217463499328, "HONDA CIVIC 2016": 0.28406761310944795, "TOYOTA CAMRY 2018": 0.1327947477896041, "TOYOTA COROLLA HYBRID TSS2 2019": 0.21792021497538405, "TOYOTA RAV4 2019": 0.12757022360707945, "HYUNDAI PALISADE 2020": 0.13391574986922777, "TOYOTA SIENNA 2018": 0.1853443239485906, "ACURA RDX 2020": 0.18058553315570297, "TOYOTA RAV4 2017": 0.14319170324556796, "HONDA RIDGELINE 2017": 0.2380553573913589, "TOYOTA PRIUS 2017": 0.2079869382946584, "TOYOTA HIGHLANDER HYBRID 2020": 0.14038812589302646, "HYUNDAI SONATA 2020": 0.08266051305053168, "KIA STINGER GT2 2018": 0.11909534626930472, "TOYOTA HIGHLANDER 2020": 0.14658637853444048, "HONDA ACCORD 2018": 0.21616610462729247, "TOYOTA PRIUS TSS2 2021": 0.20613763260512002, "NISSAN LEAF 2018": NaN, "CHRYSLER PACIFICA HYBRID 2019": 0.16250373743651828, "LEXUS NX 2020": 0.14404022591302845, "TOYOTA RAV4 HYBRID 2019": 0.1319247989758836, "HONDA CIVIC (BOSCH) 2019": 0.2575217845562353, "KIA NIRO HYBRID 2021": 0.14468633728800306, "HONDA ACCORD HYBRID 2018": 0.21150723931119184, "LEXUS NX HYBRID 2018": 0.16117151597250162, "TOYOTA COROLLA TSS2 2019": 0.21045927995242877, "VOLKSWAGEN ARTEON 1ST GEN": 0.17828895368353925, "TOYOTA CAMRY HYBRID 2021": 0.16283734136957057, "VOLKSWAGEN JETTA 7TH GEN": 0.19508489725001105, "HONDA INSIGHT 2019": 0.25750800088299297, "SUBARU FORESTER 2019": 0.11783702069698135, "HYUNDAI ELANTRA 2021": 0.09377564130711125, "HYUNDAI IONIQ ELECTRIC LIMITED 2019": 0.14740189509875762, "HYUNDAI KONA HYBRID 2020": 0.0863709736632968, "HONDA ODYSSEY 2018": 0.2125595696498247, "LEXUS RX 2016": 0.21475140622981923, "TOYOTA COROLLA 2017": 0.12325064090161544, "LEXUS ES 2019": 0.12757526660498053, "HYUNDAI SANTA FE 2019": 0.12230125806479573, "TOYOTA AVALON 2022": 0.11030226705639488, "JEEP GRAND CHEROKEE V6 2018": 0.12871972792344108, "CHEVROLET VOLT PREMIER 2017": 0.16697256960295873, "TOYOTA RAV4 HYBRID 2017": 0.14074453855329072, "LEXUS RX 2020": 0.2249895411716623, "TOYOTA HIGHLANDER HYBRID 2018": 0.16692807938039034, "TOYOTA CAMRY HYBRID 2018": 0.13418904852016877, "TESLA AP2 MODEL S": NaN, "VOLKSWAGEN GOLF 7TH GEN": 0.19324413131475543, "TOYOTA MIRAI 2021": 0.20035237756713503, "LEXUS IS 2018": 0.073103111226694, "TOYOTA HIGHLANDER 2017": 0.17502689439420385, "HYUNDAI SONATA HYBRID 2021": 0.09518615688045734, "VOLKSWAGEN ATLAS 1ST GEN": 0.12761803335799474, "LEXUS ES HYBRID 2019": 0.1682771025433274, "HYUNDAI GENESIS 2015-2016": 0.10254237048034251, "JEEP GRAND CHEROKEE 2019": 0.15702739382013717, "SUBARU ASCENT LIMITED 2019": 0.12936982863095342, "HONDA CR-V 2017": 0.22518506713451308, "HONDA FIT 2018": 0.10803295063463647, "TOYOTA CAMRY 2021": 0.15512845523424743, "AUDI Q3 2ND GEN": 0.14083949977629878, "AUDI A3 3RD GEN": 0.1611945965384188, "LEXUS RX HYBRID 2017": 0.19322020114452776, "HONDA CIVIC 2022": 0.24279247053469405, "GENESIS G70 2018": 0.06869638264150804, "CHRYSLER PACIFICA HYBRID 2018": 0.13887505891474383, "VOLKSWAGEN PASSAT 8TH GEN": 0.21714039653367842, "HONDA CR-V 2016": 0.41726236462791455, "HYUNDAI IONIQ PHEV 2020": 0.13800461817330806, "GMC ACADIA DENALI 2018": 0.3447163106452739, "HYUNDAI SONATA 2019": 0.15371520337813344, "TOYOTA AVALON 2019": 0.10392921606262978, "TOYOTA C-HR 2018": 0.2015190716953846, "HONDA CR-V HYBRID 2019": 0.19595630321202379, "CHRYSLER PACIFICA 2020": 0.14337114313208268, "HYUNDAI IONIQ ELECTRIC 2020": 0.08104502306679212, "VOLKSWAGEN TIGUAN 2ND GEN": NaN, "LEXUS NX 2018": 0.1471001686544422, "KIA OPTIMA SX 2019 & 2016": 0.11703652166984638, "TOYOTA AVALON HYBRID 2019": 0.10863628402866225, "TOYOTA RAV4 HYBRID 2022": 0.14334213238415072, "HONDA PASSPORT 2021": 0.19826160782809032, "KIA K5 2021": 0.1027179720106188, "ACURA ILX 2016": 0.35663988815912573, "HYUNDAI IONIQ HYBRID 2017-2019": 0.12332151728479951, "KIA NIRO EV 2020": 0.0892074288578785, "SUBARU IMPREZA SPORT 2020": 0.15841234487251604, "CHRYSLER PACIFICA HYBRID 2017": 0.1345638758810282, "HYUNDAI KONA ELECTRIC 2019": 0.08503096350356723, "HYUNDAI ELANTRA HYBRID 2021": 0.09887804390243872, "HYUNDAI SANTA FE HYBRID 2022": 0.11171499761140577, "CHRYSLER PACIFICA 2018": 0.13611561752951415, "NISSAN ROGUE 2019": NaN, "KIA SORENTO GT LINE 2018": 0.10502695501512567, "COMMA BODY": NaN, "NISSAN LEAF 2018 Instrument Cluster": NaN, "LEXUS RX HYBRID 2020": 0.21818156330777305, "MAZDA CX-9 2021": 0.1793735649504697, "HYUNDAI SANTA FE 2022": 0.09184808719698756, "HYUNDAI SANTA FE PlUG-IN HYBRID 2022": 0.14050744688135813, "HONDA HRV 2019": 0.17840321248608593, "TOYOTA AVALON HYBRID 2022": 0.16159049452515487, "SUBARU IMPREZA LIMITED 2019": 0.20322553080306893, "GENESIS G80 2017": 0.07934444681782107, "VOLKSWAGEN TAOS 1ST GEN": 0.18276122764341485, "KIA FORTE E 2018 & GT 2021": 0.11406160665068436, "CADILLAC ESCALADE ESV 2016": 0.15063766975884627, "TOYOTA C-HR 2021": 0.22798633346500694, "TOYOTA C-HR HYBRID 2018": 0.2036375866375624}, "ERROR_RATIO": {"HONDA PILOT 2017": 0.6158457247286419, "HONDA CIVIC 2016": 2.0785618623350928, "TOYOTA CAMRY 2018": 0.17356565057429169, "TOYOTA COROLLA HYBRID TSS2 2019": 0.10094741777075293, "TOYOTA RAV4 2019": 0.11812042718338775, "HYUNDAI PALISADE 2020": 0.30639442561268304, "TOYOTA SIENNA 2018": 0.1117307389748361, "ACURA RDX 2020": 1.9801454495960717, "TOYOTA RAV4 2017": 0.08589486116378196, "HONDA RIDGELINE 2017": 0.4319851914417577, "TOYOTA PRIUS 2017": 0.17281316158588575, "TOYOTA HIGHLANDER HYBRID 2020": 0.046325388721577, "HYUNDAI SONATA 2020": 0.4109860794021653, "KIA STINGER GT2 2018": 0.3517628781488943, "TOYOTA HIGHLANDER 2020": 0.14155072865224166, "HONDA ACCORD 2018": 2.510398061115294, "TOYOTA PRIUS TSS2 2021": 0.13593456264106363, "NISSAN LEAF 2018": NaN, "CHRYSLER PACIFICA HYBRID 2019": 0.08794943266738546, "LEXUS NX 2020": 0.3743942573190866, "TOYOTA RAV4 HYBRID 2019": 0.0845492503791727, "HONDA CIVIC (BOSCH) 2019": 2.4329816697390063, "KIA NIRO HYBRID 2021": NaN, "HONDA ACCORD HYBRID 2018": 2.9252406767451804, "LEXUS NX HYBRID 2018": 0.23060712246809048, "TOYOTA COROLLA TSS2 2019": 0.13822363784977285, "VOLKSWAGEN ARTEON 1ST GEN": 0.009661691674299285, "TOYOTA CAMRY HYBRID 2021": 0.029451711159377333, "VOLKSWAGEN JETTA 7TH GEN": 0.16591473170144055, "HONDA INSIGHT 2019": 1.3398692842898896, "SUBARU FORESTER 2019": 0.5269683780697442, "HYUNDAI ELANTRA 2021": NaN, "HYUNDAI IONIQ ELECTRIC LIMITED 2019": 0.02971857401969039, "HYUNDAI KONA HYBRID 2020": NaN, "HONDA ODYSSEY 2018": 1.0245957242729038, "LEXUS RX 2016": 0.07392586589971588, "TOYOTA COROLLA 2017": 0.31336988069649124, "LEXUS ES 2019": 0.08933657038050916, "HYUNDAI SANTA FE 2019": 0.2276812089092099, "TOYOTA AVALON 2022": 0.07120118798045925, "JEEP GRAND CHEROKEE V6 2018": 0.2065164316228118, "CHEVROLET VOLT PREMIER 2017": 0.2316223989408518, "TOYOTA RAV4 HYBRID 2017": 0.055653752888652736, "LEXUS RX 2020": 0.047792182371008345, "TOYOTA HIGHLANDER HYBRID 2018": 0.019259474082467202, "TOYOTA CAMRY HYBRID 2018": 0.11949733140330816, "TESLA AP2 MODEL S": NaN, "VOLKSWAGEN GOLF 7TH GEN": 0.1996863736436734, "TOYOTA MIRAI 2021": 0.11019259478417197, "LEXUS IS 2018": NaN, "TOYOTA HIGHLANDER 2017": 0.05279963713251727, "HYUNDAI SONATA HYBRID 2021": 0.3543918194389536, "VOLKSWAGEN ATLAS 1ST GEN": 0.21694647502209782, "LEXUS ES HYBRID 2019": 0.14775474433507507, "HYUNDAI GENESIS 2015-2016": 0.0814892037361157, "JEEP GRAND CHEROKEE 2019": 0.3126997097753535, "SUBARU ASCENT LIMITED 2019": NaN, "HONDA CR-V 2017": 5.652613829506629, "HONDA FIT 2018": 1.5217432826711779, "TOYOTA CAMRY 2021": 0.07910435053686729, "AUDI Q3 2ND GEN": 0.13535089102138698, "AUDI A3 3RD GEN": 0.14353941401245793, "LEXUS RX HYBRID 2017": 0.048663813961824696, "HONDA CIVIC 2022": 1.0748206908458815, "GENESIS G70 2018": 0.688303429295532, "CHRYSLER PACIFICA HYBRID 2018": 0.11083725786301112, "VOLKSWAGEN PASSAT 8TH GEN": 0.13315924904555493, "HONDA CR-V 2016": 0.488871482749128, "HYUNDAI IONIQ PHEV 2020": 0.2756096845519595, "GMC ACADIA DENALI 2018": 0.24055364003040136, "HYUNDAI SONATA 2019": 0.4473315280277132, "TOYOTA AVALON 2019": 0.026428086100632363, "TOYOTA C-HR 2018": 0.06075105822970755, "HONDA CR-V HYBRID 2019": 2.9906016360828276, "CHRYSLER PACIFICA 2020": 0.07748732608487266, "HYUNDAI IONIQ ELECTRIC 2020": NaN, "VOLKSWAGEN TIGUAN 2ND GEN": NaN, "LEXUS NX 2018": 0.16318394527060903, "KIA OPTIMA SX 2019 & 2016": 0.4378756841929454, "TOYOTA AVALON HYBRID 2019": NaN, "TOYOTA RAV4 HYBRID 2022": 0.36478548056633514, "HONDA PASSPORT 2021": 0.5993860184637646, "KIA K5 2021": 0.6271500841947655, "ACURA ILX 2016": 0.8435442647921855, "HYUNDAI IONIQ HYBRID 2017-2019": NaN, "KIA NIRO EV 2020": 0.40355577782011604, "SUBARU IMPREZA SPORT 2020": 0.11071291640854522, "CHRYSLER PACIFICA HYBRID 2017": 0.029812269495458284, "HYUNDAI KONA ELECTRIC 2019": NaN, "HYUNDAI ELANTRA HYBRID 2021": NaN, "HYUNDAI SANTA FE HYBRID 2022": NaN, "CHRYSLER PACIFICA 2018": 0.01705753895996445, "NISSAN ROGUE 2019": NaN, "KIA SORENTO GT LINE 2018": NaN, "COMMA BODY": NaN, "NISSAN LEAF 2018 Instrument Cluster": NaN, "LEXUS RX HYBRID 2020": 0.05790668871480552, "MAZDA CX-9 2021": NaN, "HYUNDAI SANTA FE 2022": 0.018126919430513307, "HYUNDAI SANTA FE PlUG-IN HYBRID 2022": 0.1331760659016062, "HONDA HRV 2019": 1.599688433820939, "TOYOTA AVALON HYBRID 2022": NaN, "SUBARU IMPREZA LIMITED 2019": 0.2514545160390271, "GENESIS G80 2017": NaN, "VOLKSWAGEN TAOS 1ST GEN": 0.09725484306423876, "KIA FORTE E 2018 & GT 2021": 0.20381871942480628, "CADILLAC ESCALADE ESV 2016": NaN, "TOYOTA C-HR 2021": 0.05016813984196128, "TOYOTA C-HR HYBRID 2018": 0.2521485862766935}} \ No newline at end of file diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml new file mode 100644 index 000000000..0a958a9d0 --- /dev/null +++ b/selfdrive/car/torque_data/override.yaml @@ -0,0 +1,33 @@ +legend: [LAT_ACCEL_FACTOR, MAX_LAT_ACCEL_MEASURED, FRICTION] +### angle control +# Nissan appears to have torque +NISSAN X-TRAIL 2017: [.nan, 1.5, .nan] +NISSAN ALTIMA 2020: [.nan, 1.5, .nan] +NISSAN LEAF 2018 Instrument Cluster: [.nan, 1.5, .nan] +NISSAN LEAF 2018: [.nan, 1.5, .nan] +NISSAN ROGUE 2019: [.nan, 1.5, .nan] + +# Tesla has high torque +TESLA AP1 MODEL S: [.nan, 2.5, .nan] +TESLA AP2 MODEL S: [.nan, 2.5, .nan] + +# Guess +FORD ESCAPE 4TH GEN: [.nan, 1.5, .nan] +FORD FOCUS 4TH GEN: [.nan, 1.5, .nan] +### + +# No steering wheel +COMMA BODY: [.nan, 1000, .nan] + +# Totally new cars +KIA EV6 2022: [3.5, 2.5, 0.0] +RAM 1500 5TH GEN: [2.0, 2.0, 0.0] +RAM HD 5TH GEN: [1.4, 1.4, 0.0] +SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11] +CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05] + +# Dashcam or fallback configured as ideal car +mock: [10.0, 10, 0.0] + +# Manually checked +HONDA CIVIC 2022: [2.5, 1.2, 0.15] diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml new file mode 100644 index 000000000..160f60548 --- /dev/null +++ b/selfdrive/car/torque_data/params.yaml @@ -0,0 +1,96 @@ +ACURA ILX 2016: [1.524988973896102, 0.519011053086259, 0.34236219253028] +ACURA RDX 2018: [0.9987728568686902, 0.5323765166196301, 0.303218805715844] +ACURA RDX 2020: [1.4314459806646749, 0.33874701282109954, 0.18048847083897598] +AUDI A3 3RD GEN: [1.5122414863077502, 1.7443517531719404, 0.15194151892450905] +AUDI Q3 2ND GEN: [1.4439223359448605, 1.2254955789112076, 0.1413798895978097] +CHEVROLET VOLT PREMIER 2017: [1.5961527626411784, 1.8422651988094612, 0.1572393918005158] +CHRYSLER PACIFICA 2018: [1.593387270257916, 1.3366521181047952, 0.13776367250652022] +CHRYSLER PACIFICA 2020: [1.4323553627965695, 1.509076559398423, 0.14328246159386085] +CHRYSLER PACIFICA HYBRID 2017: [1.3032470208409048, 1.06831764583744, 0.13287170990024627] +CHRYSLER PACIFICA HYBRID 2018: [1.6068280248761635, 1.2943025830995154, 0.1358557824293823] +CHRYSLER PACIFICA HYBRID 2019: [1.4624643614072217, 1.1958788168371808, 0.15748488008472716] +GENESIS G70 2018: [3.8520195946707947, 2.354697063349854, 0.06830285485626221] +GMC ACADIA DENALI 2018: [1.3181430320331884, 1.1853735340610179, 0.3450592280031644] +HONDA ACCORD 2018: [1.7135052593468778, 0.3461280068322071, 0.21579936052863807] +HONDA ACCORD HYBRID 2018: [1.6651615004829625, 0.30322180951193245, 0.2083000440586149] +HONDA CIVIC (BOSCH) 2019: [1.691708637466905, 0.40132900729454185, 0.25460295304024094] +HONDA CIVIC 2016: [1.6528895627785531, 0.4018518740819229, 0.25458812851328544] +HONDA CR-V 2016: [0.7667141440182675, 0.5927571534745969, 0.40909087636157127] +HONDA CR-V 2017: [2.01323205142022, 0.2700612209345081, 0.2238412881331528] +HONDA CR-V HYBRID 2019: [2.072034634644233, 0.7152085160516978, 0.20237105008376083] +HONDA FIT 2018: [1.5719981427109775, 0.5712761407108976, 0.110773383324281] +HONDA HRV 2019: [2.0661212805710205, 0.7521343418694775, 0.17760375789242094] +HONDA INSIGHT 2019: [1.5201671214069354, 0.5660229120683284, 0.25808042580281876] +HONDA ODYSSEY 2018: [1.8774809275211801, 0.8394431662987996, 0.2096978613792822] +HONDA PASSPORT 2021: [1.5305538930036766, 0.7956063674638759, 0.19599407381531284] +HONDA PILOT 2017: [1.7262026201812795, 0.9470005614967523, 0.21351430733218763] +HONDA RIDGELINE 2017: [1.4146525028237624, 0.7356572861629564, 0.23307177552211328] +HYUNDAI GENESIS 2015-2016: [1.8466226943929824, 1.5552063647830634, 0.0984484465421171] +HYUNDAI IONIQ ELECTRIC LIMITED 2019: [1.7662975472852054, 1.613755614526594, 0.17087579756306276] +HYUNDAI IONIQ PHEV 2020: [3.2928700076638537, 2.1193482926455656, 0.12463700961468778] +HYUNDAI IONIQ PLUG-IN HYBRID 2019: [2.970807902012267, 1.6312321830002083, 0.1088964990357482] +HYUNDAI KONA ELECTRIC 2019: [4.398306735170212, 3.2961956260770484, 0.08651833437845884] +HYUNDAI PALISADE 2020: [2.544642494803999, 1.8721703683337008, 0.1301424599248651] +HYUNDAI SANTA FE 2019: [3.0787027729757632, 2.6173437483495565, 0.1207019341823945] +HYUNDAI SANTA FE HYBRID 2022: [3.501877602644835, 2.729064118456137, 0.10384068104538963] +HYUNDAI SANTA FE PlUG-IN HYBRID 2022: [1.6953050513611045, 1.5837614296206861, 0.12672855941458458] +HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.14039920986586393] +HYUNDAI SONATA 2020: [3.284505627881726, 2.1259108157250735, 0.08452048323586728] +HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382] +JEEP GRAND CHEROKEE 2019: [1.7321233388827006, 1.289689569171081, 0.15046331002097185] +JEEP GRAND CHEROKEE V6 2018: [1.8776598027756923, 1.4057367824262523, 0.11725947414922003] +KIA K5 2021: [2.405339728085138, 1.460032270828705, 0.11650989850813716] +KIA NIRO EV 2020: [2.9215954981365337, 2.1500583840260044, 0.09236802474810267] +KIA SORENTO GT LINE 2018: [2.464854685101844, 1.5335274218367956, 0.12056170567599558] +KIA STINGER GT2 2018: [2.7499043387418967, 1.849652021986449, 0.12048334239559202] +LEXUS ES 2019: [2.0203086922726112, 2.134803912579666, 0.12757526789308554] +LEXUS ES HYBRID 2019: [2.392442298703042, 1.863360677810788, 0.17690002108856212] +LEXUS NX 2018: [2.302625600642627, 2.1382378491466625, 0.14986840878892838] +LEXUS NX 2020: [2.4331999786982936, 2.1045680431705414, 0.14099899317761067] +LEXUS NX HYBRID 2018: [2.4025593501080955, 1.8080446063815507, 0.15349361249519017] +LEXUS RX 2016: [1.5876816543130423, 1.0427699298523752, 0.21334066732397142] +LEXUS RX 2020: [1.5228812994274734, 1.431102486563665, 0.2093316728710659] +LEXUS RX HYBRID 2017: [1.6984261557042386, 1.3211501880159107, 0.1820354534928893] +LEXUS RX HYBRID 2020: [1.5522309889823778, 1.255230465866663, 0.2220954003055114] +MAZDA CX-9 2021: [1.7601682915983443, 1.0889677335154337, 0.17713792194297195] +SKODA SUPERB 3RD GEN: [1.166437404652981, 1.1686163012668165, 0.12194533036948708] +SUBARU FORESTER 2019: [3.6617001649776793, 2.342197172531713, 0.11075960785398745] +SUBARU IMPREZA LIMITED 2019: [1.0670704910352047, 0.8234374840709592, 0.20986563268614938] +SUBARU IMPREZA SPORT 2020: [2.6068223389108303, 2.134872342760203, 0.15261513193561627] +TOYOTA AVALON 2016: [2.5185770183845646, 1.7153346784214922, 0.10603968787111022] +TOYOTA AVALON 2019: [1.7036141952825095, 1.239619084240008, 0.08459830394899492] +TOYOTA AVALON 2022: [2.3154403649717357, 2.7777922854327124, 0.11453999639164605] +TOYOTA C-HR 2018: [1.5591084333664578, 1.271271459066948, 0.20259087058453193] +TOYOTA C-HR 2021: [1.7678810166088303, 1.3742176337919942, 0.2319674583741509] +TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.13519250664782062] +TOYOTA CAMRY 2021: [2.6922769557433055, 2.3476510120007434, 0.1450430192989234] +TOYOTA CAMRY HYBRID 2018: [2.0974120828287774, 1.7996193116697359, 0.13823613467632756] +TOYOTA CAMRY HYBRID 2021: [2.6426668350384457, 2.3901492458927986, 0.16103875108816076] +TOYOTA COROLLA 2017: [3.117154369115421, 1.8438132575043773, 0.12289685869250652] +TOYOTA COROLLA HYBRID TSS2 2019: [2.3287672277252005, 1.8118712531729109, 0.2215868445753317] +TOYOTA COROLLA TSS2 2019: [2.4204464833010175, 1.9258612322678952, 0.20670411068012526] +TOYOTA HIGHLANDER 2017: [1.8696367437248915, 1.626293990451463, 0.17485372210240796] +TOYOTA HIGHLANDER 2020: [2.022340166827233, 1.6183134804881791, 0.14592306380054457] +TOYOTA HIGHLANDER HYBRID 2018: [1.9421825202382728, 1.6433903296845025, 0.16928956792275918] +TOYOTA HIGHLANDER HYBRID 2020: [2.103373061114133, 2.104015182965606, 0.14447040132184993] +TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565] +TOYOTA PRIUS 2017: [2.0183401513314294, 1.5023147650693636, 0.20856908464957724] +TOYOTA PRIUS TSS2 2021: [2.327639738920072, 1.9104337425537743, 0.2030762265549664] +TOYOTA RAV4 2017: [2.085695074355425, 2.2142832316984733, 0.13339165270103975] +TOYOTA RAV4 2019: [2.5038362866776835, 2.0993589721530252, 0.1552425356342368] +TOYOTA RAV4 2019 8965: [2.5084506298290377, 2.4216520504763475, 0.11992835265067918] +TOYOTA RAV4 2019 x02: [2.7209621987605024, 2.2148637653781593, 0.10862567142268198] +TOYOTA RAV4 HYBRID 2017: [1.9796257271652042, 1.7503987331707576, 0.14628860048885406] +TOYOTA RAV4 HYBRID 2019: [2.2271858492309153, 2.074844961405639, 0.14382216826893632] +TOYOTA RAV4 HYBRID 2019 8965: [2.1077397198131336, 1.8162215166877735, 0.13891369391200137] +TOYOTA RAV4 HYBRID 2019 x02: [2.803624333289342, 2.272367966572498, 0.11364569214387774] +TOYOTA RAV4 HYBRID 2022: [2.241883248393209, 1.9304407208090029, 0.1565442715453653] +TOYOTA RAV4 HYBRID 2022 x02: [3.044930631831037, 2.3979189796380918, 0.14023209146703736] +TOYOTA SIENNA 2018: [1.8660896232147548, 1.3208264576110418, 0.18799149615227198] +VOLKSWAGEN ARTEON 1ST GEN: [1.45136518053819, 1.3639364049316804, 0.23806361745695032] +VOLKSWAGEN ATLAS 1ST GEN: [1.4677006726964945, 1.6733266634075656, 0.12959584092073367] +VOLKSWAGEN GOLF 7TH GEN: [1.3750394140491293, 1.5814743077200641, 0.2018321939386586] +VOLKSWAGEN JETTA 7TH GEN: [1.2271623034089392, 1.216955117387, 0.19437384688370712] +VOLKSWAGEN PASSAT 8TH GEN: [1.3432120736752917, 1.7087275587362314, 0.19444383787326647] +VOLKSWAGEN TIGUAN 2ND GEN: [0.9711965500094828, 1.0001565939459098, 0.1465626137072916] +legend: [LAT_ACCEL_FACTOR, MAX_LAT_ACCEL_MEASURED, FRICTION] diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml new file mode 100644 index 000000000..279e28ff5 --- /dev/null +++ b/selfdrive/car/torque_data/substitute.yaml @@ -0,0 +1,77 @@ +MAZDA 3: MAZDA CX-9 2021 +MAZDA 6: MAZDA CX-9 2021 +MAZDA CX-5: MAZDA CX-9 2021 +MAZDA CX-5 2022: MAZDA CX-9 2021 +MAZDA CX-9: MAZDA CX-9 2021 + +TOYOTA ALPHARD HYBRID 2021 : TOYOTA SIENNA 2018 +TOYOTA ALPHARD 2020: TOYOTA SIENNA 2018 +TOYOTA PRIUS v 2017 : TOYOTA PRIUS 2017 +TOYOTA RAV4 2022: TOYOTA RAV4 HYBRID 2022 +TOYOTA C-HR HYBRID 2018: TOYOTA C-HR 2018 +LEXUS IS 2018: LEXUS NX 2018 +LEXUS CT HYBRID 2018 : LEXUS NX 2018 +LEXUS ES HYBRID 2018: TOYOTA CAMRY HYBRID 2018 +LEXUS NX HYBRID 2020: LEXUS NX 2020 +LEXUS RC 2020: LEXUS NX 2020 +TOYOTA AVALON HYBRID 2019: TOYOTA AVALON 2019 +TOYOTA AVALON HYBRID 2022: TOYOTA AVALON 2022 + +KIA OPTIMA SX 2019 & 2016: HYUNDAI SONATA 2020 +KIA OPTIMA HYBRID 2017 & SPORTS 2019: HYUNDAI SONATA 2020 +KIA FORTE E 2018 & GT 2021: HYUNDAI SONATA 2020 +KIA CEED INTRO ED 2019: HYUNDAI SONATA 2020 +KIA SELTOS 2021: HYUNDAI SONATA 2020 +KIA NIRO HYBRID 2019: KIA NIRO EV 2020 +KIA NIRO HYBRID 2021: KIA NIRO EV 2020 +HYUNDAI VELOSTER 2019: HYUNDAI SONATA 2019 +HYUNDAI I30 N LINE 2019 & GT 2018 DCT: HYUNDAI SONATA 2019 +HYUNDAI KONA 2020: HYUNDAI KONA ELECTRIC 2019 +HYUNDAI KONA HYBRID 2020: HYUNDAI KONA ELECTRIC 2019 +HYUNDAI KONA ELECTRIC 2022: HYUNDAI KONA ELECTRIC 2019 +HYUNDAI IONIQ 5 2022: KIA EV6 2022 +HYUNDAI IONIQ HYBRID 2017-2019: HYUNDAI IONIQ PLUG-IN HYBRID 2019 +HYUNDAI IONIQ HYBRID 2020-2022: HYUNDAI IONIQ PLUG-IN HYBRID 2019 +HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019 +HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019 +HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 +HYUNDAI ELANTRA 2021: HYUNDAI SONATA 2020 +HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 +HYUNDAI SANTA FE 2022: HYUNDAI SANTA FE HYBRID 2022 +GENESIS G90 2017: GENESIS G70 2018 +GENESIS G80 2017: GENESIS G70 2018 +GENESIS G70 2020: HYUNDAI SONATA 2020 + +HONDA FREED 2020: HONDA ODYSSEY 2018 +HONDA CR-V EU 2016: HONDA CR-V 2016 +HONDA CIVIC SEDAN 1.6 DIESEL 2019: HONDA CIVIC (BOSCH) 2019 +HONDA E 2020: HONDA CIVIC (BOSCH) 2019 +HONDA ODYSSEY CHN 2019: HONDA ODYSSEY 2018 + +BUICK REGAL ESSENCE 2018: CHEVROLET VOLT PREMIER 2017 +CADILLAC ESCALADE ESV 2016: CHEVROLET VOLT PREMIER 2017 +CADILLAC ATS Premium Performance 2018: CHEVROLET VOLT PREMIER 2017 +CHEVROLET MALIBU PREMIER 2017: CHEVROLET VOLT PREMIER 2017 +HOLDEN ASTRA RS-V BK 2017: CHEVROLET VOLT PREMIER 2017 + +SKODA OCTAVIA 3RD GEN: SKODA SUPERB 3RD GEN +SKODA SCALA 1ST GEN: SKODA SUPERB 3RD GEN +SKODA KODIAQ 1ST GEN: SKODA SUPERB 3RD GEN +SKODA KAROQ 1ST GEN: SKODA SUPERB 3RD GEN +SKODA KAMIQ 1ST GEN: SKODA SUPERB 3RD GEN +VOLKSWAGEN T-ROC 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN +VOLKSWAGEN T-CROSS 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN +VOLKSWAGEN TOURAN 2ND GEN: VOLKSWAGEN TIGUAN 2ND GEN +VOLKSWAGEN TRANSPORTER T6.1: VOLKSWAGEN TIGUAN 2ND GEN +AUDI Q2 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN +VOLKSWAGEN TAOS 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN +VOLKSWAGEN POLO 6TH GEN: VOLKSWAGEN GOLF 7TH GEN +SEAT LEON 3RD GEN: VOLKSWAGEN GOLF 7TH GEN +SEAT ATECA 1ST GEN: VOLKSWAGEN GOLF 7TH GEN + +# Old subarus don't have much data guessing it's like low torque impreza +SUBARU OUTBACK 2018 - 2019: SUBARU IMPREZA LIMITED 2019 +SUBARU OUTBACK 2015 - 2017: SUBARU IMPREZA LIMITED 2019 +SUBARU FORESTER 2017 - 2018: SUBARU IMPREZA LIMITED 2019 +SUBARU LEGACY 2015 - 2018: SUBARU IMPREZA LIMITED 2019 +SUBARU ASCENT LIMITED 2019: SUBARU FORESTER 2019 diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 33e3fe118..3326d7c96 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -20,7 +20,6 @@ class CarController: self.alert_active = False self.last_standstill = False self.standstill_req = False - self.steer_rate_limited = False self.packer = CANPacker(dbc_name) self.gas = 0 @@ -52,7 +51,6 @@ class CarController: # steer torque new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX)) apply_steer = apply_toyota_steer_torque_limits(new_steer, self.last_steer, CS.out.steeringTorqueEps, self.torque_rate_limits) - self.steer_rate_limited = new_steer != apply_steer if not CC.latActive: apply_steer = 0 @@ -83,7 +81,7 @@ 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, self.frame)) + can_sends.append(create_steer_command(self.packer, apply_steer, apply_steer_req)) if self.frame % 2 == 0 and self.CP.carFingerprint in TSS2_CAR: can_sends.append(create_lta_steer_command(self.packer, 0, 0, self.frame // 2)) @@ -126,12 +124,12 @@ class CarController: # forcing the pcm to disengage causes a bad fault sound so play a good sound instead send_ui = True - if self.frame % 100 == 0 or send_ui: + if (self.frame % 100 == 0 or send_ui) and (self.CP.carFingerprint != CAR.PRIUS_V): 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)) - if self.frame % 100 == 0 and self.CP.enableDsu: + if (self.frame % 100 == 0 or send_ui) and self.CP.enableDsu: can_sends.append(create_fcw_command(self.packer, fcw_alert)) # *** static msgs *** diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index d0f478977..642c39952 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -2,7 +2,6 @@ from cereal import car from common.conversions import Conversions as CV from panda import Panda -from selfdrive.controls.lib.latcontrol_torque import set_torque_tune from selfdrive.car.toyota.tunes import LatTunes, LongTunes, set_long_tune, set_lat_tune from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, CarControllerParams from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config @@ -32,9 +31,8 @@ class CarInterface(CarInterfaceBase): ret.stoppingControl = False # Toyota starts braking more when it thinks you want to stop stop_and_go = False - torque_params = CarInterfaceBase.get_torque_params(candidate) steering_angle_deadzone_deg = 0.0 - set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION'], steering_angle_deadzone_deg) + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) if candidate == CAR.PRIUS: stop_and_go = True @@ -46,7 +44,7 @@ class CarInterface(CarInterfaceBase): for fw in car_fw: if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00': steering_angle_deadzone_deg = 1.0 - set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION'], steering_angle_deadzone_deg) + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) elif candidate == CAR.PRIUS_V: stop_and_go = True @@ -54,10 +52,7 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 17.4 tire_stiffness_factor = 0.5533 ret.mass = 3340. * CV.LB_TO_KG + STD_CARGO_KG - # TODO override until there is enough data - ret.maxLateralAccel = 1.8 - torque_params = CarInterfaceBase.get_torque_params(CAR.PRIUS) - set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION'], steering_angle_deadzone_deg) + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) elif candidate in (CAR.RAV4, CAR.RAV4H): stop_and_go = True if (candidate in CAR.RAV4H) else False @@ -98,20 +93,12 @@ class CarInterface(CarInterfaceBase): if candidate not in (CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2): set_lat_tune(ret.lateralTuning, LatTunes.PID_C) - elif candidate in (CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2): + elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2): stop_and_go = True - ret.wheelbase = 2.84988 # 112.2 in = 2.84988 m + ret.wheelbase = 2.8194 # average of 109.8 and 112.2 in ret.steerRatio = 16.0 tire_stiffness_factor = 0.8 - ret.mass = 4700. * CV.LB_TO_KG + STD_CARGO_KG # 4260 + 4-5 people - set_lat_tune(ret.lateralTuning, LatTunes.PID_G) - - elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH): - stop_and_go = True - ret.wheelbase = 2.78 - ret.steerRatio = 16.0 - tire_stiffness_factor = 0.8 - ret.mass = 4607. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid limited + ret.mass = 4516. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid set_lat_tune(ret.lateralTuning, LatTunes.PID_G) elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALONH_2019, CAR.AVALON_TSS2, CAR.AVALONH_TSS2): @@ -132,10 +119,6 @@ class CarInterface(CarInterfaceBase): ret.mass = 3585. * CV.LB_TO_KG + STD_CARGO_KG # Average between ICE and Hybrid set_lat_tune(ret.lateralTuning, LatTunes.PID_D) - # TODO: remove once there's data - if candidate == CAR.RAV4_TSS2_2022: - ret.maxLateralAccel = CarInterfaceBase.get_torque_params(CAR.RAV4H_TSS2_2022)['MAX_LAT_ACCEL_MEASURED'] - # 2019+ RAV4 TSS2 uses two different steering racks and specific tuning seems to be necessary. # See https://github.com/commaai/openpilot/pull/21429#issuecomment-873652891 for fw in car_fw: @@ -260,8 +243,6 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) - ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False - # events events = self.create_common_events(ret) @@ -283,5 +264,4 @@ class CarInterface(CarInterfaceBase): # pass in a car.CarControl # to be called @ 100hz def apply(self, c): - ret = self.CC.update(c, self.CS) - return ret + return self.CC.update(c, self.CS) diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index d57131dcb..7ab3ab3e7 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -1,10 +1,9 @@ -def create_steer_command(packer, steer, steer_req, raw_cnt): +def create_steer_command(packer, steer, steer_req): """Creates a CAN message for the Toyota Steer Command.""" values = { "STEER_REQUEST": steer_req, "STEER_TORQUE_CMD": steer, - "COUNTER": raw_cnt, "SET_ME_1": 1, } return packer.make_can_msg("STEERING_LKA", 0, values) diff --git a/selfdrive/car/toyota/tunes.py b/selfdrive/car/toyota/tunes.py index 3de6daae2..a8b8758d8 100644 --- a/selfdrive/car/toyota/tunes.py +++ b/selfdrive/car/toyota/tunes.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 from enum import Enum -from selfdrive.controls.lib.latcontrol_torque import set_torque_tune class LongTunes(Enum): PEDAL = 0 @@ -24,7 +23,6 @@ class LatTunes(Enum): PID_L = 13 PID_M = 14 PID_N = 15 - TORQUE = 16 ###### LONG ###### @@ -51,9 +49,7 @@ def set_long_tune(tune, name): ###### LAT ###### def set_lat_tune(tune, name, MAX_LAT_ACCEL=2.5, FRICTION=0.01, steering_angle_deadzone_deg=0.0, use_steering_angle=True): - if name == LatTunes.TORQUE: - set_torque_tune(tune, MAX_LAT_ACCEL, FRICTION, steering_angle_deadzone_deg) - elif 'PID' in str(name): + if 'PID' in str(name): tune.init('pid') tune.pid.kiBP = [0.0] tune.pid.kpBP = [0.0] diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 2c151266c..b6181c1fd 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -87,11 +87,11 @@ class CAR: class Footnote(Enum): DSU = CarFootnote( - "When disconnecting the Driver Support Unit (DSU), openpilot Adaptive Cruise Control (ACC) will replace stock " + - "Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).", + "When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace " + + "stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).", Column.LONGITUDINAL, star=Star.HALF) CAMRY = CarFootnote( - "28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.", + "openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.", Column.FSR_LONGITUDINAL) @@ -105,9 +105,12 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { # Toyota CAR.ALPHARD_TSS2: ToyotaCarInfo("Toyota Alphard 2019-20"), CAR.ALPHARDH_TSS2: ToyotaCarInfo("Toyota Alphard Hybrid 2021"), - CAR.AVALON: ToyotaCarInfo("Toyota Avalon 2016-18", "TSS-P", footnotes=[Footnote.DSU]), - CAR.AVALON_2019: ToyotaCarInfo("Toyota Avalon 2019-21", "TSS-P", footnotes=[Footnote.DSU]), - CAR.AVALONH_2019: ToyotaCarInfo("Toyota Avalon Hybrid 2019-21", "TSS-P", footnotes=[Footnote.DSU]), + CAR.AVALON: [ + ToyotaCarInfo("Toyota Avalon 2016", "Toyota Safety Sense P", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Toyota Avalon 2017-18", footnotes=[Footnote.DSU]), + ], + CAR.AVALON_2019: ToyotaCarInfo("Toyota Avalon 2019-21", footnotes=[Footnote.DSU]), + CAR.AVALONH_2019: ToyotaCarInfo("Toyota Avalon Hybrid 2019-21", footnotes=[Footnote.DSU]), CAR.AVALON_TSS2: ToyotaCarInfo("Toyota Avalon 2022"), CAR.AVALONH_TSS2: ToyotaCarInfo("Toyota Avalon Hybrid 2022"), CAR.CAMRY: ToyotaCarInfo("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]), @@ -119,46 +122,55 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19", footnotes=[Footnote.DSU]), CAR.COROLLA_TSS2: [ ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), + ToyotaCarInfo("Toyota Corolla Cross (Non-US only) 2020-21", min_enable_speed=7.5), ToyotaCarInfo("Toyota Corolla Hatchback 2019-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), ], CAR.COROLLAH_TSS2: [ ToyotaCarInfo("Toyota Corolla Hybrid 2020-22"), - ToyotaCarInfo("Lexus UX Hybrid 2019-21"), + ToyotaCarInfo("Toyota Corolla Cross Hybrid (Non-US only) 2020-22", min_enable_speed=7.5), + ToyotaCarInfo("Lexus UX Hybrid 2019-22"), ], CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo", footnotes=[Footnote.DSU]), CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-22"), CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19", footnotes=[Footnote.DSU]), CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-22"), CAR.PRIUS: [ - ToyotaCarInfo("Toyota Prius 2016-20", "TSS-P", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0", footnotes=[Footnote.DSU]), ToyotaCarInfo("Toyota Prius Prime 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0", footnotes=[Footnote.DSU]), ], - CAR.PRIUS_V: ToyotaCarInfo("Toyota Prius v 2017", "TSS-P", min_enable_speed=MIN_ACC_SPEED, footnotes=[Footnote.DSU]), + CAR.PRIUS_V: ToyotaCarInfo("Toyota Prius v 2017", "Toyota Safety Sense P", min_enable_speed=MIN_ACC_SPEED, footnotes=[Footnote.DSU]), CAR.PRIUS_TSS2: [ ToyotaCarInfo("Toyota Prius 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"), ToyotaCarInfo("Toyota Prius Prime 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"), ], - CAR.RAV4: ToyotaCarInfo("Toyota RAV4 2016-18", "TSS-P", footnotes=[Footnote.DSU]), - CAR.RAV4H: ToyotaCarInfo("Toyota RAV4 Hybrid 2016-18", "TSS-P", footnotes=[Footnote.DSU]), + CAR.RAV4: [ + ToyotaCarInfo("Toyota RAV4 2016", "Toyota Safety Sense P", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Toyota RAV4 2017-18", footnotes=[Footnote.DSU]) + ], + CAR.RAV4H: [ + ToyotaCarInfo("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", video_link="https://youtu.be/LhT5VzJVfNI?t=26", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Toyota RAV4 Hybrid 2017-18", video_link="https://youtu.be/LhT5VzJVfNI?t=26", footnotes=[Footnote.DSU]) + ], CAR.RAV4_TSS2: ToyotaCarInfo("Toyota RAV4 2019-21", video_link="https://www.youtube.com/watch?v=wJxjDd42gGA"), CAR.RAV4_TSS2_2022: ToyotaCarInfo("Toyota RAV4 2022"), CAR.RAV4H_TSS2: ToyotaCarInfo("Toyota RAV4 Hybrid 2019-21"), CAR.RAV4H_TSS2_2022: ToyotaCarInfo("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"), CAR.MIRAI: ToyotaCarInfo("Toyota Mirai 2021"), - CAR.SIENNA: ToyotaCarInfo("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", footnotes=[Footnote.DSU]), + CAR.SIENNA: ToyotaCarInfo("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", footnotes=[Footnote.DSU], min_enable_speed=MIN_ACC_SPEED), # Lexus - CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "LSS", footnotes=[Footnote.DSU]), - CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18", "LSS", footnotes=[Footnote.DSU]), - CAR.LEXUS_ES_TSS2: ToyotaCarInfo("Lexus ES 2019-21"), - CAR.LEXUS_ESH_TSS2: ToyotaCarInfo("Lexus ES Hybrid 2019-22"), + CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "Lexus Safety System+", footnotes=[Footnote.DSU]), + CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18", "Lexus Safety System+", footnotes=[Footnote.DSU]), + CAR.LEXUS_ES_TSS2: ToyotaCarInfo("Lexus ES 2019-22"), + CAR.LEXUS_ESH_TSS2: ToyotaCarInfo("Lexus ES Hybrid 2019-22", video_link="https://youtu.be/BZ29osRVJeg?t=12"), CAR.LEXUS_IS: ToyotaCarInfo("Lexus IS 2017-19"), CAR.LEXUS_NX: ToyotaCarInfo("Lexus NX 2018-19", footnotes=[Footnote.DSU]), CAR.LEXUS_NXH: ToyotaCarInfo("Lexus NX Hybrid 2018-19", footnotes=[Footnote.DSU]), - CAR.LEXUS_NX_TSS2: ToyotaCarInfo("Lexus NX 2020"), - CAR.LEXUS_NXH_TSS2: ToyotaCarInfo("Lexus NX Hybrid 2020"), - CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2020"), - CAR.LEXUS_RX: ToyotaCarInfo("Lexus RX 2016-18", footnotes=[Footnote.DSU]), + CAR.LEXUS_NX_TSS2: ToyotaCarInfo("Lexus NX 2020-21"), + CAR.LEXUS_NXH_TSS2: ToyotaCarInfo("Lexus NX Hybrid 2020-21"), + CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2017-20"), + CAR.LEXUS_RX: ToyotaCarInfo("Lexus RX 2016-19", footnotes=[Footnote.DSU]), CAR.LEXUS_RXH: ToyotaCarInfo("Lexus RX Hybrid 2016-19", footnotes=[Footnote.DSU]), CAR.LEXUS_RX_TSS2: ToyotaCarInfo("Lexus RX 2020-22"), CAR.LEXUS_RXH_TSS2: ToyotaCarInfo("Lexus RX Hybrid 2020-21"), @@ -787,6 +799,7 @@ FW_VERSIONS = { (Ecu.eps, 0x7a1, None): [ b'8965B12361\x00\x00\x00\x00\x00\x00', b'8965B12451\x00\x00\x00\x00\x00\x00', + b'8965B16011\x00\x00\x00\x00\x00\x00', b'8965B76012\x00\x00\x00\x00\x00\x00', b'8965B76050\x00\x00\x00\x00\x00\x00', b'\x018965B12350\x00\x00\x00\x00\x00\x00', @@ -807,14 +820,16 @@ FW_VERSIONS = { b'F152612800\x00\x00\x00\x00\x00\x00', b'F152612820\x00\x00\x00\x00\x00\x00', b'F152612840\x00\x00\x00\x00\x00\x00', + b'F152612842\x00\x00\x00\x00\x00\x00', b'F152612890\x00\x00\x00\x00\x00\x00', b'F152612A00\x00\x00\x00\x00\x00\x00', b'F152612A10\x00\x00\x00\x00\x00\x00', + b'F152612D00\x00\x00\x00\x00\x00\x00', + b'F152616011\x00\x00\x00\x00\x00\x00', b'F152642540\x00\x00\x00\x00\x00\x00', b'F152676293\x00\x00\x00\x00\x00\x00', b'F152676303\x00\x00\x00\x00\x00\x00', b'F152676304\x00\x00\x00\x00\x00\x00', - b'F152612D00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301100\x00\x00\x00\x00', @@ -830,6 +845,7 @@ FW_VERSIONS = { b'\x028646F1202000\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', + b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b"\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00", b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', @@ -952,11 +968,13 @@ FW_VERSIONS = { b'\x01F15264873500\x00\x00\x00\x00', b'\x01F152648C6300\x00\x00\x00\x00', b'\x01F152648J4000\x00\x00\x00\x00', + b'\x01F152648J6000\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ + b'\x01896630EE4000\x00\x00\x00\x00', + b'\x01896630EE6000\x00\x00\x00\x00', b'\x01896630E67000\x00\x00\x00\x00', b'\x01896630EA1000\x00\x00\x00\x00', - b'\x01896630EE4000\x00\x00\x00\x00', b'\x01896630EA1000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630EB3000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', @@ -1260,6 +1278,7 @@ FW_VERSIONS = { b'\x01F152642711\x00\x00\x00\x00\x00\x00', b'\x01F152642750\x00\x00\x00\x00\x00\x00', b'\x01F152642751\x00\x00\x00\x00\x00\x00', + b'\x01F15260R292\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B42170\x00\x00\x00\x00\x00\x00', @@ -1294,8 +1313,10 @@ FW_VERSIONS = { b'\x028965B0R01500\x00\x00\x00\x008965B0R02500\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ + b'\x01896634AA0000\x00\x00\x00\x00', b'\x01896634AA1000\x00\x00\x00\x00', b'\x01896634A88000\x00\x00\x00\x00', + b'\x01896634A89000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F0R01100\x00\x00\x00\x00', @@ -1313,6 +1334,7 @@ FW_VERSIONS = { b'\x018966342X6000\x00\x00\x00\x00', b'\x01896634A25000\x00\x00\x00\x00', b'\x018966342W5000\x00\x00\x00\x00', + b'\x018966342W7000\x00\x00\x00\x00', b'\x028966342W4001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', b'\x02896634A13000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02896634A13001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', @@ -1374,11 +1396,13 @@ FW_VERSIONS = { b'8965B42172\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ - b'\x01896634A62000\x00\x00\x00\x00', - b'\x01896634A08000\x00\x00\x00\x00', - b'\x01896634A61000\x00\x00\x00\x00', b'\x01896634A02001\x00\x00\x00\x00', b'\x01896634A03000\x00\x00\x00\x00', + b'\x01896634A08000\x00\x00\x00\x00', + b'\x01896634A61000\x00\x00\x00\x00', + b'\x01896634A62000\x00\x00\x00\x00', + b'\x01896634A62100\x00\x00\x00\x00', + b'\x01896634A63000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F0R01100\x00\x00\x00\x00', @@ -1442,6 +1466,7 @@ FW_VERSIONS = { }, CAR.LEXUS_ES_TSS2: { (Ecu.engine, 0x700, None): [ + b'\x018966306U6000\x00\x00\x00\x00', b'\x01896630EC9100\x00\x00\x00\x00', b'\x018966333T5000\x00\x00\x00\x00', b'\x018966333T5100\x00\x00\x00\x00', @@ -1458,18 +1483,21 @@ FW_VERSIONS = { b'8965B33252\x00\x00\x00\x00\x00\x00', b'8965B33590\x00\x00\x00\x00\x00\x00', b'8965B33690\x00\x00\x00\x00\x00\x00', + b'8965B33721\x00\x00\x00\x00\x00\x00', b'8965B48271\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301100\x00\x00\x00\x00', b'\x018821F3301200\x00\x00\x00\x00', b'\x018821F3301400\x00\x00\x00\x00', + b'\x018821F6201300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F33030D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3303200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3304100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F3304300\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', + b'\x028646F3309100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', b'\x028646F4810200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', ], }, @@ -1758,13 +1786,14 @@ FW_VERSIONS = { b'\x01896630EB0000\x00\x00\x00\x00', b'\x01896630EC9000\x00\x00\x00\x00', b'\x01896630ED0000\x00\x00\x00\x00', + b'\x01896630ED0100\x00\x00\x00\x00', b'\x01896630ED6000\x00\x00\x00\x00', b'\x018966348W5100\x00\x00\x00\x00', b'\x018966348W9000\x00\x00\x00\x00', b'\x01896634D12000\x00\x00\x00\x00', b'\x01896634D12100\x00\x00\x00\x00', b'\x01896634D43000\x00\x00\x00\x00', - b'\x01896630ED0100\x00\x00\x00\x00', + b'\x01896634D44000\x00\x00\x00\x00', ], (Ecu.esp, 0x7b0, None): [ b'\x01F15260E031\x00\x00\x00\x00\x00\x00', @@ -1794,15 +1823,18 @@ FW_VERSIONS = { b'\x02348Y3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0234D14000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0234D16000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348X4000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.esp, 0x7b0, None): [ b'F152648831\x00\x00\x00\x00\x00\x00', b'F152648891\x00\x00\x00\x00\x00\x00', b'F152648D00\x00\x00\x00\x00\x00\x00', b'F152648D60\x00\x00\x00\x00\x00\x00', + b'F152648811\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B48271\x00\x00\x00\x00\x00\x00', + b'8965B48261\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301400\x00\x00\x00\x00', @@ -1839,6 +1871,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F4707000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F4710000\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', + b'\x028646F4712000\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', ], }, CAR.MIRAI: { diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index 7413c3f23..5ace68649 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import re import struct import traceback @@ -15,25 +16,30 @@ UDS_VIN_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + struct.pac UDS_VIN_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + struct.pack("!H", uds.DATA_IDENTIFIER_TYPE.VIN) VIN_UNKNOWN = "0" * 17 +VIN_RE = "[A-HJ-NPR-Z0-9]{17}" + + +def is_valid_vin(vin: str): + return re.fullmatch(VIN_RE, vin) is not None def get_vin(logcan, sendcan, bus, timeout=0.1, retry=5, debug=False): - for request, response in ((UDS_VIN_REQUEST, UDS_VIN_RESPONSE), (OBD_VIN_REQUEST, OBD_VIN_RESPONSE)): - for i in range(retry): + for i in range(retry): + for request, response in ((UDS_VIN_REQUEST, UDS_VIN_RESPONSE), (OBD_VIN_REQUEST, OBD_VIN_RESPONSE)): try: query = IsoTpParallelQuery(sendcan, logcan, bus, FUNCTIONAL_ADDRS, [request, ], [response, ], functional_addr=True, debug=debug) - for addr, vin in query.get_data(timeout).items(): + for (addr, rx_addr), vin in query.get_data(timeout).items(): # Honda Bosch response starts with a length, trim to correct length if vin.startswith(b'\x11'): vin = vin[1:18] - return addr[0], vin.decode() + return addr[0], rx_addr, vin.decode() print(f"vin query retry ({i+1}) ...") except Exception: cloudlog.warning(f"VIN query exception: {traceback.format_exc()}") - return 0, VIN_UNKNOWN + return 0, 0, VIN_UNKNOWN if __name__ == "__main__": @@ -41,5 +47,5 @@ if __name__ == "__main__": sendcan = messaging.pub_sock('sendcan') logcan = messaging.sub_sock('can') time.sleep(1) - addr, vin = get_vin(logcan, sendcan, 1, debug=False) - print(hex(addr), vin) + addr, vin_rx_addr, vin = get_vin(logcan, sendcan, 1, debug=False) + print(f'TX: {hex(addr)}, RX: {hex(vin_rx_addr)}, VIN: {vin}') diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index ca84fbc02..f4bcf5bce 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -1,48 +1,45 @@ -from common.numpy_fast import clip from cereal import car +from opendbc.can.packer import CANPacker +from common.numpy_fast import clip from common.conversions import Conversions as CV from selfdrive.car import apply_std_steer_torque_limits -from selfdrive.car.volkswagen import volkswagencan -from selfdrive.car.volkswagen.values import DBC_FILES, CANBUS, MQB_LDW_MESSAGES, BUTTON_STATES, CarControllerParams as P -from opendbc.can.packer import CANPacker +from selfdrive.car.volkswagen import mqbcan +from selfdrive.car.volkswagen.values import CANBUS, CarControllerParams VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState -class CarController(): + +class CarController: def __init__(self, dbc_name, CP, VM): - self.apply_steer_last = 0 self.CP = CP + self.CCP = CarControllerParams(CP) + self.CCS = mqbcan + self.packer_pt = CANPacker(dbc_name) - self.packer_pt = CANPacker(DBC_FILES.mqb) - + self.apply_steer_last = 0 + self.frame = 0 self.hcaSameTorqueCount = 0 self.hcaEnabledFrameCount = 0 - self.graButtonStatesToSend = None - self.graMsgSentCount = 0 - self.graMsgStartFramePrev = 0 - self.graMsgBusCounterPrev = 0 - - self.steer_rate_limited = False self.acc_starting = False self.acc_stopping = False - def update(self, c, CS, frame, ext_bus, actuators, visual_alert, left_lane_visible, right_lane_visible, left_lane_depart, - right_lane_depart, lead_visible, set_speed): - """ Controls thread """ + def update(self, CC, CS, ext_bus): + actuators = CC.actuators + hud_control = CC.hudControl can_sends = [] # **** Acceleration and Braking Controls ******************************** # if CS.CP.openpilotLongitudinalControl: - if frame % P.ACC_CONTROL_STEP == 0: + if self.frame % self.CCP.ACC_CONTROL_STEP == 0: if CS.tsk_status in [2, 3, 4, 5]: - acc_status = 3 if c.longActive else 2 + acc_status = 3 if CC.longActive else 2 else: acc_status = CS.tsk_status - accel = clip(actuators.accel, P.ACCEL_MIN, P.ACCEL_MAX) if c.longActive else 0 + accel = clip(actuators.accel, self.CCP.ACCEL_MIN, self.CCP.ACCEL_MAX) if CC.longActive else 0 # FIXME: this needs to become a proper state machine acc_hold_request, acc_hold_release, acc_hold_type, stopping_distance = False, False, 0, 20.46 @@ -55,7 +52,7 @@ class CarController(): else: acc_hold_type = 3 # hold_standby stopping_distance = 0.5 - elif c.longActive: + elif CC.longActive: if self.acc_stopping: self.acc_starting = True self.acc_stopping = False @@ -65,32 +62,29 @@ class CarController(): else: self.acc_stopping, self.acc_starting = False, False - cb_pos = 0.0 if lead_visible or CS.out.vEgo < 2.0 else 0.1 # react faster to lead cars, also don't get hung up at DSG clutch release/kiss points when creeping to stop + cb_pos = 0.0 if hud_control.lead_visible or CS.out.vEgo < 2.0 else 0.1 # react faster to lead cars, also don't get hung up at DSG clutch release/kiss points when creeping to stop cb_neg = 0.0 if accel < 0 else 0.2 # IDK why, but stock likes to zero this out when accel is negative - idx = (frame / P.ACC_CONTROL_STEP) % 16 - can_sends.append(volkswagencan.create_mqb_acc_06_control(self.packer_pt, CANBUS.pt, c.longActive, acc_status, + can_sends.append(self.CCS.create_acc_06_control(self.packer_pt, CANBUS.pt, CC.longActive, acc_status, accel, self.acc_stopping, self.acc_starting, - cb_pos, cb_neg, CS.acc_type, idx)) - can_sends.append(volkswagencan.create_mqb_acc_07_control(self.packer_pt, CANBUS.pt, c.longActive, + cb_pos, cb_neg, CS.acc_type)) + can_sends.append(self.CCS.create_acc_07_control(self.packer_pt, CANBUS.pt, CC.longActive, accel, acc_hold_request, acc_hold_release, - acc_hold_type, stopping_distance, idx)) + acc_hold_type, stopping_distance)) - if frame % P.ACC_HUD_STEP == 0: - if lead_visible: + if self.frame % self.CCP.ACC_HUD_STEP == 0: + if hud_control.lead_visible: lead_distance = 512 if CS.digital_cluster_installed else 8 # TODO: look up actual distance to lead else: lead_distance = 0 - idx = (frame / P.ACC_HUD_STEP) % 16 - can_sends.append(volkswagencan.create_mqb_acc_02_control(self.packer_pt, CANBUS.pt, CS.tsk_status, - set_speed * CV.MS_TO_KPH, lead_distance, idx)) - can_sends.append(volkswagencan.create_mqb_acc_04_control(self.packer_pt, CANBUS.pt, CS.acc_04_stock_values, - idx)) + can_sends.append(self.CCS.create_acc_02_control(self.packer_pt, CANBUS.pt, CS.tsk_status, + hud_control.set_speed * CV.MS_TO_KPH, lead_distance)) + can_sends.append(self.CCS.create_acc_04_control(self.packer_pt, CANBUS.pt, CS.acc_04_stock_values)) # **** Steering Controls ************************************************ # - if frame % P.HCA_STEP == 0: + if self.frame % self.CCP.HCA_STEP == 0: # Logic to avoid HCA state 4 "refused": # * Don't steer unless HCA is in state 3 "ready" or 5 "active" # * Don't steer at standstill @@ -101,23 +95,22 @@ class CarController(): # torque value. Do that anytime we happen to have 0 torque, or failing that, # when exceeding ~1/3 the 360 second timer. - if c.latActive: - new_steer = int(round(actuators.steer * P.STEER_MAX)) - apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, P) - self.steer_rate_limited = new_steer != apply_steer + if CC.latActive: + new_steer = int(round(actuators.steer * self.CCP.STEER_MAX)) + apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.CCP) if apply_steer == 0: hcaEnabled = False self.hcaEnabledFrameCount = 0 else: self.hcaEnabledFrameCount += 1 - if self.hcaEnabledFrameCount >= 118 * (100 / P.HCA_STEP): # 118s + if self.hcaEnabledFrameCount >= 118 * (100 / self.CCP.HCA_STEP): # 118s hcaEnabled = False self.hcaEnabledFrameCount = 0 else: hcaEnabled = True if self.apply_steer_last == apply_steer: self.hcaSameTorqueCount += 1 - if self.hcaSameTorqueCount > 1.9 * (100 / P.HCA_STEP): # 1.9s + if self.hcaSameTorqueCount > 1.9 * (100 / self.CCP.HCA_STEP): # 1.9s apply_steer -= (1, -1)[apply_steer < 0] self.hcaSameTorqueCount = 0 else: @@ -127,52 +120,28 @@ class CarController(): apply_steer = 0 self.apply_steer_last = apply_steer - idx = (frame / P.HCA_STEP) % 16 - can_sends.append(volkswagencan.create_mqb_steering_control(self.packer_pt, CANBUS.pt, apply_steer, - idx, hcaEnabled)) + can_sends.append(self.CCS.create_steering_control(self.packer_pt, CANBUS.pt, apply_steer, hcaEnabled)) # **** HUD Controls ***************************************************** # - if frame % P.LDW_STEP == 0: - if visual_alert in (VisualAlert.steerRequired, VisualAlert.ldw): - hud_alert = MQB_LDW_MESSAGES["laneAssistTakeOverSilent"] - else: - hud_alert = MQB_LDW_MESSAGES["none"] - - can_sends.append(volkswagencan.create_mqb_hud_control(self.packer_pt, CANBUS.pt, c.enabled, - CS.out.steeringPressed, hud_alert, left_lane_visible, - right_lane_visible, CS.ldw_stock_values, - left_lane_depart, right_lane_depart)) - - # **** ACC Button Controls ********************************************** # - - # FIXME: this entire section is in desperate need of refactoring - - if self.CP.pcmCruise: - if frame > self.graMsgStartFramePrev + P.GRA_VBP_STEP: - if c.cruiseControl.cancel: - # Cancel ACC if it's engaged with OP disengaged. - self.graButtonStatesToSend = BUTTON_STATES.copy() - self.graButtonStatesToSend["cancel"] = True - elif c.enabled and CS.out.cruiseState.standstill: - # Blip the Resume button if we're engaged at standstill. - # FIXME: This is a naive implementation, improve with visiond or radar input. - self.graButtonStatesToSend = BUTTON_STATES.copy() - self.graButtonStatesToSend["resumeCruise"] = True - - if CS.graMsgBusCounter != self.graMsgBusCounterPrev: - self.graMsgBusCounterPrev = CS.graMsgBusCounter - if self.graButtonStatesToSend is not None: - if self.graMsgSentCount == 0: - self.graMsgStartFramePrev = frame - idx = (CS.graMsgBusCounter + 1) % 16 - can_sends.append(volkswagencan.create_mqb_acc_buttons_control(self.packer_pt, ext_bus, self.graButtonStatesToSend, CS, idx)) - self.graMsgSentCount += 1 - if self.graMsgSentCount >= P.GRA_VBP_COUNT: - self.graButtonStatesToSend = None - self.graMsgSentCount = 0 + if self.frame % self.CCP.LDW_STEP == 0: + hud_alert = 0 + if hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw): + hud_alert = self.CCP.LDW_MESSAGES["laneAssistTakeOver"] + can_sends.append(self.CCS.create_lka_hud_control(self.packer_pt, CANBUS.pt, CS.ldw_stock_values, CC.enabled, + CS.out.steeringPressed, hud_alert, hud_control)) + + # **** Stock ACC Button Controls **************************************** # + + if self.CP.pcmCruise and self.frame % self.CCP.GRA_ACC_STEP == 0: + idx = (CS.gra_stock_values["COUNTER"] + 1) % 16 + if CC.cruiseControl.cancel: + can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, ext_bus, CS.gra_stock_values, idx, cancel=True)) + elif CC.cruiseControl.resume: + can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, ext_bus, CS.gra_stock_values, idx, resume=True)) new_actuators = actuators.copy() - new_actuators.steer = self.apply_steer_last / P.STEER_MAX + new_actuators.steer = self.apply_steer_last / self.CCP.STEER_MAX + self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 014fc9a57..92258ddc0 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -3,21 +3,31 @@ from cereal import car from common.conversions import Conversions as CV from selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser -from opendbc.can.can_define import CANDefine -from selfdrive.car.volkswagen.values import DBC_FILES, CANBUS, NetworkLocation, TransmissionType, GearShifter, BUTTON_STATES, CarControllerParams +from selfdrive.car.volkswagen.values import DBC, CANBUS, NetworkLocation, TransmissionType, GearShifter, \ + CarControllerParams + class CarState(CarStateBase): def __init__(self, CP): super().__init__(CP) - can_define = CANDefine(DBC_FILES.mqb) - if CP.transmissionType == TransmissionType.automatic: - self.shifter_values = can_define.dv["Getriebe_11"]["GE_Fahrstufe"] - elif CP.transmissionType == TransmissionType.direct: - self.shifter_values = can_define.dv["EV_Gearshift"]["GearPosition"] - self.hca_status_values = can_define.dv["LH_EPS_03"]["EPS_HCA_Status"] - self.buttonStates = BUTTON_STATES.copy() + self.CCP = CarControllerParams(CP) + self.button_states = {button.event_type: False for button in self.CCP.BUTTONS} self.digital_cluster_installed = False + def create_button_events(self, pt_cp, buttons): + button_events = [] + + for button in buttons: + state = pt_cp.vl[button.can_addr][button.can_msg] in button.values + if self.button_states[button.event_type] != state: + event = car.CarState.ButtonEvent.new_message() + event.type = button.event_type + event.pressed = state + button_events.append(event) + self.button_states[button.event_type] = state + + return button_events + def update(self, pt_cp, cam_cp, ext_cp, trans_type): ret = car.CarState.new_message() # Update vehicle speed and acceleration from ABS wheel speeds. @@ -37,11 +47,11 @@ class CarState(CarStateBase): ret.steeringAngleDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradwinkel"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradwinkel"])] ret.steeringRateDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradw_Geschw"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradw_Geschw"])] ret.steeringTorque = pt_cp.vl["LH_EPS_03"]["EPS_Lenkmoment"] * (1, -1)[int(pt_cp.vl["LH_EPS_03"]["EPS_VZ_Lenkmoment"])] - ret.steeringPressed = abs(ret.steeringTorque) > CarControllerParams.STEER_DRIVER_ALLOWANCE + ret.steeringPressed = abs(ret.steeringTorque) > self.CCP.STEER_DRIVER_ALLOWANCE ret.yawRate = pt_cp.vl["ESP_02"]["ESP_Gierrate"] * (1, -1)[int(pt_cp.vl["ESP_02"]["ESP_VZ_Gierrate"])] * CV.DEG_TO_RAD # Verify EPS readiness to accept steering commands - hca_status = self.hca_status_values.get(pt_cp.vl["LH_EPS_03"]["EPS_HCA_Status"]) + hca_status = self.CCP.hca_status_values.get(pt_cp.vl["LH_EPS_03"]["EPS_HCA_Status"]) ret.steerFaultPermanent = hca_status in ("DISABLED", "FAULT") ret.steerFaultTemporary = hca_status in ("INITIALIZING", "REJECTED") @@ -54,9 +64,9 @@ class CarState(CarStateBase): # Update gear and/or clutch position data. if trans_type == TransmissionType.automatic: - ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["Getriebe_11"]["GE_Fahrstufe"], None)) + ret.gearShifter = self.parse_gear_shifter(self.CCP.shifter_values.get(pt_cp.vl["Getriebe_11"]["GE_Fahrstufe"], None)) elif trans_type == TransmissionType.direct: - ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["EV_Gearshift"]["GearPosition"], None)) + ret.gearShifter = self.parse_gear_shifter(self.CCP.shifter_values.get(pt_cp.vl["EV_Gearshift"]["GearPosition"], None)) elif trans_type == TransmissionType.manual: ret.clutchPressed = not pt_cp.vl["Motor_14"]["MO_Kuppl_schalter"] if bool(pt_cp.vl["Gateway_72"]["BCM1_Rueckfahrlicht_Schalter"]): @@ -118,26 +128,11 @@ class CarState(CarStateBase): if ret.cruiseState.speed > 90: ret.cruiseState.speed = 0 - # Update control button states for turn signals and ACC controls. - self.buttonStates["accelCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Hoch"]) - self.buttonStates["decelCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Runter"]) - self.buttonStates["cancel"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Abbrechen"]) - self.buttonStates["setCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Setzen"]) - self.buttonStates["resumeCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Wiederaufnahme"]) - self.buttonStates["gapAdjustCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Verstellung_Zeitluecke"]) + # Update button states for turn signals and ACC controls, capture all ACC button state/config for passthrough ret.leftBlinker = bool(pt_cp.vl["Blinkmodi_02"]["Comfort_Signal_Left"]) ret.rightBlinker = bool(pt_cp.vl["Blinkmodi_02"]["Comfort_Signal_Right"]) - - # Read ACC hardware button type configuration info that has to pass thru - # to the radar. Ends up being different for steering wheel buttons vs - # third stalk type controls. - self.graHauptschalter = pt_cp.vl["GRA_ACC_01"]["GRA_Hauptschalter"] - self.graTypHauptschalter = pt_cp.vl["GRA_ACC_01"]["GRA_Typ_Hauptschalter"] - self.graButtonTypeInfo = pt_cp.vl["GRA_ACC_01"]["GRA_ButtonTypeInfo"] - self.graTipStufe2 = pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Stufe_2"] - # Pick up the GRA_ACC_01 CAN message counter so we can sync to it for - # later cruise-control button spamming. - self.graMsgBusCounter = pt_cp.vl["GRA_ACC_01"]["COUNTER"] + ret.buttonEvents = self.create_button_events(pt_cp, self.CCP.BUTTONS) + self.gra_stock_values = pt_cp.vl["GRA_ACC_01"] # Additional safety checks performed in CarInterface. ret.espDisabled = pt_cp.vl["ESP_21"]["ESP_Tastung_passiv"] != 0 @@ -189,6 +184,7 @@ class CarState(CarStateBase): ("GRA_Tip_Wiederaufnahme", "GRA_ACC_01"), # ACC button, resume ("GRA_Verstellung_Zeitluecke", "GRA_ACC_01"), # ACC button, time gap adj ("GRA_Typ_Hauptschalter", "GRA_ACC_01"), # ACC main button type + ("GRA_Codierung", "GRA_ACC_01"), # ACC button configuration/coding ("GRA_Tip_Stufe_2", "GRA_ACC_01"), # unknown related to stalk type ("GRA_ButtonTypeInfo", "GRA_ACC_01"), # unknown related to stalk type ("COUNTER", "GRA_ACC_01"), # GRA_ACC_01 CAN message counter @@ -231,7 +227,7 @@ class CarState(CarStateBase): signals += MqbExtraSignals.bsm_radar_signals checks += MqbExtraSignals.bsm_radar_checks - return CANParser(DBC_FILES.mqb, signals, checks, CANBUS.pt) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CANBUS.pt) @staticmethod def get_cam_can_parser(CP): @@ -259,7 +255,7 @@ class CarState(CarStateBase): signals += MqbExtraSignals.bsm_radar_signals checks += MqbExtraSignals.bsm_radar_checks - return CANParser(DBC_FILES.mqb, signals, checks, CANBUS.cam) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CANBUS.cam) class MqbExtraSignals: # Additional signal and message lists for optional or bus-portable controllers diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index d7679b47e..29c9be83a 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,11 +1,12 @@ from cereal import car from common.params import Params from panda import Panda -from selfdrive.car.volkswagen.values import CAR, BUTTON_STATES, CANBUS, NetworkLocation, TransmissionType, GearShifter -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, create_button_enable_events, scale_rot_inertia, scale_tire_stiffness, \ + gen_empty_fingerprint, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase +from selfdrive.car.volkswagen.values import CAR, CANBUS, NetworkLocation, TransmissionType, GearShifter + -ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -13,8 +14,6 @@ class CarInterface(CarInterfaceBase): def __init__(self, CP, CarController, CarState): super().__init__(CP, CarController, CarState) - self.buttonStatesPrev = BUTTON_STATES.copy() - if CP.networkLocation == NetworkLocation.fwdCamera: self.ext_bus = CANBUS.pt self.cp_ext = self.cp @@ -47,7 +46,7 @@ class CarInterface(CarInterfaceBase): if Params().get_bool("DisableRadar") and ret.networkLocation == NetworkLocation.gateway: ret.openpilotLongitudinalControl = True - ret.safetyConfigs[0].safetyParam |= Panda.FLAG_VOLKSWAGEN_MQB_LONG + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_VOLKSWAGEN_LONG_CONTROL if ret.transmissionType == TransmissionType.manual: ret.minEnableSpeed = 4.5 # FIXME: estimated, fine-tune @@ -179,19 +178,7 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): - buttonEvents = [] - ret = self.CS.update(self.cp, self.cp_cam, self.cp_ext, self.CP.transmissionType) - ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False - - # Check for and process state-change events (button press or release) from - # the turn stalk switch or ACC steering wheel/control stalk buttons. - for button in self.CS.buttonStates: - if self.CS.buttonStates[button] != self.buttonStatesPrev[button]: - be = car.CarState.ButtonEvent.new_message() - be.type = button - be.pressed = self.CS.buttonStates[button] - buttonEvents.append(be) events = self.create_common_events(ret, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], pcm_enable=not self.CS.CP.openpilotLongitudinalControl) @@ -209,31 +196,11 @@ class CarInterface(CarInterfaceBase): events.add(EventName.belowEngageSpeed) if c.enabled and ret.vEgo < self.CP.minEnableSpeed: events.add(EventName.speedTooLow) - for b in buttonEvents: - # do enable on falling edge of both accel and decel buttons - if b.type in (ButtonType.setCruise, ButtonType.resumeCruise) and not b.pressed: - events.add(EventName.buttonEnable) - # do disable on rising edge of cancel - if b.type == "cancel" and b.pressed: - events.add(EventName.buttonCancel) + events.events.extend(create_button_enable_events(ret.buttonEvents, pcm_cruise=self.CP.pcmCruise)) ret.events = events.to_msg() - ret.buttonEvents = buttonEvents - - # update previous car states - self.buttonStatesPrev = self.CS.buttonStates.copy() return ret def apply(self, c): - hud_control = c.hudControl - ret = self.CC.update(c, self.CS, self.frame, self.ext_bus, c.actuators, - hud_control.visualAlert, - hud_control.leftLaneVisible, - hud_control.rightLaneVisible, - hud_control.leftLaneDepart, - hud_control.rightLaneDepart, - hud_control.leadVisible, - hud_control.setSpeed) - self.frame += 1 - return ret + return self.CC.update(c, self.CS, self.ext_bus) diff --git a/selfdrive/car/volkswagen/mqbcan.py b/selfdrive/car/volkswagen/mqbcan.py new file mode 100644 index 000000000..d9b275c8a --- /dev/null +++ b/selfdrive/car/volkswagen/mqbcan.py @@ -0,0 +1,91 @@ +def create_steering_control(packer, bus, apply_steer, lkas_enabled): + values = { + "SET_ME_0X3": 0x3, + "Assist_Torque": abs(apply_steer), + "Assist_Requested": lkas_enabled, + "Assist_VZ": 1 if apply_steer < 0 else 0, + "HCA_Available": 1, + "HCA_Standby": not lkas_enabled, + "HCA_Active": lkas_enabled, + "SET_ME_0XFE": 0xFE, + "SET_ME_0X07": 0x07, + } + return packer.make_can_msg("HCA_01", bus, values) + + +def create_lka_hud_control(packer, bus, ldw_stock_values, enabled, steering_pressed, hud_alert, hud_control): + values = ldw_stock_values.copy() + + values.update({ + "LDW_Status_LED_gelb": 1 if enabled and steering_pressed else 0, + "LDW_Status_LED_gruen": 1 if enabled and not steering_pressed else 0, + "LDW_Lernmodus_links": 3 if hud_control.leftLaneDepart else 1 + hud_control.leftLaneVisible, + "LDW_Lernmodus_rechts": 3 if hud_control.rightLaneDepart else 1 + hud_control.rightLaneVisible, + "LDW_Texte": hud_alert, + }) + return packer.make_can_msg("LDW_02", bus, values) + + +def create_acc_buttons_control(packer, bus, gra_stock_values, idx, cancel=False, resume=False): + values = gra_stock_values.copy() + + values.update({ + "COUNTER": idx, + "GRA_Abbrechen": cancel, + "GRA_Tip_Wiederaufnahme": resume, + }) + + return packer.make_can_msg("GRA_ACC_01", bus, values) + + +def create_acc_02_control(packer, bus, acc_status, set_speed, lead_distance): + values = { + "ACC_Status_Anzeige": 3 if acc_status == 5 else acc_status, + "ACC_Wunschgeschw": set_speed if set_speed < 250 else 327.36, + "ACC_Gesetzte_Zeitluecke": 3, + "ACC_Display_Prio": 3, + "ACC_Abstandsindex": lead_distance, + } + + return packer.make_can_msg("ACC_02", bus, values) + + +def create_acc_04_control(packer, bus, acc_04_stock_values): + values = acc_04_stock_values.copy() + + # Suppress disengagement alert from stock radar when OP long is in use, but passthru FCW/AEB alerts + if values["ACC_Texte_braking_guard"] == 4: + values["ACC_Texte_braking_guard"] = 0 + + return packer.make_can_msg("ACC_04", bus, values) + + +def create_acc_06_control(packer, bus, enabled, acc_status, accel, acc_stopping, acc_starting, cb_pos, cb_neg, acc_type): + values = { + "ACC_Typ": acc_type, + "ACC_Status_ACC": acc_status, + "ACC_StartStopp_Info": enabled, + "ACC_Sollbeschleunigung_02": accel if enabled else 3.01, + "ACC_zul_Regelabw_unten": cb_neg, + "ACC_zul_Regelabw_oben": cb_pos, + "ACC_neg_Sollbeschl_Grad_02": 4.0 if enabled else 0, + "ACC_pos_Sollbeschl_Grad_02": 4.0 if enabled else 0, + "ACC_Anfahren": acc_starting, + "ACC_Anhalten": acc_stopping, + } + + return packer.make_can_msg("ACC_06", bus, values) + + +def create_acc_07_control(packer, bus, enabled, accel, acc_hold_request, acc_hold_release, acc_hold_type, stopping_distance): + values = { + "ACC_Distance_to_Stop": stopping_distance, + "ACC_Hold_Request": acc_hold_request, + "ACC_Freewheel_Type": 2 if enabled else 0, + "ACC_Hold_Type": acc_hold_type, + "ACC_Hold_Release": acc_hold_release, + "ACC_Accel_Secondary": 3.02, # not using this unless and until we understand its impact + "ACC_Accel_TSK": accel if enabled else 3.01, + } + + return packer.make_can_msg("ACC_07", bus, values) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index e541a29a8..1434ee8ac 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1,9 +1,10 @@ -from collections import defaultdict +from collections import defaultdict, namedtuple from dataclasses import dataclass from enum import Enum from typing import Dict, List, Union from cereal import car +from opendbc.can.can_define import CANDefine from selfdrive.car import dbc_dict from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, Harness @@ -11,62 +12,65 @@ Ecu = car.CarParams.Ecu NetworkLocation = car.CarParams.NetworkLocation TransmissionType = car.CarParams.TransmissionType GearShifter = car.CarState.GearShifter +Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) class CarControllerParams: - HCA_STEP = 2 # HCA_01 message frequency 50Hz - LDW_STEP = 10 # LDW_02 message frequency 10Hz - GRA_ACC_STEP = 3 # GRA_ACC_01 message frequency 33Hz - ACC_CONTROL_STEP = 2 # ACC_06 message frequency 50Hz - ACC_HUD_STEP = 6 # ACC_02 message frequency 16Hz + HCA_STEP = 2 # HCA_01/HCA_1 message frequency 50Hz + GRA_ACC_STEP = 3 # GRA_ACC_01/GRA_Neu message frequency 33Hz + ACC_CONTROL_STEP = 2 # ACC_06/ACC_07/ACC_System frequency 50Hz - GRA_VBP_STEP = 100 # Send ACC virtual button presses once a second - GRA_VBP_COUNT = 16 # Send VBP messages for ~0.5s (GRA_ACC_STEP * 16) + ACCEL_MAX = 2.0 # 2.0 m/s max acceleration + ACCEL_MIN = -3.5 # 3.5 m/s max deceleration - # Observed documented MQB limits: 3.00 Nm max, rate of change 5.00 Nm/sec. - # Limiting rate-of-change based on real-world testing and Comma's safety - # requirements for minimum time to lane departure. - STEER_MAX = 300 # Max heading control assist torque 3.00 Nm - STEER_DELTA_UP = 4 # Max HCA reached in 1.50s (STEER_MAX / (50Hz * 1.50)) - STEER_DELTA_DOWN = 10 # Min HCA reached in 0.60s (STEER_MAX / (50Hz * 0.60)) - STEER_DRIVER_ALLOWANCE = 80 - STEER_DRIVER_MULTIPLIER = 3 # weight driver torque heavily - STEER_DRIVER_FACTOR = 1 # from dbc - ACCEL_MAX = 2.0 # 2.0 m/s max acceleration - ACCEL_MIN = -3.5 # 3.5 m/s max deceleration + def __init__(self, CP): + # Documented lateral limits: 3.00 Nm max, rate of change 5.00 Nm/sec. + # MQB vs PQ maximums are shared, but rate-of-change limited differently + # based on safety requirements driven by lateral accel testing. + self.STEER_MAX = 300 # Max heading control assist torque 3.00 Nm + self.STEER_DRIVER_MULTIPLIER = 3 # weight driver torque heavily + self.STEER_DRIVER_FACTOR = 1 # from dbc + can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) -class CANBUS: - pt = 0 - cam = 2 + if True: # pylint: disable=using-constant-test + self.LDW_STEP = 10 # LDW_02 message frequency 10Hz + self.ACC_HUD_STEP = 6 # ACC_02/ACC_04 message frequency 16Hz + self.STEER_DRIVER_ALLOWANCE = 80 # Driver intervention threshold 0.8 Nm + self.STEER_DELTA_UP = 4 # Max HCA reached in 1.50s (STEER_MAX / (50Hz * 1.50)) + self.STEER_DELTA_DOWN = 10 # Min HCA reached in 0.60s (STEER_MAX / (50Hz * 0.60)) + if CP.transmissionType == TransmissionType.automatic: + self.shifter_values = can_define.dv["Getriebe_11"]["GE_Fahrstufe"] + elif CP.transmissionType == TransmissionType.direct: + self.shifter_values = can_define.dv["EV_Gearshift"]["GearPosition"] + self.hca_status_values = can_define.dv["LH_EPS_03"]["EPS_HCA_Status"] -class DBC_FILES: - mqb = "vw_mqb_2010" # Used for all cars with MQB-style CAN messaging + self.BUTTONS = [ + Button(car.CarState.ButtonEvent.Type.setCruise, "GRA_ACC_01", "GRA_Tip_Setzen", [1]), + Button(car.CarState.ButtonEvent.Type.resumeCruise, "GRA_ACC_01", "GRA_Tip_Wiederaufnahme", [1]), + Button(car.CarState.ButtonEvent.Type.accelCruise, "GRA_ACC_01", "GRA_Tip_Hoch", [1]), + Button(car.CarState.ButtonEvent.Type.decelCruise, "GRA_ACC_01", "GRA_Tip_Runter", [1]), + Button(car.CarState.ButtonEvent.Type.cancel, "GRA_ACC_01", "GRA_Abbrechen", [1]), + Button(car.CarState.ButtonEvent.Type.gapAdjustCruise, "GRA_ACC_01", "GRA_Verstellung_Zeitluecke", [1]), + ] + self.LDW_MESSAGES = { + "none": 0, # Nothing to display + "laneAssistUnavailChime": 1, # "Lane Assist currently not available." with chime + "laneAssistUnavailNoSensorChime": 3, # "Lane Assist not available. No sensor view." with chime + "laneAssistTakeOverUrgent": 4, # "Lane Assist: Please Take Over Steering" with urgent beep + "emergencyAssistUrgent": 6, # "Emergency Assist: Please Take Over Steering" with urgent beep + "laneAssistTakeOverChime": 7, # "Lane Assist: Please Take Over Steering" with chime + "laneAssistTakeOver": 8, # "Lane Assist: Please Take Over Steering" silent + "emergencyAssistChangingLanes": 9, # "Emergency Assist: Changing lanes..." with urgent beep + "laneAssistDeactivated": 10, # "Lane Assist deactivated." silent with persistent icon afterward + } -DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict(DBC_FILES.mqb, None)) -BUTTON_STATES = { - "accelCruise": False, - "decelCruise": False, - "cancel": False, - "setCruise": False, - "resumeCruise": False, - "gapAdjustCruise": False -} - -MQB_LDW_MESSAGES = { - "none": 0, # Nothing to display - "laneAssistUnavailChime": 1, # "Lane Assist currently not available." with chime - "laneAssistUnavailNoSensorChime": 3, # "Lane Assist not available. No sensor view." with chime - "laneAssistTakeOverUrgent": 4, # "Lane Assist: Please Take Over Steering" with urgent beep - "emergencyAssistUrgent": 6, # "Emergency Assist: Please Take Over Steering" with urgent beep - "laneAssistTakeOverChime": 7, # "Lane Assist: Please Take Over Steering" with chime - "laneAssistTakeOverSilent": 8, # "Lane Assist: Please Take Over Steering" silent - "emergencyAssistChangingLanes": 9, # "Emergency Assist: Changing lanes..." with urgent beep - "laneAssistDeactivated": 10, # "Lane Assist deactivated." silent with persistent icon afterward -} +class CANBUS: + pt = 0 + cam = 2 # Check the 7th and 8th characters of the VIN before adding a new CAR. If the @@ -100,47 +104,71 @@ class CAR: SKODA_OCTAVIA_MK3 = "SKODA OCTAVIA 3RD GEN" # Chassis NE, Mk3 Skoda Octavia and variants +DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("vw_mqb_2010", None)) + + class Footnote(Enum): KAMIQ = CarFootnote( "Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.", Column.MODEL) PASSAT = CarFootnote( - "Not including the USA/China market Passat, which is based on the (currently) unsupported PQ35/NMS platform.", + "Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.", Column.MODEL) VW_HARNESS = CarFootnote( "Model-years 2021 and beyond may have a new camera harness design, which isn't yet available from the comma " + "store. Before ordering, remove the Lane Assist camera cover and check to see if the connector is black " + - "(older design) or light brown (newer design). For the newer design, in the interim, choose \"VW J533 Development\" " + - "from the vehicle drop-down for a harness that integrates at the CAN gateway inside the dashboard.", + "(older design) or light brown (newer design). In the interim, if your car has a J533 connector CAN gateway " + + "inside the dashboard, choose \"VW J533 Development\" from the vehicle drop-down for a suitable harness. " + + "(Some newer models are also observed to not have a J533 connector.)", + Column.MODEL) + VW_VARIANT = CarFootnote( + "Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.)", Column.MODEL) @dataclass class VWCarInfo(CarInfo): package: str = "Driver Assistance" - good_torque: bool = True harness: Enum = Harness.vw CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { - CAR.ARTEON_MK1: VWCarInfo("Volkswagen Arteon 2018, 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - CAR.ATLAS_MK1: VWCarInfo("Volkswagen Atlas 2018-19, 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + CAR.ARTEON_MK1: [ + VWCarInfo("Volkswagen Arteon 2018-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon R 2020-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon eHybrid 2020-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen CC 2018-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), + ], + CAR.ATLAS_MK1: [ + VWCarInfo("Volkswagen Atlas 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Atlas Cross Sport 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Teramont 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Teramont Cross Sport 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Teramont X 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + ], CAR.GOLF_MK7: [ - VWCarInfo("Volkswagen e-Golf 2014, 2018-20"), - VWCarInfo("Volkswagen Golf 2015-20"), - VWCarInfo("Volkswagen Golf Alltrack 2017-18"), - VWCarInfo("Volkswagen Golf GTE 2016"), - VWCarInfo("Volkswagen Golf GTI 2018-21"), - VWCarInfo("Volkswagen Golf R 2016-19"), - VWCarInfo("Volkswagen Golf SportsVan 2016"), - VWCarInfo("Volkswagen Golf SportWagen 2015"), + VWCarInfo("Volkswagen e-Golf 2014-20"), + VWCarInfo("Volkswagen Golf 2015-20", footnotes=[Footnote.VW_VARIANT]), + VWCarInfo("Volkswagen Golf Alltrack 2015-19"), + VWCarInfo("Volkswagen Golf GTD 2015-20"), + VWCarInfo("Volkswagen Golf GTE 2015-20"), + VWCarInfo("Volkswagen Golf GTI 2015-21"), + VWCarInfo("Volkswagen Golf R 2015-19", footnotes=[Footnote.VW_VARIANT]), + VWCarInfo("Volkswagen Golf SportsVan 2015-20"), ], CAR.JETTA_MK7: [ - VWCarInfo("Volkswagen Jetta 2018-21"), - VWCarInfo("Volkswagen Jetta GLI 2021"), + VWCarInfo("Volkswagen Jetta 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Jetta GLI 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + ], + CAR.PASSAT_MK8: [ + VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.VW_HARNESS, Footnote.PASSAT, Footnote.VW_VARIANT], harness=Harness.j533), + VWCarInfo("Volkswagen Passat Alltrack 2015-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Passat GTE 2015-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533), + ], + CAR.POLO_MK6: [ + VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), ], - CAR.PASSAT_MK8: VWCarInfo("Volkswagen Passat 2015-19", footnotes=[Footnote.PASSAT]), - CAR.POLO_MK6: VWCarInfo("Volkswagen Polo 2020"), CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), @@ -161,7 +189,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR.SEAT_ATECA_MK1: VWCarInfo("SEAT Ateca 2018"), CAR.SEAT_LEON_MK3: VWCarInfo("SEAT Leon 2014-20"), CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.KAMIQ]), - CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019"), + CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21", footnotes=[Footnote.VW_HARNESS]), CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2018-19"), CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020"), CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-18"), @@ -210,6 +238,7 @@ FW_VERSIONS = { CAR.ATLAS_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8703H906026AA\xf1\x899970', + b'\xf1\x8703H906026AJ\xf1\x890638', b'\xf1\x8703H906026AT\xf1\x891922', b'\xf1\x8703H906026F \xf1\x896696', b'\xf1\x8703H906026F \xf1\x899970', @@ -221,6 +250,7 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158A \xf1\x893387', b'\xf1\x8709G927158DR\xf1\x893536', + b'\xf1\x8709G927158DR\xf1\x893742', b'\xf1\x8709G927158FT\xf1\x893835', ], (Ecu.srs, 0x715, None): [ @@ -235,6 +265,7 @@ FW_VERSIONS = { b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6090105', ], (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572R \xf1\x890372', b'\xf1\x872Q0907572T \xf1\x890383', b'\xf1\x875Q0907572H \xf1\x890620', b'\xf1\x875Q0907572J \xf1\x890654', @@ -250,6 +281,7 @@ FW_VERSIONS = { b'\xf1\x8704E906023BN\xf1\x894518', b'\xf1\x8704E906024K \xf1\x896811', b'\xf1\x8704E906027GR\xf1\x892394', + b'\xf1\x8704E906027HD\xf1\x892603', b'\xf1\x8704E906027HD\xf1\x893742', b'\xf1\x8704E906027MA\xf1\x894958', b'\xf1\x8704L906021DT\xf1\x895520', @@ -283,10 +315,12 @@ FW_VERSIONS = { b'\xf1\x878V0906264L \xf1\x890002', b'\xf1\x878V0906264M \xf1\x890001', b'\xf1\x878V09C0BB01 \xf1\x890001', + b'\xf1\x8704E906024K \xf1\x899970', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927749AP\xf1\x892943', b'\xf1\x8709S927158A \xf1\x893585', + b'\xf1\x870CW300040H \xf1\x890606', b'\xf1\x870CW300041H \xf1\x891010', b'\xf1\x870CW300042F \xf1\x891604', b'\xf1\x870CW300043B \xf1\x891601', @@ -317,75 +351,76 @@ FW_VERSIONS = { b'\xf1\x870GC300043T \xf1\x899999', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\0211413001113120043114317121C111C9113', - b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\0211413001113120053114317121C111C9113', - b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\0211413001113120043114317121C111C9113', - b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\0211413001113120043114417121411149113', - b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\0211413001113120053114317121C111C9113', - b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\02314160011123300314211012230229333463100', + b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\x111413001113120043114317121C111C9113', + b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\x111413001113120053114317121C111C9113', + b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120043114317121C111C9113', + b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120043114417121411149113', + b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120053114317121C111C9113', + b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333463100', b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\023141600111233003142404A2252229333463100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\023141600111233003142405A2252229333463100', - b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\0211413001112120004110415121610169112', - b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\0211413001113120006110417121A101A9113', - b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023271112111312--071104171825102591131211', - b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023271212111312--071104171838103891131211', - b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023341512112212--071104172328102891131211', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2252229333463100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2252229333463100', + b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112', + b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\x111413001113120006110417121A101A9113', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271112111312--071104171825102591131211', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271212111312--071104171838103891131211', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13341512112212--071104172328102891131211', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13272512111312--07110417182C102C91131211', - b'\xf1\x875Q0959655M \xf1\x890361\xf1\x82\0211413001112120041114115121611169112', - b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011211200621143171717111791132111', - b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02324230011211200061104171724102491132111', - b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02324230011211200621143171724112491132111', + b'\xf1\x875Q0959655M \xf1\x890361\xf1\x82\x111413001112120041114115121611169112', + b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200621143171717111791132111', + b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200061104171724102491132111', + b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200621143171724112491132111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200061104171717101791132111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200631143171724122491132111', - b'\xf1\x875Q0959655T \xf1\x890825\xf1\x82\023271200111312--071104171837103791132111', + b'\xf1\x875Q0959655T \xf1\x890825\xf1\x82\x13271200111312--071104171837103791132111', b'\xf1\x875Q0959655T \xf1\x890830\xf1\x82\x13271100111312--071104171826102691131211', b'\xf1\x875QD959655 \xf1\x890388\xf1\x82\x111413001113120006110417121D101D9112', ], (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\00561A01612A0', - b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\00566A0J612A1', - b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\00566A00514A1', - b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\00566A0J712A1', - b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\00571A0J714A1', + b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\x0561A01612A0', + b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\x0566A0J612A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A00514A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A0J712A1', + b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571A0J714A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571A0JA15A1', - b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\00571A01A18A1', - b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\00571A0JA16A1', + b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A01A18A1', + b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A0JA16A1', b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820519A9040203', - b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\00521A00441A1', + b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00441A1', b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00608A1', b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00641A1', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521A00442A1', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521A00642A1', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521A07B05A1', - b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\00521A00602A0', - b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\00522A00402A0', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A00442A1', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A00642A1', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A07B05A1', + b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0521A00602A0', + b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0522A00402A0', b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0521A00502A0', - b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00511A00403A0', - b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\00516A00604A1', - b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\00516A00604A1', - b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\00516A07A02A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521A00507A1', + b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0511A00403A0', + b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00404A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00604A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A07A02A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A00507A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A07B04A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521A20B03A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A20B03A1', b'\xf1\x875QD909144B \xf1\x891072\xf1\x82\x0521A00507A1', b'\xf1\x875QM909144A \xf1\x891072\xf1\x82\x0521A20B03A1', - b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\00521A00442A1', - b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\00571A01A16A1', - b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\00571A01A18A1', + b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A00442A1', + b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A16A1', + b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A18A1', b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A17A1', - b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\00571A01A18A1', + b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\x0571A01A18A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A2000400', ], (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x875Q0907567G \xf1\x890390\xf1\x82\00101', + b'\xf1\x875Q0907567G \xf1\x890390\xf1\x82\x0101', b'\xf1\x875Q0907567J \xf1\x890396\xf1\x82\x0101', - b'\xf1\x875Q0907572A \xf1\x890141\xf1\x82\00101', - b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\00101', - b'\xf1\x875Q0907572C \xf1\x890210\xf1\x82\00101', - b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\00101', - b'\xf1\x875Q0907572E \xf1\x89X310\xf1\x82\00101', - b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\00101', + b'\xf1\x875Q0907572A \xf1\x890141\xf1\x82\x0101', + b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\x0101', + b'\xf1\x875Q0907572C \xf1\x890210\xf1\x82\x0101', + b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', + b'\xf1\x875Q0907572E \xf1\x89X310\xf1\x82\x0101', + b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101', b'\xf1\x875Q0907572G \xf1\x890571', b'\xf1\x875Q0907572H \xf1\x890620', b'\xf1\x875Q0907572J \xf1\x890654', @@ -442,6 +477,7 @@ FW_VERSIONS = { }, CAR.PASSAT_MK8: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8703N906026E \xf1\x892114', b'\xf1\x8704E906023AH\xf1\x893379', b'\xf1\x8704L906026ET\xf1\x891990', b'\xf1\x8704L906026GA\xf1\x892013', @@ -454,17 +490,20 @@ FW_VERSIONS = { b'\xf1\x870D9300014L \xf1\x895002', b'\xf1\x870D9300041A \xf1\x894801', b'\xf1\x870DD300045T \xf1\x891601', + b'\xf1\x870DL300011H \xf1\x895201', b'\xf1\x870GC300042H \xf1\x891404', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AE\xf1\x890195\xf1\x82\r56140056130012416612124111', b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311', + b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012516612125111', b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211', b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\0165915005914001344701311442900', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011111200631145171716121691132111', ], (Ecu.eps, 0x712, None): [ + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00611A1', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0060803', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0080803', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1', @@ -658,6 +697,7 @@ FW_VERSIONS = { b'\xf1\x870D9300013B \xf1\x894931', b'\xf1\x870D9300041N \xf1\x894512', b'\xf1\x870D9300043T \xf1\x899699', + b'\xf1\x870DD300046 \xf1\x891604', b'\xf1\x870DD300046A \xf1\x891602', b'\xf1\x870DD300046F \xf1\x891602', b'\xf1\x870DD300046G \xf1\x891601', @@ -668,6 +708,7 @@ FW_VERSIONS = { (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AB\xf1\x890388\xf1\x82\0211111001111111206110412111321139114', b'\xf1\x875Q0959655AM\xf1\x890315\xf1\x82\x1311111111111111311411011231129321212100', + b'\xf1\x875Q0959655AM\xf1\x890318\xf1\x82\x1311111111111112311411011531159321212100', b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1311110011131100311111011731179321342100', b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\023111112111111--171115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023121111111211--261117141112231291163221', @@ -797,18 +838,23 @@ FW_VERSIONS = { CAR.SKODA_KAROQ_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8705E906018P \xf1\x896020', + b'\xf1\x8705L906022BS\xf1\x890913', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870CW300041S \xf1\x891615', + b'\xf1\x870GC300014L \xf1\x892802', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\0161213001211001101131122012100', + b'\xf1\x873Q0959655DE\xf1\x890731\xf1\x82\x0e1213001211001101131121012J00', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\00567T6100500', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T6100700', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572M \xf1\x890233', + b'\xf1\x872Q0907572T \xf1\x890383', ], }, CAR.SKODA_KODIAQ_MK1: { diff --git a/selfdrive/car/volkswagen/volkswagencan.py b/selfdrive/car/volkswagen/volkswagencan.py deleted file mode 100644 index 402e90d35..000000000 --- a/selfdrive/car/volkswagen/volkswagencan.py +++ /dev/null @@ -1,100 +0,0 @@ -# CAN controls for MQB platform Volkswagen, Audi, Skoda and SEAT. -# PQ35/PQ46/NMS, and any future MLB, to come later. - -def create_mqb_steering_control(packer, bus, apply_steer, idx, lkas_enabled): - values = { - "SET_ME_0X3": 0x3, - "Assist_Torque": abs(apply_steer), - "Assist_Requested": lkas_enabled, - "Assist_VZ": 1 if apply_steer < 0 else 0, - "HCA_Available": 1, - "HCA_Standby": not lkas_enabled, - "HCA_Active": lkas_enabled, - "SET_ME_0XFE": 0xFE, - "SET_ME_0X07": 0x07, - } - return packer.make_can_msg("HCA_01", bus, values, idx) - -def create_mqb_hud_control(packer, bus, enabled, steering_pressed, hud_alert, left_lane_visible, right_lane_visible, - ldw_stock_values, left_lane_depart, right_lane_depart): - # Lane color reference: - # 0 (LKAS disabled) - off - # 1 (LKAS enabled, no lane detected) - dark gray - # 2 (LKAS enabled, lane detected) - light gray on VW, green or white on Audi depending on year or virtual cockpit. On a color MFD on a 2015 A3 TDI it is white, virtual cockpit on a 2018 A3 e-Tron its green. - # 3 (LKAS enabled, lane departure detected) - white on VW, red on Audi - values = ldw_stock_values.copy() - values.update({ - "LDW_Status_LED_gelb": 1 if enabled and steering_pressed else 0, - "LDW_Status_LED_gruen": 1 if enabled and not steering_pressed else 0, - "LDW_Lernmodus_links": 3 if left_lane_depart else 1 + left_lane_visible, - "LDW_Lernmodus_rechts": 3 if right_lane_depart else 1 + right_lane_visible, - "LDW_Texte": hud_alert, - }) - return packer.make_can_msg("LDW_02", bus, values) - -def create_mqb_acc_buttons_control(packer, bus, buttonStatesToSend, CS, idx): - values = { - "GRA_Hauptschalter": CS.graHauptschalter, - "GRA_Abbrechen": buttonStatesToSend["cancel"], - "GRA_Tip_Setzen": buttonStatesToSend["setCruise"], - "GRA_Tip_Hoch": buttonStatesToSend["accelCruise"], - "GRA_Tip_Runter": buttonStatesToSend["decelCruise"], - "GRA_Tip_Wiederaufnahme": buttonStatesToSend["resumeCruise"], - "GRA_Verstellung_Zeitluecke": 3 if buttonStatesToSend["gapAdjustCruise"] else 0, - "GRA_Typ_Hauptschalter": CS.graTypHauptschalter, - "GRA_Codierung": 2, - "GRA_Tip_Stufe_2": CS.graTipStufe2, - "GRA_ButtonTypeInfo": CS.graButtonTypeInfo - } - return packer.make_can_msg("GRA_ACC_01", bus, values, idx) - -def create_mqb_acc_02_control(packer, bus, acc_status, set_speed, lead_distance, idx): - values = { - "ACC_Status_Anzeige": 3 if acc_status == 5 else acc_status, - "ACC_Wunschgeschw": set_speed if set_speed < 250 else 327.36, - "ACC_Gesetzte_Zeitluecke": 3, - "ACC_Display_Prio": 3, - "ACC_Abstandsindex": lead_distance, - } - - return packer.make_can_msg("ACC_02", bus, values, idx) - -def create_mqb_acc_04_control(packer, bus, acc_04_stock_values, idx): - values = acc_04_stock_values.copy() - - # Suppress disengagement alert from stock radar when OP long is in use, but passthru FCW/AEB alerts - if values["ACC_Texte_braking_guard"] == 4: - values["ACC_Texte_braking_guard"] = 0 - - return packer.make_can_msg("ACC_04", bus, values, idx) - -def create_mqb_acc_06_control(packer, bus, enabled, acc_status, accel, acc_stopping, acc_starting, - cb_pos, cb_neg, acc_type, idx): - values = { - "ACC_Typ": acc_type, - "ACC_Status_ACC": acc_status, - "ACC_StartStopp_Info": enabled, - "ACC_Sollbeschleunigung_02": accel if enabled else 3.01, - "ACC_zul_Regelabw_unten": cb_neg, - "ACC_zul_Regelabw_oben": cb_pos, - "ACC_neg_Sollbeschl_Grad_02": 4.0 if enabled else 0, - "ACC_pos_Sollbeschl_Grad_02": 4.0 if enabled else 0, - "ACC_Anfahren": acc_starting, - "ACC_Anhalten": acc_stopping, - } - - return packer.make_can_msg("ACC_06", bus, values, idx) - -def create_mqb_acc_07_control(packer, bus, enabled, accel, acc_hold_request, acc_hold_release, - acc_hold_type, stopping_distance, idx): - values = { - "ACC_Distance_to_Stop": stopping_distance, - "ACC_Hold_Request": acc_hold_request, - "ACC_Freewheel_Type": 2 if enabled else 0, - "ACC_Hold_Type": acc_hold_type, - "ACC_Hold_Release": acc_hold_release, - "ACC_Accel_Secondary": 3.02, # not using this unless and until we understand its impact - "ACC_Accel_TSK": accel if enabled else 3.01, - } - - return packer.make_can_msg("ACC_07", bus, values, idx) diff --git a/selfdrive/common/tests/.gitignore b/selfdrive/common/tests/.gitignore deleted file mode 100644 index 1350b3b82..000000000 --- a/selfdrive/common/tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -test_util -test_swaglog diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 033072aa7..3b2307f04 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -12,6 +12,7 @@ import cereal.messaging as messaging from common.conversions import Conversions as CV from panda import ALTERNATIVE_EXPERIENCE from system.swaglog import cloudlog +from system.version import 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.lane_planner import CAMERA_OFFSET @@ -38,7 +39,7 @@ REPLAY = "REPLAY" in os.environ SIMULATION = "SIMULATION" in os.environ NOSENSOR = "NOSENSOR" in os.environ IGNORE_PROCESSES = {"uploader", "deleter", "loggerd", "logmessaged", "tombstoned", "statsd", - "logcatd", "proclogd", "clocksd", "updated", "timezoned", "manage_athenad"} | \ + "logcatd", "proclogd", "clocksd", "updated", "timezoned", "manage_athenad", "laikad"} | \ {k for k, v in managed_processes.items() if not v.enabled} ThermalStatus = log.DeviceState.ThermalStatus @@ -62,6 +63,9 @@ class Controls: def __init__(self, sm=None, pm=None, can_sock=None, CI=None): config_realtime_process(4, Priority.CTRL_HIGH) + # Ensure the current branch is cached, otherwise the first iteration of controlsd lags + self.branch = get_short_branch("") + # Setup sockets self.pm = pm if self.pm is None: @@ -92,7 +96,11 @@ class Controls: self.sm = sm if self.sm is None: - ignore = ['driverCameraState', 'managerState'] if SIMULATION else None + ignore = [] + if SIMULATION: + ignore += ['driverCameraState', 'managerState'] + if params.get_bool('WideCameraOnly'): + ignore += ['roadCameraState'] self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman', 'managerState', 'liveParameters', 'radarState'] + self.camera_packets + joystick_packet, @@ -104,6 +112,9 @@ class Controls: if not self.disengage_on_accelerator: self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS + if self.CP.dashcamOnly and params.get_bool("DashcamOverride"): + self.CP.dashcamOnly = False + # read params self.is_metric = params.get_bool("IsMetric") self.is_ldw_enabled = params.get_bool("IsLdwEnabled") @@ -152,6 +163,7 @@ class Controls: self.can_rcv_error = False self.soft_disable_timer = 0 self.v_cruise_kph = 255 + self.v_cruise_cluster_kph = 255 self.v_cruise_kph_last = 0 self.mismatch_counter = 0 self.cruise_mismatch_counter = 0 @@ -164,6 +176,7 @@ class Controls: self.logged_comm_issue = None self.button_timers = {ButtonEvent.Type.decelCruise: 0, ButtonEvent.Type.accelCruise: 0} self.last_actuators = car.CarControl.Actuators.new_message() + self.steer_limited = False self.desired_curvature = 0.0 self.desired_curvature_rate = 0.0 @@ -217,12 +230,8 @@ class Controls: if not self.CP.notCar: self.events.add_from_msg(self.sm['driverMonitoringState'].events) - # Handle car events. Ignore when CAN is invalid - if CS.canTimeout: - self.events.add(EventName.canBusMissing) - elif not CS.canValid: - self.events.add(EventName.canError) - else: + # Add car events, ignore if CAN isn't valid + if CS.canValid: self.events.add_from_msg(CS.events) # Create events for temperature, disk space, and memory @@ -302,19 +311,24 @@ class Controls: self.events.add(EventName.cameraFrameRate) if self.rk.lagging: self.events.add(EventName.controlsdLagging) - if len(self.sm['radarState'].radarErrors): + if len(self.sm['radarState'].radarErrors) or not self.sm.all_checks(['radarState']): self.events.add(EventName.radarFault) if not self.sm.valid['pandaStates']: self.events.add(EventName.usbError) + if CS.canTimeout: + self.events.add(EventName.canBusMissing) + elif not CS.canValid: + self.events.add(EventName.canError) # generic catch-all. ideally, a more specific event should be added above instead - no_system_errors = len(self.events) != num_events - if (not self.sm.all_checks() or self.can_rcv_error) and no_system_errors and CS.canValid and not CS.canTimeout: + has_disable_events = self.events.any(ET.NO_ENTRY) and (self.events.any(ET.SOFT_DISABLE) or self.events.any(ET.IMMEDIATE_DISABLE)) + no_system_errors = (not has_disable_events) or (len(self.events) == num_events) + if (not self.sm.all_checks() or self.can_rcv_error) and no_system_errors: if not self.sm.all_alive(): self.events.add(EventName.commIssue) elif not self.sm.all_freq_ok(): self.events.add(EventName.commIssueAvgFreq) - else: # invalid or can_rcv_error. + else: # invalid or can_rcv_error. self.events.add(EventName.commIssue) logs = { @@ -333,7 +347,7 @@ class Controls: self.events.add(EventName.vehicleModelInvalid) if not self.sm['lateralPlan'].mpcSolutionValid: self.events.add(EventName.plannerError) - if not self.sm['liveLocationKalman'].sensorsOK and not NOSENSOR: + 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: @@ -399,7 +413,8 @@ class Controls: if not self.initialized: all_valid = CS.canValid and self.sm.all_checks() - if all_valid or self.sm.frame * DT_CTRL > 3.5 or SIMULATION: + timed_out = self.sm.frame * DT_CTRL > (6. if REPLAY else 3.5) + if all_valid or timed_out or SIMULATION: if not self.read_only: self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan']) self.initialized = True @@ -441,11 +456,14 @@ class Controls: if not self.CP.pcmCruise: self.v_cruise_kph = update_v_cruise(self.v_cruise_kph, CS.vEgo, CS.gasPressed, CS.buttonEvents, self.button_timers, self.enabled, self.is_metric) + self.v_cruise_cluster_kph = self.v_cruise_kph else: if CS.cruiseState.available: self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH + self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH else: self.v_cruise_kph = 0 + self.v_cruise_cluster_kph = 0 # decrement the soft disable timer at every step, as it's reset on # entrance in SOFT_DISABLING state @@ -525,6 +543,7 @@ class Controls: self.current_alert_types.append(ET.ENABLE) if not self.CP.pcmCruise: self.v_cruise_kph = initialize_v_cruise(CS.vEgo, CS.buttonEvents, self.v_cruise_kph_last) + self.v_cruise_cluster_kph = self.v_cruise_kph # Check if openpilot is engaged and actuators are enabled self.enabled = self.state in ENABLED_STATES @@ -576,7 +595,7 @@ class Controls: lat_plan.curvatures, lat_plan.curvatureRates) actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, params, - self.last_actuators, self.desired_curvature, + self.last_actuators, self.steer_limited, self.desired_curvature, self.desired_curvature_rate, self.sm['liveLocationKalman']) else: lac_log = log.ControlsState.LateralDebugState.new_message() @@ -595,13 +614,25 @@ class Controls: lac_log.saturated = abs(actuators.steer) >= 0.9 # Send a "steering required alert" if saturation count has reached the limit - if lac_log.active and lac_log.saturated and not CS.steeringPressed: + if lac_log.active and not CS.steeringPressed and self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode: + undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2 + turning = abs(lac_log.desiredLateralAccel) > 1.0 + good_speed = CS.vEgo > 5 + max_torque = abs(self.last_actuators.steer) > 0.99 + if undershooting and turning and good_speed and max_torque: + self.events.add(EventName.steerSaturated) + elif lac_log.active and not CS.steeringPressed and lac_log.saturated: dpath_points = lat_plan.dPathPoints if len(dpath_points): # Check if we deviated from the path # TODO use desired vs actual curvature - left_deviation = actuators.steer > 0 and dpath_points[0] < -0.20 - right_deviation = actuators.steer < 0 and dpath_points[0] > 0.20 + if self.CP.steerControlType == car.CarParams.SteerControlType.angle: + steering_value = actuators.steeringAngleDeg + else: + steering_value = actuators.steer + + left_deviation = steering_value > 0 and dpath_points[0] < -0.20 + right_deviation = steering_value < 0 and dpath_points[0] > 0.20 if left_deviation or right_deviation: self.events.add(EventName.steerSaturated) @@ -644,8 +675,12 @@ class Controls: if self.joystick_mode and self.sm.rcv_frame['testJoystick'] > 0 and self.sm['testJoystick'].buttons[0]: CC.cruiseControl.cancel = True + speeds = self.sm['longitudinalPlan'].speeds + if len(speeds): + CC.cruiseControl.resume = self.enabled and CS.cruiseState.standstill and speeds[-1] > 0.1 + hudControl = CC.hudControl - hudControl.setSpeed = float(self.v_cruise_kph * CV.KPH_TO_MS) + hudControl.setSpeed = float(self.v_cruise_cluster_kph * CV.KPH_TO_MS) hudControl.speedVisible = self.enabled hudControl.lanesVisible = self.enabled hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead @@ -692,6 +727,7 @@ class Controls: self.last_actuators, can_sends = self.CI.apply(CC) self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid)) CC.actuatorsOutput = self.last_actuators + self.steer_limited = abs(CC.actuators.steer - CC.actuatorsOutput.steer) > 1e-2 force_decel = (self.sm['driverMonitoringState'].awarenessStatus < 0.) or \ (self.state == State.softDisabling) @@ -728,6 +764,7 @@ class Controls: controlsState.longControlState = self.LoC.long_control_state controlsState.vPid = float(self.LoC.v_pid) controlsState.vCruise = float(self.v_cruise_kph) + controlsState.vCruiseCluster = float(self.v_cruise_cluster_kph) controlsState.upAccelCmd = float(self.LoC.pid.p) controlsState.uiAccelCmd = float(self.LoC.pid.i) controlsState.ufAccelCmd = float(self.LoC.pid.f) diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index 978c38653..19ab2da21 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -40,12 +40,12 @@ class DesireHelper: self.prev_one_blinker = False self.desire = log.LateralPlan.Desire.none - def update(self, carstate, active, lane_change_prob): + def update(self, carstate, lateral_active, lane_change_prob): v_ego = carstate.vEgo one_blinker = carstate.leftBlinker != carstate.rightBlinker below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN - if not active or self.lane_change_timer > LANE_CHANGE_TIME_MAX: + if not lateral_active or self.lane_change_timer > LANE_CHANGE_TIME_MAX: self.lane_change_state = LaneChangeState.off self.lane_change_direction = LaneChangeDirection.none else: diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 1fecdd7c6..d79f94bbf 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -122,7 +122,7 @@ def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures, curvature_rates): # This is the "desired rate of the setpoint" not an actual desired rate desired_curvature_rate = curvature_rates[0] - max_curvature_rate = MAX_LATERAL_JERK / (v_ego**2) + max_curvature_rate = MAX_LATERAL_JERK / (v_ego**2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755 safe_desired_curvature_rate = clip(desired_curvature_rate, -max_curvature_rate, max_curvature_rate) diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index cc63d4995..de666fd50 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -222,7 +222,7 @@ def user_soft_disable_alert(alert_text_2: str) -> AlertCallbackType: return func def startup_master_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: - branch = get_short_branch("") + branch = get_short_branch("") # Ensure get_short_branch is cached to avoid lags on startup if "REPLAY" in os.environ: branch = "replay" @@ -439,7 +439,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { "Steering Temporarily Unavailable", "", AlertStatus.userPrompt, AlertSize.small, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.), + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.8), }, EventName.preDriverDistracted: { @@ -656,11 +656,12 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { EventName.sensorDataInvalid: { ET.PERMANENT: Alert( - "No Data from Device Sensors", - "Reboot your Device", + "Sensor Data Invalid", + "Ensure device is mounted securely", AlertStatus.normal, AlertSize.mid, Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=1.), - ET.NO_ENTRY: NoEntryAlert("No Data from Device Sensors"), + ET.NO_ENTRY: NoEntryAlert("Sensor Data Invalid"), + ET.SOFT_DISABLE: soft_disable_alert("Sensor Data Invalid"), }, EventName.noGps: { diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index 785c8faa8..78b59fda5 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -16,14 +16,14 @@ class LatControl(ABC): self.steer_max = 1.0 @abstractmethod - def update(self, active, CS, VM, params, last_actuators, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): pass def reset(self): self.sat_count = 0. - def _check_saturation(self, saturated, CS): - if saturated and CS.vEgo > 10. and not CS.steeringRateLimited and not CS.steeringPressed: + def _check_saturation(self, saturated, CS, steer_limited): + if saturated and CS.vEgo > 10. and not steer_limited and not CS.steeringPressed: self.sat_count += self.sat_count_rate else: self.sat_count -= self.sat_count_rate diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index 2eee71c70..0e5be4a97 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -7,7 +7,7 @@ STEER_ANGLE_SATURATION_THRESHOLD = 2.5 # Degrees class LatControlAngle(LatControl): - def update(self, active, CS, VM, params, last_actuators, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): angle_log = log.ControlsState.LateralAngleState.new_message() if CS.vEgo < MIN_STEER_SPEED or not active: @@ -19,7 +19,7 @@ class LatControlAngle(LatControl): angle_steers_des += params.angleOffsetDeg angle_control_saturated = abs(angle_steers_des - CS.steeringAngleDeg) > STEER_ANGLE_SATURATION_THRESHOLD - angle_log.saturated = self._check_saturation(angle_control_saturated, CS) + angle_log.saturated = self._check_saturation(angle_control_saturated, CS, steer_limited) angle_log.steeringAngleDeg = float(CS.steeringAngleDeg) angle_log.steeringAngleDesiredDeg = angle_steers_des return 0, float(angle_steers_des), angle_log diff --git a/selfdrive/controls/lib/latcontrol_indi.py b/selfdrive/controls/lib/latcontrol_indi.py index 79c881d11..2bc3cef76 100644 --- a/selfdrive/controls/lib/latcontrol_indi.py +++ b/selfdrive/controls/lib/latcontrol_indi.py @@ -63,7 +63,7 @@ class LatControlINDI(LatControl): self.steer_filter.x = 0. self.speed = 0. - def update(self, active, CS, VM, params, last_actuators, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): self.speed = CS.vEgo # Update Kalman filter y = np.array([[math.radians(CS.steeringAngleDeg)], [math.radians(CS.steeringRateDeg)]]) @@ -115,6 +115,6 @@ class LatControlINDI(LatControl): indi_log.delayedOutput = float(self.steer_filter.x) indi_log.delta = float(delta_u) indi_log.output = float(output_steer) - indi_log.saturated = self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS) + indi_log.saturated = self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited) return float(output_steer), float(steers_des), indi_log diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index 5dd1b20ae..6bd678073 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -17,7 +17,7 @@ class LatControlPID(LatControl): super().reset() self.pid.reset() - def update(self, active, CS, VM, params, last_actuators, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): pid_log = log.ControlsState.LateralPIDState.new_message() pid_log.steeringAngleDeg = float(CS.steeringAngleDeg) pid_log.steeringRateDeg = float(CS.steeringRateDeg) @@ -43,6 +43,6 @@ class LatControlPID(LatControl): pid_log.i = self.pid.i pid_log.f = self.pid.f pid_log.output = output_steer - pid_log.saturated = self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS) + pid_log.saturated = self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited) return output_steer, angle_steers_des, pid_log diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index f72ffc4b8..c4604d90e 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -22,16 +22,6 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY FRICTION_THRESHOLD = 0.2 -def set_torque_tune(tune, MAX_LAT_ACCEL=2.5, FRICTION=0.01, steering_angle_deadzone_deg=0.0): - tune.init('torque') - tune.torque.useSteeringAngle = True - tune.torque.kp = 1.0 / MAX_LAT_ACCEL - tune.torque.kf = 1.0 / MAX_LAT_ACCEL - tune.torque.ki = 0.1 / MAX_LAT_ACCEL - tune.torque.friction = FRICTION - tune.torque.steeringAngleDeadzoneDeg = steering_angle_deadzone_deg - - class LatControlTorque(LatControl): def __init__(self, CP, CI): super().__init__(CP, CI) @@ -43,7 +33,7 @@ class LatControlTorque(LatControl): self.kf = CP.lateralTuning.torque.kf self.steering_angle_deadzone_deg = CP.lateralTuning.torque.steeringAngleDeadzoneDeg - def update(self, active, CS, VM, params, last_actuators, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): pid_log = log.ControlsState.LateralTorqueState.new_message() if CS.vEgo < MIN_STEER_SPEED or not active: @@ -66,17 +56,17 @@ class LatControlTorque(LatControl): lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 - low_speed_factor = interp(CS.vEgo, [0, 15], [500, 0]) + low_speed_factor = interp(CS.vEgo, [0, 10, 20], [500, 500, 200]) setpoint = desired_lateral_accel + low_speed_factor * desired_curvature measurement = actual_lateral_accel + low_speed_factor * actual_curvature - error = apply_deadzone(setpoint - measurement, lateral_accel_deadzone) + error = setpoint - measurement pid_log.error = error ff = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY # convert friction into lateral accel units for feedforward - friction_compensation = interp(error, [-FRICTION_THRESHOLD, FRICTION_THRESHOLD], [-self.friction, self.friction]) + friction_compensation = interp(apply_deadzone(error, lateral_accel_deadzone), [-FRICTION_THRESHOLD, FRICTION_THRESHOLD], [-self.friction, self.friction]) ff += friction_compensation / self.kf - freeze_integrator = CS.steeringRateLimited or CS.steeringPressed or CS.vEgo < 5 + freeze_integrator = steer_limited or CS.steeringPressed or CS.vEgo < 5 output_torque = self.pid.update(error, feedforward=ff, speed=CS.vEgo, @@ -88,7 +78,7 @@ class LatControlTorque(LatControl): pid_log.d = self.pid.d pid_log.f = self.pid.f pid_log.output = -output_torque - pid_log.saturated = self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS) + pid_log.saturated = self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited) pid_log.actualLateralAccel = actual_lateral_accel pid_log.desiredLateralAccel = desired_lateral_accel diff --git a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py index a1037f040..c0e735816 100755 --- a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py +++ b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py @@ -89,7 +89,7 @@ def gen_lat_ocp(): # TODO hacky weights to keep behavior the same ocp.model.cost_y_expr = vertcat(y_ego, ((v_ego +5.0) * psi_ego), - ((v_ego +5.0) * 4 * curv_rate)) + ((v_ego + 5.0) * 4.0 * curv_rate)) ocp.model.cost_y_expr_e = vertcat(y_ego, ((v_ego +5.0) * psi_ego)) @@ -147,7 +147,7 @@ class LateralMpc(): #TODO hacky weights to keep behavior the same self.solver.cost_set(N, 'W', (3/20.)*W[:2,:2]) - def run(self, x0, p, y_pts, heading_pts): + def run(self, x0, p, y_pts, heading_pts, curv_rate_pts): x0_cp = np.copy(x0) p_cp = np.copy(p) self.solver.constraints_set(0, "lbx", x0_cp) @@ -156,6 +156,7 @@ class LateralMpc(): v_ego = p_cp[0] # rotation_radius = p_cp[1] self.yref[:,1] = heading_pts*(v_ego+5.0) + self.yref[:,2] = curv_rate_pts * (v_ego+5.0) * 4.0 for i in range(N): self.solver.cost_set(i, "yref", self.yref[i]) self.solver.set(i, "p", p_cp) diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 019a19fb1..29dfc7711 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -3,7 +3,7 @@ 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.drive_helpers import CONTROL_N, MPC_COST_LAT, LAT_MPC_N, CAR_ROTATION_RADIUS +from selfdrive.controls.lib.drive_helpers import CONTROL_N, MPC_COST_LAT, LAT_MPC_N from selfdrive.controls.lib.lane_planner import LanePlanner, TRAJECTORY_SIZE from selfdrive.controls.lib.desire_helper import DesireHelper import cereal.messaging as messaging @@ -11,17 +11,21 @@ from cereal import log class LateralPlanner: - def __init__(self, use_lanelines=True, wide_camera=False): + def __init__(self, CP, use_lanelines=True, wide_camera=False): self.use_lanelines = use_lanelines self.LP = LanePlanner(wide_camera) self.DH = DesireHelper() + # Vehicle model parameters used to calculate lateral movement of car + self.factor1 = CP.wheelbase - CP.centerToFront + self.factor2 = (CP.centerToFront * CP.mass) / (CP.wheelbase * CP.tireStiffnessRear) self.last_cloudlog_t = 0 self.solution_invalid_cnt = 0 self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3)) self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3)) self.plan_yaw = np.zeros((TRAJECTORY_SIZE,)) + self.plan_curv_rate = np.zeros((TRAJECTORY_SIZE,)) self.t_idxs = np.arange(TRAJECTORY_SIZE) self.y_pts = np.zeros(TRAJECTORY_SIZE) @@ -42,13 +46,13 @@ class LateralPlanner: if len(md.position.x) == TRAJECTORY_SIZE and len(md.orientation.x) == TRAJECTORY_SIZE: self.path_xyz = np.column_stack([md.position.x, md.position.y, md.position.z]) self.t_idxs = np.array(md.position.t) - self.plan_yaw = list(md.orientation.z) + self.plan_yaw = np.array(md.orientation.z) if len(md.position.xStd) == TRAJECTORY_SIZE: self.path_xyz_stds = np.column_stack([md.position.xStd, md.position.yStd, md.position.zStd]) # Lane change logic lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob - self.DH.update(sm['carState'], sm['controlsState'].active, lane_change_prob) + self.DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob) # Turn off lanes during lane change if self.DH.desire == log.LateralPlan.Desire.laneChangeRight or self.DH.desire == log.LateralPlan.Desire.laneChangeLeft: @@ -67,16 +71,19 @@ class LateralPlanner: y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1]) heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw) + curv_rate_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_curv_rate) self.y_pts = y_pts assert len(y_pts) == LAT_MPC_N + 1 assert len(heading_pts) == LAT_MPC_N + 1 - # self.x0[4] = v_ego - p = np.array([v_ego, CAR_ROTATION_RADIUS]) + assert len(curv_rate_pts) == LAT_MPC_N + 1 + lateral_factor = max(0, self.factor1 - (self.factor2 * v_ego**2)) + p = np.array([v_ego, lateral_factor]) self.lat_mpc.run(self.x0, p, y_pts, - heading_pts) + heading_pts, + curv_rate_pts) # init state for next # mpc.u_sol is the desired curvature rate given x0 curv state. # with x0[3] = measured_curvature, this would be the actual desired rate. diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index bc0fc9fea..94efd7a87 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -251,7 +251,7 @@ class LongitudinalMpc: self.solver.cost_set(i, 'Zl', Zl) def set_weights_for_xva_policy(self): - W = np.asfortranarray(np.diag([0., 10., 1., 10., 0.0, 1.])) + W = np.asfortranarray(np.diag([0., 0.2, 0.25, 1., 0.0, .1])) for i in range(N): self.solver.cost_set(i, 'W', W) # Setting the slice without the copy make the array not contiguous, diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index d4a6aaef8..cf5113677 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -66,11 +66,11 @@ class Planner: v_cruise_kph = min(v_cruise_kph, V_CRUISE_MAX) v_cruise = v_cruise_kph * CV.KPH_TO_MS - long_control_state = sm['controlsState'].longControlState + long_control_off = sm['controlsState'].longControlState == LongCtrlState.off force_slow_decel = sm['controlsState'].forceDecel # Reset current state when not engaged, or user is controlling the speed - reset_state = long_control_state == LongCtrlState.off + reset_state = long_control_off if self.CP.openpilotLongitudinalControl else not sm['controlsState'].enabled # No change cost when user is controlling the speed, or when standstill prev_accel_constraint = not (reset_state or sm['carState'].standstill) diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py index 503eaaa6a..f15ab2fa5 100755 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -26,7 +26,6 @@ class TestLatControl(unittest.TestCase): controller = controller(CP, CI) - CS = car.CarState.new_message() CS.vEgo = 30 @@ -35,7 +34,7 @@ class TestLatControl(unittest.TestCase): params = log.LiveParametersData.new_message() for _ in range(1000): - _, _, lac_log = controller.update(True, CS, CP, VM, params, last_actuators, 1, 0) + _, _, lac_log = controller.update(True, CS, CP, VM, params, last_actuators, True, 1, 0) self.assertTrue(lac_log.saturated) diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 9356a55d8..ca7523f2e 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -22,10 +22,10 @@ def plannerd_thread(sm=None, pm=None): cloudlog.event("e2e mode", on=use_lanelines) longitudinal_planner = Planner(CP) - lateral_planner = LateralPlanner(use_lanelines=use_lanelines, wide_camera=wide_camera) + lateral_planner = LateralPlanner(CP, use_lanelines=use_lanelines, wide_camera=wide_camera) if sm is None: - sm = messaging.SubMaster(['carState', 'controlsState', 'radarState', 'modelV2'], + sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2'], poll=['radarState', 'modelV2'], ignore_avg_freq=['radarState']) if pm is None: diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index b2c991445..3d958139d 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -102,7 +102,7 @@ class RadarD(): self.ready = False - def update(self, sm, rr, enable_lead): + def update(self, sm, rr): self.current_time = 1e-9*max(sm.logMonoTime.values()) if sm.updated['carState']: @@ -169,11 +169,10 @@ class RadarD(): radarState.radarErrors = list(rr.errors) radarState.carStateMonoTime = sm.logMonoTime['carState'] - if enable_lead: - leads_v3 = sm['modelV2'].leadsV3 - if len(leads_v3) > 1: - radarState.leadOne = get_lead(self.v_ego, self.ready, clusters, leads_v3[0], low_speed_override=True) - radarState.leadTwo = get_lead(self.v_ego, self.ready, clusters, leads_v3[1], low_speed_override=False) + leads_v3 = sm['modelV2'].leadsV3 + if len(leads_v3) > 1: + radarState.leadOne = get_lead(self.v_ego, self.ready, clusters, leads_v3[0], low_speed_override=True) + radarState.leadTwo = get_lead(self.v_ego, self.ready, clusters, leads_v3[1], low_speed_override=False) return dat @@ -203,9 +202,6 @@ def radard_thread(sm=None, pm=None, can_sock=None): rk = Ratekeeper(1.0 / CP.radarTimeStep, print_delay_threshold=None) RD = RadarD(CP.radarTimeStep, RI.delay) - # TODO: always log leads once we can hide them conditionally - enable_lead = CP.openpilotLongitudinalControl or not CP.radarOffCan - while 1: can_strings = messaging.drain_sock_raw(can_sock, wait_for_one=True) rr = RI.update(can_strings) @@ -215,7 +211,7 @@ def radard_thread(sm=None, pm=None, can_sock=None): sm.update(0) - dat = RD.update(sm, rr, enable_lead) + dat = RD.update(sm, rr) dat.radarState.cumLagMs = -rk.remaining*1000. pm.send('radarState', dat) diff --git a/selfdrive/controls/tests/test_alerts.py b/selfdrive/controls/tests/test_alerts.py index 2bd904b57..79c56d6bc 100755 --- a/selfdrive/controls/tests/test_alerts.py +++ b/selfdrive/controls/tests/test_alerts.py @@ -48,20 +48,17 @@ class TestAlerts(unittest.TestCase): # ensure alert text doesn't exceed allowed width def test_alert_text_length(self): font_path = os.path.join(BASEDIR, "selfdrive/assets/fonts") - regular_font_path = os.path.join(font_path, "opensans_semibold.ttf") - bold_font_path = os.path.join(font_path, "opensans_semibold.ttf") - semibold_font_path = os.path.join(font_path, "opensans_semibold.ttf") - - max_text_width = 1920 - 300 # full screen width is useable, minus sidebar - # TODO: get exact scale factor. found this empirically, works well enough - font_scale_factor = 1.55 # factor to scale from nanovg units to PIL + regular_font_path = os.path.join(font_path, "Inter-SemiBold.ttf") + bold_font_path = os.path.join(font_path, "Inter-Bold.ttf") + semibold_font_path = os.path.join(font_path, "Inter-SemiBold.ttf") + max_text_width = 2160 - 300 # full screen width is useable, minus sidebar draw = ImageDraw.Draw(Image.new('RGB', (0, 0))) fonts = { - AlertSize.small: [ImageFont.truetype(semibold_font_path, int(40 * font_scale_factor))], - AlertSize.mid: [ImageFont.truetype(bold_font_path, int(48 * font_scale_factor)), - ImageFont.truetype(regular_font_path, int(36 * font_scale_factor))], + AlertSize.small: [ImageFont.truetype(semibold_font_path, 74)], + AlertSize.mid: [ImageFont.truetype(bold_font_path, 88), + ImageFont.truetype(regular_font_path, 66)], } for alert in ALERTS: diff --git a/selfdrive/controls/tests/test_lateral_mpc.py b/selfdrive/controls/tests/test_lateral_mpc.py index 4630e28a6..4864dbdc0 100644 --- a/selfdrive/controls/tests/test_lateral_mpc.py +++ b/selfdrive/controls/tests/test_lateral_mpc.py @@ -13,6 +13,7 @@ def run_mpc(lat_mpc=None, v_ref=30., x_init=0., y_init=0., psi_init=0., curvatur y_pts = poly_shift * np.ones(LAT_MPC_N + 1) heading_pts = np.zeros(LAT_MPC_N + 1) + curv_rate_pts = np.zeros(LAT_MPC_N + 1) x0 = np.array([x_init, y_init, psi_init, curvature_init]) p = np.array([v_ref, CAR_ROTATION_RADIUS]) @@ -20,7 +21,7 @@ def run_mpc(lat_mpc=None, v_ref=30., x_init=0., y_init=0., psi_init=0., curvatur # converge in no more than 10 iterations for _ in range(10): lat_mpc.run(x0, p, - y_pts, heading_pts) + y_pts, heading_pts, curv_rate_pts) return lat_mpc.x_sol diff --git a/selfdrive/controls/tests/test_startup.py b/selfdrive/controls/tests/test_startup.py index 9d1345304..a94311c8c 100755 --- a/selfdrive/controls/tests/test_startup.py +++ b/selfdrive/controls/tests/test_startup.py @@ -42,27 +42,27 @@ class TestStartup(unittest.TestCase): # TODO: test EventName.startup for release branches # officially supported car - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS), - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS), + (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS, "toyota"), + (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS, "toyota"), # dashcamOnly car - (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS), - (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS), + (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS, "mazda"), + (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS, "mazda"), # unrecognized car with no fw - (EventName.startupNoFw, None, None), - (EventName.startupNoFw, None, None), + (EventName.startupNoFw, None, None, ""), + (EventName.startupNoFw, None, None, ""), # unrecognized car - (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1]), - (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1]), + (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1], "toyota"), + (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1], "toyota"), # fuzzy match - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY), - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY), + (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), + (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), ]) @with_processes(['controlsd']) - def test_startup_alert(self, expected_event, car_model, fw_versions): + def test_startup_alert(self, expected_event, car_model, fw_versions, brand): # TODO: this should be done without any real sockets controls_sock = messaging.sub_sock("controlsState") @@ -82,6 +82,7 @@ class TestStartup(unittest.TestCase): f.ecu = ecu f.address = addr f.fwVersion = version + f.brand = brand if subaddress is not None: f.subAddress = subaddress diff --git a/selfdrive/debug/can_table.py b/selfdrive/debug/can_table.py index e8cd084a3..11d070e70 100755 --- a/selfdrive/debug/can_table.py +++ b/selfdrive/debug/can_table.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import argparse -import pandas as pd # pylint: disable=import-error +import pandas as pd import cereal.messaging as messaging diff --git a/selfdrive/debug/check_freq.py b/selfdrive/debug/check_freq.py index 424ad67b6..b6f3c91bd 100755 --- a/selfdrive/debug/check_freq.py +++ b/selfdrive/debug/check_freq.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -# type: ignore - import argparse import numpy as np from collections import defaultdict, deque +from typing import DefaultDict, Deque + from common.realtime import sec_since_boot import cereal.messaging as messaging @@ -19,8 +19,8 @@ if __name__ == "__main__": socket_names = args.socket sockets = {} - rcv_times = defaultdict(lambda: deque(maxlen=100)) - valids = defaultdict(lambda: deque(maxlen=100)) + rcv_times: DefaultDict[str, Deque[float]] = defaultdict(lambda: deque(maxlen=100)) + valids: DefaultDict[str, Deque[bool]] = defaultdict(lambda: deque(maxlen=100)) t = sec_since_boot() for name in socket_names: @@ -31,6 +31,9 @@ if __name__ == "__main__": while True: for socket in poller.poll(100): msg = messaging.recv_one(socket) + if msg is None: + continue + name = msg.which() t = sec_since_boot() diff --git a/selfdrive/debug/check_lag.py b/selfdrive/debug/check_lag.py index c92264298..141156db9 100755 --- a/selfdrive/debug/check_lag.py +++ b/selfdrive/debug/check_lag.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# type: ignore +from typing import Dict import cereal.messaging as messaging from cereal.services import service_list @@ -10,7 +10,7 @@ TO_CHECK = ['carState'] if __name__ == "__main__": sm = messaging.SubMaster(TO_CHECK) - prev_t = {} + prev_t: Dict[str, float] = {} while True: sm.update() diff --git a/selfdrive/debug/check_timings.py b/selfdrive/debug/check_timings.py index 03e39fd70..69304f97b 100755 --- a/selfdrive/debug/check_timings.py +++ b/selfdrive/debug/check_timings.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 -# type: ignore + import sys import time import numpy as np +from typing import DefaultDict, Deque from collections import defaultdict, deque import cereal.messaging as messaging socks = {s: messaging.sub_sock(s, conflate=False) for s in sys.argv[1:]} -ts = defaultdict(lambda: deque(maxlen=100)) +ts: DefaultDict[str, Deque[float]] = defaultdict(lambda: deque(maxlen=100)) if __name__ == "__main__": while True: diff --git a/selfdrive/debug/count_events.py b/selfdrive/debug/count_events.py index c3870e0f9..93dd5bdc4 100755 --- a/selfdrive/debug/count_events.py +++ b/selfdrive/debug/count_events.py @@ -1,8 +1,11 @@ #!/usr/bin/env python3 import sys +import math +import datetime from collections import Counter from pprint import pprint from tqdm import tqdm +from typing import cast from cereal.services import service_list from tools.lib.route import Route @@ -17,6 +20,8 @@ if __name__ == "__main__": cams = [s for s in service_list if s.endswith('CameraState')] cnt_cameras = dict.fromkeys(cams, 0) + start_time = math.inf + end_time = -math.inf for q in tqdm(r.qlog_paths()): if q is None: continue @@ -31,6 +36,10 @@ if __name__ == "__main__": if not msg.valid: cnt_valid[msg.which()] += 1 + end_time = max(end_time, msg.logMonoTime) + start_time = min(start_time, msg.logMonoTime) + + duration = (end_time - start_time) / 1e9 print("Events") pprint(cnt_events) @@ -42,4 +51,9 @@ if __name__ == "__main__": print("\n") print("Cameras") for k, v in cnt_cameras.items(): - print(" ", k.ljust(20), v) + s = service_list[k] + expected_frames = int(s.frequency * duration / cast(float, s.decimation)) + print(" ", k.ljust(20), f"{v}, {v/expected_frames:.1%} of expected") + + print("\n") + print("Route duration", datetime.timedelta(seconds=duration)) diff --git a/selfdrive/debug/disable_ecu.py b/selfdrive/debug/disable_ecu.py deleted file mode 100644 index af007207e..000000000 --- a/selfdrive/debug/disable_ecu.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 -import traceback - -import cereal.messaging as messaging -from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery -from system.swaglog import cloudlog - -EXT_DIAG_REQUEST = b'\x10\x03' -EXT_DIAG_RESPONSE = b'\x50\x03' -COM_CONT_REQUEST = b'\x28\x83\x03' -COM_CONT_RESPONSE = b'' - -def disable_ecu(ecu_addr, logcan, sendcan, bus, timeout=0.5, retry=5, debug=False): - print(f"ecu disable {hex(ecu_addr)} ...") - for i in range(retry): - try: - # enter extended diagnostic session - query = IsoTpParallelQuery(sendcan, logcan, bus, [ecu_addr], [EXT_DIAG_REQUEST], [EXT_DIAG_RESPONSE], debug=debug) - for addr, dat in query.get_data(timeout).items(): # pylint: disable=unused-variable - print("ecu communication control disable tx/rx ...") - # communication control disable tx and rx - query = IsoTpParallelQuery(sendcan, logcan, bus, [ecu_addr], [COM_CONT_REQUEST], [COM_CONT_RESPONSE], debug=debug) - query.get_data(0) - return True - print(f"ecu disable retry ({i+1}) ...") - except Exception: - cloudlog.warning(f"ecu disable exception: {traceback.format_exc()}") - - return False - - -if __name__ == "__main__": - import time - sendcan = messaging.pub_sock('sendcan') - logcan = messaging.sub_sock('can') - time.sleep(1) - - # honda bosch radar disable - disabled = disable_ecu(0x18DAB0F1, logcan, sendcan, 1, debug=False) - print(f"disabled: {disabled}") diff --git a/selfdrive/debug/dump_car_info.py b/selfdrive/debug/dump_car_info.py new file mode 100755 index 000000000..c9a21c284 --- /dev/null +++ b/selfdrive/debug/dump_car_info.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import argparse +import pickle + +from selfdrive.car.docs import get_all_car_info + + +def dump_car_info(path): + with open(path, 'wb') as f: + pickle.dump(get_all_car_info(), f) + print(f'Dumping car info to {path}') + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--path", required=True) + args = parser.parse_args() + dump_car_info(args.path) diff --git a/selfdrive/debug/live_cpu_and_temp.py b/selfdrive/debug/live_cpu_and_temp.py index baeebb1c4..c35afc474 100755 --- a/selfdrive/debug/live_cpu_and_temp.py +++ b/selfdrive/debug/live_cpu_and_temp.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 import argparse import capnp +from collections import defaultdict from cereal.messaging import SubMaster from common.numpy_fast import mean -from typing import Optional +from typing import Optional, Dict def cputime_total(ct): return ct.user + ct.nice + ct.system + ct.idle + ct.iowait + ct.irq + ct.softirq @@ -75,7 +76,7 @@ if __name__ == "__main__": print(f"CPU {100.0 * mean(cores):.2f}% - RAM: {last_mem:.2f}% - Temp {last_temp:.2f}C") if args.cpu and prev_proclog is not None and prev_proclog_t is not None: - procs = {} + procs: Dict[str, float] = defaultdict(float) dt = (sm.logMonoTime['procLog'] - prev_proclog_t) / 1e9 for proc in m.procs: try: @@ -83,12 +84,12 @@ if __name__ == "__main__": prev_proc = [p for p in prev_proclog.procs if proc.pid == p.pid][0] cpu_time = proc_cputime_total(proc) - proc_cputime_total(prev_proc) cpu_usage = cpu_time / dt * 100. - procs[name] = cpu_usage + procs[name] += cpu_usage except IndexError: pass print("Top CPU usage:") - for k, v in sorted(procs.items(), key=lambda item: item[1], reverse=True)[:10]: # type: ignore + for k, v in sorted(procs.items(), key=lambda item: item[1], reverse=True)[:10]: print(f"{k.rjust(70)} {v:.2f} %") print() diff --git a/selfdrive/debug/print_docs_diff.py b/selfdrive/debug/print_docs_diff.py new file mode 100755 index 000000000..b7721ca3a --- /dev/null +++ b/selfdrive/debug/print_docs_diff.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +import argparse +from collections import defaultdict +import difflib +import pickle + +from selfdrive.car.docs import get_all_car_info +from selfdrive.car.docs_definitions import Column + +FOOTNOTE_TAG = "{}" +STAR_ICON = '' +COLUMNS = "|" + "|".join([column.value for column in Column]) + "|" +COLUMN_HEADER = "|---|---|---|:---:|:---:|:---:|:---:|" +ARROW_SYMBOL = "➡️" + + +def load_base_car_info(path): + with open(path, "rb") as f: + return pickle.load(f) + + +def match_cars(base_cars, new_cars): + changes = [] + additions = [] + for new in new_cars: + # Addition if no close matches or close match already used + # Change if close match and not already used + matches = difflib.get_close_matches(new.name, [b.name for b in base_cars], cutoff=0.) + if not len(matches) or matches[0] in [c[1].name for c in changes]: + additions.append(new) + else: + changes.append((new, next(car for car in base_cars if car.name == matches[0]))) + + # Removal if base car not in changes + removals = [b for b in base_cars if b.name not in [c[1].name for c in changes]] + return changes, additions, removals + + +def build_column_diff(base_car, new_car): + row_builder = [] + for column in Column: + base_column = base_car.get_column(column, STAR_ICON, FOOTNOTE_TAG) + new_column = new_car.get_column(column, STAR_ICON, FOOTNOTE_TAG) + + if base_column != new_column: + row_builder.append(f"{base_column} {ARROW_SYMBOL} {new_column}") + else: + row_builder.append(new_column) + + return format_row(row_builder) + + +def format_row(builder): + return "|" + "|".join(builder) + "|" + + +def print_car_info_diff(path): + base_car_info = defaultdict(list) + new_car_info = defaultdict(list) + + for car in load_base_car_info(path): + base_car_info[car.car_fingerprint].append(car) + for car in get_all_car_info(): + new_car_info[car.car_fingerprint].append(car) + + # Add new platforms to base cars so we can detect additions and removals in one pass + base_car_info.update({car: [] for car in new_car_info if car not in base_car_info}) + + changes = defaultdict(list) + for base_car_model, base_cars in base_car_info.items(): + # Match car info changes, and get additions and removals + new_cars = new_car_info[base_car_model] + car_changes, car_additions, car_removals = match_cars(base_cars, new_cars) + + # Removals + for car_info in car_removals: + changes["removals"].append(format_row([car_info.get_column(column, STAR_ICON, FOOTNOTE_TAG) for column in Column])) + + # Additions + for car_info in car_additions: + changes["additions"].append(format_row([car_info.get_column(column, STAR_ICON, FOOTNOTE_TAG) for column in Column])) + + for new_car, base_car in car_changes: + # Tier changes + if base_car.tier != new_car.tier: + changes["tier"].append(f"- Tier for {base_car.name} changed! ({base_car.tier.name.title()} {ARROW_SYMBOL} {new_car.tier.name.title()})") + + # Column changes + row_diff = build_column_diff(base_car, new_car) + if ARROW_SYMBOL in row_diff: + changes["column"].append(row_diff) + + # Detail sentence changes + if base_car.detail_sentence != new_car.detail_sentence: + changes["detail"].append(f"- Sentence for {base_car.name} changed!\n" + + " ```diff\n" + + f" - {base_car.detail_sentence}\n" + + f" + {new_car.detail_sentence}\n" + + " ```") + + # Print diff + if any(len(c) for c in changes.values()): + markdown_builder = ["### ⚠️ This PR makes changes to [CARS.md](../blob/master/docs/CARS.md) ⚠️"] + + for title, category in (("## 🏅 Tier Changes", "tier"), ("## 🔀 Column Changes", "column"), ("## ❌ Removed", "removals"), ("## ➕ Added", "additions"), ("## 📖 Detail Sentence Changes", "detail")): + if len(changes[category]): + markdown_builder.append(title) + if category not in ("tier", "detail"): + markdown_builder.append(COLUMNS) + markdown_builder.append(COLUMN_HEADER) + markdown_builder.extend(changes[category]) + + print("\n".join(markdown_builder)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--path", required=True) + args = parser.parse_args() + print_car_info_diff(args.path) diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index 011dd6c9a..868813b0f 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -8,24 +8,16 @@ import traceback from tqdm import tqdm from tools.lib.logreader import LogReader from tools.lib.route import Route +from selfdrive.car.interfaces import get_interface_attr from selfdrive.car.car_helpers import interface_names -from selfdrive.car.fw_versions import match_fw_to_car_exact, match_fw_to_car_fuzzy, build_fw_dict -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 selfdrive.car.mazda.values import FW_VERSIONS as MAZDA_FW_VERSIONS -from selfdrive.car.subaru.values import FW_VERSIONS as SUBARU_FW_VERSIONS +from selfdrive.car.fw_versions import match_fw_to_car NO_API = "NO_API" in os.environ -SUPPORTED_CARS = set(interface_names['toyota']) -SUPPORTED_CARS |= set(interface_names['honda']) -SUPPORTED_CARS |= set(interface_names['hyundai']) -SUPPORTED_CARS |= set(interface_names['volkswagen']) -SUPPORTED_CARS |= set(interface_names['mazda']) -SUPPORTED_CARS |= set(interface_names['subaru']) -SUPPORTED_CARS |= set(interface_names['nissan']) +VERSIONS = get_interface_attr('FW_VERSIONS', ignore_none=True) +SUPPORTED_BRANDS = VERSIONS.keys() +SUPPORTED_CARS = [brand for brand in SUPPORTED_BRANDS for brand in interface_names[brand]] +UNKNOWN_BRAND = "unknown" try: from xx.pipeline.c.CarState import migration @@ -97,9 +89,8 @@ if __name__ == "__main__": print("not in supported cars") break - fw_versions_dict = build_fw_dict(car_fw) - exact_matches = match_fw_to_car_exact(fw_versions_dict) - fuzzy_matches = match_fw_to_car_fuzzy(fw_versions_dict) + _, exact_matches = match_fw_to_car(car_fw, allow_exact=True, allow_fuzzy=False) + _, fuzzy_matches = match_fw_to_car(car_fw, allow_exact=False, allow_fuzzy=True) if (len(exact_matches) == 1) and (list(exact_matches)[0] == live_fingerprint): good_exact += 1 @@ -120,18 +111,22 @@ if __name__ == "__main__": print("New style (exact):", exact_matches) print("New style (fuzzy):", fuzzy_matches) - for version in car_fw: + padding = max([len(fw.brand or UNKNOWN_BRAND) for fw in car_fw]) + for version in sorted(car_fw, key=lambda fw: fw.brand): subaddr = None if version.subAddress == 0 else hex(version.subAddress) - print(f" (Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}],") + print(f" Brand: {version.brand or UNKNOWN_BRAND:{padding}}, bus: {version.bus} - (Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}],") print("Mismatches") found = False - for car_fws in [TOYOTA_FW_VERSIONS, HONDA_FW_VERSIONS, HYUNDAI_FW_VERSIONS, VW_FW_VERSIONS, MAZDA_FW_VERSIONS, SUBARU_FW_VERSIONS]: + for brand in SUPPORTED_BRANDS: + car_fws = VERSIONS[brand] if live_fingerprint in car_fws: found = True expected = car_fws[live_fingerprint] for (_, expected_addr, expected_sub_addr), v in expected.items(): for version in car_fw: + if version.brand != brand and len(version.brand): + continue sub_addr = None if version.subAddress == 0 else version.subAddress addr = version.address diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index e092c939a..9e6536f9b 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -12,13 +12,11 @@ import capnp import numpy as np from typing import List, NoReturn, Optional -from cereal import car, log +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.model import model_height -from common.transformations.camera import get_view_frame_from_road_frame from common.transformations.orientation import rot_from_euler, euler_from_rot from system.swaglog import cloudlog @@ -62,7 +60,7 @@ class Calibrator: def __init__(self, param_put: bool = False): self.param_put = param_put - self.CP = car.CarParams.from_bytes(Params().get("CarParams", block=True)) + self.not_car = False # Read saved calibration params = Params() @@ -180,7 +178,6 @@ class Calibrator: def get_msg(self) -> capnp.lib.capnp._DynamicStructBuilder: smooth_rpy = self.get_smooth_rpy() - extrinsic_matrix = get_view_frame_from_road_frame(0, smooth_rpy[1], smooth_rpy[2], model_height) msg = messaging.new_message('liveCalibration') liveCalibration = msg.liveCalibration @@ -188,16 +185,13 @@ class Calibrator: liveCalibration.validBlocks = self.valid_blocks liveCalibration.calStatus = self.cal_status liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100) - liveCalibration.extrinsicMatrix = extrinsic_matrix.flatten().tolist() liveCalibration.rpyCalib = smooth_rpy.tolist() liveCalibration.rpyCalibSpread = self.calib_spread.tolist() - if self.CP.notCar: - extrinsic_matrix = get_view_frame_from_road_frame(0, 0, 0, model_height) + if self.not_car: liveCalibration.validBlocks = INPUTS_NEEDED liveCalibration.calStatus = Calibration.CALIBRATED liveCalibration.calPerc = 100. - liveCalibration.extrinsicMatrix = extrinsic_matrix.flatten().tolist() liveCalibration.rpyCalib = [0, 0, 0] liveCalibration.rpyCalibSpread = self.calib_spread.tolist() @@ -212,7 +206,7 @@ def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[m set_realtime_priority(1) if sm is None: - sm = messaging.SubMaster(['cameraOdometry', 'carState'], poll=['cameraOdometry']) + sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll=['cameraOdometry']) if pm is None: pm = messaging.PubMaster(['liveCalibration']) @@ -223,6 +217,8 @@ def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[m timeout = 0 if sm.frame == -1 else 100 sm.update(timeout) + calibrator.not_car = sm['carParams'].notCar + if sm.updated['cameraOdometry']: calibrator.handle_v_ego(sm['carState'].vEgo) new_rpy = calibrator.handle_cam_odom(sm['cameraOdometry'].trans, diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index 8ec570e2b..830eb3320 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -1,8 +1,11 @@ #!/usr/bin/env python3 import json +import math +import os import time from collections import defaultdict from concurrent.futures import Future, ProcessPoolExecutor +from datetime import datetime from enum import IntEnum from typing import List, Optional @@ -12,10 +15,11 @@ from cereal import log, messaging from common.params import Params, put_nonblocking from laika import AstroDog from laika.constants import SECS_IN_HR, SECS_IN_MIN +from laika.downloader import DownloadFailed from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_ephem from laika.gps_time import GPSTime from laika.helpers import ConstellationId -from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox +from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox, read_raw_qcom from selfdrive.locationd.laikad_helpers import calc_pos_fix_gauss_newton, get_posfix_sympy_fun from selfdrive.locationd.models.constants import GENERATED_DIR, ObservationKind from selfdrive.locationd.models.gnss_kf import GNSSKalman @@ -24,27 +28,49 @@ from system.swaglog import cloudlog MAX_TIME_GAP = 10 EPHEMERIS_CACHE = 'LaikadEphemeris' +DOWNLOADS_CACHE_FOLDER = "/tmp/comma_download_cache" CACHE_VERSION = 0.1 +POS_FIX_RESIDUAL_THRESHOLD = 100.0 class Laikad: - def __init__(self, valid_const=("GPS", "GLONASS"), auto_update=False, valid_ephem_types=(EphemerisType.ULTRA_RAPID_ORBIT, EphemerisType.NAV), - save_ephemeris=False, last_known_position=None): - self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, clear_old_ephemeris=True) - self.gnss_kf = GNSSKalman(GENERATED_DIR) - self.orbit_fetch_executor = ProcessPoolExecutor() + def __init__(self, valid_const=("GPS", "GLONASS"), auto_fetch_orbits=True, auto_update=False, + valid_ephem_types=(EphemerisType.ULTRA_RAPID_ORBIT, EphemerisType.NAV), + save_ephemeris=False, use_qcom=False): + """ + valid_const: GNSS constellation which can be used + auto_fetch_orbits: If true fetch orbits from internet when needed + auto_update: If true download AstroDog will download all files needed. This can be ephemeris or correction data like ionosphere. + valid_ephem_types: Valid ephemeris types to be used by AstroDog + save_ephemeris: If true saves and loads nav and orbit ephemeris to cache. + """ + self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, clear_old_ephemeris=True, cache_dir=DOWNLOADS_CACHE_FOLDER) + self.gnss_kf = GNSSKalman(GENERATED_DIR, cython=True, erratic_clock=use_qcom) + + self.auto_fetch_orbits = auto_fetch_orbits + self.orbit_fetch_executor: Optional[ProcessPoolExecutor] = None self.orbit_fetch_future: Optional[Future] = None + self.last_fetch_orbits_t = None + self.got_first_gnss_msg = False self.last_cached_t = None self.save_ephemeris = save_ephemeris self.load_cache() + self.posfix_functions = {constellation: get_posfix_sympy_fun(constellation) for constellation in (ConstellationId.GPS, ConstellationId.GLONASS)} - self.last_pos_fix = last_known_position + self.last_pos_fix = [] + self.last_pos_residual = [] + self.last_pos_fix_t = None + self.use_qcom = use_qcom def load_cache(self): + if not self.save_ephemeris: + return + cache = Params().get(EPHEMERIS_CACHE) if not cache: return + try: cache = json.loads(cache, object_hook=deserialize_hook) self.astro_dog.add_orbits(cache['orbits']) @@ -52,75 +78,122 @@ class Laikad: self.last_fetch_orbits_t = cache['last_fetch_orbits_t'] except json.decoder.JSONDecodeError: cloudlog.exception("Error parsing cache") + timestamp = self.last_fetch_orbits_t.as_datetime() if self.last_fetch_orbits_t is not None else 'Nan' + cloudlog.debug( + f"Loaded nav ({sum([len(v) for v in cache['nav']])}) and orbits ({sum([len(v) for v in cache['orbits']])}) cache with timestamp: {timestamp}. Unique orbit and nav sats: {list(cache['orbits'].keys())} {list(cache['nav'].keys())} " + + f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in self.astro_dog.orbit_fetched_times._ranges]}") def cache_ephemeris(self, t: GPSTime): if self.save_ephemeris and (self.last_cached_t is None or t - self.last_cached_t > SECS_IN_MIN): put_nonblocking(EPHEMERIS_CACHE, json.dumps( {'version': CACHE_VERSION, 'last_fetch_orbits_t': self.last_fetch_orbits_t, 'orbits': self.astro_dog.orbits, 'nav': self.astro_dog.nav}, cls=CacheSerializer)) + cloudlog.debug("Cache saved") self.last_cached_t = t - def process_ublox_msg(self, ublox_msg, ublox_mono_time: int, block=False): - if ublox_msg.which == 'measurementReport': - report = ublox_msg.measurementReport - if report.gpsWeek > 0: - latest_msg_t = GPSTime(report.gpsWeek, report.rcvTow) - self.fetch_orbits(latest_msg_t + SECS_IN_MIN, block) - new_meas = read_raw_ublox(report) - processed_measurements = process_measurements(new_meas, self.astro_dog) - - min_measurements = 5 if any(p.constellation_id == ConstellationId.GLONASS for p in processed_measurements) else 4 + def get_est_pos(self, t, processed_measurements): + if self.last_pos_fix_t is None or abs(self.last_pos_fix_t - t) >= 2: + min_measurements = 6 if any(p.constellation_id == ConstellationId.GLONASS for p in processed_measurements) else 5 pos_fix, pos_fix_residual = calc_pos_fix_gauss_newton(processed_measurements, self.posfix_functions, min_measurements=min_measurements) if len(pos_fix) > 0: - self.last_pos_fix = pos_fix[:3] - est_pos = self.last_pos_fix + self.last_pos_fix_t = t + residual_median = np.median(np.abs(pos_fix_residual)) + if np.median(np.abs(pos_fix_residual)) < POS_FIX_RESIDUAL_THRESHOLD: + cloudlog.debug(f"Pos fix is within threshold with median: {residual_median.round()}") + self.last_pos_fix = pos_fix[:3] + self.last_pos_residual = pos_fix_residual + else: + cloudlog.debug(f"Pos fix failed with median: {residual_median.round()}. All residuals: {np.round(pos_fix_residual)}") + return self.last_pos_fix + + def is_good_report(self, gnss_msg): + if gnss_msg.which == 'drMeasurementReport' and self.use_qcom: + constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source) + # TODO support GLONASS + return constellation_id in [ConstellationId.GPS, ConstellationId.SBAS] + elif gnss_msg.which == 'measurementReport' and not self.use_qcom: + return True + else: + return False + + def read_report(self, gnss_msg): + if self.use_qcom: + report = gnss_msg.drMeasurementReport + week = report.gpsWeek + tow = report.gpsMilliseconds / 1000.0 + new_meas = read_raw_qcom(report) + else: + report = gnss_msg.measurementReport + week = report.gpsWeek + tow = report.rcvTow + new_meas = read_raw_ublox(report) + return week, tow, new_meas - corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog) if est_pos is not None else [] + def process_gnss_msg(self, gnss_msg, gnss_mono_time: int, block=False): + if self.is_good_report(gnss_msg): + week, tow, new_meas = self.read_report(gnss_msg) + + t = gnss_mono_time * 1e-9 + if week > 0: + self.got_first_gnss_msg = True + latest_msg_t = GPSTime(week, tow) + if self.auto_fetch_orbits: + self.fetch_orbits(latest_msg_t, block) + + # Filter measurements with unexpected pseudoranges for GPS and GLONASS satellites + new_meas = [m for m in new_meas if 1e7 < m.observables['C1C'] < 3e7] + + processed_measurements = process_measurements(new_meas, self.astro_dog) + est_pos = self.get_est_pos(t, processed_measurements) + + corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog) if len(est_pos) > 0 else [] + if gnss_mono_time % 10 == 0: + cloudlog.debug(f"Measurements Incoming/Processed/Corrected: {len(new_meas), len(processed_measurements), len(corrected_measurements)}") - t = ublox_mono_time * 1e-9 self.update_localizer(est_pos, t, corrected_measurements) kf_valid = all(self.kf_valid(t)) - ecef_pos = self.gnss_kf.x[GStates.ECEF_POS].tolist() - ecef_vel = self.gnss_kf.x[GStates.ECEF_VELOCITY].tolist() + ecef_pos = self.gnss_kf.x[GStates.ECEF_POS] + ecef_vel = self.gnss_kf.x[GStates.ECEF_VELOCITY] - pos_std = np.sqrt(abs(self.gnss_kf.P[GStates.ECEF_POS].diagonal())).tolist() - vel_std = np.sqrt(abs(self.gnss_kf.P[GStates.ECEF_VELOCITY].diagonal())).tolist() + p = self.gnss_kf.P.diagonal() + pos_std = np.sqrt(p[GStates.ECEF_POS]) + vel_std = np.sqrt(p[GStates.ECEF_VELOCITY]) meas_msgs = [create_measurement_msg(m) for m in corrected_measurements] dat = messaging.new_message("gnssMeasurements") measurement_msg = log.LiveLocationKalman.Measurement.new_message dat.gnssMeasurements = { - "gpsWeek": report.gpsWeek, - "gpsTimeOfWeek": report.rcvTow, - "positionECEF": measurement_msg(value=ecef_pos, std=pos_std, valid=kf_valid), - "velocityECEF": measurement_msg(value=ecef_vel, std=vel_std, valid=kf_valid), - "positionFixECEF": measurement_msg(value=pos_fix, std=pos_fix_residual, valid=len(pos_fix) > 0), - "ubloxMonoTime": ublox_mono_time, + "gpsWeek": week, + "gpsTimeOfWeek": tow, + "positionECEF": measurement_msg(value=ecef_pos.tolist(), std=pos_std.tolist(), valid=kf_valid), + "velocityECEF": measurement_msg(value=ecef_vel.tolist(), std=vel_std.tolist(), valid=kf_valid), + "positionFixECEF": measurement_msg(value=self.last_pos_fix, std=self.last_pos_residual, valid=self.last_pos_fix_t == t), + "ubloxMonoTime": gnss_mono_time, "correctedMeasurements": meas_msgs } return dat - elif ublox_msg.which == 'ephemeris': - ephem = convert_ublox_ephem(ublox_msg.ephemeris) + # TODO this only works on GLONASS, qcom needs live ephemeris parsing too + elif gnss_msg.which == 'ephemeris': + ephem = convert_ublox_ephem(gnss_msg.ephemeris) self.astro_dog.add_navs({ephem.prn: [ephem]}) self.cache_ephemeris(t=ephem.epoch) - # elif ublox_msg.which == 'ionoData': + #elif gnss_msg.which == 'ionoData': # todo add this. Needed to better correct messages offline. First fix ublox_msg.cc to sent them. def update_localizer(self, est_pos, t: float, measurements: List[GNSSMeasurement]): # Check time and outputs are valid valid = self.kf_valid(t) if not all(valid): - if not valid[0]: - cloudlog.info("Init gnss kalman filter") + if not valid[0]: # Filter not initialized + pass elif not valid[1]: cloudlog.error("Time gap of over 10s detected, gnss kalman reset") elif not valid[2]: cloudlog.error("Gnss kalman filter state is nan") - if est_pos is not None: + if len(est_pos) > 0: cloudlog.info(f"Reset kalman filter with {est_pos}") self.init_gnss_localizer(est_pos) else: - cloudlog.info("Could not reset kalman filter") return if len(measurements) > 0: kf_add_observations(self.gnss_kf, t, measurements) @@ -128,10 +201,10 @@ class Laikad: # Ensure gnss filter is updated even with no new measurements self.gnss_kf.predict(t) - def kf_valid(self, t: float): - filter_time = self.gnss_kf.filter.filter_time - return [filter_time is not None, - filter_time is not None and abs(t - filter_time) < MAX_TIME_GAP, + def kf_valid(self, t: float) -> List[bool]: + filter_time = self.gnss_kf.filter.get_filter_time() + return [not math.isnan(filter_time), + abs(t - filter_time) < MAX_TIME_GAP, all(np.isfinite(self.gnss_kf.x[GStates.ECEF_POS]))] def init_gnss_localizer(self, est_pos): @@ -141,33 +214,41 @@ class Laikad: self.gnss_kf.init_state(x_initial, covs_diag=p_initial_diag) def fetch_orbits(self, t: GPSTime, block): - if t not in self.astro_dog.orbit_fetched_times and (self.last_fetch_orbits_t is None or t - self.last_fetch_orbits_t > SECS_IN_HR): - astro_dog_vars = self.astro_dog.valid_const, self.astro_dog.auto_update, self.astro_dog.valid_ephem_types - if self.orbit_fetch_future is None: + # Download new orbits if 1 hour of orbits data left + if t + SECS_IN_HR not in self.astro_dog.orbit_fetched_times and (self.last_fetch_orbits_t is None or abs(t - self.last_fetch_orbits_t) > SECS_IN_MIN): + astro_dog_vars = self.astro_dog.valid_const, self.astro_dog.auto_update, self.astro_dog.valid_ephem_types, self.astro_dog.cache_dir + ret = None + + if block: # Used for testing purposes + ret = get_orbit_data(t, *astro_dog_vars) + elif self.orbit_fetch_future is None: + self.orbit_fetch_executor = ProcessPoolExecutor(max_workers=1) self.orbit_fetch_future = self.orbit_fetch_executor.submit(get_orbit_data, t, *astro_dog_vars) - if block: - self.orbit_fetch_future.result() - if self.orbit_fetch_future.done(): + elif self.orbit_fetch_future.done(): ret = self.orbit_fetch_future.result() - self.last_fetch_orbits_t = t - if ret: - self.astro_dog.orbits, self.astro_dog.orbit_fetched_times = ret + self.orbit_fetch_executor = self.orbit_fetch_future = None + + if ret is not None: + if ret[0] is None: + self.last_fetch_orbits_t = ret[2] + else: + self.astro_dog.orbits, self.astro_dog.orbit_fetched_times, self.last_fetch_orbits_t = ret self.cache_ephemeris(t=t) - self.orbit_fetch_future = None -def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types): - astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types) +def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types, cache_dir): + astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, cache_dir=cache_dir) cloudlog.info(f"Start to download/parse orbits for time {t.as_datetime()}") start_time = time.monotonic() - data = None try: astro_dog.get_orbit_data(t, only_predictions=True) - data = (astro_dog.orbits, astro_dog.orbit_fetched_times) - except RuntimeError as e: - cloudlog.info(f"No orbit data found. {e}") - cloudlog.info(f"Done parsing orbits. Took {time.monotonic() - start_time:.1f}s") - return data + cloudlog.info(f"Done parsing orbits. Took {time.monotonic() - start_time:.1f}s") + cloudlog.debug(f"Downloaded orbits ({sum([len(v) for v in astro_dog.orbits])}): {list(astro_dog.orbits.keys())}" + + f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in astro_dog.orbit_fetched_times._ranges]}") + return astro_dog.orbits, astro_dog.orbit_fetched_times, t + except (DownloadFailed, RuntimeError, ValueError, IOError) as e: + cloudlog.warning(f"No orbit data found or parsing failure: {e}") + return None, None, t def create_measurement_msg(meas: GNSSMeasurement): @@ -201,7 +282,7 @@ def create_measurement_msg(meas: GNSSMeasurement): c.ephemerisSource.type = source_type.value c.ephemerisSource.gpsWeek = week - c.ephemerisSource.gpsTimeOfWeek = time_of_week + c.ephemerisSource.gpsTimeOfWeek = int(time_of_week) return c @@ -211,13 +292,13 @@ def kf_add_observations(gnss_kf: GNSSKalman, t: float, measurements: List[GNSSMe m_arr = m.as_array() if m.constellation_id == ConstellationId.GPS: ekf_data[ObservationKind.PSEUDORANGE_GPS].append(m_arr) - ekf_data[ObservationKind.PSEUDORANGE_RATE_GPS].append(m_arr) elif m.constellation_id == ConstellationId.GLONASS: ekf_data[ObservationKind.PSEUDORANGE_GLONASS].append(m_arr) - ekf_data[ObservationKind.PSEUDORANGE_RATE_GLONASS].append(m_arr) - + ekf_data[ObservationKind.PSEUDORANGE_RATE_GPS] = ekf_data[ObservationKind.PSEUDORANGE_GPS] + ekf_data[ObservationKind.PSEUDORANGE_RATE_GLONASS] = ekf_data[ObservationKind.PSEUDORANGE_GLONASS] for kind, data in ekf_data.items(): - gnss_kf.predict_and_observe(t, kind, data) + if len(data) > 0: + gnss_kf.predict_and_observe(t, kind, data) class CacheSerializer(json.JSONEncoder): @@ -246,19 +327,35 @@ class EphemerisSourceType(IntEnum): glonassIacUltraRapid = 2 -def main(): - sm = messaging.SubMaster(['ubloxGnss']) - pm = messaging.PubMaster(['gnssMeasurements']) - # todo get last_known_position - laikad = Laikad(save_ephemeris=True) +def main(sm=None, pm=None): + use_qcom = os.path.isfile("/persist/comma/use-quectel-rawgps") + if use_qcom: + raw_gnss_socket = "qcomGnss" + else: + raw_gnss_socket = "ubloxGnss" + + if sm is None: + sm = messaging.SubMaster([raw_gnss_socket, 'clocks']) + if pm is None: + pm = messaging.PubMaster(['gnssMeasurements']) + + replay = "REPLAY" in os.environ + use_internet = "LAIKAD_NO_INTERNET" not in os.environ + laikad = Laikad(save_ephemeris=not replay, auto_fetch_orbits=use_internet, use_qcom=use_qcom) + while True: sm.update() - if sm.updated['ubloxGnss']: - ublox_msg = sm['ubloxGnss'] - msg = laikad.process_ublox_msg(ublox_msg, sm.logMonoTime['ubloxGnss']) + if sm.updated[raw_gnss_socket]: + gnss_msg = sm[raw_gnss_socket] + msg = laikad.process_gnss_msg(gnss_msg, sm.logMonoTime[raw_gnss_socket], block=replay) if msg is not None: pm.send('gnssMeasurements', msg) + if not laikad.got_first_gnss_msg and sm.updated['clocks']: + clocks_msg = sm['clocks'] + t = GPSTime.from_datetime(datetime.utcfromtimestamp(clocks_msg.wallTimeNanos * 1E-9)) + if laikad.auto_fetch_orbits: + laikad.fetch_orbits(t, block=replay) if __name__ == "__main__": diff --git a/selfdrive/locationd/laikad_helpers.py b/selfdrive/locationd/laikad_helpers.py index 81f5ac3dd..f13e8e73b 100644 --- a/selfdrive/locationd/laikad_helpers.py +++ b/selfdrive/locationd/laikad_helpers.py @@ -86,4 +86,4 @@ def get_posfix_sympy_fun(constellation): res = [res] + [sympy.diff(res, v) for v in var] - return sympy.lambdify([x, y, z, bc, bg, pr, sat_x, sat_y, sat_z, weight], res) + return sympy.lambdify([x, y, z, bc, bg, pr, sat_x, sat_y, sat_z, weight], res, modules=["numpy"]) diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index c33ff6a49..7714931b9 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -284,7 +284,7 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R MatrixXdr ecef_pos_R = Vector3d::Constant(std::pow(10.0 * log.getAccuracy(),2) + std::pow(10.0 * log.getVerticalAccuracy(),2)).asDiagonal(); MatrixXdr ecef_vel_R = Vector3d::Constant(std::pow(log.getSpeedAccuracy() * 10.0, 2)).asDiagonal(); - this->unix_timestamp_millis = log.getTimestamp(); + this->unix_timestamp_millis = log.getUnixTimestampMillis(); double gps_est_error = (this->kf->get_x().segment(STATE_ECEF_POS_START) - ecef_pos).norm(); VectorXd orientation_ecef = quat2euler(vector2quat(this->kf->get_x().segment(STATE_ECEF_ORIENTATION_START))); @@ -443,6 +443,8 @@ void Localizer::handle_msg(const cereal::Event::Reader& log) { this->time_check(t); if (log.isSensorEvents()) { this->handle_sensors(t, log.getSensorEvents()); + } else if (log.isGpsLocation()) { + this->handle_gps(t, log.getGpsLocation()); } else if (log.isGpsLocationExternal()) { this->handle_gps(t, log.getGpsLocationExternal()); } else if (log.isCarState()) { @@ -490,11 +492,17 @@ void Localizer::determine_gps_mode(double current_time) { } int Localizer::locationd_thread() { - const std::initializer_list service_list = {"gpsLocationExternal", "sensorEvents", "cameraOdometry", "liveCalibration", "carState", "carParams"}; + const char* gps_location_socket; + if (util::file_exists("/persist/comma/use-quectel-rawgps")) { + gps_location_socket = "gpsLocation"; + } else { + gps_location_socket = "gpsLocationExternal"; + } + const std::initializer_list service_list = {gps_location_socket, "sensorEvents", "cameraOdometry", "liveCalibration", "carState", "carParams"}; PubMaster pm({"liveLocationKalman"}); // TODO: remove carParams once we're always sending at 100Hz - SubMaster sm(service_list, {}, nullptr, {"gpsLocationExternal", "carParams"}); + SubMaster sm(service_list, {}, nullptr, {gps_location_socket, "carParams"}); uint64_t cnt = 0; bool filterInitialized = false; diff --git a/selfdrive/locationd/models/car_kf.py b/selfdrive/locationd/models/car_kf.py index fe7d2e650..3faf4f8d4 100755 --- a/selfdrive/locationd/models/car_kf.py +++ b/selfdrive/locationd/models/car_kf.py @@ -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 # pylint: disable=no-name-in-module, import-error + from rednose.helpers.ekf_sym_pyx import EKF_sym_pyx # pylint: disable=no-name-in-module, import-error i = 0 @@ -171,7 +171,7 @@ class CarKalman(KalmanFilter): if P_initial is not None: self.P_initial = P_initial # init filter - self.filter = EKF_sym(generated_dir, self.name, self.Q, self.initial_x, self.P_initial, dim_state, dim_state_err, global_vars=self.global_vars, logger=cloudlog) + self.filter = EKF_sym_pyx(generated_dir, self.name, self.Q, self.initial_x, self.P_initial, dim_state, dim_state_err, global_vars=self.global_vars, logger=cloudlog) if __name__ == "__main__": diff --git a/selfdrive/locationd/models/gnss_helpers.py b/selfdrive/locationd/models/gnss_helpers.py index a3bcbc000..b6c1771ec 100644 --- a/selfdrive/locationd/models/gnss_helpers.py +++ b/selfdrive/locationd/models/gnss_helpers.py @@ -1,16 +1,14 @@ import numpy as np +from laika.raw_gnss import GNSSMeasurement def parse_prr(m): - from laika.raw_gnss import GNSSMeasurement sat_pos_vel_i = np.concatenate((m[GNSSMeasurement.SAT_POS], m[GNSSMeasurement.SAT_VEL])) R_i = np.atleast_2d(m[GNSSMeasurement.PRR_STD]**2) z_i = m[GNSSMeasurement.PRR] return z_i, R_i, sat_pos_vel_i - def parse_pr(m): - from laika.raw_gnss import GNSSMeasurement pseudorange = m[GNSSMeasurement.PR] pseudorange_stdev = m[GNSSMeasurement.PR_STD] sat_pos_freq_i = np.concatenate((m[GNSSMeasurement.SAT_POS], diff --git a/selfdrive/locationd/models/gnss_kf.py b/selfdrive/locationd/models/gnss_kf.py index ce0ff84dc..0d661dc32 100755 --- a/selfdrive/locationd/models/gnss_kf.py +++ b/selfdrive/locationd/models/gnss_kf.py @@ -3,12 +3,17 @@ import sys from typing import List import numpy as np -import sympy as sp -from rednose.helpers.ekf_sym import EKF_sym, gen_code from selfdrive.locationd.models.constants import ObservationKind from 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 + class States(): ECEF_POS = slice(0, 3) # x, y and z in ECEF in meters @@ -34,12 +39,6 @@ class GNSSKalman(): 1e14, (100)**2, (0.2)**2, (10)**2, (1)**2]) - # process noise - Q = np.diag([0.03**2, 0.03**2, 0.03**2, - 3**2, 3**2, 3**2, - (.1)**2, (0)**2, (0.005)**2, - .1**2, (.01)**2]) - maha_test_kinds: List[int] = [] # ObservationKind.PSEUDORANGE_RATE, ObservationKind.PSEUDORANGE, ObservationKind.PSEUDORANGE_GLONASS] @staticmethod @@ -115,12 +114,20 @@ class GNSSKalman(): gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state, maha_test_kinds=maha_test_kinds) - def __init__(self, generated_dir): + def __init__(self, generated_dir, cython=False, erratic_clock=False): + # process noise + clock_error_drift = 100.0 if erratic_clock else 0.1 + self.Q = np.diag([0.03**2, 0.03**2, 0.03**2, + 3**2, 3**2, 3**2, + (clock_error_drift)**2, (0)**2, (0.005)**2, + .1**2, (.01)**2]) + self.dim_state = self.x_initial.shape[0] # init filter - self.filter = EKF_sym(generated_dir, self.name, self.Q, self.x_initial, self.P_initial, self.dim_state, - self.dim_state, maha_test_kinds=self.maha_test_kinds) + filter_cls = EKF_sym_pyx if cython else EKF_sym + self.filter = filter_cls(generated_dir, self.name, self.Q, self.x_initial, self.P_initial, self.dim_state, + self.dim_state, maha_test_kinds=self.maha_test_kinds) self.init_state(GNSSKalman.x_initial, covs=GNSSKalman.P_initial) @property diff --git a/selfdrive/locationd/models/loc_kf.py b/selfdrive/locationd/models/loc_kf.py index 6b0882869..25bf36d2c 100755 --- a/selfdrive/locationd/models/loc_kf.py +++ b/selfdrive/locationd/models/loc_kf.py @@ -25,15 +25,15 @@ class States(): ODO_SCALE_UNUSED = slice(18, 19) # odometer scale ACCELERATION = slice(19, 22) # Acceleration in device frame in m/s**2 FOCAL_SCALE_UNUSED = slice(22, 23) # focal length scale - IMU_OFFSET = slice(23, 26) # imu offset angles in radians + IMU_FROM_DEVICE_EULER = slice(23, 26) # imu offset angles in radians GLONASS_BIAS = slice(26, 27) # GLONASS bias in m expressed as bias + freq_num*freq_slope GLONASS_FREQ_SLOPE = slice(27, 28) # GLONASS bias in m expressed as bias + freq_num*freq_slope CLOCK_ACCELERATION = slice(28, 29) # clock acceleration in light-meters/s**2, ACCELEROMETER_SCALE_UNUSED = slice(29, 30) # scale of mems accelerometer ACCELEROMETER_BIAS = slice(30, 33) # bias of mems accelerometer # TODO the offset is likely a translation of the sensor, not a rotation of the camera - WIDE_CAM_OFFSET = slice(33, 36) # wide camera offset angles in radians (tici only) - # We curently do not use ACCELEROMETER_SCALE to avoid instability due to too many free variables (ACCELEROMETER_SCALE, ACCELEROMETER_BIAS, IMU_OFFSET). + WIDE_FROM_DEVICE_EULER = slice(33, 36) # wide camera offset angles in radians (tici only) + # We curently do not use ACCELEROMETER_SCALE to avoid instability due to too many free variables (ACCELEROMETER_SCALE, ACCELEROMETER_BIAS, IMU_FROM_DEVICE_EULER). # From experiments we see that ACCELEROMETER_BIAS is more correct than ACCELEROMETER_SCALE # Error-state has different slices because it is an ESKF @@ -47,13 +47,13 @@ class States(): ODO_SCALE_ERR_UNUSED = slice(17, 18) ACCELERATION_ERR = slice(18, 21) FOCAL_SCALE_ERR_UNUSED = slice(21, 22) - IMU_OFFSET_ERR = slice(22, 25) + IMU_FROM_DEVICE_EULER_ERR = slice(22, 25) GLONASS_BIAS_ERR = slice(25, 26) GLONASS_FREQ_SLOPE_ERR = slice(26, 27) CLOCK_ACCELERATION_ERR = slice(27, 28) ACCELEROMETER_SCALE_ERR_UNUSED = slice(28, 29) ACCELEROMETER_BIAS_ERR = slice(29, 32) - WIDE_CAM_OFFSET_ERR = slice(32, 35) + WIDE_FROM_DEVICE_EULER_ERR = slice(32, 35) class LocKalman(): @@ -91,22 +91,6 @@ class LocKalman(): 0.05**2, 0.05**2, 0.05**2, 0.01**2, 0.01**2, 0.01**2]) - # process noise - Q = np.diag([0.03**2, 0.03**2, 0.03**2, - 0.0**2, 0.0**2, 0.0**2, - 0.0**2, 0.0**2, 0.0**2, - 0.1**2, 0.1**2, 0.1**2, - (.1)**2, (0.0)**2, - (0.005 / 100)**2, (0.005 / 100)**2, (0.005 / 100)**2, - (0.02 / 100)**2, - 3**2, 3**2, 3**2, - 0.001**2, - (0.05 / 60)**2, (0.05 / 60)**2, (0.05 / 60)**2, - (.1)**2, (.01)**2, - 0.005**2, - (0.02 / 100)**2, - (0.005 / 100)**2, (0.005 / 100)**2, (0.005 / 100)**2, - (0.05 / 60)**2, (0.05 / 60)**2, (0.05 / 60)**2]) # measurements that need to pass mahalanobis distance outlier rejector maha_test_kinds = [ObservationKind.ORB_FEATURES, ObservationKind.ORB_FEATURES_WIDE] # , ObservationKind.PSEUDORANGE, ObservationKind.PSEUDORANGE_RATE] @@ -140,15 +124,15 @@ class LocKalman(): cd = state[States.CLOCK_DRIFT, :] roll_bias, pitch_bias, yaw_bias = state[States.GYRO_BIAS, :] acceleration = state[States.ACCELERATION, :] - imu_angles = state[States.IMU_OFFSET, :] - imu_angles[0, 0] = 0 # not observable enough - imu_angles[2, 0] = 0 # not observable enough + imu_from_device_euler = state[States.IMU_FROM_DEVICE_EULER, :] + imu_from_device_euler[0, 0] = 0 # not observable enough + imu_from_device_euler[2, 0] = 0 # not observable enough glonass_bias = state[States.GLONASS_BIAS, :] glonass_freq_slope = state[States.GLONASS_FREQ_SLOPE, :] ca = state[States.CLOCK_ACCELERATION, :] accel_bias = state[States.ACCELEROMETER_BIAS, :] - wide_cam_angles = state[States.WIDE_CAM_OFFSET, :] - wide_cam_angles[0, 0] = 0 # not observable enough + wide_from_device_euler = state[States.WIDE_FROM_DEVICE_EULER, :] + wide_from_device_euler[0, 0] = 0 # not observable enough dt = sp.Symbol('dt') @@ -273,15 +257,15 @@ class LocKalman(): los_vector[2] * (sat_vz - vz) + cd[0]]) - imu_rot = euler_rotate(*imu_angles) - h_gyro_sym = imu_rot * sp.Matrix([vroll + roll_bias, + imu_from_device = euler_rotate(*imu_from_device_euler) + h_gyro_sym = imu_from_device * sp.Matrix([vroll + roll_bias, vpitch + pitch_bias, vyaw + yaw_bias]) pos = sp.Matrix([x, y, z]) # add 1 for stability, prevent division by 0 gravity = quat_rot.T * ((EARTH_GM / ((x**2 + y**2 + z**2 + 1)**(3.0 / 2.0))) * pos) - h_acc_sym = imu_rot * (gravity + acceleration + accel_bias) + h_acc_sym = imu_from_device * (gravity + acceleration + accel_bias) h_acc_stationary_sym = acceleration h_phone_rot_sym = sp.Matrix([vroll, vpitch, vyaw]) h_relative_motion = sp.Matrix(quat_rot.T * v) @@ -297,7 +281,7 @@ class LocKalman(): [h_phone_rot_sym, ObservationKind.CAMERA_ODO_ROTATION, None], [h_acc_stationary_sym, ObservationKind.NO_ACCEL, None]] - wide_cam_rot = euler_rotate(*wide_cam_angles) + wide_from_device = euler_rotate(*wide_from_device_euler) # MSCKF configuration if N > 0: # experimentally found this is correct value for imx298 with 910 focal length @@ -312,7 +296,7 @@ class LocKalman(): track_pos_sym = sp.Matrix([track_x - x, track_y - y, track_z - z]) track_pos_rot_sym = quat_rot.T * track_pos_sym - track_pos_rot_wide_cam_sym = wide_cam_rot * track_pos_rot_sym + track_pos_rot_wide_cam_sym = wide_from_device * track_pos_rot_sym h_track_sym[-2:, :] = sp.Matrix([focal_scale * (track_pos_rot_sym[1] / track_pos_rot_sym[0]), focal_scale * (track_pos_rot_sym[2] / track_pos_rot_sym[0])]) h_track_wide_cam_sym[-2:, :] = sp.Matrix([focal_scale * (track_pos_rot_wide_cam_sym[1] / track_pos_rot_wide_cam_sym[0]), @@ -329,7 +313,7 @@ class LocKalman(): quat_rot = quat_rotate(*q) track_pos_sym = sp.Matrix([track_x - x, track_y - y, track_z - z]) track_pos_rot_sym = quat_rot.T * track_pos_sym - track_pos_rot_wide_cam_sym = wide_cam_rot * track_pos_rot_sym + track_pos_rot_wide_cam_sym = wide_from_device * track_pos_rot_sym h_track_sym[n * 2:n * 2 + 2, :] = sp.Matrix([focal_scale * (track_pos_rot_sym[1] / track_pos_rot_sym[0]), focal_scale * (track_pos_rot_sym[2] / track_pos_rot_sym[0])]) h_track_wide_cam_sym[n * 2: n * 2 + 2, :] = sp.Matrix([focal_scale * (track_pos_rot_wide_cam_sym[1] / track_pos_rot_wide_cam_sym[0]), @@ -345,9 +329,29 @@ class LocKalman(): msckf_params = None gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state_err, eskf_params, msckf_params, maha_test_kinds) - def __init__(self, generated_dir, N=4): + def __init__(self, generated_dir, N=4, erratic_clock=False): name = f"{self.name}_{N}" + + # process noise + clock_error_drift = 100.0 if erratic_clock else 0.1 + self.Q = np.diag([0.03**2, 0.03**2, 0.03**2, + 0.0**2, 0.0**2, 0.0**2, + 0.0**2, 0.0**2, 0.0**2, + 0.1**2, 0.1**2, 0.1**2, + (clock_error_drift)**2, (0)**2, + (0.005 / 100)**2, (0.005 / 100)**2, (0.005 / 100)**2, + (0.02 / 100)**2, + 3**2, 3**2, 3**2, + 0.001**2, + (0.05 / 60)**2, (0.05 / 60)**2, (0.05 / 60)**2, + (.1)**2, (.01)**2, + 0.005**2, + (0.02 / 100)**2, + (0.005 / 100)**2, (0.005 / 100)**2, (0.005 / 100)**2, + (0.05 / 60)**2, (0.05 / 60)**2, (0.05 / 60)**2]) + + self.obs_noise = {ObservationKind.ODOMETRIC_SPEED: np.atleast_2d(0.2**2), ObservationKind.PHONE_GYRO: np.diag([0.025**2, 0.025**2, 0.025**2]), ObservationKind.PHONE_ACCEL: np.diag([.5**2, .5**2, .5**2]), diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index ae67dc28a..86672b046 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -16,6 +16,8 @@ from system.swaglog import cloudlog MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s ROLL_MAX_DELTA = np.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits ROLL_MIN, ROLL_MAX = math.radians(-10), math.radians(10) +LATERAL_ACC_SENSOR_THRESHOLD = 4.0 + class ParamsLearner: def __init__(self, CP, steer_ratio, stiffness_factor, angle_offset, P_initial=None): @@ -31,6 +33,8 @@ class ParamsLearner: self.active = False self.speed = 0.0 + self.yaw_rate = 0.0 + self.yaw_rate_std = 0.0 self.roll = 0.0 self.steering_pressed = False self.steering_angle = 0.0 @@ -39,8 +43,8 @@ class ParamsLearner: def handle_log(self, t, which, msg): if which == 'liveLocationKalman': - yaw_rate = msg.angularVelocityCalibrated.value[2] - yaw_rate_std = msg.angularVelocityCalibrated.std[2] + self.yaw_rate = msg.angularVelocityCalibrated.value[2] + self.yaw_rate_std = msg.angularVelocityCalibrated.std[2] localizer_roll = msg.orientationNED.value[0] localizer_roll_std = np.radians(1) if np.isnan(msg.orientationNED.std[0]) else msg.orientationNED.std[0] @@ -56,8 +60,8 @@ class ParamsLearner: self.roll = clip(roll, self.roll - ROLL_MAX_DELTA, self.roll + ROLL_MAX_DELTA) yaw_rate_valid = msg.angularVelocityCalibrated.valid - yaw_rate_valid = yaw_rate_valid and 0 < yaw_rate_std < 10 # rad/s - yaw_rate_valid = yaw_rate_valid and abs(yaw_rate) < 1 # rad/s + yaw_rate_valid = yaw_rate_valid and 0 < self.yaw_rate_std < 10 # rad/s + yaw_rate_valid = yaw_rate_valid and abs(self.yaw_rate) < 1 # rad/s if self.active: if msg.posenetOK: @@ -65,8 +69,8 @@ class ParamsLearner: if yaw_rate_valid: self.kf.predict_and_observe(t, ObservationKind.ROAD_FRAME_YAW_RATE, - np.array([[-yaw_rate]]), - np.array([np.atleast_2d(yaw_rate_std**2)])) + np.array([[-self.yaw_rate]]), + np.array([np.atleast_2d(self.yaw_rate_std**2)])) self.kf.predict_and_observe(t, ObservationKind.ROAD_ROLL, @@ -173,12 +177,14 @@ def main(sm=None, pm=None): angle_offset_average = clip(math.degrees(x[States.ANGLE_OFFSET]), angle_offset_average - MAX_ANGLE_OFFSET_DELTA, angle_offset_average + MAX_ANGLE_OFFSET_DELTA) angle_offset = clip(math.degrees(x[States.ANGLE_OFFSET] + x[States.ANGLE_OFFSET_FAST]), angle_offset - MAX_ANGLE_OFFSET_DELTA, angle_offset + MAX_ANGLE_OFFSET_DELTA) + # Account for the opposite signs of the yaw rates + sensors_valid = bool(abs(learner.speed * (x[States.YAW_RATE] + learner.yaw_rate)) < LATERAL_ACC_SENSOR_THRESHOLD) msg = messaging.new_message('liveParameters') liveParameters = msg.liveParameters liveParameters.posenetValid = True - liveParameters.sensorValid = True + liveParameters.sensorValid = sensors_valid liveParameters.steerRatio = float(x[States.STEER_RATIO]) liveParameters.stiffnessFactor = float(x[States.STIFFNESS]) liveParameters.roll = float(x[States.ROAD_ROLL]) diff --git a/selfdrive/locationd/test/_test_locationd_lib.py b/selfdrive/locationd/test/_test_locationd_lib.py new file mode 100755 index 000000000..8a0ed3ef0 --- /dev/null +++ b/selfdrive/locationd/test/_test_locationd_lib.py @@ -0,0 +1,106 @@ +#!/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 + +SENSOR_DECIMATION = 1 +VISION_DECIMATION = 1 + +LIBLOCATIOND_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../liblocationd.so')) + + +class TestLocationdLib(unittest.TestCase): + def setUp(self): + header = '''typedef ...* Localizer_t; +Localizer_t localizer_init(); +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() + + 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) + return log.Event.from_bytes(self.ffi.buffer(self.msg_buff), nesting_limit=self.buff_size // 8) + + 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('sensorEvents', 1) + msg.sensorEvents[0].sensor = 1 + msg.sensorEvents[0].timestamp = msg.logMonoTime + msg.sensorEvents[0].type = 1 + msg.sensorEvents[0].init('acceleration') + msg.sensorEvents[0].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('sensorEvents', 1) + msg.sensorEvents[0].sensor = 1 + msg.sensorEvents[0].timestamp = msg.logMonoTime + msg.sensorEvents[0].type = 1 + msg.sensorEvents[0].init('acceleration') + msg.sensorEvents[0].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_laikad.py b/selfdrive/locationd/test/test_laikad.py index 3a72303b0..418625f9b 100755 --- a/selfdrive/locationd/test/test_laikad.py +++ b/selfdrive/locationd/test/test_laikad.py @@ -7,6 +7,8 @@ from unittest import mock from unittest.mock import Mock, patch from common.params import Params +from laika.constants import SECS_IN_DAY +from laika.downloader import DownloadFailed from laika.ephemeris import EphemerisType, GPSEphemeris from laika.gps_time import GPSTime from laika.helpers import ConstellationId, TimeRangeHolder @@ -26,7 +28,7 @@ def get_log(segs=range(0)): def verify_messages(lr, laikad, return_one_success=False): good_msgs = [] for m in lr: - msg = laikad.process_ublox_msg(m.ubloxGnss, m.logMonoTime, block=True) + msg = laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=True) if msg is not None and len(msg.gnssMeasurements.correctedMeasurements) > 0: good_msgs.append(msg) if return_one_success: @@ -50,6 +52,9 @@ def get_measurement_mock(gpstime, sat_ephemeris): return meas +GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC = GPSTime.from_datetime(datetime(2022, month=1, day=29, hour=12)) + + class TestLaikad(unittest.TestCase): @classmethod @@ -62,11 +67,53 @@ class TestLaikad(unittest.TestCase): def setUp(self): Params().delete(EPHEMERIS_CACHE) + def test_fetch_orbits_non_blocking(self): + gpstime = GPSTime.from_datetime(datetime(2021, month=3, day=1)) + laikad = Laikad() + laikad.fetch_orbits(gpstime, block=False) + laikad.orbit_fetch_future.result(30) + # Get results and save orbits to laikad: + laikad.fetch_orbits(gpstime, block=False) + + ephem = laikad.astro_dog.orbits['G01'][0] + self.assertIsNotNone(ephem) + + laikad.fetch_orbits(gpstime+2*SECS_IN_DAY, block=False) + laikad.orbit_fetch_future.result(30) + # Get results and save orbits to laikad: + laikad.fetch_orbits(gpstime + 2 * SECS_IN_DAY, block=False) + + ephem2 = laikad.astro_dog.orbits['G01'][0] + self.assertIsNotNone(ephem) + self.assertNotEqual(ephem, ephem2) + + def test_fetch_orbits_with_wrong_clocks(self): + laikad = Laikad() + + def check_has_orbits(): + self.assertGreater(len(laikad.astro_dog.orbits), 0) + ephem = laikad.astro_dog.orbits['G01'][0] + self.assertIsNotNone(ephem) + real_current_time = GPSTime.from_datetime(datetime(2021, month=3, day=1)) + wrong_future_clock_time = real_current_time + SECS_IN_DAY + + laikad.fetch_orbits(wrong_future_clock_time, block=True) + check_has_orbits() + self.assertEqual(laikad.last_fetch_orbits_t, wrong_future_clock_time) + + # Test fetching orbits with earlier time + assert real_current_time < laikad.last_fetch_orbits_t + + laikad.astro_dog.orbits = {} + laikad.fetch_orbits(real_current_time, block=True) + check_has_orbits() + self.assertEqual(laikad.last_fetch_orbits_t, real_current_time) + def test_ephemeris_source_in_msg(self): data_mock = defaultdict(str) data_mock['sv_id'] = 1 - gpstime = GPSTime.from_datetime(datetime(2022, month=3, day=1)) + gpstime = GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC laikad = Laikad() laikad.fetch_orbits(gpstime, block=True) meas = get_measurement_mock(gpstime, laikad.astro_dog.orbits['R01'][0]) @@ -95,26 +142,44 @@ class TestLaikad(unittest.TestCase): laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT) correct_msgs = verify_messages(self.logs, laikad) - correct_msgs_expected = 560 + correct_msgs_expected = 555 self.assertEqual(correct_msgs_expected, len(correct_msgs)) self.assertEqual(correct_msgs_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) + def test_kf_becomes_valid(self): + laikad = Laikad(auto_update=False) + m = self.logs[0] + self.assertFalse(all(laikad.kf_valid(m.logMonoTime * 1e-9))) + kf_valid = False + for m in self.logs: + laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=True) + kf_valid = all(laikad.kf_valid(m.logMonoTime * 1e-9)) + if kf_valid: + break + self.assertTrue(kf_valid) + def test_laika_online_nav_only(self): laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.NAV) # Disable fetch_orbits to test NAV only laikad.fetch_orbits = Mock() correct_msgs = verify_messages(self.logs, laikad) - correct_msgs_expected = 560 + correct_msgs_expected = 559 self.assertEqual(correct_msgs_expected, len(correct_msgs)) self.assertEqual(correct_msgs_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) @mock.patch('laika.downloader.download_and_cache_file') def test_laika_offline(self, downloader_mock): - downloader_mock.side_effect = IOError + downloader_mock.side_effect = DownloadFailed("Mock download failed") + laikad = Laikad(auto_update=False) + laikad.fetch_orbits(GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC, block=True) + + @mock.patch('laika.downloader.download_and_cache_file') + def test_download_failed_russian_source(self, downloader_mock): + downloader_mock.side_effect = DownloadFailed laikad = Laikad(auto_update=False) correct_msgs = verify_messages(self.logs, laikad) - self.assertEqual(256, len(correct_msgs)) - self.assertEqual(256, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) + self.assertEqual(16, len(correct_msgs)) + self.assertEqual(16, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) def test_laika_get_orbits(self): laikad = Laikad(auto_update=False) @@ -136,7 +201,7 @@ class TestLaikad(unittest.TestCase): laikad = Laikad(auto_update=False) has_orbits = False for m in self.logs: - laikad.process_ublox_msg(m.ubloxGnss, m.logMonoTime, block=False) + laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=False) if laikad.orbit_fetch_future is not None: laikad.orbit_fetch_future.result() vals = laikad.astro_dog.orbits.values() @@ -155,7 +220,7 @@ class TestLaikad(unittest.TestCase): while Params().get(EPHEMERIS_CACHE) is None: time.sleep(0.1) max_time -= 0.1 - if max_time == 0: + if max_time < 0: self.fail("Cache has not been written after 2 seconds") # Test cache with no ephemeris @@ -170,7 +235,7 @@ class TestLaikad(unittest.TestCase): wait_for_cache() # Check both nav and orbits separate - laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.NAV) + laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.NAV, save_ephemeris=True) # Verify orbits and nav are loaded from cache self.dict_has_values(laikad.astro_dog.orbits) self.dict_has_values(laikad.astro_dog.nav) @@ -185,7 +250,7 @@ class TestLaikad(unittest.TestCase): mock_method.assert_not_called() # Verify cache is working for only orbits by running a segment - laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT) + laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT, save_ephemeris=True) msg = verify_messages(self.logs, laikad, return_one_success=True) self.assertIsNotNone(msg) # Verify orbit data is not downloaded diff --git a/selfdrive/locationd/test/test_locationd.py b/selfdrive/locationd/test/test_locationd.py index 7f5d75210..29036b838 100755 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -1,110 +1,16 @@ #!/usr/bin/env python3 -import os import json import random import unittest import time import capnp -from cffi import FFI -from cereal import log import cereal.messaging as messaging from cereal.services import service_list from common.params import Params from selfdrive.manager.process_config import managed_processes -SENSOR_DECIMATION = 1 -VISION_DECIMATION = 1 - -LIBLOCATIOND_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../liblocationd.so')) - - -class TestLocationdLib(unittest.TestCase): - def setUp(self): - header = '''typedef ...* Localizer_t; -Localizer_t localizer_init(); -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() - - 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) - return log.Event.from_bytes(self.ffi.buffer(self.msg_buff), nesting_limit=self.buff_size // 8) - - 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('sensorEvents', 1) - msg.sensorEvents[0].sensor = 1 - msg.sensorEvents[0].timestamp = msg.logMonoTime - msg.sensorEvents[0].type = 1 - msg.sensorEvents[0].init('acceleration') - msg.sensorEvents[0].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('sensorEvents', 1) - msg.sensorEvents[0].sensor = 1 - msg.sensorEvents[0].timestamp = msg.logMonoTime - msg.sensorEvents[0].type = 1 - msg.sensorEvents[0].init('acceleration') - msg.sensorEvents[0].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) - class TestLocationdProc(unittest.TestCase): MAX_WAITS = 1000 diff --git a/selfdrive/locationd/ublox_msg.cc b/selfdrive/locationd/ublox_msg.cc index c9833410e..c9f732e9a 100644 --- a/selfdrive/locationd/ublox_msg.cc +++ b/selfdrive/locationd/ublox_msg.cc @@ -144,7 +144,7 @@ kj::Array UbloxMsgParser::gen_nav_pvt(ubx_t::nav_pvt_t *msg) { timeinfo.tm_sec = msg->sec(); std::time_t utc_tt = timegm(&timeinfo); - gpsLoc.setTimestamp(utc_tt * 1e+03 + msg->nano() * 1e-06); + gpsLoc.setUnixTimestampMillis(utc_tt * 1e+03 + msg->nano() * 1e-06); float f[] = { msg->vel_n() * 1e-03f, msg->vel_e() * 1e-03f, msg->vel_d() * 1e-03f }; gpsLoc.setVNED(f); gpsLoc.setVerticalAccuracy(msg->v_acc() * 1e-03); diff --git a/selfdrive/loggerd/.gitignore b/selfdrive/loggerd/.gitignore index 6437be5e3..53dc24e6f 100644 --- a/selfdrive/loggerd/.gitignore +++ b/selfdrive/loggerd/.gitignore @@ -1,3 +1,4 @@ loggerd encoderd +bootlog tests/test_logger diff --git a/selfdrive/loggerd/encoder/encoder.h b/selfdrive/loggerd/encoder/encoder.h index 312b68ba1..21ef65cf1 100644 --- a/selfdrive/loggerd/encoder/encoder.h +++ b/selfdrive/loggerd/encoder/encoder.h @@ -8,7 +8,7 @@ #include "cereal/visionipc/visionipc.h" #include "common/queue.h" #include "selfdrive/loggerd/video_writer.h" -#include "selfdrive/camerad/cameras/camera_common.h" +#include "system/camerad/cameras/camera_common.h" #define V4L2_BUF_FLAG_KEYFRAME 8 diff --git a/selfdrive/loggerd/encoder/ffmpeg_encoder.cc b/selfdrive/loggerd/encoder/ffmpeg_encoder.cc index 22587819a..5f8d140e8 100644 --- a/selfdrive/loggerd/encoder/ffmpeg_encoder.cc +++ b/selfdrive/loggerd/encoder/ffmpeg_encoder.cc @@ -68,7 +68,9 @@ void FfmpegEncoder::encoder_open(const char* path) { void FfmpegEncoder::encoder_close() { if (!is_open) return; + writer_close(); + avcodec_free_context(&codec_ctx); is_open = false; } diff --git a/selfdrive/loggerd/encoderd.cc b/selfdrive/loggerd/encoderd.cc index 87cf4a492..9bd8e2f1d 100644 --- a/selfdrive/loggerd/encoderd.cc +++ b/selfdrive/loggerd/encoderd.cc @@ -124,10 +124,8 @@ void encoderd_thread() { std::vector encoder_threads; for (const auto &cam : cameras_logged) { - if (cam.enable) { - encoder_threads.push_back(std::thread(encoder_thread, &s, cam)); - s.max_waiting++; - } + encoder_threads.push_back(std::thread(encoder_thread, &s, cam)); + s.max_waiting++; } for (auto &t : encoder_threads) t.join(); } diff --git a/selfdrive/loggerd/logger.cc b/selfdrive/loggerd/logger.cc index 5aed47e29..8038f1926 100644 --- a/selfdrive/loggerd/logger.cc +++ b/selfdrive/loggerd/logger.cc @@ -19,15 +19,6 @@ #include "common/swaglog.h" #include "common/version.h" -// ***** logging helpers ***** - -void append_property(const char* key, const char* value, void *cookie) { - std::vector > *properties = - (std::vector > *)cookie; - - properties->push_back(std::make_pair(std::string(key), std::string(value))); -} - // ***** log metadata ***** kj::Array logger_build_init_data() { MessageBuilder msg; diff --git a/selfdrive/loggerd/loggerd.cc b/selfdrive/loggerd/loggerd.cc index 4086f4991..e0892e68b 100644 --- a/selfdrive/loggerd/loggerd.cc +++ b/selfdrive/loggerd/loggerd.cc @@ -6,7 +6,6 @@ ExitHandler do_exit; struct LoggerdState { LoggerState logger = {}; char segment_path[4096]; - std::mutex rotate_lock; std::atomic rotate_segment; std::atomic last_camera_seen_tms; std::atomic ready_to_rotate; // count of encoders ready to rotate @@ -15,15 +14,12 @@ struct LoggerdState { }; void logger_rotate(LoggerdState *s) { - { - std::unique_lock lk(s->rotate_lock); - int segment = -1; - int err = logger_next(&s->logger, LOG_ROOT.c_str(), s->segment_path, sizeof(s->segment_path), &segment); - assert(err == 0); - s->rotate_segment = segment; - s->ready_to_rotate = 0; - s->last_rotate_tms = millis_since_boot(); - } + int segment = -1; + int err = logger_next(&s->logger, LOG_ROOT.c_str(), s->segment_path, sizeof(s->segment_path), &segment); + assert(err == 0); + s->rotate_segment = segment; + s->ready_to_rotate = 0; + s->last_rotate_tms = millis_since_boot(); LOGW((s->logger.part == 0) ? "logging to %s" : "rotated to %s", s->segment_path); } @@ -208,10 +204,8 @@ void loggerd_thread() { // init encoders s.last_camera_seen_tms = millis_since_boot(); for (const auto &cam : cameras_logged) { - if (cam.enable) { - s.max_waiting++; - if (cam.has_qcamera) { s.max_waiting++; } - } + s.max_waiting++; + if (cam.has_qcamera) { s.max_waiting++; } } uint64_t msg_count = 0, bytes_count = 0; diff --git a/selfdrive/loggerd/loggerd.h b/selfdrive/loggerd/loggerd.h index 7e13e90e6..6eafbe08d 100644 --- a/selfdrive/loggerd/loggerd.h +++ b/selfdrive/loggerd/loggerd.h @@ -15,7 +15,7 @@ #include "cereal/services.h" #include "cereal/visionipc/visionipc.h" #include "cereal/visionipc/visionipc_client.h" -#include "selfdrive/camerad/cameras/camera_common.h" +#include "system/camerad/cameras/camera_common.h" #include "common/params.h" #include "common/swaglog.h" #include "common/timing.h" @@ -50,7 +50,6 @@ struct LogCameraInfo { int bitrate; bool is_h265; bool has_qcamera; - bool enable; bool record; }; @@ -63,7 +62,6 @@ const LogCameraInfo cameras_logged[] = { .bitrate = MAIN_BITRATE, .is_h265 = true, .has_qcamera = true, - .enable = true, .record = true, .frame_width = 1928, .frame_height = 1208, @@ -76,7 +74,6 @@ const LogCameraInfo cameras_logged[] = { .bitrate = DCAM_BITRATE, .is_h265 = true, .has_qcamera = false, - .enable = true, .record = Params().getBool("RecordFront"), .frame_width = 1928, .frame_height = 1208, @@ -89,7 +86,6 @@ const LogCameraInfo cameras_logged[] = { .bitrate = MAIN_BITRATE, .is_h265 = true, .has_qcamera = false, - .enable = true, .record = true, .frame_width = 1928, .frame_height = 1208, @@ -100,7 +96,6 @@ const LogCameraInfo qcam_info = { .fps = MAIN_FPS, .bitrate = 256000, .is_h265 = false, - .enable = true, .record = true, .frame_width = 526, .frame_height = 330, diff --git a/selfdrive/loggerd/tests/test_logger.cc b/selfdrive/loggerd/tests/test_logger.cc index 11a50fa2e..c8f662092 100644 --- a/selfdrive/loggerd/tests/test_logger.cc +++ b/selfdrive/loggerd/tests/test_logger.cc @@ -9,7 +9,7 @@ #include "cereal/messaging/messaging.h" #include "common/util.h" #include "selfdrive/loggerd/logger.h" -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/util.h" typedef cereal::Sentinel::SentinelType SentinelType; diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index 10b4c0a4b..12f894061 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 import os import subprocess -import sys -import time import textwrap from pathlib import Path @@ -28,62 +26,49 @@ def build(spinner: Spinner, dirty: bool = False) -> None: nproc = os.cpu_count() j_flag = "" if nproc is None else f"-j{nproc - 1}" - for retry in [True, False]: - 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 scons.returncode != 0: - # Read remaining output - r = scons.stderr.read().split(b'\n') - compile_output += r - - if retry and (not dirty): - if not os.getenv("CI"): - print("scons build failed, cleaning in") - for i in range(3, -1, -1): - print("....%d" % i) - time.sleep(1) - subprocess.check_call(["scons", "-c"], cwd=BASEDIR, env=env) - else: - print("scons build failed after retry") - sys.exit(1) - else: - # 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) - 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) - else: - break + 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 scons.returncode != 0: + # Read remaining output + r = scons.stderr.read().split(b'\n') + compile_output += r + + # 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) + 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()] diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 140c7f1d4..9c370cb3d 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -20,7 +20,7 @@ from selfdrive.manager.process_config import managed_processes from selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from system.swaglog import cloudlog, add_file_handler from system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ - terms_version, training_version + terms_version, training_version, is_tested_branch sys.path.append(os.path.join(BASEDIR, "pyextra")) @@ -78,6 +78,7 @@ def manager_init() -> None: params.put("GitCommit", get_commit(default="")) params.put("GitBranch", get_short_branch(default="")) params.put("GitRemote", get_origin(default="")) + params.put_bool("IsTestedBranch", is_tested_branch()) # set dongle id reg_res = register(show_spinner=True) diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index a9a5c78a7..dec51966a 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -18,6 +18,8 @@ def logging(started, params, CP: car.CarParams) -> bool: return started and run procs = [ + # due to qualcomm kernel bugs SIGKILLing camerad sometimes causes page table corruption + NativeProcess("camerad", "system/camerad", ["./camerad"], unkillable=True, callback=driverview), NativeProcess("clocksd", "system/clocksd", ["./clocksd"]), NativeProcess("logcatd", "system/logcatd", ["./logcatd"]), NativeProcess("proclogd", "system/proclogd", ["./proclogd"]), @@ -25,8 +27,6 @@ procs = [ PythonProcess("timezoned", "system.timezoned", enabled=not PC, offroad=True), DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), - # due to qualcomm kernel bugs SIGKILLing camerad sometimes causes page table corruption - NativeProcess("camerad", "selfdrive/camerad", ["./camerad"], unkillable=True, callback=driverview), NativeProcess("dmonitoringmodeld", "selfdrive/modeld", ["./dmonitoringmodeld"], enabled=(not PC or WEBCAM), callback=driverview), NativeProcess("encoderd", "selfdrive/loggerd", ["./encoderd"]), NativeProcess("loggerd", "selfdrive/loggerd", ["./loggerd"], onroad=False, callback=logging), @@ -41,6 +41,7 @@ procs = [ PythonProcess("controlsd", "selfdrive.controls.controlsd"), PythonProcess("deleter", "selfdrive.loggerd.deleter", offroad=True), PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", enabled=(not PC or WEBCAM), callback=driverview), + PythonProcess("laikad", "selfdrive.locationd.laikad"), PythonProcess("navd", "selfdrive.navd.navd"), PythonProcess("pandad", "selfdrive.boardd.pandad", offroad=True), PythonProcess("paramsd", "selfdrive.locationd.paramsd"), @@ -57,7 +58,6 @@ procs = [ # Experimental PythonProcess("rawgpsd", "selfdrive.sensord.rawgps.rawgpsd", enabled=os.path.isfile("/persist/comma/use-quectel-rawgps")), - PythonProcess("laikad", "selfdrive.locationd.laikad", enabled=os.path.isfile("/persist/comma/use-laikad")), ] 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 a84ff264d..f2e5319e8 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -7,7 +7,7 @@ import unittest import selfdrive.manager.manager as manager from selfdrive.manager.process import DaemonProcess from selfdrive.manager.process_config import managed_processes -from system.hardware import AGNOS, HARDWARE +from system.hardware import HARDWARE os.environ['FAKEUPLOAD'] = "1" @@ -35,8 +35,10 @@ class TestManager(unittest.TestCase): t = time.monotonic() - start assert t < MAX_STARTUP_TIME, f"startup took {t}s, expected <{MAX_STARTUP_TIME}s" - # ensure all processes exit cleanly def test_clean_exit(self): + """ + Ensure all processes exit cleanly when stopped. + """ HARDWARE.set_power_save(False) manager.manager_prepare() for p in ALL_PROCESSES: @@ -45,19 +47,18 @@ class TestManager(unittest.TestCase): time.sleep(10) for p in reversed(ALL_PROCESSES): - state = managed_processes[p].get_process_state_msg() - self.assertTrue(state.running, f"{p} not running") + with self.subTest(proc=p): + state = managed_processes[p].get_process_state_msg() + self.assertTrue(state.running, f"{p} not running") + exit_code = managed_processes[p].stop(retry=False) - exit_code = managed_processes[p].stop(retry=False) - if (AGNOS and p in ['ui',]): - # TODO: make Qt UI exit gracefully - continue + self.assertTrue(exit_code is not None, f"{p} failed to exit") - # TODO: interrupted blocking read exits with 1 in cereal. use a more unique return code - exit_codes = [0, 1] - if managed_processes[p].sigkill: - exit_codes = [-signal.SIGKILL] - assert exit_code in exit_codes, f"{p} died with {exit_code}" + # TODO: interrupted blocking read exits with 1 in cereal. use a more unique return code + exit_codes = [0, 1] + if managed_processes[p].sigkill: + exit_codes = [-signal.SIGKILL] + self.assertIn(exit_code, exit_codes, f"{p} died with {exit_code}") if __name__ == "__main__": diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 3e9738d86..a108e2c9d 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,6 +1,6 @@ import os -Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc') +Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'transformations') lenv = env.Clone() libs = [cereal, messaging, common, visionipc, gpucommon, @@ -82,4 +82,4 @@ lenv.Program('_dmonitoringmodeld', [ lenv.Program('_modeld', [ "modeld.cc", "models/driving.cc", - ]+common_model, LIBS=libs) + ]+common_model, LIBS=libs + transformations) diff --git a/selfdrive/modeld/dmonitoringmodeld.cc b/selfdrive/modeld/dmonitoringmodeld.cc index 68c49572e..cde13a9be 100644 --- a/selfdrive/modeld/dmonitoringmodeld.cc +++ b/selfdrive/modeld/dmonitoringmodeld.cc @@ -12,7 +12,7 @@ ExitHandler do_exit; void run_model(DMonitoringModelState &model, VisionIpcClient &vipc_client) { - PubMaster pm({"driverState"}); + PubMaster pm({"driverStateV2"}); SubMaster sm({"liveCalibration"}); float calib[CALIB_LEN] = {0}; double last = 0; @@ -31,11 +31,11 @@ void run_model(DMonitoringModelState &model, VisionIpcClient &vipc_client) { } double t1 = millis_since_boot(); - DMonitoringResult res = dmonitoring_eval_frame(&model, buf->addr, buf->width, buf->height, buf->stride, buf->uv_offset, calib); + 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, res, (t2 - t1) / 1000.0, model.output); + 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; diff --git a/selfdrive/modeld/modeld.cc b/selfdrive/modeld/modeld.cc index 0aac9b3c4..653661a3a 100644 --- a/selfdrive/modeld/modeld.cc +++ b/selfdrive/modeld/modeld.cc @@ -6,6 +6,8 @@ #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" @@ -14,40 +16,43 @@ #include "system/hardware/hw.h" #include "selfdrive/modeld/models/driving.h" + ExitHandler do_exit; -mat3 update_calibration(Eigen::Matrix &extrinsics, bool wide_camera, bool bigmodel_frame) { +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_road_frame - medmodel_frame_from_ground = medmodel_frame_from_road_frame[:, (0, 1, 3)] - ground_from_medmodel_frame = np.linalg.inv(medmodel_frame_from_ground) + 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 ground_from_medmodel_frame = (Eigen::Matrix() << + 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, - -1.84808520e-20, 9.00738606e-04, -4.28751576e-02).finished(); + 1.09890110e-03, 0.00000000e+00, -2.81318681e-01, + -2.25466395e-20, 1.09890110e-03,-5.23076923e-02).finished(); - static const auto ground_from_sbigmodel_frame = (Eigen::Matrix() << + 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, - -5.46146580e-20, 1.80147721e-03, -2.73464241e-01).finished(); + 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); - static const mat3 yuv_transform = get_model_yuv_transform(); - auto ground_from_model_frame = bigmodel_frame ? ground_from_sbigmodel_frame : ground_from_medmodel_frame; - auto camera_frame_from_road_frame = cam_intrinsics * extrinsics; - Eigen::Matrix camera_frame_from_ground; - camera_frame_from_ground.col(0) = camera_frame_from_road_frame.col(0); - camera_frame_from_ground.col(1) = camera_frame_from_road_frame.col(1); - camera_frame_from_ground.col(2) = camera_frame_from_road_frame.col(3); + 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; - auto warp_matrix = camera_frame_from_ground * ground_from_model_frame; mat3 transform = {}; for (int i=0; i<3*3; i++) { transform.v[i] = warp_matrix(i / 3, i % 3); } + static const mat3 yuv_transform = get_model_yuv_transform(); return matmul3(yuv_transform, transform); } @@ -55,7 +60,7 @@ mat3 update_calibration(Eigen::Matrix &extrinsics, bool wide_camera 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"}); + SubMaster sm({"lateralPlan", "roadCameraState", "liveCalibration", "driverMonitoringState"}); // setup filter to track dropped frames FirstOrderFilter frame_dropped_filter(0., 10., 1. / MODEL_FREQ); @@ -111,16 +116,16 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client_main, VisionIpcCl // 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 extrinsic_matrix = sm["liveCalibration"].getLiveCalibration().getExtrinsicMatrix(); - Eigen::Matrix extrinsic_matrix_eigen; - for (int i = 0; i < 4*3; i++) { - extrinsic_matrix_eigen(i / 4, i % 4) = extrinsic_matrix[i]; + 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(extrinsic_matrix_eigen, main_wide_camera, false); - model_transform_extra = update_calibration(extrinsic_matrix_eigen, true, true); + 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; } @@ -146,7 +151,7 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client_main, VisionIpcCl } double mt1 = millis_since_boot(); - ModelOutput *model_output = model_eval_frame(&model, buf_main, buf_extra, model_transform_main, model_transform_extra, vec_desire, prepare_only); + ModelOutput *model_output = model_eval_frame(&model, buf_main, buf_extra, model_transform_main, model_transform_extra, vec_desire, is_rhd, prepare_only); double mt2 = millis_since_boot(); float model_execution_time = (mt2 - mt1) / 1000.0; diff --git a/selfdrive/modeld/models/dmonitoring.cc b/selfdrive/modeld/models/dmonitoring.cc index 71da8dad5..e7e6d4661 100644 --- a/selfdrive/modeld/models/dmonitoring.cc +++ b/selfdrive/modeld/models/dmonitoring.cc @@ -10,8 +10,8 @@ #include "selfdrive/modeld/models/dmonitoring.h" -constexpr int MODEL_WIDTH = 320; -constexpr int MODEL_HEIGHT = 640; +constexpr int MODEL_WIDTH = 1440; +constexpr int MODEL_HEIGHT = 960; template static inline T *get_buffer(std::vector &buf, const size_t size) { @@ -19,199 +19,114 @@ static inline T *get_buffer(std::vector &buf, const size_t size) { return buf.data(); } -static inline void init_yuv_buf(std::vector &buf, const int width, int height) { - uint8_t *y = get_buffer(buf, width * height * 3 / 2); - uint8_t *u = y + width * height; - uint8_t *v = u + (width / 2) * (height / 2); - - // needed on comma two to make the padded border black - // equivalent to RGB(0,0,0) in YUV space - memset(y, 16, width * height); - memset(u, 128, (width / 2) * (height / 2)); - memset(v, 128, (width / 2) * (height / 2)); -} - void dmonitoring_init(DMonitoringModelState* s) { - s->is_rhd = Params().getBool("IsRHD"); - for (int x = 0; x < std::size(s->tensor); ++x) { - s->tensor[x] = (x - 128.f) * 0.0078125f; - } - init_yuv_buf(s->resized_buf, MODEL_WIDTH, MODEL_HEIGHT); #ifdef USE_ONNX_MODEL - s->m = new ONNXModel("models/dmonitoring_model.onnx", &s->output[0], OUTPUT_SIZE, USE_DSP_RUNTIME); + s->m = new ONNXModel("models/dmonitoring_model.onnx", &s->output[0], OUTPUT_SIZE, USE_DSP_RUNTIME, false, true); #else - s->m = new SNPEModel("models/dmonitoring_model_q.dlc", &s->output[0], OUTPUT_SIZE, USE_DSP_RUNTIME); + s->m = new SNPEModel("models/dmonitoring_model_q.dlc", &s->output[0], OUTPUT_SIZE, USE_DSP_RUNTIME, false, true); #endif s->m->addCalib(s->calib, CALIB_LEN); } -static inline auto get_yuv_buf(std::vector &buf, const int width, int height) { - uint8_t *y = get_buffer(buf, width * height * 3 / 2); - uint8_t *u = y + width * height; - uint8_t *v = u + (width /2) * (height / 2); - return std::make_tuple(y, u, v); +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]); } -struct Rect {int x, y, w, h;}; -void crop_nv12_to_yuv(uint8_t *raw, int stride, int uv_offset, uint8_t *y, uint8_t *u, uint8_t *v, const Rect &rect) { - uint8_t *raw_y = raw; - uint8_t *raw_uv = raw_y + uv_offset; - for (int r = 0; r < rect.h / 2; r++) { - memcpy(y + 2 * r * rect.w, raw_y + (2 * r + rect.y) * stride + rect.x, rect.w); - memcpy(y + (2 * r + 1) * rect.w, raw_y + (2 * r + rect.y + 1) * stride + rect.x, rect.w); - for (int h = 0; h < rect.w / 2; h++) { - u[r * rect.w/2 + h] = raw_uv[(r + (rect.y/2)) * stride + (rect.x/2 + h)*2]; - v[r * rect.w/2 + h] = raw_uv[(r + (rect.y/2)) * stride + (rect.x/2 + h)*2 + 1]; - } - } +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); } -DMonitoringResult dmonitoring_eval_frame(DMonitoringModelState* s, void* stream_buf, int width, int height, int stride, int uv_offset, float *calib) { - const int cropped_height = tici_dm_crop::width / 1.33; - Rect crop_rect = {width / 2 - tici_dm_crop::width / 2 + tici_dm_crop::x_offset, - height / 2 - cropped_height / 2 + tici_dm_crop::y_offset, - cropped_height / 2, - cropped_height}; - if (!s->is_rhd) { - crop_rect.x += tici_dm_crop::width - crop_rect.w; - } +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; - int resized_width = MODEL_WIDTH; - int resized_height = MODEL_HEIGHT; - - auto [cropped_y, cropped_u, cropped_v] = get_yuv_buf(s->cropped_buf, crop_rect.w, crop_rect.h); - if (!s->is_rhd) { - crop_nv12_to_yuv((uint8_t *)stream_buf, stride, uv_offset, cropped_y, cropped_u, cropped_v, crop_rect); - } else { - auto [mirror_y, mirror_u, mirror_v] = get_yuv_buf(s->premirror_cropped_buf, crop_rect.w, crop_rect.h); - crop_nv12_to_yuv((uint8_t *)stream_buf, stride, uv_offset, mirror_y, mirror_u, mirror_v, crop_rect); - libyuv::I420Mirror(mirror_y, crop_rect.w, - mirror_u, crop_rect.w / 2, - mirror_v, crop_rect.w / 2, - cropped_y, crop_rect.w, - cropped_u, crop_rect.w / 2, - cropped_v, crop_rect.w / 2, - crop_rect.w, crop_rect.h); - } + uint8_t *raw_buf = (uint8_t *) stream_buf; + // vertical crop free + uint8_t *raw_y_start = raw_buf + stride * v_off; - auto [resized_buf, resized_u, resized_v] = get_yuv_buf(s->resized_buf, resized_width, resized_height); - uint8_t *resized_y = resized_buf; - libyuv::FilterMode mode = libyuv::FilterModeEnum::kFilterBilinear; - libyuv::I420Scale(cropped_y, crop_rect.w, - cropped_u, crop_rect.w / 2, - cropped_v, crop_rect.w / 2, - crop_rect.w, crop_rect.h, - resized_y, resized_width, - resized_u, resized_width / 2, - resized_v, resized_width / 2, - resized_width, resized_height, - mode); - - - int yuv_buf_len = (MODEL_WIDTH/2) * (MODEL_HEIGHT/2) * 6; // Y|u|v -> y|y|y|y|u|v - float *net_input_buf = get_buffer(s->net_input_buf, yuv_buf_len); - // one shot conversion, O(n) anyway - // yuvframe2tensor, normalize - for (int r = 0; r < MODEL_HEIGHT/2; r++) { - for (int c = 0; c < MODEL_WIDTH/2; c++) { - // Y_ul - net_input_buf[(r*MODEL_WIDTH/2) + c + (0*(MODEL_WIDTH/2)*(MODEL_HEIGHT/2))] = s->tensor[resized_y[(2*r)*resized_width + 2*c]]; - // Y_dl - net_input_buf[(r*MODEL_WIDTH/2) + c + (1*(MODEL_WIDTH/2)*(MODEL_HEIGHT/2))] = s->tensor[resized_y[(2*r+1)*resized_width + 2*c]]; - // Y_ur - net_input_buf[(r*MODEL_WIDTH/2) + c + (2*(MODEL_WIDTH/2)*(MODEL_HEIGHT/2))] = s->tensor[resized_y[(2*r)*resized_width + 2*c+1]]; - // Y_dr - net_input_buf[(r*MODEL_WIDTH/2) + c + (3*(MODEL_WIDTH/2)*(MODEL_HEIGHT/2))] = s->tensor[resized_y[(2*r+1)*resized_width + 2*c+1]]; - // U - net_input_buf[(r*MODEL_WIDTH/2) + c + (4*(MODEL_WIDTH/2)*(MODEL_HEIGHT/2))] = s->tensor[resized_u[r*resized_width/2 + c]]; - // V - net_input_buf[(r*MODEL_WIDTH/2) + c + (5*(MODEL_WIDTH/2)*(MODEL_HEIGHT/2))] = s->tensor[resized_v[r*resized_width/2 + c]]; - } - } - - //printf("preprocess completed. %d \n", yuv_buf_len); - //FILE *dump_yuv_file = fopen("/tmp/rawdump.yuv", "wb"); - //fwrite(resized_buf, yuv_buf_len, sizeof(uint8_t), dump_yuv_file); - //fclose(dump_yuv_file); + uint8_t *net_input_buf = get_buffer(s->net_input_buf, yuv_buf_len); - // *** testing *** - // idat = np.frombuffer(open("/tmp/inputdump.yuv", "rb").read(), np.float32).reshape(6, 160, 320) - // imshow(cv2.cvtColor(tensor_to_frames(idat[None]/0.0078125+128)[0], cv2.COLOR_YUV2RGB_I420)) + // 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); + } - //FILE *dump_yuv_file2 = fopen("/tmp/inputdump.yuv", "wb"); - //fwrite(net_input_buf, MODEL_HEIGHT*MODEL_WIDTH*3/2, sizeof(float), dump_yuv_file2); - //fclose(dump_yuv_file2); + // 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->addImage(net_input_buf, yuv_buf_len); + s->m->addImage((float*)net_input_buf, yuv_buf_len / 4); for (int i = 0; i < CALIB_LEN; i++) { s->calib[i] = calib[i]; } s->m->execute(); double t2 = millis_since_boot(); - DMonitoringResult ret = {0}; - for (int i = 0; i < 3; ++i) { - ret.face_orientation[i] = s->output[i] * REG_SCALE; - ret.face_orientation_meta[i] = exp(s->output[6 + i]); - } - for (int i = 0; i < 2; ++i) { - ret.face_position[i] = s->output[3 + i] * REG_SCALE; - ret.face_position_meta[i] = exp(s->output[9 + i]); - } - for (int i = 0; i < 4; ++i) { - ret.ready_prob[i] = sigmoid(s->output[39 + i]); - } - for (int i = 0; i < 2; ++i) { - ret.not_ready_prob[i] = sigmoid(s->output[43 + i]); - } - ret.face_prob = sigmoid(s->output[12]); - ret.left_eye_prob = sigmoid(s->output[21]); - ret.right_eye_prob = sigmoid(s->output[30]); - ret.left_blink_prob = sigmoid(s->output[31]); - ret.right_blink_prob = sigmoid(s->output[32]); - ret.sg_prob = sigmoid(s->output[33]); - ret.poor_vision = sigmoid(s->output[34]); - ret.partial_face = sigmoid(s->output[35]); - ret.distracted_pose = sigmoid(s->output[36]); - ret.distracted_eyes = sigmoid(s->output[37]); - ret.occluded_prob = sigmoid(s->output[38]); - ret.dsp_execution_time = (t2 - t1) / 1000.; - return ret; + 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 DMonitoringResult &res, float execution_time, kj::ArrayPtr raw_pred) { +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().initDriverState(); + auto framed = msg.initEvent().initDriverStateV2(); framed.setFrameId(frame_id); framed.setModelExecutionTime(execution_time); - framed.setDspExecutionTime(res.dsp_execution_time); - - framed.setFaceOrientation(res.face_orientation); - framed.setFaceOrientationStd(res.face_orientation_meta); - framed.setFacePosition(res.face_position); - framed.setFacePositionStd(res.face_position_meta); - framed.setFaceProb(res.face_prob); - framed.setLeftEyeProb(res.left_eye_prob); - framed.setRightEyeProb(res.right_eye_prob); - framed.setLeftBlinkProb(res.left_blink_prob); - framed.setRightBlinkProb(res.right_blink_prob); - framed.setSunglassesProb(res.sg_prob); - framed.setPoorVision(res.poor_vision); - framed.setPartialFace(res.partial_face); - framed.setDistractedPose(res.distracted_pose); - framed.setDistractedEyes(res.distracted_eyes); - framed.setOccludedProb(res.occluded_prob); - framed.setReadyProb(res.ready_prob); - framed.setNotReadyProb(res.not_ready_prob); + 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("driverState", msg); + pm.send("driverStateV2", msg); } void dmonitoring_free(DMonitoringModelState* s) { diff --git a/selfdrive/modeld/models/dmonitoring.h b/selfdrive/modeld/models/dmonitoring.h index a1be91e3b..ae2bf0539 100644 --- a/selfdrive/modeld/models/dmonitoring.h +++ b/selfdrive/modeld/models/dmonitoring.h @@ -9,44 +9,42 @@ #define CALIB_LEN 3 -#define OUTPUT_SIZE 45 +#define OUTPUT_SIZE 84 #define REG_SCALE 0.25f -typedef struct DMonitoringResult { +typedef struct DriverStateResult { float face_orientation[3]; - float face_orientation_meta[3]; + float face_orientation_std[3]; float face_position[2]; - float face_position_meta[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 sg_prob; - float poor_vision; - float partial_face; - float distracted_pose; - float distracted_eyes; + 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; -} DMonitoringResult; +} DMonitoringModelResult; typedef struct DMonitoringModelState { RunModel *m; - bool is_rhd; float output[OUTPUT_SIZE]; - std::vector resized_buf; - std::vector cropped_buf; - std::vector premirror_cropped_buf; - std::vector net_input_buf; + std::vector net_input_buf; float calib[CALIB_LEN]; - float tensor[UINT8_MAX + 1]; } DMonitoringModelState; void dmonitoring_init(DMonitoringModelState* s); -DMonitoringResult 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 DMonitoringResult &res, float execution_time, kj::ArrayPtr raw_pred); +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/dmonitoring_model.current b/selfdrive/modeld/models/dmonitoring_model.current index 74bcfe17a..d1e7d1136 100644 --- a/selfdrive/modeld/models/dmonitoring_model.current +++ b/selfdrive/modeld/models/dmonitoring_model.current @@ -1,2 +1,2 @@ -a8236e30-5bee-4689-8ea0-fc102e2770e5 -d508c79bae1c1c451f3af3e2bc231ce33678cb43 \ No newline at end of file +ee8f830b-d6a1-42ef-9b1b-50fd0b2faae4 +cac8f7b69d420506707ff7a19d573d5011ef2533 \ No newline at end of file diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 51b0d1ed7..4cbd6bb7d 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00731ebd06fcff7e5837607b91bc56cad3bed5d7ee89052c911c981e8f665308 -size 3679940 +oid sha256:932e589e5cce66e5d9f48492426a33c74cd7f352a870d3ddafcede3e9156f30d +size 9157561 diff --git a/selfdrive/modeld/models/dmonitoring_model_q.dlc b/selfdrive/modeld/models/dmonitoring_model_q.dlc index 2e54f7ee4..94632030e 100644 --- a/selfdrive/modeld/models/dmonitoring_model_q.dlc +++ b/selfdrive/modeld/models/dmonitoring_model_q.dlc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:667df5e925570a0f6a33dfb890e186a1f13f101885b46db47ec45305737dffb6 -size 1145921 +oid sha256:3587976a8b7e3be274fa86c2e2233e3e464cad713f5077c4394cd1ddd3c7c6c5 +size 2636965 diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc index 59c0b249d..9bf7e6218 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -49,14 +49,12 @@ void model_init(ModelState* s, cl_device_id device_id, cl_context context) { #endif #ifdef TRAFFIC_CONVENTION - const int idx = Params().getBool("IsRHD") ? 1 : 0; - s->traffic_convention[idx] = 1.0; s->m->addTrafficConvention(s->traffic_convention, TRAFFIC_CONVENTION_LEN); #endif } ModelOutput* model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* wbuf, - const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool prepare_only) { + const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, bool prepare_only) { #ifdef DESIRE if (desire_in != NULL) { for (int i = 1; i < DESIRE_LEN; i++) { @@ -72,6 +70,10 @@ ModelOutput* model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* wbuf, } #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->getInputBuf())); s->m->addImage(net_input_buf, s->frame->buf_size); diff --git a/selfdrive/modeld/models/driving.h b/selfdrive/modeld/models/driving.h index a69105163..d551bdf48 100644 --- a/selfdrive/modeld/models/driving.h +++ b/selfdrive/modeld/models/driving.h @@ -245,7 +245,7 @@ struct ModelOutput { constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float); #ifdef TEMPORAL - constexpr int TEMPORAL_SIZE = 512+256; + constexpr int TEMPORAL_SIZE = 512; #else constexpr int TEMPORAL_SIZE = 0; #endif @@ -268,7 +268,7 @@ struct ModelState { 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 prepare_only); + const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, bool prepare_only); void model_free(ModelState* s); void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, const ModelOutput &net_outputs, uint64_t timestamp_eof, diff --git a/selfdrive/modeld/models/supercombo.dlc b/selfdrive/modeld/models/supercombo.dlc index 90f7a2e65..fe3ad5102 100644 --- a/selfdrive/modeld/models/supercombo.dlc +++ b/selfdrive/modeld/models/supercombo.dlc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c2cb3a3054f3292bbe538d6b793908dc2e234c200802d41b6766d3cb51b0b44 -size 101662751 +oid sha256:3c5c8d71a8a1434ef79073362e608b9fe02f22ce7478f11bc71c6806c1e00091 +size 94302331 diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index 049339856..39e874364 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96b60d0bfd1386c93b4f79195aa1c5e77b23e0250578a308ee2c58857ed5eb49 -size 102570834 +oid sha256:15d9eb01edd98998abceaa092d33fab149ff4a8942646b20a7c1403f999f4eca +size 95165081 diff --git a/selfdrive/modeld/runners/onnx_runner.py b/selfdrive/modeld/runners/onnx_runner.py index e282a66b6..ac7cc6881 100755 --- a/selfdrive/modeld/runners/onnx_runner.py +++ b/selfdrive/modeld/runners/onnx_runner.py @@ -9,20 +9,24 @@ os.environ["OMP_WAIT_POLICY"] = "PASSIVE" import onnxruntime as ort # pylint: disable=import-error -def read(sz): +def read(sz, tf8=False): dd = [] gt = 0 - while gt < sz * 4: - st = os.read(0, sz * 4 - gt) + 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) - return np.frombuffer(b''.join(dd), dtype=np.float32) + r = np.frombuffer(b''.join(dd), dtype=np.uint8 if tf8 else np.float32).astype(np.float32) + if tf8: + r = r / 255. + return r def write(d): os.write(1, d.tobytes()) -def run_loop(m): +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()] @@ -33,10 +37,10 @@ def run_loop(m): print("ready to run onnx model", keys, ishapes, file=sys.stderr) while 1: inputs = [] - for shp in ishapes: + for k, shp in zip(keys, ishapes): ts = np.product(shp) #print("reshaping %s with offset %d" % (str(shp), offset), file=sys.stderr) - inputs.append(read(ts).reshape(shp)) + inputs.append(read(ts, (k=='input_img' and tf8_input)).reshape(shp)) ret = m.run(None, dict(zip(keys, inputs))) #print(ret, file=sys.stderr) for r in ret: @@ -44,6 +48,7 @@ def run_loop(m): 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 @@ -63,6 +68,6 @@ if __name__ == "__main__": 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) + 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 index 9b4d6fd01..1f9f551ab 100644 --- a/selfdrive/modeld/runners/onnxmodel.cc +++ b/selfdrive/modeld/runners/onnxmodel.cc @@ -14,12 +14,13 @@ #include "common/swaglog.h" #include "common/util.h" -ONNXModel::ONNXModel(const char *path, float *_output, size_t _output_size, int runtime, bool _use_extra) { +ONNXModel::ONNXModel(const char *path, float *_output, size_t _output_size, int runtime, bool _use_extra, bool _use_tf8) { LOGD("loading model %s", path); output = _output; output_size = _output_size; use_extra = _use_extra; + use_tf8 = _use_tf8; int err = pipe(pipein); assert(err == 0); @@ -28,11 +29,12 @@ ONNXModel::ONNXModel(const char *path, float *_output, size_t _output_size, int 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, nullptr}; + char *argv[] = {(char*)onnx_runner.c_str(), (char*)path, (char*)tf8_arg.c_str(), nullptr}; dup2(pipein[0], 0); dup2(pipeout[1], 1); close(pipein[0]); diff --git a/selfdrive/modeld/runners/onnxmodel.h b/selfdrive/modeld/runners/onnxmodel.h index 567d81d29..4ac599e2a 100644 --- a/selfdrive/modeld/runners/onnxmodel.h +++ b/selfdrive/modeld/runners/onnxmodel.h @@ -6,7 +6,7 @@ class ONNXModel : public RunModel { public: - ONNXModel(const char *path, float *output, size_t output_size, int runtime, bool use_extra = false); + ONNXModel(const char *path, float *output, size_t output_size, int runtime, bool use_extra = false, bool _use_tf8 = false); ~ONNXModel(); void addRecurrent(float *state, int state_size); void addDesire(float *state, int state_size); @@ -31,6 +31,7 @@ private: int calib_size; float *image_input_buf = NULL; int image_buf_size; + bool use_tf8; float *extra_input_buf = NULL; int extra_buf_size; bool use_extra; diff --git a/selfdrive/modeld/runners/snpemodel.cc b/selfdrive/modeld/runners/snpemodel.cc index 1861494d5..4d6917e89 100644 --- a/selfdrive/modeld/runners/snpemodel.cc +++ b/selfdrive/modeld/runners/snpemodel.cc @@ -14,10 +14,11 @@ void PrintErrorStringAndExit() { std::exit(EXIT_FAILURE); } -SNPEModel::SNPEModel(const char *path, float *loutput, size_t loutput_size, int runtime, bool luse_extra) { +SNPEModel::SNPEModel(const char *path, float *loutput, size_t loutput_size, int runtime, bool luse_extra, bool luse_tf8) { output = loutput; output_size = loutput_size; use_extra = luse_extra; + use_tf8 = luse_tf8; #ifdef QCOM2 if (runtime==USE_GPU_RUNTIME) { Runtime = zdl::DlSystem::Runtime_t::GPU; @@ -70,14 +71,16 @@ SNPEModel::SNPEModel(const char *path, float *loutput, size_t loutput_size, int printf("model: %s -> %s\n", input_tensor_name, output_tensor_name); zdl::DlSystem::UserBufferEncodingFloat userBufferEncodingFloat; + zdl::DlSystem::UserBufferEncodingTf8 userBufferEncodingTf8(0, 1./255); // network takes 0-1 zdl::DlSystem::IUserBufferFactory& ubFactory = zdl::SNPE::SNPEFactory::getUserBufferFactory(); + size_t size_of_input = use_tf8 ? sizeof(uint8_t) : sizeof(float); // create input buffer { const auto &inputDims_opt = snpe->getInputDimensions(input_tensor_name); const zdl::DlSystem::TensorShape& bufferShape = *inputDims_opt; std::vector strides(bufferShape.rank()); - strides[strides.size() - 1] = sizeof(float); + strides[strides.size() - 1] = size_of_input; size_t product = 1; for (size_t i = 0; i < bufferShape.rank(); i++) product *= bufferShape[i]; size_t stride = strides[strides.size() - 1]; @@ -86,7 +89,10 @@ SNPEModel::SNPEModel(const char *path, float *loutput, size_t loutput_size, int strides[i-1] = stride; } printf("input product is %lu\n", product); - inputBuffer = ubFactory.createUserBuffer(NULL, product*sizeof(float), strides, &userBufferEncodingFloat); + inputBuffer = ubFactory.createUserBuffer(NULL, + product*size_of_input, + strides, + use_tf8 ? (zdl::DlSystem::UserBufferEncoding*)&userBufferEncodingTf8 : (zdl::DlSystem::UserBufferEncoding*)&userBufferEncodingFloat); inputMap.add(input_tensor_name, inputBuffer.get()); } diff --git a/selfdrive/modeld/runners/snpemodel.h b/selfdrive/modeld/runners/snpemodel.h index ba51fdced..ed9d58d1e 100644 --- a/selfdrive/modeld/runners/snpemodel.h +++ b/selfdrive/modeld/runners/snpemodel.h @@ -23,7 +23,7 @@ class SNPEModel : public RunModel { public: - SNPEModel(const char *path, float *loutput, size_t loutput_size, int runtime, bool luse_extra = false); + SNPEModel(const char *path, float *loutput, size_t loutput_size, int runtime, bool luse_extra = false, bool use_tf8 = false); void addRecurrent(float *state, int state_size); void addTrafficConvention(float *state, int state_size); void addCalib(float *state, int state_size); @@ -52,6 +52,7 @@ private: std::unique_ptr inputBuffer; float *input; size_t input_size; + bool use_tf8; // snpe output stuff zdl::DlSystem::UserBufferMap outputMap; diff --git a/selfdrive/modeld/thneed/compile.cc b/selfdrive/modeld/thneed/compile.cc index a2f55ffd9..f76c63b2b 100644 --- a/selfdrive/modeld/thneed/compile.cc +++ b/selfdrive/modeld/thneed/compile.cc @@ -5,7 +5,7 @@ #include "selfdrive/modeld/thneed/thneed.h" #include "system/hardware/hw.h" -#define TEMPORAL_SIZE 512+256 +#define TEMPORAL_SIZE 512 #define DESIRE_LEN 8 #define TRAFFIC_CONVENTION_LEN 2 diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 9a3196030..35eee5b03 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -3,7 +3,7 @@ import gc import cereal.messaging as messaging from cereal import car -from common.params import Params +from common.params import Params, put_bool_nonblocking from common.realtime import set_realtime_priority from selfdrive.controls.lib.events import Events from selfdrive.locationd.calibrationd import Calibration @@ -18,9 +18,9 @@ def dmonitoringd_thread(sm=None, pm=None): pm = messaging.PubMaster(['driverMonitoringState']) if sm is None: - sm = messaging.SubMaster(['driverState', 'liveCalibration', 'carState', 'controlsState', 'modelV2'], poll=['driverState']) + sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'controlsState', 'modelV2'], poll=['driverStateV2']) - driver_status = DriverStatus(rhd=Params().get_bool("IsRHD")) + driver_status = DriverStatus(rhd_saved=Params().get_bool("IsRhdDetected")) sm['liveCalibration'].calStatus = Calibration.INVALID sm['liveCalibration'].rpyCalib = [0, 0, 0] @@ -34,7 +34,7 @@ def dmonitoringd_thread(sm=None, pm=None): while True: sm.update() - if not sm.updated['driverState']: + if not sm.updated['driverStateV2']: continue # Get interaction @@ -51,7 +51,7 @@ def dmonitoringd_thread(sm=None, pm=None): # Get data from dmonitoringmodeld events = Events() - driver_status.update_states(sm['driverState'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled) + driver_status.update_states(sm['driverStateV2'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled) # Block engaging after max number of distrations if driver_status.terminal_alert_cnt >= driver_status.settings._MAX_TERMINAL_ALERTS or \ @@ -79,9 +79,15 @@ def dmonitoringd_thread(sm=None, pm=None): "isLowStd": driver_status.pose.low_std, "hiStdCount": driver_status.hi_stds, "isActiveMode": driver_status.active_monitoring_mode, + "isRHD": driver_status.wheel_on_right, } pm.send('driverMonitoringState', dat) + # save rhd virtual toggle every 5 mins + if (sm['driverStateV2'].frameId % 6000 == 0 and + driver_status.wheelpos_learner.filtered_stat.n > driver_status.settings._WHEELPOS_FILTER_MIN_COUNT and + driver_status.wheel_on_right == (driver_status.wheelpos_learner.filtered_stat.M > driver_status.settings._WHEELPOS_THRESHOLD)): + put_bool_nonblocking("IsRhdDetected", driver_status.wheel_on_right) def main(sm=None, pm=None): dmonitoringd_thread(sm, pm) diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/driver_monitor.py index 662e0d76c..9ff3125c1 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/driver_monitor.py @@ -5,6 +5,7 @@ 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 EventName = car.CarEvent.EventName @@ -25,33 +26,30 @@ class DRIVER_MONITOR_SETTINGS(): self._DISTRACTED_PRE_TIME_TILL_TERMINAL = 8. self._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 6. - self._FACE_THRESHOLD = 0.5 - self._PARTIAL_FACE_THRESHOLD = 0.8 + self._FACE_THRESHOLD = 0.7 self._EYE_THRESHOLD = 0.65 - self._SG_THRESHOLD = 0.925 - self._BLINK_THRESHOLD = 0.8 - self._BLINK_THRESHOLD_SLACK = 0.9 - self._BLINK_THRESHOLD_STRICT = self._BLINK_THRESHOLD + self._SG_THRESHOLD = 0.9 + self._BLINK_THRESHOLD = 0.87 self._EE_THRESH11 = 0.75 self._EE_THRESH12 = 3.25 self._EE_THRESH21 = 0.01 self._EE_THRESH22 = 0.35 - self._POSE_PITCH_THRESHOLD = 0.3237 - self._POSE_PITCH_THRESHOLD_SLACK = 0.3657 + self._POSE_PITCH_THRESHOLD = 0.3133 + self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 self._POSE_PITCH_THRESHOLD_STRICT = self._POSE_PITCH_THRESHOLD - self._POSE_YAW_THRESHOLD = 0.3109 - self._POSE_YAW_THRESHOLD_SLACK = 0.4294 + self._POSE_YAW_THRESHOLD = 0.4020 + self._POSE_YAW_THRESHOLD_SLACK = 0.5042 self._POSE_YAW_THRESHOLD_STRICT = self._POSE_YAW_THRESHOLD - self._PITCH_NATURAL_OFFSET = 0.057 # initial value before offset is learned - self._YAW_NATURAL_OFFSET = 0.11 # initial value before offset is learned + self._PITCH_NATURAL_OFFSET = 0.029 # initial value before offset is learned + self._YAW_NATURAL_OFFSET = 0.097 # initial value before offset is learned self._PITCH_MAX_OFFSET = 0.124 self._PITCH_MIN_OFFSET = -0.0881 self._YAW_MAX_OFFSET = 0.289 self._YAW_MIN_OFFSET = -0.0246 - self._POSESTD_THRESHOLD = 0.315 + self._POSESTD_THRESHOLD = 0.3 self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz @@ -59,6 +57,10 @@ class DRIVER_MONITOR_SETTINGS(): self._POSE_OFFSET_MIN_COUNT = int(60 / self._DT_DMON) # valid data counts before calibration completes, 1min cumulative self._POSE_OFFSET_MAX_COUNT = int(360 / self._DT_DMON) # stop deweighting new data after 6 min, aka "short term memory" + self._WHEELPOS_CALIB_MIN_SPEED = 11 + self._WHEELPOS_THRESHOLD = 0.5 + self._WHEELPOS_FILTER_MIN_COUNT = int(15 / self._DT_DMON) # allow 15 seconds to converge wheel side + self._RECOVERY_FACTOR_MAX = 5. # relative to minus step change self._RECOVERY_FACTOR_MIN = 1.25 # relative to minus step change @@ -66,9 +68,9 @@ class DRIVER_MONITOR_SETTINGS(): self._MAX_TERMINAL_DURATION = int(30 / self._DT_DMON) # not allowed to engage after 30s of terminal alerts -# model output refers to center of cropped image, so need to apply the x displacement offset -RESIZED_FOCAL = 320.0 -H, W, FULL_W = 320, 160, 426 +# model output refers to center of undistorted+leveled image +EFL = 598.0 # focal length in K +W, H = tici_d_frame_size # corrected image has same size as raw class DistractedType: NOT_DISTRACTED = 0 @@ -76,22 +78,22 @@ class DistractedType: DISTRACTED_BLINK = 2 DISTRACTED_E2E = 4 -def face_orientation_from_net(angles_desc, pos_desc, rpy_calib, is_rhd): +def face_orientation_from_net(angles_desc, pos_desc, rpy_calib): # the output of these angles are in device frame # so from driver's perspective, pitch is up and yaw is right pitch_net, yaw_net, roll_net = angles_desc - face_pixel_position = ((pos_desc[0] + .5)*W - W + FULL_W, (pos_desc[1]+.5)*H) - yaw_focal_angle = atan2(face_pixel_position[0] - FULL_W//2, RESIZED_FOCAL) - pitch_focal_angle = atan2(face_pixel_position[1] - H//2, RESIZED_FOCAL) + face_pixel_position = ((pos_desc[0]+0.5)*W, (pos_desc[1]+0.5)*H) + yaw_focal_angle = atan2(face_pixel_position[0] - W//2, EFL) + pitch_focal_angle = atan2(face_pixel_position[1] - H//2, EFL) pitch = pitch_net + pitch_focal_angle yaw = -yaw_net + yaw_focal_angle # no calib for roll pitch -= rpy_calib[1] - yaw -= rpy_calib[2] * (1 - 2 * int(is_rhd)) # lhd -> -=, rhd -> += + yaw -= rpy_calib[2] return roll_net, pitch, yaw class DriverPose(): @@ -112,15 +114,14 @@ class DriverBlink(): def __init__(self): self.left_blink = 0. self.right_blink = 0. - self.cfactor = 1. class DriverStatus(): - def __init__(self, rhd=False, settings=DRIVER_MONITOR_SETTINGS()): + def __init__(self, rhd_saved=False, settings=DRIVER_MONITOR_SETTINGS()): # init policy settings self.settings = settings # init driver status - self.is_rhd_region = rhd + self.wheelpos_learner = RunningStatFilter() self.pose = DriverPose(self.settings._POSE_OFFSET_MAX_COUNT) self.pose_calibrated = False self.blink = DriverBlink() @@ -137,8 +138,10 @@ class DriverStatus(): self.distracted_types = [] self.driver_distracted = False self.driver_distraction_filter = FirstOrderFilter(0., self.settings._DISTRACTED_FILTER_TS, self.settings._DT_DMON) + self.wheel_on_right = False + self.wheel_on_right_last = None + self.wheel_on_right_default = rhd_saved self.face_detected = False - self.face_partial = False self.terminal_alert_cnt = 0 self.terminal_time = 0 self.step_change = 0. @@ -197,7 +200,7 @@ class DriverStatus(): yaw_error > self.settings._POSE_YAW_THRESHOLD*self.pose.cfactor_yaw: distracted_types.append(DistractedType.DISTRACTED_POSE) - if (self.blink.left_blink + self.blink.right_blink)*0.5 > self.settings._BLINK_THRESHOLD*self.blink.cfactor: + if (self.blink.left_blink + self.blink.right_blink)*0.5 > self.settings._BLINK_THRESHOLD: distracted_types.append(DistractedType.DISTRACTED_BLINK) if self.ee1_calibrated: @@ -214,13 +217,7 @@ class DriverStatus(): return distracted_types def set_policy(self, model_data, car_speed): - ep = min(model_data.meta.engagedProb, 0.8) / 0.8 # engaged prob bp = model_data.meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s - # TODO: retune adaptive blink - self.blink.cfactor = interp(ep, [0, 0.5, 1], - [self.settings._BLINK_THRESHOLD_STRICT, - self.settings._BLINK_THRESHOLD, - self.settings._BLINK_THRESHOLD_SLACK]) / self.settings._BLINK_THRESHOLD k1 = max(-0.00156*((car_speed-16)**2)+0.6, 0.2) bp_normal = max(min(bp / k1, 0.5),0) self.pose.cfactor_pitch = interp(bp_normal, [0, 0.5], @@ -231,28 +228,42 @@ class DriverStatus(): self.settings._POSE_YAW_THRESHOLD_STRICT]) / self.settings._POSE_YAW_THRESHOLD def update_states(self, driver_state, cal_rpy, car_speed, op_engaged): - if not all(len(x) > 0 for x in (driver_state.faceOrientation, driver_state.facePosition, - driver_state.faceOrientationStd, driver_state.facePositionStd, - driver_state.readyProb, driver_state.notReadyProb)): + rhd_pred = driver_state.wheelOnRightProb + # calibrates only when there's movement and either face detected + if car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or + driver_state.rightDriverData.faceProb > self.settings._FACE_THRESHOLD): + self.wheelpos_learner.push_and_update(rhd_pred) + if self.wheelpos_learner.filtered_stat.n > self.settings._WHEELPOS_FILTER_MIN_COUNT: + self.wheel_on_right = self.wheelpos_learner.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD + else: + self.wheel_on_right = self.wheel_on_right_default # use default/saved if calibration is unfinished + # make sure no switching when engaged + if op_engaged and self.wheel_on_right_last is not None and self.wheel_on_right_last != self.wheel_on_right: + self.wheel_on_right = self.wheel_on_right_last + driver_data = driver_state.rightDriverData if self.wheel_on_right else driver_state.leftDriverData + if not all(len(x) > 0 for x in (driver_data.faceOrientation, driver_data.facePosition, + driver_data.faceOrientationStd, driver_data.facePositionStd, + driver_data.readyProb, driver_data.notReadyProb)): return - self.face_partial = driver_state.partialFace > self.settings._PARTIAL_FACE_THRESHOLD - self.face_detected = driver_state.faceProb > self.settings._FACE_THRESHOLD or self.face_partial - self.pose.roll, self.pose.pitch, self.pose.yaw = face_orientation_from_net(driver_state.faceOrientation, driver_state.facePosition, cal_rpy, self.is_rhd_region) - self.pose.pitch_std = driver_state.faceOrientationStd[0] - self.pose.yaw_std = driver_state.faceOrientationStd[1] - # self.pose.roll_std = driver_state.faceOrientationStd[2] + self.face_detected = driver_data.faceProb > self.settings._FACE_THRESHOLD + self.pose.roll, self.pose.pitch, self.pose.yaw = face_orientation_from_net(driver_data.faceOrientation, driver_data.facePosition, cal_rpy) + if self.wheel_on_right: + self.pose.yaw *= -1 + self.wheel_on_right_last = self.wheel_on_right + self.pose.pitch_std = driver_data.faceOrientationStd[0] + self.pose.yaw_std = driver_data.faceOrientationStd[1] model_std_max = max(self.pose.pitch_std, self.pose.yaw_std) - self.pose.low_std = model_std_max < self.settings._POSESTD_THRESHOLD and not self.face_partial - self.blink.left_blink = driver_state.leftBlinkProb * (driver_state.leftEyeProb > self.settings._EYE_THRESHOLD) * (driver_state.sunglassesProb < self.settings._SG_THRESHOLD) - self.blink.right_blink = driver_state.rightBlinkProb * (driver_state.rightEyeProb > self.settings._EYE_THRESHOLD) * (driver_state.sunglassesProb < self.settings._SG_THRESHOLD) - self.eev1 = driver_state.notReadyProb[1] - self.eev2 = driver_state.readyProb[0] + self.pose.low_std = model_std_max < self.settings._POSESTD_THRESHOLD + self.blink.left_blink = driver_data.leftBlinkProb * (driver_data.leftEyeProb > self.settings._EYE_THRESHOLD) * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) + self.blink.right_blink = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) + self.eev1 = driver_data.notReadyProb[1] + self.eev2 = driver_data.readyProb[0] self.distracted_types = self._get_distracted_types() self.driver_distracted = (DistractedType.DISTRACTED_POSE in self.distracted_types or DistractedType.DISTRACTED_BLINK in self.distracted_types) and \ - driver_state.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std + driver_data.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std self.driver_distraction_filter.update(self.driver_distracted) # update offseter diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index a84ed242b..43b5e7747 100755 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -17,19 +17,19 @@ INVISIBLE_SECONDS_TO_ORANGE = dm_settings._AWARENESS_TIME - dm_settings._AWARENE INVISIBLE_SECONDS_TO_RED = dm_settings._AWARENESS_TIME + 1 def make_msg(face_detected, distracted=False, model_uncertain=False): - ds = log.DriverState.new_message() - ds.faceOrientation = [0., 0., 0.] - ds.facePosition = [0., 0.] - ds.faceProb = 1. * face_detected - ds.leftEyeProb = 1. - ds.rightEyeProb = 1. - ds.leftBlinkProb = 1. * distracted - ds.rightBlinkProb = 1. * distracted - ds.faceOrientationStd = [1.*model_uncertain, 1.*model_uncertain, 1.*model_uncertain] - ds.facePositionStd = [1.*model_uncertain, 1.*model_uncertain] + ds = log.DriverStateV2.new_message() + ds.leftDriverData.faceOrientation = [0., 0., 0.] + ds.leftDriverData.facePosition = [0., 0.] + ds.leftDriverData.faceProb = 1. * face_detected + ds.leftDriverData.leftEyeProb = 1. + ds.leftDriverData.rightEyeProb = 1. + ds.leftDriverData.leftBlinkProb = 1. * distracted + ds.leftDriverData.rightBlinkProb = 1. * distracted + ds.leftDriverData.faceOrientationStd = [1.*model_uncertain, 1.*model_uncertain, 1.*model_uncertain] + ds.leftDriverData.facePositionStd = [1.*model_uncertain, 1.*model_uncertain] # TODO: test both separately when e2e is used - ds.readyProb = [0., 0., 0., 0.] - ds.notReadyProb = [0., 0.] + ds.leftDriverData.readyProb = [0., 0., 0., 0.] + ds.leftDriverData.notReadyProb = [0., 0.] return ds diff --git a/selfdrive/navd/.gitignore b/selfdrive/navd/.gitignore new file mode 100644 index 000000000..a070fe32b --- /dev/null +++ b/selfdrive/navd/.gitignore @@ -0,0 +1,5 @@ +moc_* +*.moc + +map_renderer +libmap_renderer.so diff --git a/selfdrive/navd/SConscript b/selfdrive/navd/SConscript new file mode 100644 index 000000000..4fbe41e80 --- /dev/null +++ b/selfdrive/navd/SConscript @@ -0,0 +1,20 @@ +Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'cereal', 'transformations') + +base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', + 'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] + +if arch == 'larch64': + base_libs.append('EGL') + +if arch in ['larch64', 'x86_64']: + if arch == 'x86_64': + rpath = [Dir(f"#third_party/mapbox-gl-native-qt/{arch}").srcnode().abspath] + qt_env["RPATH"] += rpath + + qt_libs = ["qt_widgets", "qt_util", "qmapboxgl"] + base_libs + + nav_src = ["main.cc", "map_renderer.cc"] + qt_env.Program("map_renderer", nav_src, LIBS=qt_libs + ['common', 'json11']) + + if GetOption('extras'): + qt_env.SharedLibrary("map_renderer", ["map_renderer.cc"], LIBS=qt_libs + ['common', 'messaging']) diff --git a/selfdrive/navd/main.cc b/selfdrive/navd/main.cc new file mode 100644 index 000000000..b6eec1032 --- /dev/null +++ b/selfdrive/navd/main.cc @@ -0,0 +1,31 @@ +#include +#include +#include + +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/maps/map_helpers.h" +#include "selfdrive/navd/map_renderer.h" +#include "system/hardware/hw.h" + + + +void sigHandler(int s) { + qInfo() << "Shutting down"; + std::signal(s, SIG_DFL); + + qApp->quit(); +} + + +int main(int argc, char *argv[]) { + qInstallMessageHandler(swagLogMessageHandler); + + QApplication app(argc, argv); + std::signal(SIGINT, sigHandler); + std::signal(SIGTERM, sigHandler); + + MapRenderer * m = new MapRenderer(get_mapbox_settings()); + assert(m); + + return app.exec(); +} diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc new file mode 100644 index 000000000..d0770cfb4 --- /dev/null +++ b/selfdrive/navd/map_renderer.cc @@ -0,0 +1,241 @@ +#include "selfdrive/navd/map_renderer.h" + +#include +#include +#include + +#include "common/timing.h" +#include "selfdrive/ui/qt/maps/map_helpers.h" + +const float ZOOM = 13.5; // Don't go below 13 or features will start to disappear +const int WIDTH = 256; +const int HEIGHT = WIDTH; + +const int NUM_VIPC_BUFFERS = 4; + +MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_settings(settings) { + QSurfaceFormat fmt; + fmt.setRenderableType(QSurfaceFormat::OpenGLES); + + ctx = std::make_unique(); + ctx->setFormat(fmt); + ctx->create(); + assert(ctx->isValid()); + + surface = std::make_unique(); + surface->setFormat(ctx->format()); + surface->create(); + + ctx->makeCurrent(surface.get()); + assert(QOpenGLContext::currentContext() == ctx.get()); + + gl_functions.reset(ctx->functions()); + gl_functions->initializeOpenGLFunctions(); + + QOpenGLFramebufferObjectFormat fbo_format; + fbo.reset(new QOpenGLFramebufferObject(WIDTH, HEIGHT, fbo_format)); + + m_map.reset(new QMapboxGL(nullptr, m_settings, fbo->size(), 1)); + m_map->setCoordinateZoom(QMapbox::Coordinate(0, 0), ZOOM); + m_map->setStyleUrl("mapbox://styles/commaai/ckvmksrpd4n0a14pfdo5heqzr"); + m_map->createRenderer(); + + m_map->resize(fbo->size()); + m_map->setFramebufferObject(fbo->handle(), fbo->size()); + gl_functions->glViewport(0, 0, WIDTH, HEIGHT); + + if (online) { + vipc_server.reset(new VisionIpcServer("navd")); + vipc_server->create_buffers(VisionStreamType::VISION_STREAM_MAP, NUM_VIPC_BUFFERS, false, WIDTH, HEIGHT); + vipc_server->start_listener(); + + pm.reset(new PubMaster({"navThumbnail"})); + sm.reset(new SubMaster({"liveLocationKalman", "navRoute"})); + + timer = new QTimer(this); + QObject::connect(timer, SIGNAL(timeout()), this, SLOT(msgUpdate())); + timer->start(50); + } +} + +void MapRenderer::msgUpdate() { + sm->update(0); + + if (sm->updated("liveLocationKalman")) { + auto location = (*sm)["liveLocationKalman"].getLiveLocationKalman(); + auto pos = location.getPositionGeodetic(); + auto orientation = location.getCalibratedOrientationNED(); + + bool localizer_valid = (location.getStatus() == cereal::LiveLocationKalman::Status::VALID) && pos.getValid(); + if (localizer_valid) { + updatePosition(QMapbox::Coordinate(pos.getValue()[0], pos.getValue()[1]), RAD2DEG(orientation.getValue()[2])); + } + } + + if (sm->updated("navRoute")) { + QList route; + auto coords = (*sm)["navRoute"].getNavRoute().getCoordinates(); + for (auto const &c : coords) { + route.push_back(QGeoCoordinate(c.getLatitude(), c.getLongitude())); + } + updateRoute(route); + } +} + +void MapRenderer::updatePosition(QMapbox::Coordinate position, float bearing) { + if (m_map.isNull()) { + return; + } + + m_map->setCoordinate(position); + m_map->setBearing(bearing); + update(); +} + +bool MapRenderer::loaded() { + return m_map->isFullyLoaded(); +} + +void MapRenderer::update() { + gl_functions->glClear(GL_COLOR_BUFFER_BIT); + m_map->render(); + gl_functions->glFlush(); + + sendVipc(); +} + +void MapRenderer::sendVipc() { + if (!vipc_server || !loaded()) { + return; + } + + QImage cap = fbo->toImage().convertToFormat(QImage::Format_RGB888, Qt::AutoColor); + uint64_t ts = nanos_since_boot(); + VisionBuf* buf = vipc_server->get_buffer(VisionStreamType::VISION_STREAM_MAP); + VisionIpcBufExtra extra = { + .frame_id = frame_id, + .timestamp_sof = ts, + .timestamp_eof = ts, + }; + + assert(cap.sizeInBytes() >= buf->len); + uint8_t* dst = (uint8_t*)buf->addr; + uint8_t* src = cap.bits(); + + // RGB to greyscale + memset(dst, 128, buf->len); + for (int i = 0; i < WIDTH * HEIGHT; i++) { + dst[i] = src[i * 3]; + } + + vipc_server->send(buf, &extra); + + if (frame_id % 100 == 0) { + // Write jpeg into buffer + QByteArray buffer_bytes; + QBuffer buffer(&buffer_bytes); + buffer.open(QIODevice::WriteOnly); + cap.save(&buffer, "JPG", 50); + + kj::Array buffer_kj = kj::heapArray((const capnp::byte*)buffer_bytes.constData(), buffer_bytes.size()); + + // Send thumbnail + MessageBuilder msg; + auto thumbnaild = msg.initEvent().initNavThumbnail(); + thumbnaild.setFrameId(frame_id); + thumbnaild.setTimestampEof(ts); + thumbnaild.setThumbnail(buffer_kj); + pm->send("navThumbnail", msg); + } + + frame_id++; +} + +uint8_t* MapRenderer::getImage() { + QImage cap = fbo->toImage().convertToFormat(QImage::Format_RGB888, Qt::AutoColor); + + uint8_t* src = cap.bits(); + uint8_t* dst = new uint8_t[WIDTH * HEIGHT]; + + for (int i = 0; i < WIDTH * HEIGHT; i++) { + dst[i] = src[i * 3]; + } + + return dst; +} + +void MapRenderer::updateRoute(QList coordinates) { + if (m_map.isNull()) return; + initLayers(); + + auto route_points = coordinate_list_to_collection(coordinates); + QMapbox::Feature feature(QMapbox::Feature::LineStringType, route_points, {}, {}); + QVariantMap navSource; + navSource["type"] = "geojson"; + navSource["data"] = QVariant::fromValue(feature); + m_map->updateSource("navSource", navSource); + m_map->setLayoutProperty("navLayer", "visibility", "visible"); +} + +void MapRenderer::initLayers() { + if (!m_map->layerExists("navLayer")) { + QVariantMap nav; + nav["id"] = "navLayer"; + nav["type"] = "line"; + nav["source"] = "navSource"; + m_map->addLayer(nav, "road-intersection"); + m_map->setPaintProperty("navLayer", "line-color", QColor("grey")); + m_map->setPaintProperty("navLayer", "line-width", 3); + m_map->setLayoutProperty("navLayer", "line-cap", "round"); + } +} + +MapRenderer::~MapRenderer() { +} + +extern "C" { + MapRenderer* map_renderer_init(char *maps_host = nullptr, char *token = nullptr) { + char *argv[] = { + (char*)"navd", + nullptr + }; + int argc = 0; + QApplication *app = new QApplication(argc, argv); + assert(app); + + QMapboxGLSettings settings; + settings.setApiBaseUrl(maps_host == nullptr ? MAPS_HOST : maps_host); + settings.setAccessToken(token == nullptr ? get_mapbox_token() : token); + + return new MapRenderer(settings, false); + } + + void map_renderer_update_position(MapRenderer *inst, float lat, float lon, float bearing) { + inst->updatePosition({lat, lon}, bearing); + QApplication::processEvents(); + } + + void map_renderer_update_route(MapRenderer *inst, char* polyline) { + inst->updateRoute(polyline_to_coordinate_list(QString::fromUtf8(polyline))); + } + + void map_renderer_update(MapRenderer *inst) { + inst->update(); + } + + void map_renderer_process(MapRenderer *inst) { + QApplication::processEvents(); + } + + bool map_renderer_loaded(MapRenderer *inst) { + return inst->loaded(); + } + + uint8_t * map_renderer_get_image(MapRenderer *inst) { + return inst->getImage(); + } + + void map_renderer_free_image(MapRenderer *inst, uint8_t * buf) { + delete[] buf; + } +} diff --git a/selfdrive/navd/map_renderer.h b/selfdrive/navd/map_renderer.h new file mode 100644 index 000000000..855dc9189 --- /dev/null +++ b/selfdrive/navd/map_renderer.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cereal/visionipc/visionipc_server.h" +#include "cereal/messaging/messaging.h" + + +class MapRenderer : public QObject { + Q_OBJECT + +public: + MapRenderer(const QMapboxGLSettings &, bool online=true); + uint8_t* getImage(); + void update(); + bool loaded(); + ~MapRenderer(); + + +private: + std::unique_ptr ctx; + std::unique_ptr surface; + std::unique_ptr gl_functions; + std::unique_ptr fbo; + + std::unique_ptr vipc_server; + std::unique_ptr pm; + std::unique_ptr sm; + void sendVipc(); + + QMapboxGLSettings m_settings; + QScopedPointer m_map; + + void initLayers(); + + uint32_t frame_id = 0; + + QTimer* timer; + +public slots: + void updatePosition(QMapbox::Coordinate position, float bearing); + void updateRoute(QList coordinates); + void msgUpdate(); +}; diff --git a/selfdrive/navd/map_renderer.py b/selfdrive/navd/map_renderer.py new file mode 100755 index 000000000..900062292 --- /dev/null +++ b/selfdrive/navd/map_renderer.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# You might need to uninstall the PyQt5 pip package to avoid conflicts + +import os +import time +import numpy as np +from cffi import FFI + +from common.ffi_wrapper import suffix +from common.basedir import BASEDIR + +HEIGHT = WIDTH = 256 + + +def get_ffi(): + lib = os.path.join(BASEDIR, "selfdrive", "navd", "libmap_renderer" + suffix()) + + ffi = FFI() + ffi.cdef(""" +void* map_renderer_init(char *maps_host, char *token); +void map_renderer_update_position(void *inst, float lat, float lon, float bearing); +void map_renderer_update_route(void *inst, char *polyline); +void map_renderer_update(void *inst); +void map_renderer_process(void *inst); +bool map_renderer_loaded(void *inst); +uint8_t* map_renderer_get_image(void *inst); +void map_renderer_free_image(void *inst, uint8_t *buf); +""") + return ffi, ffi.dlopen(lib) + + +def wait_ready(lib, renderer): + while not lib.map_renderer_loaded(renderer): + lib.map_renderer_update(renderer) + + # The main qt app is not execed, so we need to periodically process events for e.g. network requests + lib.map_renderer_process(renderer) + + time.sleep(0.01) + + +def get_image(lib, renderer): + buf = lib.map_renderer_get_image(renderer) + r = list(buf[0:WIDTH * HEIGHT]) + lib.map_renderer_free_image(renderer, buf) + + # Convert to numpy + r = np.asarray(r) + return r.reshape((WIDTH, HEIGHT)) + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + ffi, lib = get_ffi() + renderer = lib.map_renderer_init(ffi.NULL, ffi.NULL) + wait_ready(lib, renderer) + + geometry = r"{yxk}@|obn~Eg@@eCFqc@J{RFw@?kA@gA?q|@Riu@NuJBgi@ZqVNcRBaPBkG@iSD{I@_H@cH?gG@mG@gG?aD@{LDgDDkVVyQLiGDgX@q_@@qI@qKhS{R~[}NtYaDbGoIvLwNfP_b@|f@oFnF_JxHel@bf@{JlIuxAlpAkNnLmZrWqFhFoh@jd@kX|TkJxH_RnPy^|[uKtHoZ~Um`DlkCorC``CuShQogCtwB_ThQcr@fk@sVrWgRhVmSb\\oj@jxA{Qvg@u]tbAyHzSos@xjBeKbWszAbgEc~@~jCuTrl@cYfo@mRn\\_m@v}@ij@jp@om@lk@y|A`pAiXbVmWzUod@xj@wNlTw}@|uAwSn\\kRfYqOdS_IdJuK`KmKvJoOhLuLbHaMzGwO~GoOzFiSrEsOhD}PhCqw@vJmnAxSczA`Vyb@bHk[fFgl@pJeoDdl@}}@zIyr@hG}X`BmUdBcM^aRR}Oe@iZc@mR_@{FScHxAn_@vz@zCzH~GjPxAhDlB~DhEdJlIbMhFfG|F~GlHrGjNjItLnGvQ~EhLnBfOn@p`@AzAAvn@CfC?fc@`@lUrArStCfSxEtSzGxM|ElFlBrOzJlEbDnC~BfDtCnHjHlLvMdTnZzHpObOf^pKla@~G|a@dErg@rCbj@zArYlj@ttJ~AfZh@r]LzYg@`TkDbj@gIdv@oE|i@kKzhA{CdNsEfOiGlPsEvMiDpLgBpHyB`MkB|MmArPg@|N?|P^rUvFz~AWpOCdAkB|PuB`KeFfHkCfGy@tAqC~AsBPkDs@uAiAcJwMe@s@eKkPMoXQux@EuuCoH?eI?Kas@}Dy@wAUkMOgDL" + lib.map_renderer_update_route(renderer, geometry.encode()) + + POSITIONS = [ + (32.71569271952601, -117.16384270868463, 0), (32.71569271952601, -117.16384270868463, 45), # San Diego + (52.378641991483136, 4.902623379456488, 0), (52.378641991483136, 4.902623379456488, 45), # Amsterdam + ] + plt.figure() + + for i, pos in enumerate(POSITIONS): + t = time.time() + lib.map_renderer_update_position(renderer, *pos) + wait_ready(lib, renderer) + + print(f"{pos} took {time.time() - t:.2f} s") + + plt.subplot(2, 2, i + 1) + plt.imshow(get_image(lib, renderer), cmap='gray') + + plt.show() diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index f02de43c7..89a1c9bdf 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -4,12 +4,14 @@ 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, distance_along_geometry, maxspeed_to_ms, minimum_distance, @@ -18,6 +20,7 @@ from system.swaglog import cloudlog REROUTE_DISTANCE = 25 MANEUVER_TRANSITION_THRESHOLD = 10 +VALID_POS_STD = 50.0 class RouteEngine: @@ -72,13 +75,21 @@ class RouteEngine: def update_location(self): location = self.sm['liveLocationKalman'] - self.gps_ok = location.gpsOK + laikad = self.sm['gnssMeasurements'] - self.localizer_valid = (location.status == log.LiveLocationKalman.Status.valid) and location.positionGeodetic.valid + 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 - if self.localizer_valid: + self.localizer_valid = locationd_valid or laikad_valid + self.gps_ok = location.gpsOK or laikad_valid + + if locationd_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: @@ -276,7 +287,7 @@ class RouteEngine: def main(sm=None, pm=None): if sm is None: - sm = messaging.SubMaster(['liveLocationKalman', 'managerState']) + sm = messaging.SubMaster(['liveLocationKalman', 'gnssMeasurements', 'managerState']) if pm is None: pm = messaging.PubMaster(['navInstruction', 'navRoute']) diff --git a/selfdrive/sensord/rawgps/rawgpsd.py b/selfdrive/sensord/rawgps/rawgpsd.py index 3aa6d4b07..7c4582902 100755 --- a/selfdrive/sensord/rawgps/rawgpsd.py +++ b/selfdrive/sensord/rawgps/rawgpsd.py @@ -7,10 +7,10 @@ import math import time from typing import NoReturn from struct import unpack_from, calcsize, pack - import cereal.messaging as messaging from cereal import log from system.swaglog import cloudlog +from laika.gps_time import GPSTime from selfdrive.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv from selfdrive.sensord.rawgps.structs import dict_unpacker @@ -81,10 +81,9 @@ def main() -> NoReturn: LOG_GNSS_OEMDRE_MEASUREMENT_REPORT, ] pub_types = ['qcomGnss'] - if int(os.getenv("PUBLISH_EXTERNAL", "0")) == 1: - unpack_position, _ = dict_unpacker(position_report) - log_types.append(LOG_GNSS_POSITION_REPORT) - pub_types.append("gpsLocationExternal") + unpack_position, _ = dict_unpacker(position_report) + log_types.append(LOG_GNSS_POSITION_REPORT) + pub_types.append("gpsLocation") # connect to modem diag = ModemDiag() @@ -204,23 +203,23 @@ def main() -> NoReturn: vNED = [report["q_FltVelEnuMps[1]"], report["q_FltVelEnuMps[0]"], -report["q_FltVelEnuMps[2]"]] vNEDsigma = [report["q_FltVelSigmaMps[1]"], report["q_FltVelSigmaMps[0]"], -report["q_FltVelSigmaMps[2]"]] - msg = messaging.new_message('gpsLocationExternal') - gps = msg.gpsLocationExternal + msg = messaging.new_message('gpsLocation') + gps = msg.gpsLocation gps.flags = 1 gps.latitude = report["t_DblFinalPosLatLon[0]"] * 180/math.pi gps.longitude = report["t_DblFinalPosLatLon[1]"] * 180/math.pi gps.altitude = report["q_FltFinalPosAlt"] gps.speed = math.sqrt(sum([x**2 for x in vNED])) gps.bearingDeg = report["q_FltHeadingRad"] * 180/math.pi - # TODO: this probably isn't right, use laika for this - gps.timestamp = report['w_GpsWeekNumber']*604800*1000 + report['q_GpsFixTimeMs'] + gps.unixTimestampMillis = GPSTime(report['w_GpsWeekNumber'], + 1e-3*report['q_GpsFixTimeMs']).as_unix_timestamp()*1e3 gps.source = log.GpsLocationData.SensorSource.qcomdiag gps.vNED = vNED gps.verticalAccuracy = report["q_FltVdop"] gps.bearingAccuracyDeg = report["q_FltHeadingUncRad"] * 180/math.pi gps.speedAccuracy = math.sqrt(sum([x**2 for x in vNEDsigma])) - pm.send('gpsLocationExternal', msg) + pm.send('gpsLocation', msg) if log_type in [LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GLONASS_MEASUREMENT_REPORT]: msg = messaging.new_message('qcomGnss') diff --git a/selfdrive/test/longitudinal_maneuvers/maneuver.py b/selfdrive/test/longitudinal_maneuvers/maneuver.py index 9b4d01643..0d605a5fc 100644 --- a/selfdrive/test/longitudinal_maneuvers/maneuver.py +++ b/selfdrive/test/longitudinal_maneuvers/maneuver.py @@ -16,6 +16,7 @@ class Maneuver(): self.only_lead2 = kwargs.get("only_lead2", False) self.only_radar = kwargs.get("only_radar", False) + self.ensure_start = kwargs.get("ensure_start", False) self.duration = duration self.title = title @@ -52,5 +53,9 @@ class Maneuver(): print("Crashed!!!!") valid = False + if self.ensure_start and log['v_rel'] > 0 and log['speeds'][-1] <= 0.1: + print('Planner not starting!') + valid = False + print("maneuver end", valid) return valid, np.array(logs) diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index 13025a9f0..3bd50ebcf 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -28,6 +28,7 @@ class Plant(): self.distance = 0. self.speed = speed self.acceleration = 0.0 + self.speeds = [] # lead car self.distance_lead = distance_lead @@ -98,6 +99,7 @@ class Plant(): self.planner.update(sm) self.speed = self.planner.v_desired_filter.x self.acceleration = self.planner.a_desired + self.speeds = self.planner.v_desired_trajectory.tolist() fcw = self.planner.fcw self.distance_lead = self.distance_lead + v_lead * self.ts @@ -129,6 +131,7 @@ class Plant(): "distance": self.distance, "speed": self.speed, "acceleration": self.acceleration, + "speeds": self.speeds, "distance_lead": self.distance_lead, "fcw": fcw, } diff --git a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index 698877dd3..ec698d88f 100755 --- a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -3,6 +3,7 @@ import os 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 @@ -106,6 +107,17 @@ maneuvers = [ breakpoints=[1., 1.01, 11.], cruise_values=[float("nan"), 15., 15.], ), + # controls relies on planner commanding to move for stock-ACC resume spamming + Maneuver( + "resume from a stop", + duration=20., + initial_speed=0., + lead_relevancy=True, + initial_distance_lead=STOP_DISTANCE, + speed_lead_values=[0., 0., 2.], + breakpoints=[1., 10., 15.], + ensure_start=True, + ), ] diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md index 9fa4ca644..531ddb3a0 100644 --- a/selfdrive/test/process_replay/README.md +++ b/selfdrive/test/process_replay/README.md @@ -5,7 +5,7 @@ Process replay is a regression test designed to identify any changes in the outp If the test fails, make sure that you didn't unintentionally change anything. If there are intentional changes, the reference logs will be updated. Use `test_processes.py` to run the test locally. -Use `FILEREADER_CACHE='1' test_processes.py` to cache log files. +Use `FILEREADER_CACHE='1' test_processes.py` to cache log files. Currently the following processes are tested: @@ -15,6 +15,7 @@ Currently the following processes are tested: * calibrationd * dmonitoringd * locationd +* laikad * paramsd * ubloxd diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index 057e46cd9..bf6daf5fe 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -46,11 +46,25 @@ def remove_ignored_fields(msg, ignore): return msg.as_reader() -def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=None): +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): 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 log1, log2 = (list(filter(lambda m: m.which() not in ignore_msgs, log)) for log in (log1, log2)) @@ -72,7 +86,6 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non msg1_dict = msg1.to_dict(verbose=True) msg2_dict = msg2.to_dict(verbose=True) - tolerance = EPSILON if tolerance is None else tolerance dd = dictdiffer.diff(msg1_dict, msg2_dict, ignore=ignore_fields) # Dictdiffer only supports relative tolerance, we also want to check for absolute @@ -80,10 +93,13 @@ 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(tolerance, tolerance * max(abs(a), abs(b))) + return abs(a - b) > max(field_tolerance, field_tolerance * max(abs(a), abs(b))) except TypeError: pass return True diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index f3bb28663..12f3583ee 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -10,7 +10,7 @@ import cereal.messaging as messaging from cereal.visionipc import VisionIpcServer, VisionStreamType from common.spinner import Spinner from common.timeout import Timeout -from common.transformations.camera import get_view_frame_from_road_frame, tici_f_frame_size, tici_d_frame_size +from common.transformations.camera import tici_f_frame_size, tici_d_frame_size from system.hardware import PC from selfdrive.manager.process_config import managed_processes from selfdrive.test.openpilotci import BASE_URL, get_url @@ -22,7 +22,7 @@ from tools.lib.logreader import LogReader TEST_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" SEGMENT = 0 -MAX_FRAMES = 20 if PC else 1300 +MAX_FRAMES = 10 if PC else 1300 SEND_EXTRA_INPUTS = bool(os.getenv("SEND_EXTRA_INPUTS", "0")) @@ -35,7 +35,7 @@ def get_log_fn(ref_commit, test_route): def replace_calib(msg, calib): msg = msg.as_builder() if calib is not None: - msg.liveCalibration.extrinsicMatrix = get_view_frame_from_road_frame(*calib, 1.22).flatten().tolist() + msg.liveCalibration.rpyCalib = calib.tolist() return msg @@ -52,7 +52,7 @@ def model_replay(lr, frs): vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, *(tici_f_frame_size)) vipc_server.start_listener() - sm = messaging.SubMaster(['modelV2', 'driverState']) + sm = messaging.SubMaster(['modelV2', 'driverStateV2']) pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'driverCameraState', 'liveCalibration', 'lateralPlan']) try: @@ -112,7 +112,7 @@ def model_replay(lr, frs): if min(frame_idxs['roadCameraState'], frame_idxs['wideRoadCameraState']) > recv_cnt['modelV2']: recv = "modelV2" elif msg.which() == 'driverCameraState': - recv = "driverState" + recv = "driverStateV2" # wait for a response with Timeout(15, f"timed out waiting for {recv}"): @@ -170,14 +170,15 @@ if __name__ == "__main__": 'logMonoTime', 'modelV2.frameDropPerc', 'modelV2.modelExecutionTime', - 'driverState.modelExecutionTime', - 'driverState.dspExecutionTime' + 'driverStateV2.modelExecutionTime', + 'driverStateV2.dspExecutionTime' ] # TODO this tolerence is absurdly large tolerance = 5e-1 if PC else None results: Any = {TEST_ROUTE: {}} + log_paths: Any = {TEST_ROUTE: {'ref': BASE_URL + log_fn, 'new': log_fn}} results[TEST_ROUTE]["models"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore) - diff1, diff2, failed = format_diff(results, ref_commit) + diff1, diff2, failed = format_diff(results, log_paths, ref_commit) print(diff2) print('-------------\n'*5) diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 54c2ed54a..e5bbfa74c 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -629eaa7b26d1721a71547f9de880f99732cb27f3 +507241e0a41ee757cca4eb6ff12cff3f02c0b98a diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index b016eb1c9..32a3c2f3b 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -14,8 +14,8 @@ from cereal import car, log from cereal.services import service_list from common.params import Params from common.timeout import Timeout +from common.realtime import DT_CTRL from panda.python import ALTERNATIVE_EXPERIENCE -from selfdrive.car.fingerprints import FW_VERSIONS from selfdrive.car.car_helpers import get_car, interfaces from selfdrive.test.process_replay.helpers import OpenpilotPrefix from selfdrive.manager.process import PythonProcess @@ -28,14 +28,14 @@ TIMEOUT = 15 PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__)) FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/") -ProcessConfig = namedtuple('ProcessConfig', ['proc_name', 'pub_sub', 'ignore', 'init_callback', 'should_recv_callback', 'tolerance', 'fake_pubsubmaster', 'submaster_config'], defaults=({},)) +ProcessConfig = namedtuple('ProcessConfig', ['proc_name', 'pub_sub', 'ignore', 'init_callback', 'should_recv_callback', 'tolerance', 'fake_pubsubmaster', 'submaster_config', 'environ', 'subtest_name', "field_tolerances"], defaults=({}, {}, "", {})) def wait_for_event(evt): if not evt.wait(TIMEOUT): if threading.currentThread().getName() == "MainThread": # tested process likely died. don't let test just hang - raise Exception("Timeout reached. Tested process likely crashed.") + raise Exception(f"Timeout reached. Tested process {os.environ['PROC_NAME']} likely crashed.") else: # done testing this process, let it die sys.exit(0) @@ -191,6 +191,7 @@ def get_car_params(msgs, fsm, can_sock, fingerprint): _, CP = get_car(can, sendcan) Params().put("CarParams", CP.to_bytes()) + def controlsd_rcv_callback(msg, CP, cfg, fsm): # no sendcan until controlsd is initialized socks = [s for s in cfg.pub_sub[msg.which()] if @@ -199,6 +200,7 @@ def controlsd_rcv_callback(msg, CP, cfg, fsm): socks.remove("sendcan") return socks, len(socks) > 0 + def radar_rcv_callback(msg, CP, cfg, fsm): if msg.which() != "can": return [], False @@ -237,6 +239,13 @@ def ublox_rcv_callback(msg): return [] +def laika_rcv_callback(msg, CP, cfg, fsm): + if msg.which() == 'ubloxGnss' and msg.ubloxGnss.which() == "measurementReport": + return ["gnssMeasurements"], True + else: + return [], True + + CONFIGS = [ ProcessConfig( proc_name="controlsd", @@ -271,7 +280,7 @@ CONFIGS = [ proc_name="plannerd", pub_sub={ "modelV2": ["lateralPlan", "longitudinalPlan"], - "carState": [], "controlsState": [], "radarState": [], + "carControl": [], "carState": [], "controlsState": [], "radarState": [], }, ignore=["logMonoTime", "valid", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime", "lateralPlan.solverExecutionTime"], init_callback=get_car_params, @@ -283,7 +292,8 @@ CONFIGS = [ proc_name="calibrationd", pub_sub={ "carState": ["liveCalibration"], - "cameraOdometry": [] + "cameraOdometry": [], + "carParams": [], }, ignore=["logMonoTime", "valid"], init_callback=get_car_params, @@ -294,7 +304,7 @@ CONFIGS = [ ProcessConfig( proc_name="dmonitoringd", pub_sub={ - "driverState": ["driverMonitoringState"], + "driverStateV2": ["driverMonitoringState"], "liveCalibration": [], "carState": [], "modelV2": [], "controlsState": [], }, ignore=["logMonoTime", "valid"], @@ -338,6 +348,32 @@ CONFIGS = [ tolerance=None, fake_pubsubmaster=False, ), + ProcessConfig( + proc_name="laikad", + subtest_name="Offline", + pub_sub={ + "ubloxGnss": ["gnssMeasurements"], + "clocks": [] + }, + ignore=["logMonoTime"], + init_callback=get_car_params, + should_recv_callback=laika_rcv_callback, + tolerance=NUMPY_TOLERANCE, + fake_pubsubmaster=True, + environ={"LAIKAD_NO_INTERNET": "1"}, + ), + ProcessConfig( + proc_name="laikad", + pub_sub={ + "ubloxGnss": ["gnssMeasurements"], + "clocks": [] + }, + ignore=["logMonoTime"], + init_callback=get_car_params, + should_recv_callback=laika_rcv_callback, + tolerance=NUMPY_TOLERANCE, + fake_pubsubmaster=True, + ), ] @@ -348,7 +384,8 @@ def replay_process(cfg, lr, fingerprint=None): else: return cpp_replay_process(cfg, lr, fingerprint) -def setup_env(simulation=False, CP=None): + +def setup_env(simulation=False, CP=None, cfg=None): params = Params() params.clear_all() params.put_bool("OpenpilotEnabledToggle", True) @@ -362,6 +399,16 @@ def setup_env(simulation=False, CP=None): os.environ['SKIP_FW_QUERY'] = "" os.environ['FINGERPRINT'] = "" + if cfg is not None: + # Clear all custom processConfig environment variables + for config in CONFIGS: + for k, _ in config.environ.items(): + if k in os.environ: + del os.environ[k] + + os.environ.update(cfg.environ) + os.environ['PROC_NAME'] = cfg.proc_name + if simulation: os.environ["SIMULATION"] = "1" elif "SIMULATION" in os.environ: @@ -372,12 +419,13 @@ def setup_env(simulation=False, CP=None): if CP.alternativeExperience == ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS: params.put_bool("DisengageOnAccelerator", False) - if CP.fingerprintSource == "fw" and CP.carFingerprint in FW_VERSIONS: + if CP.fingerprintSource == "fw": params.put("CarParamsCache", CP.as_builder().to_bytes()) else: os.environ['SKIP_FW_QUERY'] = "1" os.environ['FINGERPRINT'] = CP.carFingerprint + def python_replay_process(cfg, lr, fingerprint=None): sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub] pub_sockets = [s for s in cfg.pub_sub.keys() if s != 'can'] @@ -395,10 +443,10 @@ def python_replay_process(cfg, lr, fingerprint=None): if fingerprint is not None: os.environ['SKIP_FW_QUERY'] = "1" os.environ['FINGERPRINT'] = fingerprint - setup_env() + setup_env(cfg=cfg) else: CP = [m for m in lr if m.which() == 'carParams'][0].carParams - setup_env(CP=CP) + setup_env(CP=CP, cfg=cfg) assert(type(managed_processes[cfg.proc_name]) is PythonProcess) managed_processes[cfg.proc_name].prepare() @@ -459,7 +507,7 @@ def cpp_replay_process(cfg, lr, fingerprint=None): log_msgs = [] # We need to fake SubMaster alive since we can't inject a fake clock - setup_env(simulation=True) + setup_env(simulation=True, cfg=cfg) managed_processes[cfg.proc_name].prepare() managed_processes[cfg.proc_name].start() @@ -501,11 +549,17 @@ def cpp_replay_process(cfg, lr, fingerprint=None): def check_enabled(msgs): + cur_enabled_count = 0 + max_enabled_count = 0 for msg in msgs: if msg.which() == "carParams": if msg.carParams.notCar: return True elif msg.which() == "controlsState": if msg.controlsState.active: - return True - return False + cur_enabled_count += 1 + else: + cur_enabled_count = 0 + max_enabled_count = max(max_enabled_count, cur_enabled_count) + + return max_enabled_count > int(10. / DT_CTRL) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 8ed68d5bd..8aa80d6b7 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -ed1dfb8b155ebcd8fdad4e06462b3bb7869fc67b +118d78e2040103c00b4bfcc875fcdcd6a15e2211 diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index dfc62a455..39f75ce42 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -15,6 +15,8 @@ from cereal.visionipc import VisionIpcServer, VisionStreamType from common.params import Params from common.realtime import Ratekeeper, DT_MDL, DT_DMON, sec_since_boot from common.transformations.camera import eon_f_frame_size, eon_d_frame_size, tici_f_frame_size, tici_d_frame_size +from panda.python import Panda +from selfdrive.car.toyota.values import EPS_SCALE from selfdrive.manager.process import ensure_running from selfdrive.manager.process_config import managed_processes from selfdrive.test.process_replay.process_replay import FAKEDATA, setup_env, check_enabled @@ -30,8 +32,8 @@ def replay_panda_states(s, msgs): # TODO: new safety params from flags, remove after getting new routes for Toyota safety_param_migration = { - "TOYOTA PRIUS 2017": 578, - "TOYOTA RAV4 2017": 329 + "TOYOTA PRIUS 2017": EPS_SCALE["TOYOTA PRIUS 2017"] | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL, + "TOYOTA RAV4 2017": EPS_SCALE["TOYOTA RAV4 2017"] | Panda.FLAG_TOYOTA_ALT_BRAKE, } # Migrate safety param base on carState @@ -127,7 +129,10 @@ def replay_cameras(lr, frs, disable_tqdm=False): ] def replay_camera(s, stream, dt, vipc_server, frames, size, use_extra_client): - pm = messaging.PubMaster([s, ]) + services = [(s, stream)] + if use_extra_client: + services.append(("wideRoadCameraState", VisionStreamType.VISION_STREAM_WIDE_ROAD)) + pm = messaging.PubMaster([s for s, _ in services]) rk = Ratekeeper(1 / dt, print_delay_threshold=None) img = b"\x00" * int(size[0] * size[1] * 3 / 2) @@ -137,16 +142,15 @@ def replay_cameras(lr, frs, disable_tqdm=False): rk.keep_time() - m = messaging.new_message(s) - msg = getattr(m, s) - msg.frameId = rk.frame - msg.timestampSof = m.logMonoTime - msg.timestampEof = m.logMonoTime - pm.send(s, m) + for s, stream in services: + m = messaging.new_message(s) + msg = getattr(m, s) + msg.frameId = rk.frame + msg.timestampSof = m.logMonoTime + msg.timestampEof = m.logMonoTime + pm.send(s, m) - vipc_server.send(stream, img, msg.frameId, msg.timestampSof, msg.timestampEof) - if use_extra_client: - vipc_server.send(VisionStreamType.VISION_STREAM_WIDE_ROAD, img, msg.frameId, msg.timestampSof, msg.timestampEof) + vipc_server.send(stream, img, msg.frameId, msg.timestampSof, msg.timestampEof) init_data = [m for m in lr if m.which() == 'initData'][0] cameras = tici_cameras if (init_data.initData.deviceType == 'tici') else eon_cameras @@ -175,8 +179,22 @@ def replay_cameras(lr, frs, disable_tqdm=False): return vs, p +def migrate_carparams(lr): + all_msgs = [] + for msg in lr: + if msg.which() == 'carParams': + CP = messaging.new_message('carParams') + CP.carParams = msg.carParams.as_builder() + for car_fw in CP.carParams.carFw: + car_fw.brand = CP.carParams.carName + msg = CP.as_reader() + all_msgs.append(msg) + + return all_msgs + + def regen_segment(lr, frs=None, outdir=FAKEDATA, disable_tqdm=False): - lr = list(lr) + lr = migrate_carparams(list(lr)) if frs is None: frs = dict() @@ -245,7 +263,7 @@ def regen_segment(lr, frs=None, outdir=FAKEDATA, disable_tqdm=False): seg_path = os.path.join(outdir, segment) # check to make sure openpilot is engaged in the route if not check_enabled(LogReader(os.path.join(seg_path, "rlog"))): - raise Exception(f"Route never enabled: {segment}") + raise Exception(f"Route did not engage for long enough: {segment}") return seg_path diff --git a/selfdrive/test/process_replay/regen_all.py b/selfdrive/test/process_replay/regen_all.py index c3ea1d41e..f10d7ea03 100755 --- a/selfdrive/test/process_replay/regen_all.py +++ b/selfdrive/test/process_replay/regen_all.py @@ -11,12 +11,12 @@ from selfdrive.test.process_replay.test_processes import FAKEDATA, original_segm from tools.lib.route import SegmentName -def regen_job(segment, disable_tqdm): +def regen_job(segment, upload, disable_tqdm): with OpenpilotPrefix(): sn = SegmentName(segment[1]) fake_dongle_id = 'regen' + ''.join(random.choice('0123456789ABCDEF') for _ in range(11)) try: - relr = regen_and_save(sn.route_name.canonical_name, sn.segment_num, upload=True, use_route_meta=False, outdir=os.path.join(FAKEDATA, fake_dongle_id), disable_tqdm=disable_tqdm) + relr = regen_and_save(sn.route_name.canonical_name, sn.segment_num, upload=upload, use_route_meta=False, outdir=os.path.join(FAKEDATA, fake_dongle_id), disable_tqdm=disable_tqdm) relr = '|'.join(relr.split('/')[-2:]) return f' ("{segment[0]}", "{relr}"), ' except Exception as e: @@ -26,12 +26,13 @@ def regen_job(segment, disable_tqdm): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Generate new segments from old ones") parser.add_argument("-j", "--jobs", type=int, default=1) + parser.add_argument("--no-upload", action="store_true") args = parser.parse_args() with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool: - p = list(pool.map(regen_job, segments, [args.jobs > 1] * args.jobs)) + p = pool.map(regen_job, segments, [not args.no_upload] * len(segments), [args.jobs > 1] * len(segments)) msg = "Copy these new segments into test_processes.py:" - for seg in tqdm(p, desc="Generating segments"): + for seg in tqdm(p, desc="Generating segments", total=len(segments)): msg += "\n" + str(seg) print() print() diff --git a/selfdrive/test/process_replay/test_debayer.py b/selfdrive/test/process_replay/test_debayer.py index eff77fc47..1b3e0f112 100755 --- a/selfdrive/test/process_replay/test_debayer.py +++ b/selfdrive/test/process_replay/test_debayer.py @@ -10,7 +10,7 @@ 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 selfdrive.camerad.snapshot.snapshot import yuv_to_rgb +from system.camerad.snapshot.snapshot import yuv_to_rgb from tools.lib.logreader import LogReader from tools.lib.filereader import FileReader @@ -62,7 +62,7 @@ def unbzip_frames(url): def init_kernels(frame_offset=0): ctx = cl.create_some_context(interactive=False) - with open(os.path.join(BASEDIR, 'selfdrive/camerad/cameras/real_debayer.cl')) as f: + with open(os.path.join(BASEDIR, 'system/camerad/cameras/real_debayer.cl')) as f: build_args = ' -cl-fast-relaxed-math -cl-denorms-are-zero -cl-single-precision-constant' + \ f' -DFRAME_STRIDE={FRAME_STRIDE} -DRGB_WIDTH={FRAME_WIDTH} -DRGB_HEIGHT={FRAME_HEIGHT} -DFRAME_OFFSET={frame_offset} -DCAM_NUM=0' if PC: diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 25fbd210c..9e6fee0de 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -5,7 +5,7 @@ import os import sys from collections import defaultdict from tqdm import tqdm -from typing import Any, Dict +from typing import Any, DefaultDict, Dict from selfdrive.car.car_helpers import interface_names from selfdrive.test.openpilotci import get_url, upload_file @@ -18,12 +18,14 @@ from tools.lib.logreader import LogReader original_segments = [ ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA + ("HYUNDAI", "d824e27e8c60172c|2022-07-08--21-21-15--0"), # HYUNDAI.KIA_EV6 ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI) ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR) ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 ("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.CIVIC (NIDEC) ("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.ACCORD (BOSCH) ("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.PACIFICA + ("RAM", "2f4452b03ccb98f0|2022-07-07--08-01-56--3"), # CHRYSLER.RAM_1500 ("SUBARU", "4d70bc5e608678be|2021-01-15--17-02-04--5"), # SUBARU.IMPREZA ("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.VOLT ("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.XTRAIL @@ -35,19 +37,21 @@ original_segments = [ ] segments = [ - ("BODY", "bd6a637565e91581|2022-04-04--22-05-08--0"), - ("HYUNDAI", "fakedata|2022-01-20--17-49-04--0"), - ("TOYOTA", "fakedata|2022-04-29--15-57-12--0"), - ("TOYOTA2", "fakedata|2022-04-29--16-08-01--0"), - ("TOYOTA3", "fakedata|2022-04-29--16-17-39--0"), - ("HONDA", "fakedata|2022-01-20--17-56-40--0"), - ("HONDA2", "fakedata|2022-04-29--16-31-55--0"), - ("CHRYSLER", "fakedata|2022-01-20--18-00-11--0"), - ("SUBARU", "fakedata|2022-01-20--18-01-57--0"), - ("GM", "fakedata|2022-01-20--18-03-41--0"), - ("NISSAN", "fakedata|2022-01-20--18-05-29--0"), - ("VOLKSWAGEN", "fakedata|2022-01-20--18-07-15--0"), - ("MAZDA", "fakedata|2022-01-20--18-09-32--0"), + ("BODY", "regen660D86654BA|2022-07-06--14-27-15--0"), + ("HYUNDAI", "regen114E5FF24D8|2022-07-14--17-08-47--0"), + ("HYUNDAI", "d824e27e8c60172c|2022-07-08--21-21-15--0"), + ("TOYOTA", "regenBA97410FBEC|2022-07-06--14-26-49--0"), + ("TOYOTA2", "regenDEDB1D9C991|2022-07-06--14-54-08--0"), + ("TOYOTA3", "regenDDC1FE60734|2022-07-06--14-32-06--0"), + ("HONDA", "regenE62960EEC38|2022-07-14--19-33-24--0"), + ("HONDA2", "regenC3EBD92F029|2022-07-14--19-29-47--0"), + ("CHRYSLER", "regen38346FB33D0|2022-07-14--18-05-26--0"), + ("RAM", "2f4452b03ccb98f0|2022-07-07--08-01-56--3"), + ("SUBARU", "regen54A1E2BE5AA|2022-07-14--18-07-50--0"), + ("GM", "regen01D09D915B5|2022-07-06--14-36-20--0"), + ("NISSAN", "regenCA0B0DC946E|2022-07-14--18-10-17--0"), + ("VOLKSWAGEN", "regen007098CA0EF|2022-07-06--15-01-26--0"), + ("MAZDA", "regen61BA413D53B|2022-07-06--14-39-42--0"), ] # dashcamOnly makes don't need to be tested until a full port is done @@ -62,7 +66,7 @@ def run_test_process(data): res = None if not args.upload_only: lr = LogReader.from_bytes(lr_dat) - res, log_msgs = test_process(cfg, lr, ref_log_path, args.ignore_fields, args.ignore_msgs) + res, log_msgs = test_process(cfg, lr, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) # save logs so we can upload when updating refs save_log(cur_log_fn, log_msgs) @@ -71,7 +75,7 @@ def run_test_process(data): assert os.path.exists(cur_log_fn), f"Cannot find log to upload: {cur_log_fn}" upload_file(cur_log_fn, os.path.basename(cur_log_fn)) os.remove(cur_log_fn) - return (segment, cfg.proc_name, res) + return (segment, cfg.proc_name, cfg.subtest_name, res) def get_log_data(segment): @@ -80,7 +84,7 @@ def get_log_data(segment): return (segment, f.read()) -def test_process(cfg, lr, ref_log_path, ignore_fields=None, ignore_msgs=None): +def test_process(cfg, lr, ref_log_path, new_log_path, ignore_fields=None, ignore_msgs=None): if ignore_fields is None: ignore_fields = [] if ignore_msgs is None: @@ -93,15 +97,15 @@ def test_process(cfg, lr, ref_log_path, ignore_fields=None, ignore_msgs=None): # check to make sure openpilot is engaged in the route if cfg.proc_name == "controlsd": if not check_enabled(log_msgs): - raise Exception(f"Route never enabled: {ref_log_path}") + return f"Route did not enable at all or for long enough: {new_log_path}", log_msgs try: - return compare_logs(ref_log_msgs, log_msgs, ignore_fields + cfg.ignore, ignore_msgs, cfg.tolerance), log_msgs + return compare_logs(ref_log_msgs, log_msgs, ignore_fields + cfg.ignore, ignore_msgs, cfg.tolerance, cfg.field_tolerances), log_msgs except Exception as e: return str(e), log_msgs -def format_diff(results, ref_commit): +def format_diff(results, log_paths, ref_commit): diff1, diff2 = "", "" diff2 += f"***** tested against commit {ref_commit} *****\n" @@ -111,13 +115,22 @@ def format_diff(results, ref_commit): diff2 += f"***** differences for segment {segment} *****\n" for proc, diff in list(result.items()): - diff1 += f"\t{proc}\n" + # long diff diff2 += f"*** process: {proc} ***\n" + diff2 += f"\tref: {log_paths[segment]['ref']}\n\n" + diff2 += f"\tnew: {log_paths[segment]['new']}\n\n" + # short diff + diff1 += f" {proc}\n" if isinstance(diff, str): - diff1 += f"\t\t{diff}\n" + diff1 += f" ref: {log_paths[segment]['ref']}\n" + diff1 += f" new: {log_paths[segment]['new']}\n\n" + diff1 += f" {diff}\n" failed = True elif len(diff): + diff1 += f" ref: {log_paths[segment]['ref']}\n" + diff1 += f" new: {log_paths[segment]['new']}\n\n" + cnt: Dict[str, int] = {} for d in diff: diff2 += f"\t{str(d)}\n" @@ -126,7 +139,7 @@ def format_diff(results, ref_commit): cnt[k] = 1 if k not in cnt else cnt[k] + 1 for k, v in sorted(cnt.items()): - diff1 += f"\t\t{k}: {v}\n" + diff1 += f" {k}: {v}\n" failed = True return diff1, diff2, failed @@ -183,6 +196,8 @@ if __name__ == "__main__": untested = (set(interface_names) - set(excluded_interfaces)) - {c.lower() for c in tested_cars} assert len(untested) == 0, f"Cars missing routes: {str(untested)}" + + log_paths: DefaultDict[str, Dict[str, str]] = defaultdict(dict) with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool: if not args.upload_only: download_segments = [seg for car, seg in segments if car in tested_cars] @@ -200,23 +215,26 @@ if __name__ == "__main__": if cfg.proc_name not in tested_procs: continue - cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.bz2") + cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}{cfg.subtest_name}_{cur_commit}.bz2") if args.update_refs: # reference logs will not exist if routes were just regenerated ref_log_path = get_url(*segment.rsplit("--", 1)) else: - ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.bz2") + ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}{cfg.subtest_name}_{ref_commit}.bz2") ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn) dat = None if args.upload_only else log_data[segment] pool_args.append((segment, cfg, args, cur_log_fn, ref_log_path, dat)) + log_paths[segment]['ref'] = ref_log_path + log_paths[segment]['new'] = cur_log_fn + results: Any = defaultdict(dict) p2 = pool.map(run_test_process, pool_args) - for (segment, proc, result) in tqdm(p2, desc="Running Tests", total=len(pool_args)): - if isinstance(result, list): - results[segment][proc] = result + for (segment, proc, subtest_name, result) in tqdm(p2, desc="Running Tests", total=len(pool_args)): + if not args.upload_only: + results[segment][proc + subtest_name] = result - diff1, diff2, failed = format_diff(results, ref_commit) + diff1, diff2, failed = format_diff(results, log_paths, ref_commit) if not upload: with open(os.path.join(PROC_REPLAY_DIR, "diff.txt"), "w") as f: f.write(diff2) diff --git a/selfdrive/test/profiling/profiler.py b/selfdrive/test/profiling/profiler.py index 5f73176fa..91226fc57 100755 --- a/selfdrive/test/profiling/profiler.py +++ b/selfdrive/test/profiling/profiler.py @@ -53,6 +53,7 @@ def profile(proc, func, car='toyota'): msgs = list(LogReader(rlog_url)) * int(os.getenv("LOOP", "1")) os.environ['FINGERPRINT'] = fingerprint + os.environ['REPLAY'] = "1" def run(sm, pm, can_sock): try: @@ -81,12 +82,14 @@ if __name__ == '__main__': 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 procs = { 'radard': radard_thread, 'controlsd': controlsd_thread, 'paramsd': paramsd_thread, 'plannerd': plannerd_thread, + 'laikad': laikad_thread, } proc = sys.argv[1] diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index 99acc050e..bf2f93e1c 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -21,26 +21,26 @@ umount /data/safe_staging/merged/ || true sudo umount /data/safe_staging/merged/ || true rm -rf /data/safe_staging/* || true -export KEYS_PARAM_PATH="/data/params/d/GithubSshKeys" -export KEYS_PATH="/usr/comma/setup_keys" -export CONTINUE_PATH="/data/continue.sh" - -if ! grep -F "$KEYS_PATH" /etc/ssh/sshd_config; then - echo "setting up keys" - sudo mount -o rw,remount / - sudo systemctl enable ssh - sudo sed -i "s,$KEYS_PARAM_PATH,$KEYS_PATH," /etc/ssh/sshd_config - sudo mount -o ro,remount / -fi - +CONTINUE_PATH="/data/continue.sh" tee $CONTINUE_PATH << EOF #!/usr/bin/bash +sudo abctl --set_success + +# patch sshd config +sudo mount -o rw,remount / +sudo sed -i "s,/data/params/d/GithubSshKeys,/usr/comma/setup_keys," /etc/ssh/sshd_config +sudo systemctl daemon-reload +sudo systemctl restart ssh +sudo systemctl disable ssh-param-watcher.path +sudo systemctl disable ssh-param-watcher.service +sudo mount -o ro,remount / + while true; do if ! sudo systemctl is-active -q ssh; then sudo systemctl start ssh fi - sleep 10s + sleep 5s done sleep infinity diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 3586f86f1..31651706c 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -5,7 +5,7 @@ import subprocess import time import numpy as np import unittest -from collections import Counter +from collections import Counter, defaultdict from pathlib import Path from cereal import car @@ -24,7 +24,7 @@ PROCS = { "selfdrive.controls.controlsd": 35.0, "./loggerd": 10.0, "./encoderd": 12.5, - "./camerad": 16.5, + "./camerad": 14.5, "./locationd": 9.1, "selfdrive.controls.plannerd": 11.7, "./_ui": 19.2, @@ -33,7 +33,7 @@ PROCS = { "selfdrive.controls.radard": 4.5, "./_modeld": 4.48, "./boardd": 3.63, - "./_dmonitoringmodeld": 10.0, + "./_dmonitoringmodeld": 5.0, "selfdrive.thermald.thermald": 3.87, "selfdrive.locationd.calibrationd": 2.0, "./_soundd": 1.0, @@ -60,7 +60,7 @@ TIMINGS = { "roadCameraState": [2.5, 0.35], "driverCameraState": [2.5, 0.35], "modelV2": [2.5, 0.35], - "driverState": [2.5, 0.40], + "driverStateV2": [2.5, 0.40], "liveLocationKalman": [2.5, 0.35], "wideRoadCameraState": [1.5, 0.35], } @@ -70,32 +70,40 @@ def cputime_total(ct): return ct.cpuUser + ct.cpuSystem + ct.cpuChildrenUser + ct.cpuChildrenSystem -def check_cpu_usage(first_proc, last_proc): +def check_cpu_usage(proclogs): result = "\n" result += "------------------------------------------------\n" result += "------------------ CPU Usage -------------------\n" result += "------------------------------------------------\n" + plogs_by_proc = defaultdict(list) + for pl in proclogs: + for x in pl.procLog.procs: + if len(x.cmdline) > 0: + n = list(x.cmdline)[0] + plogs_by_proc[n].append(x) + + print(plogs_by_proc.keys()) + r = True - dt = (last_proc.logMonoTime - first_proc.logMonoTime) / 1e9 - for proc_name, normal_cpu_usage in PROCS.items(): + dt = (proclogs[-1].logMonoTime - proclogs[0].logMonoTime) / 1e9 + for proc_name, expected_cpu in PROCS.items(): err = "" - first, last = None, None - try: - first = [p for p in first_proc.procLog.procs if proc_name in p.cmdline][0] - last = [p for p in last_proc.procLog.procs if proc_name in p.cmdline][0] - cpu_time = cputime_total(last) - cputime_total(first) + cpu_usage = 0. + x = plogs_by_proc[proc_name] + if len(x) > 2: + cpu_time = cputime_total(x[-1]) - cputime_total(x[0]) cpu_usage = cpu_time / dt * 100. - if cpu_usage > max(normal_cpu_usage * 1.15, normal_cpu_usage + 5.0): + if cpu_usage > max(expected_cpu * 1.15, expected_cpu + 5.0): # cpu usage is high while playing sounds if not (proc_name == "./_soundd" and cpu_usage < 65.): err = "using more CPU than normal" - elif cpu_usage < min(normal_cpu_usage * 0.65, max(normal_cpu_usage - 1.0, 0.0)): + elif cpu_usage < min(expected_cpu * 0.65, max(expected_cpu - 1.0, 0.0)): err = "using less CPU than normal" - except IndexError: - err = f"NO METRICS FOUND {first=} {last=}\n" + else: + err = "NO METRICS FOUND" - result += f"{proc_name.ljust(35)} {cpu_usage:5.2f}% ({normal_cpu_usage:5.2f}%) {err}\n" + result += f"{proc_name.ljust(35)} {cpu_usage:5.2f}% ({expected_cpu:5.2f}%) {err}\n" if len(err) > 0: r = False @@ -111,6 +119,7 @@ class TestOnroad(unittest.TestCase): if "DEBUG" in os.environ: segs = filter(lambda x: os.path.exists(os.path.join(x, "rlog")), Path(ROOT).iterdir()) segs = sorted(segs, key=lambda x: x.stat().st_mtime) + print(segs[-1]) cls.lr = list(LogReader(os.path.join(segs[-1], "rlog"))) return @@ -118,6 +127,7 @@ class TestOnroad(unittest.TestCase): os.environ['REPLAY'] = "1" os.environ['SKIP_FW_QUERY'] = "1" os.environ['FINGERPRINT'] = "TOYOTA COROLLA TSS2 2019" + os.environ['LOGPRINT'] = 'debug' params = Params() params.clear_all() @@ -179,7 +189,7 @@ class TestOnroad(unittest.TestCase): def test_cpu_usage(self): proclogs = [m for m in self.lr if m.which() == 'procLog'] self.assertGreater(len(proclogs), service_list['procLog'].frequency * 45, "insufficient samples") - cpu_ok = check_cpu_usage(proclogs[0], proclogs[-1]) + cpu_ok = check_cpu_usage(proclogs) self.assertTrue(cpu_ok) def test_camera_processing_time(self): @@ -221,7 +231,7 @@ class TestOnroad(unittest.TestCase): # TODO: this went up when plannerd cpu usage increased, why? cfgs = [ ("modelV2", 0.050, 0.036), - ("driverState", 0.050, 0.026), + ("driverStateV2", 0.050, 0.026), ] for (s, instant_max, avg_max) in cfgs: ts = [getattr(getattr(m, s), "modelExecutionTime") for m in self.lr if m.which() == s] diff --git a/selfdrive/thermald/tests/test_power_monitoring.py b/selfdrive/thermald/tests/test_power_monitoring.py index c0524add6..efe607155 100755 --- a/selfdrive/thermald/tests/test_power_monitoring.py +++ b/selfdrive/thermald/tests/test_power_monitoring.py @@ -119,7 +119,8 @@ class TestPowerMonitoring(unittest.TestCase): @parameterized.expand(ALL_PANDA_TYPES) def test_max_time_offroad(self, hw_type): MOCKED_MAX_OFFROAD_TIME = 3600 - with pm_patch("MAX_TIME_OFFROAD_S", MOCKED_MAX_OFFROAD_TIME, constant=True), pm_patch("HARDWARE.get_current_power_draw", None): + POWER_DRAW = 0 # To stop shutting down for other reasons + with pm_patch("MAX_TIME_OFFROAD_S", MOCKED_MAX_OFFROAD_TIME, constant=True), pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh start_time = ssb diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 6cf5c428a..c765af664 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -34,7 +34,7 @@ DISCONNECT_TIMEOUT = 5. # wait 5 seconds before going offroad after disconnect PANDA_STATES_TIMEOUT = int(1000 * 1.5 * DT_TRML) # 1.5x the expected pandaState frequency ThermalBand = namedtuple("ThermalBand", ['min_temp', 'max_temp']) -HardwareState = namedtuple("HardwareState", ['network_type', 'network_metered', 'network_strength', 'network_info', 'nvme_temps', 'modem_temps']) +HardwareState = namedtuple("HardwareState", ['network_type', 'network_info', 'network_strength', 'network_stats', 'network_metered', 'nvme_temps', 'modem_temps']) # List of thermal bands. We will stay within this region as long as we are within the bounds. # When exiting the bounds, we'll jump to the lower or higher band. Bands are ordered in the dict. @@ -96,7 +96,6 @@ def set_offroad_alert_if_changed(offroad_alert: str, show_alert: bool, extra_tex def hw_state_thread(end_event, hw_queue): """Handles non critical hardware state, and sends over queue""" count = 0 - registered_count = 0 prev_hw_state = None modem_version = None @@ -120,11 +119,14 @@ def hw_state_thread(end_event, hw_queue): if (modem_version is not None) and (modem_nv is not None): cloudlog.event("modem version", version=modem_version, nv=modem_nv) + tx, rx = HARDWARE.get_modem_data_usage() + hw_state = HardwareState( network_type=network_type, - network_metered=HARDWARE.get_network_metered(network_type), - network_strength=HARDWARE.get_network_strength(network_type), network_info=HARDWARE.get_network_info(), + network_strength=HARDWARE.get_network_strength(network_type), + network_stats={'wwanTx': tx, 'wwanRx': rx}, + network_metered=HARDWARE.get_network_metered(network_type), nvme_temps=HARDWARE.get_nvme_temperatures(), modem_temps=modem_temps, ) @@ -134,16 +136,6 @@ def hw_state_thread(end_event, hw_queue): except queue.Full: pass - if AGNOS and (hw_state.network_info is not None) and (hw_state.network_info.get('state', None) == "REGISTERED"): - registered_count += 1 - else: - registered_count = 0 - - if registered_count > 10: - cloudlog.warning(f"Modem stuck in registered state {hw_state.network_info}. nmcli conn up lte") - os.system("nmcli conn up lte") - registered_count = 0 - # TODO: remove this once the config is in AGNOS if not modem_configured and len(HARDWARE.get_sim_info().get('sim_id', '')) > 0: cloudlog.warning("configuring modem") @@ -177,9 +169,10 @@ def thermald_thread(end_event, hw_queue): last_hw_state = HardwareState( network_type=NetworkType.none, + network_info=None, network_metered=False, network_strength=NetworkStrength.unknown, - network_info=None, + network_stats={'wwanTx': -1, 'wwanRx': -1}, nvme_temps=[], modem_temps=[], ) @@ -238,6 +231,7 @@ def thermald_thread(end_event, hw_queue): msg.deviceState.networkType = last_hw_state.network_type msg.deviceState.networkMetered = last_hw_state.network_metered msg.deviceState.networkStrength = last_hw_state.network_strength + msg.deviceState.networkStats = last_hw_state.network_stats if last_hw_state.network_info is not None: msg.deviceState.networkInfo = last_hw_state.network_info @@ -348,12 +342,13 @@ def thermald_thread(end_event, hw_queue): power_monitor.calculate(peripheralState, onroad_conditions["ignition"]) msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used() msg.deviceState.carBatteryCapacityUwh = max(0, power_monitor.get_car_battery_capacity()) - current_power_draw = HARDWARE.get_current_power_draw() # pylint: disable=assignment-from-none - if current_power_draw is not None: - statlog.sample("power_draw", current_power_draw) - msg.deviceState.powerDrawW = current_power_draw - else: - msg.deviceState.powerDrawW = 0 + current_power_draw = HARDWARE.get_current_power_draw() + statlog.sample("power_draw", current_power_draw) + msg.deviceState.powerDrawW = current_power_draw + + som_power_draw = HARDWARE.get_som_power_draw() + statlog.sample("som_power_draw", som_power_draw) + msg.deviceState.somPowerDrawW = som_power_draw # Check if we need to disable charging (handled by boardd) msg.deviceState.chargingDisabled = power_monitor.should_disable_charging(onroad_conditions["ignition"], in_car, off_ts) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 28fcc5f56..9e89576a5 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -18,10 +18,11 @@ if arch == "Darwin": del base_libs[base_libs.index('OpenCL')] qt_env['FRAMEWORKS'] += ['OpenCL'] -widgets_src = ["ui.cc", "qt/util.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", +qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs) +widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "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/api.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_env['CPPDEFINES'] = [] @@ -31,7 +32,7 @@ if maps: qt_env['CPPDEFINES'] += ["ENABLE_MAPS"] widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) -qt_libs = [widgets] + base_libs +qt_libs = [widgets, qt_util] + base_libs # build assets assets = "#selfdrive/assets/assets.cc" @@ -57,6 +58,16 @@ 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/onboarding.cc", "qt/offroad/driverview.cc"] qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs) +if GetOption('test'): + 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) + + +# build translation files +translation_sources = Glob("#selfdrive/ui/translations/*.ts", strings=True) +translation_targets = [src.replace(".ts", ".qm") for src in translation_sources] +lrelease = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease' +qt_env.Command(translation_targets, translation_sources, f"{lrelease} $SOURCES") # setup and factory resetter @@ -107,17 +118,6 @@ if GetOption('extras'): # keep installers small assert f[0].get_size() < 300*1e3 - -# build headless replay +# build watch3 if arch in ['x86_64', 'Darwin'] or GetOption('extras'): - qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] - - replay_lib_src = ["replay/replay.cc", "replay/consoleui.cc", "replay/camera.cc", "replay/filereader.cc", "replay/logreader.cc", "replay/framereader.cc", "replay/route.cc", "replay/util.cc"] - - replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=base_libs) - replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv', 'ncurses'] + qt_libs - qt_env.Program("replay/replay", ["replay/main.cc"], LIBS=replay_libs) qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11', 'zmq', 'visionipc', 'messaging']) - - if GetOption('test'): - qt_env.Program('replay/tests/test_replay', ['replay/tests/test_runner.cc', 'replay/tests/test_replay.cc'], LIBS=[replay_libs]) diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 3588026ba..ee98c4d3d 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -53,7 +53,7 @@ Installer::Installer(QWidget *parent) : QWidget(parent) { layout->setContentsMargins(150, 290, 150, 150); layout->setSpacing(0); - QLabel *title = new QLabel("Installing..."); + QLabel *title = new QLabel(tr("Installing...")); title->setStyleSheet("font-size: 90px; font-weight: 600;"); layout->addWidget(title, 0, Qt::AlignTop); @@ -141,9 +141,9 @@ void Installer::cachedFetch(const QString &cache) { void Installer::readProgress() { const QVector> stages = { // prefix, weight in percentage - {"Receiving objects: ", 91}, - {"Resolving deltas: ", 2}, - {"Updating files: ", 7}, + {tr("Receiving objects: "), 91}, + {tr("Resolving deltas: "), 2}, + {tr("Updating files: "), 7}, }; auto line = QString(proc.readAllStandardError()); @@ -182,6 +182,7 @@ void Installer::cloneFinished(int exitCode, QProcess::ExitStatus exitStatus) { std::map params = { {"SshEnabled", "1"}, {"RecordFrontLock", "1"}, + {"DisableRadar_Allow", "1"}, {"GithubSshKeys", SSH_KEYS}, }; for (const auto& [key, value] : params) { diff --git a/selfdrive/ui/main.cc b/selfdrive/ui/main.cc index 1eecd78b1..ed54d5aa1 100644 --- a/selfdrive/ui/main.cc +++ b/selfdrive/ui/main.cc @@ -1,6 +1,7 @@ #include #include +#include #include "system/hardware/hw.h" #include "selfdrive/ui/qt/qt_window.h" @@ -13,7 +14,15 @@ int main(int argc, char *argv[]) { qInstallMessageHandler(swagLogMessageHandler); initApp(argc, argv); + QTranslator translator; + QString translation_file = QString::fromStdString(Params().get("LanguageSetting")); + if (!translator.load(translation_file, "translations") && translation_file.length()) { + qCritical() << "Failed to load translation file:" << translation_file; + } + QApplication a(argc, argv); + a.installTranslator(&translator); + MainWindow w; setMainWindow(&w); a.installEventFilter(&w); diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index e522d6160..0edeb252b 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -85,6 +85,7 @@ void HomeWindow::mousePressEvent(QMouseEvent* e) { } void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) { + HomeWindow::mousePressEvent(e); const SubMaster &sm = *(uiState()->sm); if (sm["carParams"].getCarParams().getNotCar()) { if (onroad->isVisible()) { @@ -92,6 +93,7 @@ void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) { } else if (body->isVisible()) { slayout->setCurrentWidget(onroad); } + showSidebar(false); } } @@ -109,7 +111,7 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { date = new QLabel(); header_layout->addWidget(date, 1, Qt::AlignHCenter | Qt::AlignLeft); - update_notif = new QPushButton("UPDATE"); + update_notif = new QPushButton(tr("UPDATE")); update_notif->setVisible(false); update_notif->setStyleSheet("background-color: #364DEF;"); QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); }); @@ -200,6 +202,6 @@ void OffroadHome::refresh() { update_notif->setVisible(updateAvailable); alert_notif->setVisible(alerts); if (alerts) { - alert_notif->setText(QString::number(alerts) + (alerts > 1 ? " ALERTS" : " ALERT")); + alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT"))); } } diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index 4c6a0a4e6..f81c3dd8f 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -1,5 +1,6 @@ #include "selfdrive/ui/qt/maps/map.h" +#include #include #include @@ -7,6 +8,7 @@ #include #include "common/swaglog.h" +#include "common/transformations/coordinates.hpp" #include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/qt/request_repeater.h" #include "selfdrive/ui/qt/util.h" @@ -22,6 +24,8 @@ const float MAX_PITCH = 50; const float MIN_PITCH = 0; const float MAP_SCALE = 2; +const float VALID_POS_STD = 50.0; // m + const QString ICON_SUFFIX = ".png"; MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05) { @@ -105,18 +109,53 @@ void MapWindow::updateState(const UIState &s) { update(); if (sm.updated("liveLocationKalman")) { - auto location = sm["liveLocationKalman"].getLiveLocationKalman(); - auto pos = location.getPositionGeodetic(); - auto orientation = location.getCalibratedOrientationNED(); - auto velocity = location.getVelocityCalibrated(); - - localizer_valid = (location.getStatus() == cereal::LiveLocationKalman::Status::VALID) && - pos.getValid() && orientation.getValid() && velocity.getValid(); - - if (localizer_valid) { - last_position = QMapbox::Coordinate(pos.getValue()[0], pos.getValue()[1]); - last_bearing = RAD2DEG(orientation.getValue()[2]); - velocity_filter.update(velocity.getValue()[0]); + auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman(); + auto locationd_pos = locationd_location.getPositionGeodetic(); + auto locationd_orientation = locationd_location.getCalibratedOrientationNED(); + auto locationd_velocity = locationd_location.getVelocityCalibrated(); + + locationd_valid = (locationd_location.getStatus() == cereal::LiveLocationKalman::Status::VALID) && + locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid(); + + 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]); + } + } + + if (sm.updated("gnssMeasurements")) { + auto laikad_location = sm["gnssMeasurements"].getGnssMeasurements(); + auto laikad_pos = laikad_location.getPositionECEF(); + auto laikad_pos_ecef = laikad_pos.getValue(); + auto laikad_pos_std = laikad_pos.getStd(); + auto laikad_velocity_ecef = laikad_location.getVelocityECEF().getValue(); + + laikad_valid = laikad_pos.getValid() && Eigen::Vector3d(laikad_pos_std[0], laikad_pos_std[1], laikad_pos_std[2]).norm() < VALID_POS_STD; + + if (laikad_valid && !locationd_valid) { + ECEF ecef = {.x = laikad_pos_ecef[0], .y = laikad_pos_ecef[1], .z = laikad_pos_ecef[2]}; + Geodetic laikad_pos_geodetic = ecef2geodetic(ecef); + last_position = QMapbox::Coordinate(laikad_pos_geodetic.lat, laikad_pos_geodetic.lon); + + // Compute NED velocity + LocalCoord converter(ecef); + ECEF next_ecef = {.x = ecef.x + laikad_velocity_ecef[0], .y = ecef.y + laikad_velocity_ecef[1], .z = ecef.z + laikad_velocity_ecef[2]}; + Eigen::VectorXd ned_vel = converter.ecef2ned(next_ecef).to_vector() - converter.ecef2ned(ecef).to_vector(); + + float velocity = ned_vel.norm(); + velocity_filter.update(velocity); + + // Convert NED velocity to angle + if (velocity > 1.0) { + float new_bearing = fmod(RAD2DEG(atan2(ned_vel[1], ned_vel[0])) + 360.0, 360.0); + if (last_bearing) { + float delta = 0.1 * angle_difference(*last_bearing, new_bearing); // Smooth heading + last_bearing = fmod(*last_bearing + delta + 360.0, 360.0); + } else { + last_bearing = new_bearing; + } + } } } @@ -136,15 +175,13 @@ void MapWindow::updateState(const UIState &s) { loaded_once = loaded_once || m_map->isFullyLoaded(); if (!loaded_once) { - map_instructions->showError("Map Loading"); + map_instructions->showError(tr("Map Loading")); return; } initLayers(); - if (!localizer_valid) { - map_instructions->showError("Waiting for GPS"); - } else { + if (locationd_valid || laikad_valid) { map_instructions->noError(); // Update current location marker @@ -154,6 +191,8 @@ void MapWindow::updateState(const UIState &s) { carPosSource["type"] = "geojson"; carPosSource["data"] = QVariant::fromValue(feature1); m_map->updateSource("carPosSource", carPosSource); + } else { + map_instructions->showError(tr("Waiting for GPS")); } if (pan_counter == 0) { @@ -174,7 +213,7 @@ void MapWindow::updateState(const UIState &s) { auto i = sm["navInstruction"].getNavInstruction(); emit ETAChanged(i.getTimeRemaining(), i.getTimeRemainingTypical(), i.getDistanceRemaining()); - if (localizer_valid) { + if (locationd_valid || laikad_valid) { m_map->setPitch(MAX_PITCH); // TODO: smooth pitching based on maneuver distance emit distanceChanged(i.getManeuverDistance()); // TODO: combine with instructionsChanged emit instructionsChanged(i); @@ -379,10 +418,10 @@ void MapInstructions::updateDistance(float d) { if (uiState()->scene.is_metric) { if (d > 500) { distance_str.setNum(d / 1000, 'f', 1); - distance_str += " km"; + distance_str += tr(" km"); } else { distance_str.setNum(50 * int(d / 50)); - distance_str += " m"; + distance_str += tr(" m"); } } else { float miles = d * METER_TO_MILE; @@ -390,10 +429,10 @@ void MapInstructions::updateDistance(float d) { if (feet > 500) { distance_str.setNum(miles, 'f', 1); - distance_str += " mi"; + distance_str += tr(" mi"); } else { distance_str.setNum(50 * int(feet / 50)); - distance_str += " ft"; + distance_str += tr(" ft"); } } @@ -487,9 +526,12 @@ void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruct fn += "turn_straight"; } + if (!active) { + fn += "_inactive"; + } + auto icon = new QLabel; - int wh = active ? 125 : 75; - icon->setPixmap(loadPixmap(fn + ICON_SUFFIX, {wh, wh}, Qt::IgnoreAspectRatio)); + icon->setPixmap(loadPixmap(fn + ICON_SUFFIX, {125, 125}, Qt::IgnoreAspectRatio)); icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); lane_layout->addWidget(icon); } @@ -576,7 +618,7 @@ void MapETA::updateETA(float s, float s_typical, float d) { auto eta_time = QDateTime::currentDateTime().addSecs(s).time(); if (params.getBool("NavSettingTime24h")) { eta->setText(eta_time.toString("HH:mm")); - eta_unit->setText("eta"); + eta_unit->setText(tr("eta")); } else { auto t = eta_time.toString("h:mm a").split(' '); eta->setText(t[0]); @@ -586,11 +628,11 @@ void MapETA::updateETA(float s, float s_typical, float d) { // Remaining time if (s < 3600) { time->setText(QString::number(int(s / 60))); - time_unit->setText("min"); + time_unit->setText(tr("min")); } else { int hours = int(s) / 3600; time->setText(QString::number(hours) + ":" + QString::number(int((s - hours * 3600) / 60)).rightJustified(2, '0')); - time_unit->setText("hr"); + time_unit->setText(tr("hr")); } QString color; @@ -610,10 +652,10 @@ void MapETA::updateETA(float s, float s_typical, float d) { float num = 0; if (uiState()->scene.is_metric) { num = d / 1000.0; - distance_unit->setText("km"); + distance_unit->setText(tr("km")); } else { num = d * METER_TO_MILE; - distance_unit->setText("mi"); + distance_unit->setText(tr("mi")); } distance_str.setNum(num, 'f', num < 100 ? 1 : 0); diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 7c39b24c3..ecba867ed 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -104,7 +104,8 @@ private: std::optional last_position; std::optional last_bearing; FirstOrderFilter velocity_filter; - bool localizer_valid = false; + bool laikad_valid = false; + bool locationd_valid = false; MapInstructions* map_instructions; MapETA* map_eta; diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc index 2b2c27418..f97137a7f 100644 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -101,6 +101,48 @@ QMapbox::CoordinatesCollections coordinate_list_to_collection(QList polyline_to_coordinate_list(const QString &polylineString) { + QList path; + if (polylineString.isEmpty()) + return path; + + QByteArray data = polylineString.toLatin1(); + + bool parsingLatitude = true; + + int shift = 0; + int value = 0; + + QGeoCoordinate coord(0, 0); + + for (int i = 0; i < data.length(); ++i) { + unsigned char c = data.at(i) - 63; + + value |= (c & 0x1f) << shift; + shift += 5; + + // another chunk + if (c & 0x20) + continue; + + int diff = (value & 1) ? ~(value >> 1) : (value >> 1); + + if (parsingLatitude) { + coord.setLatitude(coord.latitude() + (double)diff/1e6); + } else { + coord.setLongitude(coord.longitude() + (double)diff/1e6); + path.append(coord); + } + + parsingLatitude = !parsingLatitude; + + value = 0; + shift = 0; + } + + return path; +} + std::optional coordinate_from_param(std::string param) { QString json_str = QString::fromStdString(Params().get(param)); if (json_str.isEmpty()) return {}; @@ -116,3 +158,8 @@ std::optional coordinate_from_param(std::string param) { return {}; } } + +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 344246bb0..2e1402cce 100644 --- a/selfdrive/ui/qt/maps/map_helpers.h +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -24,5 +24,7 @@ QMapbox::CoordinatesCollections model_to_collection( QMapbox::CoordinatesCollections coordinate_to_collection(QMapbox::Coordinate c); QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader &coordinate_list); QMapbox::CoordinatesCollections coordinate_list_to_collection(QList coordinate_list); +QList polyline_to_coordinate_list(const QString &polylineString); std::optional coordinate_from_param(std::string param); +double angle_difference(double angle1, double angle2); diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc index 674c7fa7c..d143b44e7 100644 --- a/selfdrive/ui/qt/maps/map_settings.cc +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -59,11 +59,11 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { current_widget = new QWidget(this); QVBoxLayout *current_layout = new QVBoxLayout(current_widget); - QLabel *title = new QLabel("Current Destination"); + QLabel *title = new QLabel(tr("Current Destination")); title->setStyleSheet("font-size: 55px"); current_layout->addWidget(title); - current_route = new ButtonControl("", "CLEAR"); + current_route = new ButtonControl("", tr("CLEAR")); current_route->setStyleSheet("padding-left: 40px;"); current_layout->addWidget(current_route); QObject::connect(current_route, &ButtonControl::clicked, [=]() { @@ -78,7 +78,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { main_layout->addWidget(current_widget); // Recents - QLabel *recents_title = new QLabel("Recent Destinations"); + QLabel *recents_title = new QLabel(tr("Recent Destinations")); recents_title->setStyleSheet("font-size: 55px"); main_layout->addWidget(recents_title); main_layout->addSpacing(20); @@ -92,7 +92,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { QWidget * no_prime_widget = new QWidget; { QVBoxLayout *no_prime_layout = new QVBoxLayout(no_prime_widget); - QLabel *signup_header = new QLabel("Try the Navigation Beta"); + QLabel *signup_header = new QLabel(tr("Try the Navigation Beta")); signup_header->setStyleSheet(R"(font-size: 75px; color: white; font-weight:600;)"); signup_header->setAlignment(Qt::AlignCenter); @@ -104,7 +104,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { screenshot->setPixmap(pm.scaledToWidth(1080, Qt::SmoothTransformation)); no_prime_layout->addWidget(screenshot, 0, Qt::AlignHCenter); - QLabel *signup = new QLabel("Get turn-by-turn directions displayed and more with a comma \nprime subscription. Sign up now: https://connect.comma.ai"); + QLabel *signup = new QLabel(tr("Get turn-by-turn directions displayed and more with a comma\nprime subscription. Sign up now: https://connect.comma.ai")); signup->setStyleSheet(R"(font-size: 45px; color: white; font-weight:300;)"); signup->setAlignment(Qt::AlignCenter); @@ -161,12 +161,12 @@ void MapPanel::showEvent(QShowEvent *event) { void MapPanel::clear() { home_button->setIcon(QPixmap("../assets/navigation/home_inactive.png")); home_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); - home_address->setText("No home\nlocation set"); + home_address->setText(tr("No home\nlocation set")); home_button->disconnect(); work_button->setIcon(QPixmap("../assets/navigation/work_inactive.png")); work_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); - work_address->setText("No work\nlocation set"); + work_address->setText(tr("No work\nlocation set")); work_button->disconnect(); clearLayout(recent_layout); @@ -279,7 +279,7 @@ void MapPanel::parseResponse(const QString &response, bool success) { } if (!has_recents) { - QLabel *no_recents = new QLabel("no recent destinations"); + QLabel *no_recents = new QLabel(tr("no recent destinations")); no_recents->setStyleSheet(R"(font-size: 50px; color: #9c9c9c)"); recent_layout->addWidget(no_recents); } diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index 06578b455..be8b84d45 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -25,13 +25,12 @@ void DriverViewWindow::mouseReleaseEvent(QMouseEvent* e) { emit done(); } -DriverViewScene::DriverViewScene(QWidget* parent) : sm({"driverState"}), QWidget(parent) { +DriverViewScene::DriverViewScene(QWidget* parent) : sm({"driverStateV2"}), QWidget(parent) { face_img = loadPixmap("../assets/img_driver_face.png", {FACE_IMG_SIZE, FACE_IMG_SIZE}); } void DriverViewScene::showEvent(QShowEvent* event) { frame_updated = false; - is_rhd = params.getBool("IsRHD"); params.putBool("IsDriverViewEnabled", true); } @@ -53,47 +52,40 @@ void DriverViewScene::paintEvent(QPaintEvent* event) { p.setPen(Qt::white); p.setRenderHint(QPainter::TextAntialiasing); configFont(p, "Inter", 100, "Bold"); - p.drawText(geometry(), Qt::AlignCenter, "camera starting"); + p.drawText(geometry(), Qt::AlignCenter, tr("camera starting")); return; } - const int width = 4 * height() / 3; - const QRect rect2 = {rect().center().x() - width / 2, rect().top(), width, rect().height()}; - const QRect valid_rect = {is_rhd ? rect2.right() - rect2.height() / 2 : rect2.left(), rect2.top(), rect2.height() / 2, rect2.height()}; + cereal::DriverStateV2::Reader driver_state = sm["driverStateV2"].getDriverStateV2(); + cereal::DriverStateV2::DriverData::Reader driver_data; - // blackout - const QColor bg(0, 0, 0, 140); - const QRect& blackout_rect = rect(); - p.fillRect(blackout_rect.adjusted(0, 0, valid_rect.left() - blackout_rect.right(), 0), bg); - p.fillRect(blackout_rect.adjusted(valid_rect.right() - blackout_rect.left(), 0, 0, 0), bg); - p.fillRect(blackout_rect.adjusted(valid_rect.left()-blackout_rect.left()+1, 0, valid_rect.right()-blackout_rect.right()-1, -valid_rect.height()*7/10), bg); // top dz + is_rhd = driver_state.getWheelOnRightProb() > 0.5; + driver_data = is_rhd ? driver_state.getRightDriverData() : driver_state.getLeftDriverData(); - // face bounding box - cereal::DriverState::Reader driver_state = sm["driverState"].getDriverState(); - bool face_detected = driver_state.getFaceProb() > 0.5; + bool face_detected = driver_data.getFaceProb() > 0.7; if (face_detected) { - auto fxy_list = driver_state.getFacePosition(); - auto std_list = driver_state.getFaceOrientationStd(); + auto fxy_list = driver_data.getFacePosition(); + auto std_list = driver_data.getFaceOrientationStd(); float face_x = fxy_list[0]; float face_y = fxy_list[1]; float face_std = std::max(std_list[0], std_list[1]); float alpha = 0.7; - if (face_std > 0.08) { - alpha = std::max(0.7 - (face_std-0.08)*7, 0.0); + if (face_std > 0.15) { + alpha = std::max(0.7 - (face_std-0.15)*3.5, 0.0); } - const int box_size = 0.6 * rect2.height() / 2; - const float rhd_offset = 0.05; // lhd is shifted, so rhd is not mirrored - int fbox_x = valid_rect.center().x() + (is_rhd ? (face_x + rhd_offset) : -face_x) * valid_rect.width(); - int fbox_y = valid_rect.center().y() + face_y * valid_rect.height(); + const int box_size = 220; + // use approx instead of distort_points + int fbox_x = 1080.0 - 1714.0 * face_x; + int fbox_y = -135.0 + (504.0 + std::abs(face_x)*112.0) + (1205.0 - std::abs(face_x)*724.0) * face_y; p.setPen(QPen(QColor(255, 255, 255, alpha * 255), 10)); p.drawRoundedRect(fbox_x - box_size / 2, fbox_y - box_size / 2, box_size, box_size, 35.0, 35.0); } // icon - const int img_offset = 30; - const int img_x = is_rhd ? rect2.right() - FACE_IMG_SIZE - img_offset : rect2.left() + img_offset; - const int img_y = rect2.bottom() - FACE_IMG_SIZE - img_offset; - p.setOpacity(face_detected ? 1.0 : 0.3); + const int img_offset = 60; + const int img_x = is_rhd ? rect().right() - FACE_IMG_SIZE - img_offset : rect().left() + img_offset; + const int img_y = rect().bottom() - FACE_IMG_SIZE - img_offset; + p.setOpacity(face_detected ? 1.0 : 0.2); p.drawPixmap(img_x, img_y, face_img); } diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/offroad/networking.cc index 418ccd317..c7341d198 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/offroad/networking.cc @@ -27,7 +27,7 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) { QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen); vlayout->setContentsMargins(20, 20, 20, 20); if (show_advanced) { - QPushButton* advancedSettings = new QPushButton("Advanced"); + QPushButton* advancedSettings = new QPushButton(tr("Advanced")); advancedSettings->setObjectName("advanced_btn"); advancedSettings->setStyleSheet("margin-right: 30px;"); advancedSettings->setFixedSize(400, 100); @@ -84,7 +84,7 @@ void Networking::connectToNetwork(const Network &n) { } else if (n.security_type == SecurityType::OPEN) { wifi->connect(n); } else if (n.security_type == SecurityType::WPA) { - QString pass = InputDialog::getText("Enter password", this, "for \"" + n.ssid + "\"", true, 8); + QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8); if (!pass.isEmpty()) { wifi->connect(n, pass); } @@ -94,7 +94,7 @@ void Networking::connectToNetwork(const Network &n) { void Networking::wrongPassword(const QString &ssid) { if (wifi->seenNetworks.contains(ssid)) { const Network &n = wifi->seenNetworks.value(ssid); - QString pass = InputDialog::getText("Wrong password", this, "for \"" + n.ssid +"\"", true, 8); + QString pass = InputDialog::getText(tr("Wrong password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8); if (!pass.isEmpty()) { wifi->connect(n, pass); } @@ -118,7 +118,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid main_layout->setSpacing(20); // Back button - QPushButton* back = new QPushButton("Back"); + QPushButton* back = new QPushButton(tr("Back")); back->setObjectName("back_btn"); back->setFixedSize(400, 100); connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); @@ -126,14 +126,14 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid ListWidget *list = new ListWidget(this); // Enable tethering layout - tetheringToggle = new ToggleControl("Enable Tethering", "", "", wifi->isTetheringEnabled()); + tetheringToggle = new ToggleControl(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled()); list->addItem(tetheringToggle); QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering); // Change tethering password - ButtonControl *editPasswordButton = new ButtonControl("Tethering Password", "EDIT"); + ButtonControl *editPasswordButton = new ButtonControl(tr("Tethering Password"), tr("EDIT")); connect(editPasswordButton, &ButtonControl::clicked, [=]() { - QString pass = InputDialog::getText("Enter new tethering password", this, "", true, 8, wifi->getTetheringPassword()); + QString pass = InputDialog::getText(tr("Enter new tethering password"), this, "", true, 8, wifi->getTetheringPassword()); if (!pass.isEmpty()) { wifi->changeTetheringPassword(pass); } @@ -141,7 +141,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid list->addItem(editPasswordButton); // IP address - ipLabel = new LabelControl("IP Address", wifi->ipv4_address); + ipLabel = new LabelControl(tr("IP Address"), wifi->ipv4_address); list->addItem(ipLabel); // SSH keys @@ -150,7 +150,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid // Roaming toggle const bool roamingEnabled = params.getBool("GsmRoaming"); - ToggleControl *roamingToggle = new ToggleControl("Enable Roaming", "", "", roamingEnabled); + ToggleControl *roamingToggle = new ToggleControl(tr("Enable Roaming"), "", "", roamingEnabled); QObject::connect(roamingToggle, &SshToggle::toggleFlipped, [=](bool state) { params.putBool("GsmRoaming", state); wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn"))); @@ -158,11 +158,11 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid list->addItem(roamingToggle); // APN settings - ButtonControl *editApnButton = new ButtonControl("APN Setting", "EDIT"); + ButtonControl *editApnButton = new ButtonControl(tr("APN Setting"), tr("EDIT")); connect(editApnButton, &ButtonControl::clicked, [=]() { const bool roamingEnabled = params.getBool("GsmRoaming"); const QString cur_apn = QString::fromStdString(params.get("GsmApn")); - QString apn = InputDialog::getText("Enter APN", this, "leave blank for automatic configuration", false, -1, cur_apn).trimmed(); + QString apn = InputDialog::getText(tr("Enter APN"), this, tr("leave blank for automatic configuration"), false, -1, cur_apn).trimmed(); if (apn.isEmpty()) { params.remove("GsmApn"); @@ -174,7 +174,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid list->addItem(editApnButton); // Set initial config - wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn"))); + wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn"))); main_layout->addWidget(new ScrollView(list, this)); main_layout->addStretch(1); @@ -207,7 +207,7 @@ WifiUI::WifiUI(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi) 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); - QLabel *scanning = new QLabel("Scanning for networks..."); + QLabel *scanning = new QLabel(tr("Scanning for networks...")); scanning->setStyleSheet("font-size: 65px;"); main_layout->addWidget(scanning, 0, Qt::AlignCenter); @@ -260,7 +260,7 @@ void WifiUI::refresh() { clearLayout(main_layout); if (wifi->seenNetworks.size() == 0) { - QLabel *scanning = new QLabel("Scanning for networks..."); + QLabel *scanning = new QLabel(tr("Scanning for networks...")); scanning->setStyleSheet("font-size: 65px;"); main_layout->addWidget(scanning, 0, Qt::AlignCenter); return; @@ -286,17 +286,17 @@ void WifiUI::refresh() { hlayout->addWidget(ssidLabel, network.connected == ConnectedType::CONNECTING ? 0 : 1); if (network.connected == ConnectedType::CONNECTING) { - QPushButton *connecting = new QPushButton("CONNECTING..."); + QPushButton *connecting = new QPushButton(tr("CONNECTING...")); connecting->setObjectName("connecting"); hlayout->addWidget(connecting, 2, Qt::AlignLeft); } // Forget button if (wifi->isKnownConnection(network.ssid) && !wifi->isTetheringEnabled()) { - QPushButton *forgetBtn = new QPushButton("FORGET"); + QPushButton *forgetBtn = new QPushButton(tr("FORGET")); forgetBtn->setObjectName("forgetBtn"); QObject::connect(forgetBtn, &QPushButton::clicked, [=]() { - if (ConfirmationDialog::confirm("Forget Wi-Fi Network \"" + QString::fromUtf8(network.ssid) + "\"?", this)) { + if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"%1\"?").arg(QString::fromUtf8(network.ssid)), this)) { wifi->forgetConnection(network.ssid); } }); diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index d32cc26e4..f3e50b572 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -76,7 +76,7 @@ void TermsPage::showEvent(QShowEvent *event) { main_layout->setContentsMargins(45, 35, 45, 45); main_layout->setSpacing(0); - QLabel *title = new QLabel("Terms & Conditions"); + QLabel *title = new QLabel(tr("Terms & Conditions")); title->setStyleSheet("font-size: 90px; font-weight: 600;"); main_layout->addWidget(title); @@ -104,11 +104,11 @@ void TermsPage::showEvent(QShowEvent *event) { buttons->setSpacing(45); main_layout->addLayout(buttons); - QPushButton *decline_btn = new QPushButton("Decline"); + QPushButton *decline_btn = new QPushButton(tr("Decline")); buttons->addWidget(decline_btn); QObject::connect(decline_btn, &QPushButton::clicked, this, &TermsPage::declinedTerms); - accept_btn = new QPushButton("Scroll to accept"); + accept_btn = new QPushButton(tr("Scroll to accept")); accept_btn->setEnabled(false); accept_btn->setStyleSheet(R"( QPushButton { @@ -123,7 +123,7 @@ void TermsPage::showEvent(QShowEvent *event) { } void TermsPage::enableAccept() { - accept_btn->setText("Agree"); + accept_btn->setText(tr("Agree")); accept_btn->setEnabled(true); } @@ -137,7 +137,7 @@ void DeclinePage::showEvent(QShowEvent *event) { main_layout->setSpacing(40); QLabel *text = new QLabel(this); - text->setText("You must accept the Terms and Conditions in order to use openpilot."); + text->setText(tr("You must accept the Terms and Conditions in order to use openpilot.")); text->setStyleSheet(R"(font-size: 80px; font-weight: 300; margin: 200px;)"); text->setWordWrap(true); main_layout->addWidget(text, 0, Qt::AlignCenter); @@ -146,12 +146,12 @@ void DeclinePage::showEvent(QShowEvent *event) { buttons->setSpacing(45); main_layout->addLayout(buttons); - QPushButton *back_btn = new QPushButton("Back"); + QPushButton *back_btn = new QPushButton(tr("Back")); buttons->addWidget(back_btn); QObject::connect(back_btn, &QPushButton::clicked, this, &DeclinePage::getBack); - QPushButton *uninstall_btn = new QPushButton(QString("Decline, uninstall %1").arg(getBrand())); + QPushButton *uninstall_btn = new QPushButton(tr("Decline, uninstall %1").arg(getBrand())); uninstall_btn->setStyleSheet("background-color: #B73D3D"); buttons->addWidget(uninstall_btn); QObject::connect(uninstall_btn, &QPushButton::clicked, [=]() { diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 175e6cf5a..5e0e87e64 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -13,6 +13,7 @@ #endif #include "common/params.h" +#include "common/watchdog.h" #include "common/util.h" #include "system/hardware/hw.h" #include "selfdrive/ui/qt/widgets/controls.h" @@ -23,53 +24,54 @@ #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 std::vector> toggles{ { "OpenpilotEnabledToggle", - "Enable openpilot", - "Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off.", + tr("Enable openpilot"), + tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), "../assets/offroad/icon_openpilot.png", }, { "IsLdwEnabled", - "Enable Lane Departure Warnings", - "Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h).", + tr("Enable Lane Departure Warnings"), + tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."), "../assets/offroad/icon_warning.png", }, - { - "IsRHD", - "Enable Right-Hand Drive", - "Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat.", - "../assets/offroad/icon_openpilot_mirrored.png", - }, { "IsMetric", - "Use Metric System", - "Display speed in km/h instead of mph.", + tr("Use Metric System"), + tr("Display speed in km/h instead of mph."), "../assets/offroad/icon_metric.png", }, { "RecordFront", - "Record and Upload Driver Camera", - "Upload data from the driver facing camera and help improve the driver monitoring algorithm.", + tr("Record and Upload Driver Camera"), + tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), "../assets/offroad/icon_monitoring.png", }, { "DisengageOnAccelerator", - "Disengage On Accelerator Pedal", - "When enabled, pressing the accelerator pedal will disengage openpilot.", + tr("Disengage On Accelerator Pedal"), + tr("When enabled, pressing the accelerator pedal will disengage openpilot."), "../assets/offroad/icon_disengage_on_accelerator.svg", }, #ifdef ENABLE_MAPS { "NavSettingTime24h", - "Show ETA in 24h format", - "Use 24h format instead of am/pm", + tr("Show ETA in 24h Format"), + tr("Use 24h format instead of am/pm"), "../assets/offroad/icon_metric.png", }, + { + "NavSettingLeftSide", + tr("Show Map on Left Side of UI"), + tr("Show map on left side when in split screen view."), + "../assets/offroad/icon_road.png", + }, #endif }; @@ -79,8 +81,8 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { if (params.getBool("DisableRadar_Allow")) { toggles.push_back({ "DisableRadar", - "openpilot Longitudinal Control", - "openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB!", + tr("openpilot Longitudinal Control"), + tr("openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB!"), "../assets/offroad/icon_speed_limit.png", }); } @@ -95,29 +97,29 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { setSpacing(50); - addItem(new LabelControl("Dongle ID", getDongleId().value_or("N/A"))); - addItem(new LabelControl("Serial", params.get("HardwareSerial").c_str())); + addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A")))); + addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str())); // offroad-only buttons - auto dcamBtn = new ButtonControl("Driver Camera", "PREVIEW", - "Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)"); + auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"), + tr("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)")); connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); }); addItem(dcamBtn); - auto resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", " "); + auto resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), ""); connect(resetCalibBtn, &ButtonControl::showDescription, this, &DevicePanel::updateCalibDescription); connect(resetCalibBtn, &ButtonControl::clicked, [&]() { - if (ConfirmationDialog::confirm("Are you sure you want to reset calibration?", this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), this)) { params.remove("CalibrationParams"); } }); addItem(resetCalibBtn); if (!params.getBool("Passive")) { - auto retrainingBtn = new ButtonControl("Review Training Guide", "REVIEW", "Review the rules, features, and limitations of openpilot"); + auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot")); connect(retrainingBtn, &ButtonControl::clicked, [=]() { - if (ConfirmationDialog::confirm("Are you sure you want to review the training guide?", this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), this)) { emit reviewTrainingGuide(); } }); @@ -125,7 +127,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { } if (Hardware::TICI()) { - auto regulatoryBtn = new ButtonControl("Regulatory", "VIEW", ""); + auto regulatoryBtn = new ButtonControl(tr("Regulatory"), tr("VIEW"), ""); connect(regulatoryBtn, &ButtonControl::clicked, [=]() { const std::string txt = util::read_file("../assets/offroad/fcc.html"); RichTextDialog::alert(QString::fromStdString(txt), this); @@ -133,6 +135,20 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { addItem(regulatoryBtn); } + auto translateBtn = new ButtonControl(tr("Change Language"), tr("CHANGE"), ""); + connect(translateBtn, &ButtonControl::clicked, [=]() { + QMap langs = getSupportedLanguages(); + QString currentLang = QString::fromStdString(Params().get("LanguageSetting")); + QString selection = MultiOptionDialog::getSelection(tr("Select a language"), langs.keys(), langs.key(currentLang), this); + if (!selection.isEmpty()) { + // put language setting, exit Qt UI, and trigger fast restart + Params().put("LanguageSetting", langs[selection].toStdString()); + qApp->exit(18); + watchdog_kick(0); + } + }); + addItem(translateBtn); + QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { for (auto btn : findChildren()) { btn->setEnabled(offroad); @@ -143,12 +159,12 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { QHBoxLayout *power_layout = new QHBoxLayout(); power_layout->setSpacing(30); - QPushButton *reboot_btn = new QPushButton("Reboot"); + QPushButton *reboot_btn = new QPushButton(tr("Reboot")); reboot_btn->setObjectName("reboot_btn"); power_layout->addWidget(reboot_btn); QObject::connect(reboot_btn, &QPushButton::clicked, this, &DevicePanel::reboot); - QPushButton *poweroff_btn = new QPushButton("Power Off"); + QPushButton *poweroff_btn = new QPushButton(tr("Power Off")); poweroff_btn->setObjectName("poweroff_btn"); power_layout->addWidget(poweroff_btn); QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff); @@ -168,8 +184,8 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { void DevicePanel::updateCalibDescription() { QString desc = - "openpilot requires the device to be mounted within 4° left or right and " - "within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required."; + tr("openpilot requires the device to be mounted within 4° left or right and " + "within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required."); std::string calib_bytes = Params().get("CalibrationParams"); if (!calib_bytes.empty()) { try { @@ -179,9 +195,9 @@ void DevicePanel::updateCalibDescription() { if (calib.getCalStatus() != 0) { double pitch = calib.getRpyCalib()[1] * (180 / M_PI); double yaw = calib.getRpyCalib()[2] * (180 / M_PI); - desc += QString(" Your device is pointed %1° %2 and %3° %4.") - .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "down" : "up", - QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "left" : "right"); + desc += tr(" Your device is pointed %1° %2 and %3° %4.") + .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? tr("down") : tr("up"), + QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? tr("left") : tr("right")); } } catch (kj::Exception) { qInfo() << "invalid CalibrationParams"; @@ -192,66 +208,82 @@ void DevicePanel::updateCalibDescription() { void DevicePanel::reboot() { if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), this)) { // Check engaged again in case it changed while the dialog was open if (!uiState()->engaged()) { Params().putBool("DoReboot", true); } } } else { - ConfirmationDialog::alert("Disengage to Reboot", this); + ConfirmationDialog::alert(tr("Disengage to Reboot"), this); } } void DevicePanel::poweroff() { if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), this)) { // Check engaged again in case it changed while the dialog was open if (!uiState()->engaged()) { Params().putBool("DoShutdown", true); } } } else { - ConfirmationDialog::alert("Disengage to Power Off", this); + ConfirmationDialog::alert(tr("Disengage to Power Off"), this); } } SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { - gitBranchLbl = new LabelControl("Git Branch"); - gitCommitLbl = new LabelControl("Git Commit"); - osVersionLbl = new LabelControl("OS Version"); - versionLbl = new LabelControl("Version", "", QString::fromStdString(params.get("ReleaseNotes")).trimmed()); - lastUpdateLbl = new LabelControl("Last Update Check", "", "The last time openpilot successfully checked for an update. The updater only runs while the car is off."); - updateBtn = new ButtonControl("Check for Update", ""); + gitBranchLbl = new LabelControl(tr("Git Branch")); + gitCommitLbl = new LabelControl(tr("Git Commit")); + osVersionLbl = new LabelControl(tr("OS Version")); + versionLbl = new LabelControl(tr("Version"), "", QString::fromStdString(params.get("ReleaseNotes")).trimmed()); + lastUpdateLbl = new LabelControl(tr("Last Update Check"), "", tr("The last time openpilot successfully checked for an update. The updater only runs while the car is off.")); + updateBtn = new ButtonControl(tr("Check for Update"), ""); connect(updateBtn, &ButtonControl::clicked, [=]() { if (params.getBool("IsOffroad")) { fs_watch->addPath(QString::fromStdString(params.getParamPath("LastUpdateTime"))); fs_watch->addPath(QString::fromStdString(params.getParamPath("UpdateFailedCount"))); - updateBtn->setText("CHECKING"); + updateBtn->setText(tr("CHECKING")); updateBtn->setEnabled(false); } std::system("pkill -1 -f selfdrive.updated"); }); + connect(uiState(), &UIState::offroadTransition, updateBtn, &QPushButton::setEnabled); + + branchSwitcherBtn = new ButtonControl(tr("Switch Branch"), tr("ENTER"), tr("The new branch will be pulled the next time the updater runs.")); + connect(branchSwitcherBtn, &ButtonControl::clicked, [=]() { + QString branch = InputDialog::getText(tr("Enter branch name"), this, tr("The new branch will be pulled the next time the updater runs."), + false, -1, QString::fromStdString(params.get("SwitchToBranch"))); + if (branch.isEmpty()) { + params.remove("SwitchToBranch"); + } else { + params.put("SwitchToBranch", branch.toStdString()); + } + std::system("pkill -1 -f selfdrive.updated"); + }); + connect(uiState(), &UIState::offroadTransition, branchSwitcherBtn, &QPushButton::setEnabled); - - auto uninstallBtn = new ButtonControl("Uninstall " + getBrand(), "UNINSTALL"); + auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL")); connect(uninstallBtn, &ButtonControl::clicked, [&]() { - if (ConfirmationDialog::confirm("Are you sure you want to uninstall?", this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), this)) { params.putBool("DoUninstall", true); } }); connect(uiState(), &UIState::offroadTransition, uninstallBtn, &QPushButton::setEnabled); - QWidget *widgets[] = {versionLbl, lastUpdateLbl, updateBtn, gitBranchLbl, gitCommitLbl, osVersionLbl, uninstallBtn}; + QWidget *widgets[] = {versionLbl, lastUpdateLbl, updateBtn, branchSwitcherBtn, gitBranchLbl, gitCommitLbl, osVersionLbl, uninstallBtn}; for (QWidget* w : widgets) { + if (w == branchSwitcherBtn && params.getBool("IsTestedBranch")) { + continue; + } addItem(w); } fs_watch = new QFileSystemWatcher(this); QObject::connect(fs_watch, &QFileSystemWatcher::fileChanged, [=](const QString path) { if (path.contains("UpdateFailedCount") && std::atoi(params.get("UpdateFailedCount").c_str()) > 0) { - lastUpdateLbl->setText("failed to fetch update"); - updateBtn->setText("CHECK"); + lastUpdateLbl->setText(tr("failed to fetch update")); + updateBtn->setText(tr("CHECK")); updateBtn->setEnabled(true); } else if (path.contains("LastUpdateTime")) { updateLabels(); @@ -272,17 +304,13 @@ void SoftwarePanel::updateLabels() { versionLbl->setText(getBrandVersion()); lastUpdateLbl->setText(lastUpdate); - updateBtn->setText("CHECK"); + updateBtn->setText(tr("CHECK")); updateBtn->setEnabled(true); gitBranchLbl->setText(QString::fromStdString(params.get("GitBranch"))); gitCommitLbl->setText(QString::fromStdString(params.get("GitCommit")).left(10)); osVersionLbl->setText(QString::fromStdString(Hardware::get_os_version()).trimmed()); } -QWidget *network_panel(QWidget *parent) { - return new Networking(parent); -} - void SettingsWindow::showEvent(QShowEvent *event) { panel_widget->setCurrentIndex(0); nav_btns->buttons()[0]->setChecked(true); @@ -301,7 +329,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { )"); // close button - QPushButton *close_btn = new QPushButton("×"); + QPushButton *close_btn = new QPushButton(tr("×")); close_btn->setStyleSheet(R"( QPushButton { font-size: 140px; @@ -327,15 +355,15 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView); QList> panels = { - {"Device", device}, - {"Network", network_panel(this)}, - {"Toggles", new TogglesPanel(this)}, - {"Software", new SoftwarePanel(this)}, + {tr("Device"), device}, + {tr("Network"), new Networking(this)}, + {tr("Toggles"), new TogglesPanel(this)}, + {tr("Software"), new SoftwarePanel(this)}, }; #ifdef ENABLE_MAPS auto map_panel = new MapPanel(this); - panels.push_back({"Navigation", map_panel}); + panels.push_back({tr("Navigation"), map_panel}); QObject::connect(map_panel, &MapPanel::closeSettings, this, &SettingsWindow::closeSettings); #endif @@ -367,7 +395,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { nav_btns->addButton(btn); sidebar_layout->addWidget(btn, 0, Qt::AlignRight); - const int lr_margin = name != "Network" ? 50 : 0; // Network panel handles its own margins + const int lr_margin = name != tr("Network") ? 50 : 0; // Network panel handles its own margins panel->setContentsMargins(lr_margin, 25, lr_margin, 25); ScrollView *panel_frame = new ScrollView(panel, this); diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 160f10f99..45efe255c 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -71,6 +71,7 @@ private: LabelControl *versionLbl; LabelControl *lastUpdateLbl; ButtonControl *updateBtn; + ButtonControl *branchSwitcherBtn; Params params; QFileSystemWatcher *fs_watch; diff --git a/selfdrive/ui/qt/offroad/wifiManager.cc b/selfdrive/ui/qt/offroad/wifiManager.cc index 124138eea..fbb64b972 100644 --- a/selfdrive/ui/qt/offroad/wifiManager.cc +++ b/selfdrive/ui/qt/offroad/wifiManager.cc @@ -303,7 +303,7 @@ void WifiManager::initConnections() { const Connection settings = getConnectionSettings(path); if (settings.value("connection").value("type") == "802-11-wireless") { knownConnections[path] = settings.value("802-11-wireless").value("ssid").toString(); - } else if (path.path() != "/") { + } else if (settings.value("connection").value("id") == "lte") { lteConnectionPath = path; } } diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 38541ef2d..4414e602e 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -52,6 +52,12 @@ void OnroadWindow::updateState(const UIState &s) { alerts->updateAlert(alert, bgColor); } + if (s.scene.map_on_left) { + split->setDirection(QBoxLayout::LeftToRight); + } else { + split->setDirection(QBoxLayout::RightToLeft); + } + nvg->updateState(s); if (bg != bgColor) { @@ -80,7 +86,7 @@ void OnroadWindow::offroadTransition(bool offroad) { QObject::connect(uiState(), &UIState::offroadTransition, m, &MapWindow::offroadTransition); m->setFixedWidth(topWidget(this)->width() / 2); - split->addWidget(m, 0, Qt::AlignRight); + split->insertWidget(0, m); // Make map visible after adding to split m->offroadTransition(offroad); @@ -146,18 +152,18 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { p.setPen(QColor(0xff, 0xff, 0xff)); p.setRenderHint(QPainter::TextAntialiasing); if (alert.size == cereal::ControlsState::AlertSize::SMALL) { - configFont(p, "Open Sans", 74, "SemiBold"); + configFont(p, "Inter", 74, "SemiBold"); p.drawText(r, Qt::AlignCenter, alert.text1); } else if (alert.size == cereal::ControlsState::AlertSize::MID) { - configFont(p, "Open Sans", 88, "Bold"); + configFont(p, "Inter", 88, "Bold"); p.drawText(QRect(0, c.y() - 125, width(), 150), Qt::AlignHCenter | Qt::AlignTop, alert.text1); - configFont(p, "Open Sans", 66, "Regular"); + configFont(p, "Inter", 66, "Regular"); p.drawText(QRect(0, c.y() + 21, width(), 90), Qt::AlignHCenter, alert.text2); } else if (alert.size == cereal::ControlsState::AlertSize::FULL) { bool l = alert.text1.length() > 15; - configFont(p, "Open Sans", l ? 132 : 177, "Bold"); + configFont(p, "Inter", l ? 132 : 177, "Bold"); p.drawText(QRect(0, r.y() + (l ? 240 : 270), width(), 600), Qt::AlignHCenter | Qt::TextWordWrap, alert.text1); - configFont(p, "Open Sans", 88, "Regular"); + configFont(p, "Inter", 88, "Regular"); p.drawText(QRect(0, r.height() - (l ? 361 : 420), width(), 300), Qt::AlignHCenter | Qt::TextWordWrap, alert.text2); } } @@ -172,27 +178,44 @@ NvgWindow::NvgWindow(VisionStreamType type, QWidget* parent) : fps_filter(UI_FRE void NvgWindow::updateState(const UIState &s) { const int SET_SPEED_NA = 255; const SubMaster &sm = *(s.sm); + + const bool cs_alive = sm.alive("controlsState"); + const bool nav_alive = sm.alive("navInstruction") && sm["navInstruction"].getValid(); + const auto cs = sm["controlsState"].getControlsState(); - float set_speed = cs.getVCruise(); + // 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; } - float cur_speed = std::max(0.0, sm["carState"].getCarState().getVEgo() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH)); + + // Handle older routes where vEgoCluster is not set + float v_ego; + if (sm["carState"].getCarState().getVEgoCluster() == 0.0 && !v_ego_cluster_seen) { + v_ego = sm["carState"].getCarState().getVEgo(); + } else { + v_ego = sm["carState"].getCarState().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; auto speed_limit_sign = sm["navInstruction"].getNavInstruction().getSpeedLimitSign(); - float speed_limit = sm["navInstruction"].getValid() ? sm["navInstruction"].getNavInstruction().getSpeedLimit() : 0.0; + float speed_limit = nav_alive ? sm["navInstruction"].getNavInstruction().getSpeedLimit() : 0.0; speed_limit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); setProperty("speedLimit", speed_limit); - setProperty("has_us_speed_limit", speed_limit > 1 && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD); - setProperty("has_eu_speed_limit", speed_limit > 1 && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA); + 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); 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 ? "km/h" : "mph"); + setProperty("speedUnit", s.scene.is_metric ? tr("km/h") : tr("mph")); setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); setProperty("status", s.status); @@ -200,6 +223,13 @@ void NvgWindow::updateState(const UIState &s) { if (sm.frame % (UI_FREQ / 2) == 0) { setProperty("engageable", cs.getEngageable() || cs.getEnabled()); setProperty("dmActive", sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode()); + setProperty("rightHandDM", sm["driverMonitoringState"].getDriverMonitoringState().getIsRHD()); + } + + if (s.scene.calibration_valid) { + CameraViewWidget::updateCalibration(s.scene.view_from_calib); + } else { + CameraViewWidget::updateCalibration(DEFAULT_CALIBRATION); } } @@ -212,15 +242,15 @@ void NvgWindow::drawHud(QPainter &p) { bg.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0)); p.fillRect(0, 0, width(), header_h, bg); - QString speedLimitStr = QString::number(std::nearbyint(speedLimit)); + QString speedLimitStr = (speedLimit > 1) ? QString::number(std::nearbyint(speedLimit)) : "–"; QString speedStr = QString::number(std::nearbyint(speed)); QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed)) : "–"; // Draw outer box + border to contain set speed and speed limit int default_rect_width = 172; int rect_width = default_rect_width; + if (is_metric || has_eu_speed_limit) rect_width = 200; if (has_us_speed_limit && speedLimitStr.size() >= 3) rect_width = 223; - else if (has_eu_speed_limit) rect_width = 200; int rect_height = 204; if (has_us_speed_limit) rect_height = 402; @@ -234,26 +264,6 @@ void NvgWindow::drawHud(QPainter &p) { p.setBrush(blackColor(166)); drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius); - // Draw set speed - if (is_cruise_set) { - if (speedLimit > 0 && status != STATUS_DISENGAGED && status != STATUS_OVERRIDE) { - p.setPen(interpColor( - setSpeed, - {speedLimit + 5, speedLimit + 15, speedLimit + 25}, - {whiteColor(), QColor(0xff, 0x95, 0x00, 0xff), QColor(0xff, 0x00, 0x00, 0xff)} - )); - } else { - p.setPen(whiteColor()); - } - } else { - p.setPen(QColor(0x72, 0x72, 0x72, 0xff)); - } - configFont(p, "Open Sans", 90, "Bold"); - QRect speed_rect = getTextRect(p, Qt::AlignCenter, setSpeedStr); - speed_rect.moveCenter({set_speed_rect.center().x(), 0}); - speed_rect.moveTop(set_speed_rect.top() + 8); - p.drawText(speed_rect, Qt::AlignCenter, setSpeedStr); - // Draw MAX if (is_cruise_set) { if (status == STATUS_DISENGAGED) { @@ -272,16 +282,38 @@ void NvgWindow::drawHud(QPainter &p) { } else { p.setPen(QColor(0xa6, 0xa6, 0xa6, 0xff)); } - configFont(p, "Open Sans", 40, "SemiBold"); - QRect max_rect = getTextRect(p, Qt::AlignCenter, "MAX"); + configFont(p, "Inter", 40, "SemiBold"); + QRect max_rect = getTextRect(p, Qt::AlignCenter, tr("MAX")); max_rect.moveCenter({set_speed_rect.center().x(), 0}); - max_rect.moveTop(set_speed_rect.top() + 123); - p.drawText(max_rect, Qt::AlignCenter, "MAX"); + max_rect.moveTop(set_speed_rect.top() + 27); + p.drawText(max_rect, Qt::AlignCenter, tr("MAX")); + + // Draw set speed + if (is_cruise_set) { + if (speedLimit > 0 && status != STATUS_DISENGAGED && status != STATUS_OVERRIDE) { + p.setPen(interpColor( + setSpeed, + {speedLimit + 5, speedLimit + 15, speedLimit + 25}, + {whiteColor(), QColor(0xff, 0x95, 0x00, 0xff), QColor(0xff, 0x00, 0x00, 0xff)} + )); + } else { + p.setPen(whiteColor()); + } + } else { + p.setPen(QColor(0x72, 0x72, 0x72, 0xff)); + } + configFont(p, "Inter", 90, "Bold"); + QRect speed_rect = getTextRect(p, Qt::AlignCenter, setSpeedStr); + speed_rect.moveCenter({set_speed_rect.center().x(), 0}); + speed_rect.moveTop(set_speed_rect.top() + 77); + p.drawText(speed_rect, Qt::AlignCenter, setSpeedStr); + + // US/Canada (MUTCD style) sign if (has_us_speed_limit) { const int border_width = 6; - const int sign_width = (speedLimitStr.size() >= 3) ? 199 : 148; + const int sign_width = rect_width - 24; const int sign_height = 186; // White outer square @@ -297,23 +329,23 @@ void NvgWindow::drawHud(QPainter &p) { p.drawRoundedRect(sign_rect, 16, 16); // "SPEED" - configFont(p, "Open Sans", 28, "SemiBold"); - QRect text_speed_rect = getTextRect(p, Qt::AlignCenter, "SPEED"); + configFont(p, "Inter", 28, "SemiBold"); + QRect text_speed_rect = getTextRect(p, Qt::AlignCenter, tr("SPEED")); text_speed_rect.moveCenter({sign_rect.center().x(), 0}); - text_speed_rect.moveTop(sign_rect_outer.top() + 20); - p.drawText(text_speed_rect, Qt::AlignCenter, "SPEED"); + text_speed_rect.moveTop(sign_rect_outer.top() + 22); + p.drawText(text_speed_rect, Qt::AlignCenter, tr("SPEED")); // "LIMIT" - QRect text_limit_rect = getTextRect(p, Qt::AlignCenter, "LIMIT"); + QRect text_limit_rect = getTextRect(p, Qt::AlignCenter, tr("LIMIT")); text_limit_rect.moveCenter({sign_rect.center().x(), 0}); - text_limit_rect.moveTop(sign_rect_outer.top() + 48); - p.drawText(text_limit_rect, Qt::AlignCenter, "LIMIT"); + text_limit_rect.moveTop(sign_rect_outer.top() + 51); + p.drawText(text_limit_rect, Qt::AlignCenter, tr("LIMIT")); // Speed limit value - configFont(p, "Open Sans", 70, "Bold"); + configFont(p, "Inter", 70, "Bold"); QRect speed_limit_rect = getTextRect(p, Qt::AlignCenter, speedLimitStr); speed_limit_rect.moveCenter({sign_rect.center().x(), 0}); - speed_limit_rect.moveTop(sign_rect.top() + 70); + speed_limit_rect.moveTop(sign_rect_outer.top() + 85); p.drawText(speed_limit_rect, Qt::AlignCenter, speedLimitStr); } @@ -334,8 +366,8 @@ void NvgWindow::drawHud(QPainter &p) { p.drawEllipse(center, inner_radius_2, inner_radius_2); // Speed limit value - int font_size = (speedLimitStr.size() >= 3) ? 62 : 70; - configFont(p, "Open Sans", font_size, "Bold"); + int font_size = (speedLimitStr.size() >= 3) ? 60 : 70; + configFont(p, "Inter", font_size, "Bold"); QRect speed_limit_rect = getTextRect(p, Qt::AlignCenter, speedLimitStr); speed_limit_rect.moveCenter(center); p.setPen(blackColor()); @@ -343,9 +375,9 @@ void NvgWindow::drawHud(QPainter &p) { } // current speed - configFont(p, "Open Sans", 176, "Bold"); + configFont(p, "Inter", 176, "Bold"); drawText(p, rect().center().x(), 210, speedStr); - configFont(p, "Open Sans", 66, "Regular"); + configFont(p, "Inter", 66, "Regular"); drawText(p, rect().center().x(), 290, speedUnit, 200); // engage-ability icon @@ -356,7 +388,8 @@ void NvgWindow::drawHud(QPainter &p) { // dm icon if (!hideDM) { - drawIcon(p, radius / 2 + (bdr_s * 2), rect().bottom() - footer_h / 2, + int dm_icon_x = rightHandDM ? rect().right() - radius / 2 - (bdr_s * 2) : radius / 2 + (bdr_s * 2); + drawIcon(p, dm_icon_x, rect().bottom() - footer_h / 2, dm_img, blackColor(70), dmActive ? 1.0 : 0.2); } p.restore(); @@ -390,23 +423,20 @@ void NvgWindow::initializeGL() { setBackgroundColor(bg_colors[STATUS_DISENGAGED]); } -void NvgWindow::updateFrameMat(int w, int h) { - CameraViewWidget::updateFrameMat(w, h); - +void NvgWindow::updateFrameMat() { + CameraViewWidget::updateFrameMat(); UIState *s = uiState(); + int w = width(), h = height(); + s->fb_w = w; s->fb_h = h; - auto intrinsic_matrix = s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; - float zoom = ZOOM / intrinsic_matrix.v[0]; - if (s->wide_camera) { - zoom *= 0.5; - } + // Apply transformation such that video pixel coordinates match video // 1) Put (0, 0) in the middle of the video // 2) Apply same scaling as video // 3) Put (0, 0) in top left corner of video s->car_space_transform.reset(); - s->car_space_transform.translate(w / 2, h / 2 + y_offset) + s->car_space_transform.translate(w / 2 - x_offset, h / 2 - y_offset) .scale(zoom, zoom) .translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]); } @@ -418,13 +448,13 @@ void NvgWindow::drawLaneLines(QPainter &painter, const UIState *s) { // lanelines for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); - painter.drawPolygon(scene.lane_line_vertices[i].v, scene.lane_line_vertices[i].cnt); + painter.drawPolygon(scene.lane_line_vertices[i]); } // road edges for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) { painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp(1.0 - scene.road_edge_stds[i], 0.0, 1.0))); - painter.drawPolygon(scene.road_edge_vertices[i].v, scene.road_edge_vertices[i].cnt); + painter.drawPolygon(scene.road_edge_vertices[i]); } // paint path @@ -443,7 +473,7 @@ void NvgWindow::drawLaneLines(QPainter &painter, const UIState *s) { bg.setColorAt(0.75 / 1.5, QColor::fromHslF(curve_hue / 360., 1.0, 0.68, 0.35)); bg.setColorAt(1.0, QColor::fromHslF(curve_hue / 360., 1.0, 0.68, 0.0)); painter.setBrush(bg); - painter.drawPolygon(scene.track_vertices.v, scene.track_vertices.cnt); + painter.drawPolygon(scene.track_vertices); painter.restore(); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index e58d7a797..25920ccc6 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -34,10 +34,12 @@ class NvgWindow : public CameraViewWidget { 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 engageable MEMBER engageable); Q_PROPERTY(bool dmActive MEMBER dmActive); Q_PROPERTY(bool hideDM MEMBER hideDM); + Q_PROPERTY(bool rightHandDM MEMBER rightHandDM); Q_PROPERTY(int status MEMBER status); public: @@ -57,18 +59,21 @@ private: float setSpeed; float speedLimit; bool is_cruise_set = false; + bool is_metric = false; bool engageable = false; bool dmActive = false; bool hideDM = false; + bool rightHandDM = false; bool has_us_speed_limit = false; bool has_eu_speed_limit = false; + bool v_ego_cluster_seen = false; int status = STATUS_DISENGAGED; protected: void paintGL() override; void initializeGL() override; void showEvent(QShowEvent *event) override; - void updateFrameMat(int w, int h) override; + void updateFrameMat() override; void drawLaneLines(QPainter &painter, const UIState *s); void drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd); void drawHud(QPainter &p); diff --git a/selfdrive/ui/qt/setup/reset.cc b/selfdrive/ui/qt/setup/reset.cc index 9ffcf7f6c..582217c1d 100644 --- a/selfdrive/ui/qt/setup/reset.cc +++ b/selfdrive/ui/qt/setup/reset.cc @@ -26,16 +26,16 @@ void Reset::doReset() { if (rm == 0 || fmt == 0) { std::system("sudo reboot"); } - body->setText("Reset failed. Reboot to try again."); + body->setText(tr("Reset failed. Reboot to try again.")); rebootBtn->show(); } void Reset::confirm() { - const QString confirm_txt = "Are you sure you want to reset your device?"; + const QString confirm_txt = tr("Are you sure you want to reset your device?"); if (body->text() != confirm_txt) { body->setText(confirm_txt); } else { - body->setText("Resetting device..."); + body->setText(tr("Resetting device...")); rejectBtn->hide(); rebootBtn->hide(); confirmBtn->hide(); @@ -50,13 +50,13 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) { main_layout->setContentsMargins(45, 220, 45, 45); main_layout->setSpacing(0); - QLabel *title = new QLabel("System Reset"); + QLabel *title = new QLabel(tr("System Reset")); title->setStyleSheet("font-size: 90px; font-weight: 600;"); main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); main_layout->addSpacing(60); - body = new QLabel("System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot."); + body = new QLabel(tr("System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot.")); body->setWordWrap(true); body->setStyleSheet("font-size: 80px; font-weight: light;"); main_layout->addWidget(body, 1, Qt::AlignTop | Qt::AlignLeft); @@ -65,11 +65,11 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) { main_layout->addLayout(blayout); blayout->setSpacing(50); - rejectBtn = new QPushButton("Cancel"); + rejectBtn = new QPushButton(tr("Cancel")); blayout->addWidget(rejectBtn); QObject::connect(rejectBtn, &QPushButton::clicked, QCoreApplication::instance(), &QCoreApplication::quit); - rebootBtn = new QPushButton("Reboot"); + rebootBtn = new QPushButton(tr("Reboot")); blayout->addWidget(rebootBtn); #ifdef __aarch64__ QObject::connect(rebootBtn, &QPushButton::clicked, [=]{ @@ -77,7 +77,7 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) { }); #endif - confirmBtn = new QPushButton("Confirm"); + confirmBtn = new QPushButton(tr("Confirm")); confirmBtn->setStyleSheet("background-color: #465BEA;"); blayout->addWidget(confirmBtn); QObject::connect(confirmBtn, &QPushButton::clicked, this, &Reset::confirm); @@ -85,7 +85,7 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) { rejectBtn->setVisible(!recover); rebootBtn->setVisible(recover); if (recover) { - body->setText("Unable to mount data partition. Press confirm to reset your device."); + body->setText(tr("Unable to mount data partition. Press confirm to reset your device.")); } setStyleSheet(R"( diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index 10a2d05f3..69dafcf74 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -70,13 +70,13 @@ QWidget * Setup::low_voltage() { inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft); inner_layout->addSpacing(80); - QLabel *title = new QLabel("WARNING: Low Voltage"); + QLabel *title = new QLabel(tr("WARNING: Low Voltage")); title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;"); inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); inner_layout->addSpacing(25); - QLabel *body = new QLabel("Power your device in a car with a harness or proceed at your own risk."); + QLabel *body = new QLabel(tr("Power your device in a car with a harness or proceed at your own risk.")); body->setWordWrap(true); body->setAlignment(Qt::AlignTop | Qt::AlignLeft); body->setStyleSheet("font-size: 80px; font-weight: 300;"); @@ -89,14 +89,14 @@ QWidget * Setup::low_voltage() { blayout->setSpacing(50); main_layout->addLayout(blayout, 0); - QPushButton *poweroff = new QPushButton("Power off"); + QPushButton *poweroff = new QPushButton(tr("Power off")); poweroff->setObjectName("navBtn"); blayout->addWidget(poweroff); QObject::connect(poweroff, &QPushButton::clicked, this, [=]() { Hardware::poweroff(); }); - QPushButton *cont = new QPushButton("Continue"); + QPushButton *cont = new QPushButton(tr("Continue")); cont->setObjectName("navBtn"); blayout->addWidget(cont); QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage); @@ -114,12 +114,12 @@ QWidget * Setup::getting_started() { vlayout->setContentsMargins(165, 280, 100, 0); main_layout->addLayout(vlayout); - QLabel *title = new QLabel("Getting Started"); + QLabel *title = new QLabel(tr("Getting Started")); title->setStyleSheet("font-size: 90px; font-weight: 500;"); vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); vlayout->addSpacing(90); - QLabel *desc = new QLabel("Before we get on the road, let’s finish installation and cover some details."); + QLabel *desc = new QLabel(tr("Before we get on the road, let’s finish installation and cover some details.")); desc->setWordWrap(true); desc->setStyleSheet("font-size: 80px; font-weight: 300;"); vlayout->addWidget(desc, 0, Qt::AlignTop | Qt::AlignLeft); @@ -144,7 +144,7 @@ QWidget * Setup::network_setup() { main_layout->setContentsMargins(55, 50, 55, 50); // title - QLabel *title = new QLabel("Connect to Wi-Fi"); + QLabel *title = new QLabel(tr("Connect to Wi-Fi")); title->setStyleSheet("font-size: 90px; font-weight: 500;"); main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); @@ -162,7 +162,7 @@ QWidget * Setup::network_setup() { main_layout->addLayout(blayout); blayout->setSpacing(50); - QPushButton *back = new QPushButton("Back"); + QPushButton *back = new QPushButton(tr("Back")); back->setObjectName("navBtn"); QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); blayout->addWidget(back); @@ -179,9 +179,9 @@ QWidget * Setup::network_setup() { cont->setEnabled(success); if (success) { const bool cell = networking->wifi->currentNetworkType() == NetworkType::CELL; - cont->setText(cell ? "Continue without Wi-Fi" : "Continue"); + cont->setText(cell ? tr("Continue without Wi-Fi") : tr("Continue")); } else { - cont->setText("Waiting for internet"); + cont->setText(tr("Waiting for internet")); } repaint(); }); @@ -235,7 +235,7 @@ QWidget * Setup::software_selection() { main_layout->setSpacing(0); // title - QLabel *title = new QLabel("Choose Software to Install"); + QLabel *title = new QLabel(tr("Choose Software to Install")); title->setStyleSheet("font-size: 90px; font-weight: 500;"); main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); @@ -245,12 +245,12 @@ QWidget * Setup::software_selection() { QButtonGroup *group = new QButtonGroup(widget); group->setExclusive(true); - QWidget *dashcam = radio_button("Dashcam", group); + QWidget *dashcam = radio_button(tr("Dashcam"), group); main_layout->addWidget(dashcam); main_layout->addSpacing(30); - QWidget *custom = radio_button("Custom Software", group); + QWidget *custom = radio_button(tr("Custom Software"), group); main_layout->addWidget(custom); main_layout->addStretch(); @@ -260,12 +260,12 @@ QWidget * Setup::software_selection() { main_layout->addLayout(blayout); blayout->setSpacing(50); - QPushButton *back = new QPushButton("Back"); + QPushButton *back = new QPushButton(tr("Back")); back->setObjectName("navBtn"); QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); blayout->addWidget(back); - QPushButton *cont = new QPushButton("Continue"); + QPushButton *cont = new QPushButton(tr("Continue")); cont->setObjectName("navBtn"); cont->setEnabled(false); cont->setProperty("primary", true); @@ -278,7 +278,7 @@ QWidget * Setup::software_selection() { }); QString url = DASHCAM_URL; if (group->checkedButton() != dashcam) { - url = InputDialog::getText("Enter URL", this, "for Custom Software"); + url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); } if (!url.isEmpty()) { QTimer::singleShot(1000, this, [=]() { @@ -300,7 +300,7 @@ QWidget * Setup::software_selection() { QWidget * Setup::downloading() { QWidget *widget = new QWidget(); QVBoxLayout *main_layout = new QVBoxLayout(widget); - QLabel *txt = new QLabel("Downloading..."); + QLabel *txt = new QLabel(tr("Downloading...")); txt->setStyleSheet("font-size: 90px; font-weight: 500;"); main_layout->addWidget(txt, 0, Qt::AlignCenter); return widget; @@ -312,13 +312,13 @@ QWidget * Setup::download_failed() { main_layout->setContentsMargins(55, 225, 55, 55); main_layout->setSpacing(0); - QLabel *title = new QLabel("Download Failed"); + QLabel *title = new QLabel(tr("Download Failed")); title->setStyleSheet("font-size: 90px; font-weight: 500;"); main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); main_layout->addSpacing(67); - QLabel *body = new QLabel("Ensure the entered URL is valid, and the device’s internet connection is good."); + QLabel *body = new QLabel(tr("Ensure the entered URL is valid, and the device’s internet connection is good.")); body->setWordWrap(true); body->setAlignment(Qt::AlignTop | Qt::AlignLeft); body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;"); @@ -331,14 +331,14 @@ QWidget * Setup::download_failed() { blayout->setSpacing(50); main_layout->addLayout(blayout, 0); - QPushButton *reboot = new QPushButton("Reboot device"); + QPushButton *reboot = new QPushButton(tr("Reboot device")); reboot->setObjectName("navBtn"); blayout->addWidget(reboot); QObject::connect(reboot, &QPushButton::clicked, this, [=]() { Hardware::reboot(); }); - QPushButton *restart = new QPushButton("Start over"); + QPushButton *restart = new QPushButton(tr("Start over")); restart->setObjectName("navBtn"); restart->setProperty("primary", true); blayout->addWidget(restart); diff --git a/selfdrive/ui/qt/setup/updater.cc b/selfdrive/ui/qt/setup/updater.cc index 6e8189f4b..fd7148c53 100644 --- a/selfdrive/ui/qt/setup/updater.cc +++ b/selfdrive/ui/qt/setup/updater.cc @@ -20,13 +20,13 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid QVBoxLayout *layout = new QVBoxLayout(prompt); layout->setContentsMargins(100, 250, 100, 100); - QLabel *title = new QLabel("Update Required"); + QLabel *title = new QLabel(tr("Update Required")); title->setStyleSheet("font-size: 80px; font-weight: bold;"); layout->addWidget(title); layout->addSpacing(75); - QLabel *desc = new QLabel("An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB."); + QLabel *desc = new QLabel(tr("An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB.")); desc->setWordWrap(true); desc->setStyleSheet("font-size: 65px;"); layout->addWidget(desc); @@ -37,14 +37,14 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid hlayout->setSpacing(30); layout->addLayout(hlayout); - QPushButton *connect = new QPushButton("Connect to Wi-Fi"); + QPushButton *connect = new QPushButton(tr("Connect to Wi-Fi")); connect->setObjectName("navBtn"); QObject::connect(connect, &QPushButton::clicked, [=]() { setCurrentWidget(wifi); }); hlayout->addWidget(connect); - QPushButton *install = new QPushButton("Install"); + QPushButton *install = new QPushButton(tr("Install")); install->setObjectName("navBtn"); install->setStyleSheet("background-color: #465BEA;"); QObject::connect(install, &QPushButton::clicked, this, &Updater::installUpdate); @@ -61,7 +61,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid networking->setStyleSheet("Networking { background-color: #292929; border-radius: 13px; }"); layout->addWidget(networking, 1); - QPushButton *back = new QPushButton("Back"); + QPushButton *back = new QPushButton(tr("Back")); back->setObjectName("navBtn"); back->setStyleSheet("padding-left: 60px; padding-right: 60px;"); QObject::connect(back, &QPushButton::clicked, [=]() { @@ -77,7 +77,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid layout->setContentsMargins(150, 330, 150, 150); layout->setSpacing(0); - text = new QLabel("Loading..."); + text = new QLabel(tr("Loading...")); text->setStyleSheet("font-size: 90px; font-weight: 600;"); layout->addWidget(text, 0, Qt::AlignTop); @@ -91,7 +91,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid layout->addStretch(); - reboot = new QPushButton("Reboot"); + reboot = new QPushButton(tr("Reboot")); reboot->setObjectName("navBtn"); reboot->setStyleSheet("padding-left: 60px; padding-right: 60px;"); QObject::connect(reboot, &QPushButton::clicked, [=]() { @@ -161,7 +161,7 @@ void Updater::updateFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode == 0) { Hardware::reboot(); } else { - text->setText("Update failed"); + text->setText(tr("Update failed")); reboot->show(); } } diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 312d8d8a5..accab67bf 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -4,13 +4,13 @@ #include "selfdrive/ui/qt/util.h" -void Sidebar::drawMetric(QPainter &p, const QString &label, QColor c, int y) { - const QRect rect = {30, y, 240, label.contains("\n") ? 124 : 100}; +void Sidebar::drawMetric(QPainter &p, const QPair &label, QColor c, int y) { + const QRect rect = {30, y, 240, 126}; p.setPen(Qt::NoPen); p.setBrush(QBrush(c)); - p.setClipRect(rect.x() + 6, rect.y(), 18, rect.height(), Qt::ClipOperation::ReplaceClip); - p.drawRoundedRect(QRect(rect.x() + 6, rect.y() + 6, 100, rect.height() - 12), 10, 10); + p.setClipRect(rect.x() + 4, rect.y(), 18, rect.height(), Qt::ClipOperation::ReplaceClip); + p.drawRoundedRect(QRect(rect.x() + 4, rect.y() + 4, 100, 118), 18, 18); p.setClipping(false); QPen pen = QPen(QColor(0xff, 0xff, 0xff, 0x55)); @@ -20,9 +20,16 @@ void Sidebar::drawMetric(QPainter &p, const QString &label, QColor c, int y) { p.drawRoundedRect(rect, 20, 20); p.setPen(QColor(0xff, 0xff, 0xff)); - configFont(p, "Open Sans", 35, "Bold"); - const QRect r = QRect(rect.x() + 30, rect.y(), rect.width() - 40, rect.height()); - p.drawText(r, Qt::AlignCenter, label); + configFont(p, "Inter", 35, "SemiBold"); + + QRect label_rect = getTextRect(p, Qt::AlignCenter, label.first); + label_rect.setWidth(218); + label_rect.moveLeft(rect.left() + 22); + label_rect.moveTop(rect.top() + 19); + p.drawText(label_rect, Qt::AlignCenter, label.first); + + label_rect.moveTop(rect.top() + 65); + p.drawText(label_rect, Qt::AlignCenter, label.second); } Sidebar::Sidebar(QWidget *parent) : QFrame(parent) { @@ -57,26 +64,26 @@ void Sidebar::updateState(const UIState &s) { ItemStatus connectStatus; auto last_ping = deviceState.getLastAthenaPingTime(); if (last_ping == 0) { - connectStatus = ItemStatus{"CONNECT\nOFFLINE", warning_color}; + connectStatus = ItemStatus{{tr("CONNECT"), tr("OFFLINE")}, warning_color}; } else { - connectStatus = nanos_since_boot() - last_ping < 80e9 ? ItemStatus{"CONNECT\nONLINE", good_color} : ItemStatus{"CONNECT\nERROR", danger_color}; + connectStatus = nanos_since_boot() - last_ping < 80e9 ? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, good_color} : ItemStatus{{tr("CONNECT"), tr("ERROR")}, danger_color}; } setProperty("connectStatus", QVariant::fromValue(connectStatus)); - ItemStatus tempStatus = {"TEMP\nHIGH", danger_color}; + ItemStatus tempStatus = {{tr("TEMP"), tr("HIGH")}, danger_color}; auto ts = deviceState.getThermalStatus(); if (ts == cereal::DeviceState::ThermalStatus::GREEN) { - tempStatus = {"TEMP\nGOOD", good_color}; + tempStatus = {{tr("TEMP"), tr("GOOD")}, good_color}; } else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) { - tempStatus = {"TEMP\nOK", warning_color}; + tempStatus = {{tr("TEMP"), tr("OK")}, warning_color}; } setProperty("tempStatus", QVariant::fromValue(tempStatus)); - ItemStatus pandaStatus = {"VEHICLE\nONLINE", good_color}; + ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, good_color}; if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { - pandaStatus = {"NO\nPANDA", danger_color}; + pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color}; } else if (s.scene.started && !sm["liveLocationKalman"].getLiveLocationKalman().getGpsOK()) { - pandaStatus = {"GPS\nSEARCH", warning_color}; + pandaStatus = {{tr("GPS"), tr("SEARCH")}, warning_color}; } setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); } @@ -103,7 +110,7 @@ void Sidebar::paintEvent(QPaintEvent *event) { x += 37; } - configFont(p, "Open Sans", 35, "Regular"); + configFont(p, "Inter", 35, "Regular"); p.setPen(QColor(0xff, 0xff, 0xff)); const QRect r = QRect(50, 247, 100, 50); p.drawText(r, Qt::AlignCenter, net_type); diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index ab3e990e6..4c6d8f47e 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -6,7 +6,7 @@ #include "common/params.h" #include "selfdrive/ui/ui.h" -typedef QPair ItemStatus; +typedef QPair, QColor> ItemStatus; Q_DECLARE_METATYPE(ItemStatus); class Sidebar : public QFrame { @@ -30,17 +30,17 @@ public slots: protected: void paintEvent(QPaintEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; - void drawMetric(QPainter &p, const QString &label, QColor c, int y); + void drawMetric(QPainter &p, const QPair &label, QColor c, int y); QPixmap home_img, settings_img; const QMap network_type = { - {cereal::DeviceState::NetworkType::NONE, "--"}, - {cereal::DeviceState::NetworkType::WIFI, "Wi-Fi"}, - {cereal::DeviceState::NetworkType::ETHERNET, "ETH"}, - {cereal::DeviceState::NetworkType::CELL2_G, "2G"}, - {cereal::DeviceState::NetworkType::CELL3_G, "3G"}, - {cereal::DeviceState::NetworkType::CELL4_G, "LTE"}, - {cereal::DeviceState::NetworkType::CELL5_G, "5G"} + {cereal::DeviceState::NetworkType::NONE, tr("--")}, + {cereal::DeviceState::NetworkType::WIFI, tr("Wi-Fi")}, + {cereal::DeviceState::NetworkType::ETHERNET, tr("ETH")}, + {cereal::DeviceState::NetworkType::CELL2_G, tr("2G")}, + {cereal::DeviceState::NetworkType::CELL3_G, tr("3G")}, + {cereal::DeviceState::NetworkType::CELL4_G, tr("LTE")}, + {cereal::DeviceState::NetworkType::CELL5_G, tr("5G")} }; const QRect settings_btn = QRect(50, 35, 200, 117); diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index ac8e7bcd1..21ec5eedc 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -33,12 +33,12 @@ int main(int argc, char *argv[]) { QPushButton *btn = new QPushButton(); #ifdef __aarch64__ - btn->setText("Reboot"); + btn->setText(QObject::tr("Reboot")); QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); #else - btn->setText("Exit"); + btn->setText(QObject::tr("Exit")); QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); #endif main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index 366aa76f4..4be88aaf1 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -1,6 +1,9 @@ #include "selfdrive/ui/qt/util.h" #include +#include +#include +#include #include #include #include @@ -15,7 +18,7 @@ QString getVersion() { } QString getBrand() { - return Params().getBool("Passive") ? "dashcam" : "openpilot"; + return Params().getBool("Passive") ? QObject::tr("dashcam") : QObject::tr("openpilot"); } QString getBrandVersion() { @@ -36,6 +39,19 @@ std::optional getDongleId() { } } +QMap getSupportedLanguages() { + QFile f("translations/languages.json"); + f.open(QIODevice::ReadOnly | QIODevice::Text); + QString val = f.readAll(); + + QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object(); + QMap map; + for (auto key : obj.keys()) { + map[key] = obj[key].toString(); + } + return map; +} + void configFont(QPainter &p, const QString &family, int size, const QString &style) { QFont f(family); f.setPixelSize(size); @@ -63,13 +79,13 @@ QString timeAgo(const QDateTime &date) { s = "now"; } else if (diff < 60 * 60) { int minutes = diff / 60; - s = QString("%1 minute%2 ago").arg(minutes).arg(minutes > 1 ? "s" : ""); + s = QObject::tr("%n minute(s) ago", "", minutes); } else if (diff < 60 * 60 * 24) { int hours = diff / (60 * 60); - s = QString("%1 hour%2 ago").arg(hours).arg(hours > 1 ? "s" : ""); + s = QObject::tr("%n hour(s) ago", "", hours); } else if (diff < 3600 * 24 * 7) { int days = diff / (60 * 60 * 24); - s = QString("%1 day%2 ago").arg(days).arg(days > 1 ? "s" : ""); + s = QObject::tr("%n day(s) ago", "", days); } else { s = date.date().toString(); } @@ -90,10 +106,19 @@ void setQtSurfaceFormat() { QSurfaceFormat::setDefaultFormat(fmt); } +void sigTermHandler(int s) { + std::signal(s, SIG_DFL); + qApp->quit(); +} + void initApp(int argc, char *argv[]) { Hardware::set_display_power(true); Hardware::set_brightness(65); + // setup signal handlers to exit gracefully + std::signal(SIGINT, sigTermHandler); + std::signal(SIGTERM, sigTermHandler); + #ifdef __APPLE__ { // Get the devicePixelRatio, and scale accordingly to maintain 1:1 rendering diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h index 9491c6798..f0e57526c 100644 --- a/selfdrive/ui/qt/util.h +++ b/selfdrive/ui/qt/util.h @@ -14,6 +14,7 @@ QString getBrand(); QString getBrandVersion(); QString getUserAgent(); std::optional getDongleId(); +QMap getSupportedLanguages(); void configFont(QPainter &p, const QString &family, int size, const QString &style); void clearLayout(QLayout* layout); void setQtSurfaceFormat(); diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 000ac4847..63d15660a 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -6,6 +6,8 @@ #include #endif +#include + #include #include @@ -59,24 +61,16 @@ const char frame_fragment_shader[] = "}\n"; #endif -const mat4 device_transform = {{ - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0, -}}; - mat4 get_driver_view_transform(int screen_width, int screen_height, int stream_width, int stream_height) { - const float driver_view_ratio = 1.333; - const float yscale = stream_height * driver_view_ratio / tici_dm_crop::width; + const float driver_view_ratio = 2.0; + const float yscale = stream_height * driver_view_ratio / stream_width; const float xscale = yscale*screen_height/screen_width*stream_width/stream_height; mat4 transform = (mat4){{ - xscale, 0.0, 0.0, xscale*tici_dm_crop::x_offset/stream_width*2, - 0.0, yscale, 0.0, yscale*tici_dm_crop::y_offset/stream_height*2, + xscale, 0.0, 0.0, 0.0, + 0.0, yscale, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, }}; - return transform; } @@ -186,45 +180,75 @@ void CameraViewWidget::hideEvent(QHideEvent *event) { } } -void CameraViewWidget::updateFrameMat(int w, int h) { +void CameraViewWidget::updateFrameMat() { + int w = width(), h = height(); + if (zoomed_view) { if (stream_type == VISION_STREAM_DRIVER) { - frame_mat = matmul(device_transform, get_driver_view_transform(w, h, stream_width, stream_height)); + frame_mat = get_driver_view_transform(w, h, stream_width, stream_height); } else { - auto intrinsic_matrix = stream_type == VISION_STREAM_WIDE_ROAD ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; - float zoom = ZOOM / intrinsic_matrix.v[0]; - if (stream_type == VISION_STREAM_WIDE_ROAD) { - zoom *= 0.5; - } + intrinsic_matrix = (stream_type == VISION_STREAM_WIDE_ROAD) ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; + zoom = (stream_type == VISION_STREAM_WIDE_ROAD) ? 2.5 : 1.1; + + // Project point at "infinity" to compute x and y offsets + // to ensure this ends up in the middle of the screen + // TODO: use proper perspective transform? + const vec3 inf = {{1000., 0., 0.}}; + const vec3 Ep = matvecmul3(calibration, inf); + const vec3 Kep = matvecmul3(intrinsic_matrix, Ep); + + float x_offset_ = (Kep.v[0] / Kep.v[2] - intrinsic_matrix.v[2]) * zoom; + float y_offset_ = (Kep.v[1] / Kep.v[2] - intrinsic_matrix.v[5]) * zoom; + + float max_x_offset = intrinsic_matrix.v[2] * zoom - w / 2 - 5; + float max_y_offset = intrinsic_matrix.v[5] * zoom - h / 2 - 5; + + x_offset = std::clamp(x_offset_, -max_x_offset, max_x_offset); + y_offset = std::clamp(y_offset_, -max_y_offset, max_y_offset); + float zx = zoom * 2 * intrinsic_matrix.v[2] / width(); float zy = zoom * 2 * intrinsic_matrix.v[5] / height(); - const mat4 frame_transform = {{ - zx, 0.0, 0.0, 0.0, - 0.0, zy, 0.0, -y_offset / height() * 2, + zx, 0.0, 0.0, -x_offset / width() * 2, + 0.0, zy, 0.0, y_offset / height() * 2, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, }}; - frame_mat = matmul(device_transform, frame_transform); + frame_mat = frame_transform; } } else if (stream_width > 0 && stream_height > 0) { // fit frame to widget size float widget_aspect_ratio = (float)width() / height(); float frame_aspect_ratio = (float)stream_width / stream_height; - frame_mat = matmul(device_transform, get_fit_view_transform(widget_aspect_ratio, frame_aspect_ratio)); + frame_mat = get_fit_view_transform(widget_aspect_ratio, frame_aspect_ratio); } } +void CameraViewWidget::updateCalibration(const mat3 &calib) { + calibration = calib; + updateFrameMat(); +} + void CameraViewWidget::paintGL() { glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); if (frames.empty()) return; - int frame_idx; - for (frame_idx = 0; frame_idx < frames.size() - 1; frame_idx++) { - if (frames[frame_idx].first == draw_frame_id) break; + int frame_idx = frames.size() - 1; + + // Always draw latest frame until sync logic is more stable + // for (frame_idx = 0; frame_idx < frames.size() - 1; frame_idx++) { + // if (frames[frame_idx].first == draw_frame_id) break; + // } + + // Log duplicate/dropped frames + if (frames[frame_idx].first == prev_frame_id) { + qDebug() << "Drawing same frame twice" << frames[frame_idx].first; + } else if (frames[frame_idx].first != prev_frame_id + 1) { + qDebug() << "Skipped frame" << frames[frame_idx].first; } + prev_frame_id = frames[frame_idx].first; glViewport(0, 0, width(), height()); glBindVertexArray(frame_vao); @@ -312,7 +336,7 @@ void CameraViewWidget::vipcConnected(VisionIpcClient *vipc_client) { assert(glGetError() == GL_NO_ERROR); #endif - updateFrameMat(width(), height()); + updateFrameMat(); } void CameraViewWidget::vipcFrameReceived(VisionBuf *buf, uint32_t frame_id) { diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index ddc3fc253..016522b05 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -17,7 +17,7 @@ #endif #include "cereal/visionipc/visionipc_client.h" -#include "selfdrive/camerad/cameras/camera_common.h" +#include "system/camerad/cameras/camera_common.h" #include "selfdrive/ui/ui.h" const int FRAME_BUFFER_SIZE = 5; @@ -42,11 +42,12 @@ signals: protected: void paintGL() override; void initializeGL() override; - void resizeGL(int w, int h) override { updateFrameMat(w, h); } + void resizeGL(int w, int h) override { updateFrameMat(); } void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override { emit clicked(); } - virtual void updateFrameMat(int w, int h); + virtual void updateFrameMat(); + void updateCalibration(const mat3 &calib); void vipcThread(); bool zoomed_view; @@ -68,8 +69,16 @@ protected: std::atomic stream_type; QThread *vipc_thread = nullptr; + // Calibration + float x_offset = 0; + float y_offset = 0; + float zoom = 1.0; + mat3 calibration = DEFAULT_CALIBRATION; + mat3 intrinsic_matrix = fcam_intrinsic_matrix; + std::deque> frames; uint32_t draw_frame_id = 0; + int prev_frame_id = 0; protected slots: void vipcConnected(VisionIpcClient *vipc_client); diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 89c95843f..3264fd3aa 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -45,21 +45,23 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons main_layout->addLayout(hlayout); // description - if (!desc.isEmpty()) { - description = new QLabel(desc); - description->setContentsMargins(40, 20, 40, 20); - description->setStyleSheet("font-size: 40px; color: grey"); - description->setWordWrap(true); - description->setVisible(false); - main_layout->addWidget(description); - - connect(title_label, &QPushButton::clicked, [=]() { - if (!description->isVisible()) { - emit showDescription(); - } + description = new QLabel(desc); + description->setContentsMargins(40, 20, 40, 20); + description->setStyleSheet("font-size: 40px; color: grey"); + description->setWordWrap(true); + description->setVisible(false); + main_layout->addWidget(description); + + connect(title_label, &QPushButton::clicked, [=]() { + if (!description->isVisible()) { + emit showDescription(); + } + + if (!description->text().isEmpty()) { description->setVisible(!description->isVisible()); - }); - } + } + }); + main_layout->addStretch(); } @@ -125,7 +127,9 @@ void ElidedLabel::paintEvent(QPaintEvent *event) { ClickableWidget::ClickableWidget(QWidget *parent) : QWidget(parent) { } void ClickableWidget::mouseReleaseEvent(QMouseEvent *event) { - emit clicked(); + if (rect().contains(event->pos())) { + emit clicked(); + } } // Fix stylesheets diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index b6684e28b..4245a9c04 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -24,7 +24,11 @@ signals: protected: void paintEvent(QPaintEvent *event) override; void resizeEvent(QResizeEvent* event) override; - void mouseReleaseEvent(QMouseEvent *event) override { emit clicked(); } + void mouseReleaseEvent(QMouseEvent *event) override { + if (rect().contains(event->pos())) { + emit clicked(); + } + } QString lastText_, elidedText_; }; diff --git a/selfdrive/ui/qt/widgets/drive_stats.cc b/selfdrive/ui/qt/widgets/drive_stats.cc index 61d47db1c..31009f03c 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.cc +++ b/selfdrive/ui/qt/widgets/drive_stats.cc @@ -34,16 +34,16 @@ DriveStats::DriveStats(QWidget* parent) : QFrame(parent) { grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft); grid_layout->addWidget(labels.hours = newLabel("0", "number"), row, 2, Qt::AlignLeft); - grid_layout->addWidget(newLabel("Drives", "unit"), row + 1, 0, Qt::AlignLeft); + grid_layout->addWidget(newLabel((tr("Drives")), "unit"), row + 1, 0, Qt::AlignLeft); grid_layout->addWidget(labels.distance_unit = newLabel(getDistanceUnit(), "unit"), row + 1, 1, Qt::AlignLeft); - grid_layout->addWidget(newLabel("Hours ", "unit"), row + 1, 2, Qt::AlignLeft); + grid_layout->addWidget(newLabel(tr("Hours"), "unit"), row + 1, 2, Qt::AlignLeft); main_layout->addLayout(grid_layout); }; - add_stats_layouts("ALL TIME", all_); + add_stats_layouts(tr("ALL TIME"), all_); main_layout->addStretch(); - add_stats_layouts("PAST WEEK", week_); + add_stats_layouts(tr("PAST WEEK"), week_); if (auto dongleId = getDongleId()) { QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats"; diff --git a/selfdrive/ui/qt/widgets/drive_stats.h b/selfdrive/ui/qt/widgets/drive_stats.h index 944629451..5e2d96b24 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.h +++ b/selfdrive/ui/qt/widgets/drive_stats.h @@ -12,7 +12,7 @@ public: private: void showEvent(QShowEvent *event) override; void updateStats(); - inline QString getDistanceUnit() const { return metric_ ? "KM" : "Miles"; } + inline QString getDistanceUnit() const { return metric_ ? tr("KM") : tr("Miles"); } bool metric_; QJsonDocument stats_; diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index 8a5d49280..d70382588 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -1,6 +1,7 @@ #include "selfdrive/ui/qt/widgets/input.h" #include +#include #include "system/hardware/hw.h" #include "selfdrive/ui/qt/util.h" @@ -67,7 +68,7 @@ InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &s vlayout->addWidget(sublabel, 1, Qt::AlignTop | Qt::AlignLeft); } - QPushButton* cancel_btn = new QPushButton("Cancel"); + QPushButton* cancel_btn = new QPushButton(tr("Cancel")); cancel_btn->setFixedSize(386, 125); cancel_btn->setStyleSheet(R"( font-size: 48px; @@ -164,7 +165,7 @@ void InputDialog::handleEnter() { done(QDialog::Accepted); emitText(line->text()); } else { - setMessage("Need at least "+QString::number(minLength)+" characters!", false); + setMessage(tr("Need at least %n character(s)!", "", minLength), false); } } @@ -217,12 +218,12 @@ ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString } bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) { - ConfirmationDialog d = ConfirmationDialog(prompt_text, "Ok", "", parent); + ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), "", parent); return d.exec(); } bool ConfirmationDialog::confirm(const QString &prompt_text, QWidget *parent) { - ConfirmationDialog d = ConfirmationDialog(prompt_text, "Ok", "Cancel", parent); + ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), tr("Cancel"), parent); return d.exec(); } @@ -254,6 +255,96 @@ RichTextDialog::RichTextDialog(const QString &prompt_text, const QString &btn_te } bool RichTextDialog::alert(const QString &prompt_text, QWidget *parent) { - auto d = RichTextDialog(prompt_text, "Ok", parent); + auto d = RichTextDialog(prompt_text, tr("Ok"), parent); return d.exec(); } + +// MultiOptionDialog + +MultiOptionDialog::MultiOptionDialog(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent) : QDialogBase(parent) { + QFrame *container = new QFrame(this); + container->setStyleSheet(R"( + QFrame { background-color: #1B1B1B; } + #confirm_btn[enabled="false"] { background-color: #2B2B2B; } + #confirm_btn:enabled { background-color: #465BEA; } + #confirm_btn:enabled:pressed { background-color: #3049F4; } + )"); + + QVBoxLayout *main_layout = new QVBoxLayout(container); + main_layout->setContentsMargins(55, 50, 55, 50); + + QLabel *title = new QLabel(prompt_text, this); + title->setStyleSheet("font-size: 70px; font-weight: 500;"); + main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); + main_layout->addSpacing(25); + + QWidget *listWidget = new QWidget(this); + QVBoxLayout *listLayout = new QVBoxLayout(listWidget); + listLayout->setSpacing(20); + listWidget->setStyleSheet(R"( + QPushButton { + height: 135; + padding: 0px 50px; + text-align: left; + font-size: 55px; + font-weight: 300; + border-radius: 10px; + background-color: #4F4F4F; + } + QPushButton:checked { background-color: #465BEA; } + )"); + + QButtonGroup *group = new QButtonGroup(listWidget); + group->setExclusive(true); + + QPushButton *confirm_btn = new QPushButton(tr("Select")); + confirm_btn->setObjectName("confirm_btn"); + confirm_btn->setEnabled(false); + + for (const QString &s : l) { + QPushButton *selectionLabel = new QPushButton(s); + selectionLabel->setCheckable(true); + selectionLabel->setChecked(s == current); + QObject::connect(selectionLabel, &QPushButton::toggled, [=](bool checked) { + if (checked) selection = s; + if (selection != current) { + confirm_btn->setEnabled(true); + } else { + confirm_btn->setEnabled(false); + } + }); + + group->addButton(selectionLabel); + listLayout->addWidget(selectionLabel); + } + + ScrollView *scroll_view = new ScrollView(listWidget, this); + scroll_view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + main_layout->addWidget(scroll_view); + main_layout->addStretch(1); + main_layout->addSpacing(35); + + // cancel + confirm buttons + QHBoxLayout *blayout = new QHBoxLayout; + main_layout->addLayout(blayout); + blayout->setSpacing(50); + + QPushButton *cancel_btn = new QPushButton(tr("Cancel")); + QObject::connect(cancel_btn, &QPushButton::clicked, this, &ConfirmationDialog::reject); + QObject::connect(confirm_btn, &QPushButton::clicked, this, &ConfirmationDialog::accept); + blayout->addWidget(cancel_btn); + blayout->addWidget(confirm_btn); + + QVBoxLayout *outer_layout = new QVBoxLayout(this); + outer_layout->setContentsMargins(50, 50, 50, 50); + outer_layout->addWidget(container); +} + +QString MultiOptionDialog::getSelection(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent) { + MultiOptionDialog d = MultiOptionDialog(prompt_text, l, current, parent); + if (d.exec()) { + return d.selection; + } + return ""; +} diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h index f81211d0e..6c47a31d8 100644 --- a/selfdrive/ui/qt/widgets/input.h +++ b/selfdrive/ui/qt/widgets/input.h @@ -68,3 +68,12 @@ public: explicit RichTextDialog(const QString &prompt_text, const QString &btn_text, QWidget* parent); static bool alert(const QString &prompt_text, QWidget *parent); }; + +class MultiOptionDialog : public QDialogBase { + Q_OBJECT + +public: + explicit MultiOptionDialog(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent); + static QString getSelection(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent); + QString selection; +}; diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.cc b/selfdrive/ui/qt/widgets/offroad_alerts.cc index 433193df5..937ea02f8 100644 --- a/selfdrive/ui/qt/widgets/offroad_alerts.cc +++ b/selfdrive/ui/qt/widgets/offroad_alerts.cc @@ -22,12 +22,12 @@ AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent QHBoxLayout *footer_layout = new QHBoxLayout(); main_layout->addLayout(footer_layout); - QPushButton *dismiss_btn = new QPushButton("Close"); + QPushButton *dismiss_btn = new QPushButton(tr("Close")); dismiss_btn->setFixedSize(400, 125); footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft); QObject::connect(dismiss_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss); - snooze_btn = new QPushButton("Snooze Update"); + snooze_btn = new QPushButton(tr("Snooze Update")); snooze_btn->setVisible(false); snooze_btn->setFixedSize(550, 125); footer_layout->addWidget(snooze_btn, 0, Qt::AlignBottom | Qt::AlignRight); @@ -38,7 +38,7 @@ AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent snooze_btn->setStyleSheet(R"(color: white; background-color: #4F4F4F;)"); if (hasRebootBtn) { - QPushButton *rebootBtn = new QPushButton("Reboot and Update"); + QPushButton *rebootBtn = new QPushButton(tr("Reboot and Update")); rebootBtn->setFixedSize(600, 125); footer_layout->addWidget(rebootBtn, 0, Qt::AlignBottom | Qt::AlignRight); QObject::connect(rebootBtn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc index 8a208bf3c..541947526 100644 --- a/selfdrive/ui/qt/widgets/prime.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -83,18 +83,21 @@ PairingPopup::PairingPopup(QWidget *parent) : QDialogBase(parent) { vlayout->addSpacing(30); - QLabel *title = new QLabel("Pair your device to your comma account", this); + QLabel *title = new QLabel(tr("Pair your device to your comma account"), this); title->setStyleSheet("font-size: 75px; color: black;"); title->setWordWrap(true); vlayout->addWidget(title); - QLabel *instructions = new QLabel(R"( + QLabel *instructions = new QLabel(QString(R"(
    -
  1. Go to https://connect.comma.ai on your phone
  2. -
  3. Click "add new device" and scan the QR code on the right
  4. -
  5. Bookmark connect.comma.ai to your home screen to use it like an app
  6. +
  7. %1
  8. +
  9. %2
  10. +
  11. %3
- )", this); + )").arg(tr("Go to https://connect.comma.ai on your phone")) + .arg(tr("Click \"add new device\" and scan the QR code on the right")) + .arg(tr("Bookmark connect.comma.ai to your home screen to use it like an app")), this); + instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;"); instructions->setWordWrap(true); vlayout->addWidget(instructions); @@ -120,19 +123,19 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) { primeLayout->setMargin(0); primeWidget->setContentsMargins(60, 50, 60, 50); - QLabel* subscribed = new QLabel("✓ SUBSCRIBED"); + QLabel* subscribed = new QLabel(tr("✓ SUBSCRIBED")); subscribed->setStyleSheet("font-size: 41px; font-weight: bold; color: #86FF4E;"); primeLayout->addWidget(subscribed, 0, Qt::AlignTop); primeLayout->addSpacing(60); - QLabel* commaPrime = new QLabel("comma prime"); + QLabel* commaPrime = new QLabel(tr("comma prime")); commaPrime->setStyleSheet("font-size: 75px; font-weight: bold;"); primeLayout->addWidget(commaPrime, 0, Qt::AlignTop); primeLayout->addSpacing(20); - QLabel* connectUrl = new QLabel("CONNECT.COMMA.AI"); + QLabel* connectUrl = new QLabel(tr("CONNECT.COMMA.AI")); connectUrl->setStyleSheet("font-size: 41px; font-family: Inter SemiBold; color: #A0A0A0;"); primeLayout->addWidget(connectUrl, 0, Qt::AlignTop); @@ -145,7 +148,7 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) { pointsLayout->setMargin(0); pointsWidget->setContentsMargins(60, 50, 60, 50); - QLabel* commaPoints = new QLabel("COMMA POINTS"); + QLabel* commaPoints = new QLabel(tr("COMMA POINTS")); commaPoints->setStyleSheet("font-size: 41px; font-family: Inter SemiBold;"); pointsLayout->addWidget(commaPoints, 0, Qt::AlignTop); @@ -181,24 +184,24 @@ PrimeAdWidget::PrimeAdWidget(QWidget* parent) : QFrame(parent) { main_layout->setContentsMargins(80, 90, 80, 60); main_layout->setSpacing(0); - QLabel *upgrade = new QLabel("Upgrade Now"); + QLabel *upgrade = new QLabel(tr("Upgrade Now")); upgrade->setStyleSheet("font-size: 75px; font-weight: bold;"); main_layout->addWidget(upgrade, 0, Qt::AlignTop); main_layout->addSpacing(50); - QLabel *description = new QLabel("Become a comma prime member at connect.comma.ai"); + QLabel *description = new QLabel(tr("Become a comma prime member at connect.comma.ai")); description->setStyleSheet("font-size: 60px; font-weight: light; color: white;"); description->setWordWrap(true); main_layout->addWidget(description, 0, Qt::AlignTop); main_layout->addStretch(); - QLabel *features = new QLabel("PRIME FEATURES:"); + QLabel *features = new QLabel(tr("PRIME FEATURES:")); features->setStyleSheet("font-size: 41px; font-weight: bold; color: #E5E5E5;"); main_layout->addWidget(features, 0, Qt::AlignBottom); main_layout->addSpacing(30); - QVector bullets = {"Remote access", "1 year of storage", "Developer perks"}; + QVector bullets = {tr("Remote access"), tr("1 year of storage"), tr("Developer perks")}; for (auto &b: bullets) { const QString check = " "; QLabel *l = new QLabel(check + b); @@ -227,20 +230,20 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) { finishRegistationLayout->setContentsMargins(30, 75, 30, 45); finishRegistationLayout->setSpacing(0); - QLabel* registrationTitle = new QLabel("Finish Setup"); + QLabel* registrationTitle = new QLabel(tr("Finish Setup")); registrationTitle->setStyleSheet("font-size: 75px; font-weight: bold; margin-left: 55px;"); finishRegistationLayout->addWidget(registrationTitle); finishRegistationLayout->addSpacing(30); - QLabel* registrationDescription = new QLabel("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."); + QLabel* registrationDescription = new QLabel(tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.")); registrationDescription->setWordWrap(true); registrationDescription->setStyleSheet("font-size: 55px; font-weight: light; margin-left: 55px;"); finishRegistationLayout->addWidget(registrationDescription); finishRegistationLayout->addStretch(); - QPushButton* pair = new QPushButton("Pair device"); + QPushButton* pair = new QPushButton(tr("Pair device")); pair->setFixedHeight(220); pair->setStyleSheet(R"( QPushButton { diff --git a/selfdrive/ui/qt/widgets/scrollview.cc b/selfdrive/ui/qt/widgets/scrollview.cc index 1aa05b415..bd4309d8d 100644 --- a/selfdrive/ui/qt/widgets/scrollview.cc +++ b/selfdrive/ui/qt/widgets/scrollview.cc @@ -37,7 +37,7 @@ ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent) { sp.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOff)); sp.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOff)); - + sp.setScrollMetric(QScrollerProperties::MousePressEventDelay, 0.01); scroller->grabGesture(this->viewport(), QScroller::LeftMouseButtonGesture); scroller->setScrollerProperties(sp); } diff --git a/selfdrive/ui/qt/widgets/ssh_keys.cc b/selfdrive/ui/qt/widgets/ssh_keys.cc index 46ccf6aee..f17604b3e 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.cc +++ b/selfdrive/ui/qt/widgets/ssh_keys.cc @@ -4,16 +4,16 @@ #include "selfdrive/ui/qt/api.h" #include "selfdrive/ui/qt/widgets/input.h" -SshControl::SshControl() : ButtonControl("SSH Keys", "", "Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username.") { +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.")) { username_label.setAlignment(Qt::AlignRight | Qt::AlignVCenter); username_label.setStyleSheet("color: #aaaaaa"); hlayout->insertWidget(1, &username_label); QObject::connect(this, &ButtonControl::clicked, [=]() { - if (text() == "ADD") { - QString username = InputDialog::getText("Enter your GitHub username", this); + if (text() == tr("ADD")) { + QString username = InputDialog::getText(tr("Enter your GitHub username"), this); if (username.length() > 0) { - setText("LOADING"); + setText(tr("LOADING")); setEnabled(false); getUserKeys(username); } @@ -31,10 +31,10 @@ void SshControl::refresh() { QString param = QString::fromStdString(params.get("GithubSshKeys")); if (param.length()) { username_label.setText(QString::fromStdString(params.get("GithubUsername"))); - setText("REMOVE"); + setText(tr("REMOVE")); } else { username_label.setText(""); - setText("ADD"); + setText(tr("ADD")); } setEnabled(true); } @@ -47,13 +47,13 @@ void SshControl::getUserKeys(const QString &username) { params.put("GithubUsername", username.toStdString()); params.put("GithubSshKeys", resp.toStdString()); } else { - ConfirmationDialog::alert(QString("Username '%1' has no keys on GitHub").arg(username), this); + ConfirmationDialog::alert(tr("Username '%1' has no keys on GitHub").arg(username), this); } } else { if (request->timeout()) { - ConfirmationDialog::alert("Request timed out", this); + ConfirmationDialog::alert(tr("Request timed out"), this); } else { - ConfirmationDialog::alert(QString("Username '%1' doesn't exist on GitHub").arg(username), this); + ConfirmationDialog::alert(tr("Username '%1' doesn't exist on GitHub").arg(username), this); } } diff --git a/selfdrive/ui/qt/widgets/ssh_keys.h b/selfdrive/ui/qt/widgets/ssh_keys.h index 596d1d83b..01e2ab83c 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.h +++ b/selfdrive/ui/qt/widgets/ssh_keys.h @@ -10,7 +10,7 @@ class SshToggle : public ToggleControl { Q_OBJECT public: - SshToggle() : ToggleControl("Enable SSH", "", "", Hardware::get_ssh_enabled()) { + SshToggle() : ToggleControl(tr("Enable SSH"), "", "", Hardware::get_ssh_enabled()) { QObject::connect(this, &SshToggle::toggleFlipped, [=](bool state) { Hardware::set_ssh_enabled(state); }); diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index d9613df0d..04ce15ef2 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -45,9 +45,6 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { }); // load fonts - QFontDatabase::addApplicationFont("../assets/fonts/opensans_regular.ttf"); - QFontDatabase::addApplicationFont("../assets/fonts/opensans_bold.ttf"); - QFontDatabase::addApplicationFont("../assets/fonts/opensans_semibold.ttf"); QFontDatabase::addApplicationFont("../assets/fonts/Inter-Black.ttf"); QFontDatabase::addApplicationFont("../assets/fonts/Inter-Bold.ttf"); QFontDatabase::addApplicationFont("../assets/fonts/Inter-ExtraBold.ttf"); diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore index 7765bab17..26335744f 100644 --- a/selfdrive/ui/tests/.gitignore +++ b/selfdrive/ui/tests/.gitignore @@ -1,3 +1,4 @@ test playsound test_sound +test_translations diff --git a/selfdrive/ui/tests/create_test_translations.sh b/selfdrive/ui/tests/create_test_translations.sh new file mode 100755 index 000000000..451a3cbfb --- /dev/null +++ b/selfdrive/ui/tests/create_test_translations.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +UI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"/.. +TEST_TEXT="(WRAPPED_SOURCE_TEXT)" +TEST_TS_FILE=$UI_DIR/translations/main_test_en.ts +TEST_QM_FILE=$UI_DIR/translations/main_test_en.qm + +# translation strings +UNFINISHED="<\/translation>" +TRANSLATED="$TEST_TEXT<\/translation>" + +mkdir -p $UI_DIR/translations +rm -f $TEST_TS_FILE $TEST_QM_FILE +lupdate -recursive "$UI_DIR" -ts $TEST_TS_FILE +sed -i "s/$UNFINISHED/$TRANSLATED/" $TEST_TS_FILE +lrelease $TEST_TS_FILE diff --git a/selfdrive/ui/tests/test_runner.cc b/selfdrive/ui/tests/test_runner.cc index b20ac86c6..ac63139d1 100644 --- a/selfdrive/ui/tests/test_runner.cc +++ b/selfdrive/ui/tests/test_runner.cc @@ -1,10 +1,25 @@ #define CATCH_CONFIG_RUNNER #include "catch2/catch.hpp" -#include + +#include +#include +#include +#include int main(int argc, char **argv) { // unit tests for Qt - QCoreApplication app(argc, argv); + QApplication app(argc, argv); + + QString language_file = "main_test_en"; + qDebug() << "Loading language:" << language_file; + + QTranslator translator; + QString translationsPath = QDir::cleanPath(qApp->applicationDirPath() + "/../translations"); + if (!translator.load(language_file, translationsPath)) { + qDebug() << "Failed to load translation file!"; + } + app.installTranslator(&translator); + const int res = Catch::Session().run(argc, argv); return (res < 0xff ? res : 0xff); } diff --git a/selfdrive/ui/tests/test_translations.cc b/selfdrive/ui/tests/test_translations.cc new file mode 100644 index 000000000..fcefc5784 --- /dev/null +++ b/selfdrive/ui/tests/test_translations.cc @@ -0,0 +1,48 @@ +#include "catch2/catch.hpp" + +#include "common/params.h" +#include "selfdrive/ui/qt/window.h" + +const QString TEST_TEXT = "(WRAPPED_SOURCE_TEXT)"; // what each string should be translated to +QRegExp RE_NUM("\\d*"); + +QStringList getParentWidgets(QWidget* widget){ + QStringList parentWidgets; + while (widget->parentWidget() != Q_NULLPTR) { + widget = widget->parentWidget(); + parentWidgets.append(widget->metaObject()->className()); + } + return parentWidgets; +} + +template +void checkWidgetTrWrap(MainWindow &w) { + for (auto widget : w.findChildren()) { + const QString text = widget->text(); + bool isNumber = RE_NUM.exactMatch(text); + bool wrapped = text.contains(TEST_TEXT); + QString parentWidgets = getParentWidgets(widget).join("->"); + + if (!text.isEmpty() && !isNumber && !wrapped) { + FAIL(("\"" + text + "\" must be wrapped. Parent widgets: " + parentWidgets).toStdString()); + } + + // warn if source string wrapped, but UI adds text + // TODO: add way to ignore this + if (wrapped && text != TEST_TEXT) { + WARN(("\"" + text + "\" is dynamic and needs a custom retranslate function. Parent widgets: " + parentWidgets).toStdString()); + } + } +} + +// Tests all strings in the UI are wrapped with tr() +TEST_CASE("UI: test all strings wrapped") { + Params().remove("LanguageSetting"); + Params().remove("HardwareSerial"); + Params().remove("DongleId"); + qputenv("TICI", "1"); + + MainWindow w; + checkWidgetTrWrap(w); + checkWidgetTrWrap(w); +} diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py new file mode 100755 index 000000000..7da4ec0d6 --- /dev/null +++ b/selfdrive/ui/tests/test_translations.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +import json +import os +import shutil +import unittest +import xml.etree.ElementTree as ET + +from selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE, update_translations + +TMP_TRANSLATIONS_DIR = os.path.join(TRANSLATIONS_DIR, "tmp") + + +class TestTranslations(unittest.TestCase): + @classmethod + def setUpClass(cls): + with open(LANGUAGES_FILE, "r") as f: + cls.translation_files = json.load(f) + + # Set up temp directory + shutil.copytree(TRANSLATIONS_DIR, TMP_TRANSLATIONS_DIR, dirs_exist_ok=True) + + @classmethod + def tearDownClass(cls): + shutil.rmtree(TMP_TRANSLATIONS_DIR, ignore_errors=True) + + @staticmethod + def _read_translation_file(path, file): + tr_file = os.path.join(path, f"{file}.ts") + with open(tr_file, "rb") as f: + # fix relative path depth + return f.read().replace(b"filename=\"../../", b"filename=\"../") + + def test_missing_translation_files(self): + for name, file in self.translation_files.items(): + with self.subTest(name=name, file=file): + self.assertTrue(os.path.exists(os.path.join(TRANSLATIONS_DIR, f"{file}.ts")), + f"{name} has no XML translation file, run selfdrive/ui/update_translations.py") + + def test_translations_updated(self): + update_translations(plural_only=["main_en"], translations_dir=TMP_TRANSLATIONS_DIR) + + for name, file in self.translation_files.items(): + with self.subTest(name=name, file=file): + # caught by test_missing_translation_files + if not os.path.exists(os.path.join(TRANSLATIONS_DIR, f"{file}.ts")): + self.skipTest(f"{name} missing translation file") + + cur_translations = self._read_translation_file(TRANSLATIONS_DIR, file) + new_translations = self._read_translation_file(TMP_TRANSLATIONS_DIR, file) + self.assertEqual(cur_translations, new_translations, + f"{file} ({name}) XML translation file out of date. Run selfdrive/ui/update_translations.py to update the translation files") + + @unittest.skip("Only test unfinished translations before going to release") + def test_unfinished_translations(self): + for name, file in self.translation_files.items(): + with self.subTest(name=name, file=file): + cur_translations = self._read_translation_file(TRANSLATIONS_DIR, file) + self.assertTrue(b"" not in cur_translations, + f"{file} ({name}) translation file has unfinished translations. Finish translations or mark them as completed in Qt Linguist") + + def test_vanished_translations(self): + for name, file in self.translation_files.items(): + with self.subTest(name=name, file=file): + cur_translations = self._read_translation_file(TRANSLATIONS_DIR, file) + self.assertTrue(b"" not in cur_translations, + f"{file} ({name}) translation file has obsolete translations. Run selfdrive/ui/update_translations.py --vanish to remove them") + + def test_plural_translations(self): + for name, file in self.translation_files.items(): + with self.subTest(name=name, file=file): + tr_xml = ET.parse(os.path.join(TRANSLATIONS_DIR, f"{file}.ts")) + + for context in tr_xml.getroot(): + for message in context.iterfind("message"): + if message.get("numerus") == "yes": + translation = message.find("translation") + numerusform = translation.findall("numerusform") + + # Do not assert finished translations + if translation.get("type") == "unfinished": + continue + + self.assertNotIn(None, [x.text for x in numerusform], "Ensure all plural translation forms are completed.") + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/ui/translations/README.md b/selfdrive/ui/translations/README.md new file mode 100644 index 000000000..349b4847a --- /dev/null +++ b/selfdrive/ui/translations/README.md @@ -0,0 +1,57 @@ +# Multilanguage + +[![language](https://raw.githubusercontent.com/commaai/openpilot/badges/translation_badge_main_en.svg)](https://github.com/commaai/openpilot/blob/master/selfdrive/ui/translations/main_en.ts) +[![language](https://raw.githubusercontent.com/commaai/openpilot/badges/translation_badge_main_pt.svg)](https://github.com/commaai/openpilot/blob/master/selfdrive/ui/translations/main_pt.ts) +[![language](https://raw.githubusercontent.com/commaai/openpilot/badges/translation_badge_main_zh-CHT.svg)](https://github.com/commaai/openpilot/blob/master/selfdrive/ui/translations/main_zh-CHT.ts) +[![language](https://raw.githubusercontent.com/commaai/openpilot/badges/translation_badge_main_zh-CHS.svg)](https://github.com/commaai/openpilot/blob/master/selfdrive/ui/translations/main_zh-CHS.ts) +[![language](https://raw.githubusercontent.com/commaai/openpilot/badges/translation_badge_main_ko.svg)](https://github.com/commaai/openpilot/blob/master/selfdrive/ui/translations/main_ko.ts) +[![language](https://raw.githubusercontent.com/commaai/openpilot/badges/translation_badge_main_ja.svg)](https://github.com/commaai/openpilot/blob/master/selfdrive/ui/translations/main_ja.ts) + +## Contributing + +Before getting started, make sure you have set up the openpilot Ubuntu development environment by reading the [tools README.md](/tools/README.md). + +### Adding a New Language + +openpilot provides a few tools to help contributors manage their translations and to ensure quality. To get started: + +1. Add your new language to [languages.json](/selfdrive/ui/translations/languages.json) with the appropriate [language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) and the localized language name (Traditional Chinese is `中文(繁體)`). +2. Generate the XML translation file (`*.ts`): + ```shell + selfdrive/ui/update_translations.py + ``` +3. Edit the translation file, marking each translation as completed: + ```shell + linguist selfdrive/ui/translations/your_language_file.ts + ``` +4. View your finished translations by compiling and starting the UI, then find it in the language selector: + ```shell + scons -j$(nproc) selfdrive/ui && selfdrive/ui/ui + ``` + +### Improving an Existing Language + +Follow step 3. above, you can review existing translations and add missing ones. Once you're done, just open a pull request to openpilot. + +### Updating the UI + +Any time you edit source code in the UI, you need to update the translations to ensure the line numbers and contexts are up to date (first step above). + +### Testing + +openpilot has a few unit tests to make sure all translations are up to date and that all strings are wrapped in a translation marker. They are run in CI, but you can also run them locally. + +Tests translation files up to date: + +```shell +selfdrive/ui/tests/test_translations.py +``` + +Tests all static source strings are wrapped: + +```shell +selfdrive/ui/tests/create_test_translations.sh && selfdrive/ui/tests/test_translations +``` + +--- +![multilanguage_onroad](https://user-images.githubusercontent.com/25857203/178912800-2c798af8-78e3-498e-9e19-35906e0bafff.png) diff --git a/selfdrive/ui/translations/create_badges.py b/selfdrive/ui/translations/create_badges.py new file mode 100755 index 000000000..d9e2d443b --- /dev/null +++ b/selfdrive/ui/translations/create_badges.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import json +import os +import requests + +from common.basedir import BASEDIR +from selfdrive.ui.update_translations import LANGUAGES_FILE, TRANSLATIONS_DIR + +TRANSLATION_TAG = "= 70 else "red" + + r = requests.get(f"https://img.shields.io/badge/LANGUAGE {name}-{percent_finished}%25 complete-{color}") + assert r.status_code == 200, "Error downloading badge" + + with open(os.path.join(BASEDIR, TRANSLATION_BADGE.format(file)), "wb") as badge_f: + badge_f.write(r.content) diff --git a/selfdrive/ui/translations/languages.json b/selfdrive/ui/translations/languages.json new file mode 100644 index 000000000..2559b2d26 --- /dev/null +++ b/selfdrive/ui/translations/languages.json @@ -0,0 +1,8 @@ +{ + "English": "main_en", + "Português": "main_pt", + "中文(繁體)": "main_zh-CHT", + "中文(简体)": "main_zh-CHS", + "한국어": "main_ko", + "日本語": "main_ja" +} diff --git a/selfdrive/ui/translations/main_en.ts b/selfdrive/ui/translations/main_en.ts new file mode 100644 index 000000000..3f9692e5f --- /dev/null +++ b/selfdrive/ui/translations/main_en.ts @@ -0,0 +1,38 @@ + + + + + InputDialog + + Need at least %n character(s)! + + Need at least %n character! + Need at least %n characters! + + + + + QObject + + %n minute(s) ago + + %n minute ago + %n minutes ago + + + + %n hour(s) ago + + %n hour ago + %n hours ago + + + + %n day(s) ago + + %n day ago + %n days ago + + + + diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts new file mode 100644 index 000000000..b66bc4b80 --- /dev/null +++ b/selfdrive/ui/translations/main_ja.ts @@ -0,0 +1,1303 @@ + + + + + AbstractAlert + + + Close + 閉じる + + + + Snooze Update + 更新の一時停止 + + + + Reboot and Update + 再起動してアップデート + + + + AdvancedNetworking + + + Back + 戻る + + + + Enable Tethering + テザリングを有効化 + + + + Tethering Password + テザリングパスワード + + + + + EDIT + 編集 + + + + Enter new tethering password + 新しいテザリングパスワードを入力 + + + + IP Address + IP アドレス + + + + Enable Roaming + ローミングを有効化 + + + + APN Setting + APN 設定 + + + + Enter APN + APN を入力 + + + + leave blank for automatic configuration + 空白のままにして、自動設定にします + + + + ConfirmationDialog + + + + Ok + OK + + + + Cancel + キャンセル + + + + DeclinePage + + + You must accept the Terms and Conditions in order to use openpilot. + openpilot をご利用される前に、利用規約に同意する必要があります。 + + + + Back + 戻る + + + + Decline, uninstall %1 + 拒否して %1 をアンインストール + + + + DevicePanel + + + Dongle ID + ドングル番号 (Dongle ID) + + + + N/A + N/A + + + + Serial + シリアル番号 + + + + Driver Camera + 車内カメラ + + + + PREVIEW + 見る + + + + Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) + 車内カメラをプレビューして、ドライバー監視システムの視界を確認ができます。(車両の電源を切る必要があります) + + + + Reset Calibration + キャリブレーションをリセット + + + + RESET + リセット + + + + Are you sure you want to reset calibration? + キャリブレーションをリセットしてもよろしいですか? + + + + Review Training Guide + 入門書を見る + + + + REVIEW + 見る + + + + Review the rules, features, and limitations of openpilot + openpilot の特徴を見る + + + + Are you sure you want to review the training guide? + 入門書を見てもよろしいですか? + + + + Regulatory + 認証情報 + + + + VIEW + 見る + + + + Change Language + 言語を変更 + + + + CHANGE + 変更 + + + + Select a language + 言語を選択 + + + + Reboot + 再起動 + + + + Power Off + 電源を切る + + + + openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. + openpilot は、左または右の4°以内、上の5°または下の8°以内にデバイスを取付ける必要があります。キャリブレーションを引き続きます、リセットはほとんど必要ありません。 + + + + Your device is pointed %1° %2 and %3° %4. + このデバイスは%2の%1°、%4の%3°に向けます。 + + + + down + + + + + up + + + + + left + + + + + right + + + + + Are you sure you want to reboot? + 再起動してもよろしいですか? + + + + Disengage to Reboot + openpilot をキャンセルして再起動ができます + + + + Are you sure you want to power off? + シャットダウンしてもよろしいですか? + + + + Disengage to Power Off + openpilot をキャンセルしてシャットダウンができます + + + + DriveStats + + + Drives + 運転履歴 + + + + Hours + 時間 + + + + ALL TIME + 累計 + + + + PAST WEEK + 先週 + + + + KM + km + + + + Miles + マイル + + + + DriverViewScene + + + camera starting + カメラを起動しています + + + + InputDialog + + + Cancel + キャンセル + + + + Need at least %n character(s)! + + %n文字以上でお願いします! + + + + + Installer + + + Installing... + インストールしています... + + + + Receiving objects: + オブジェクトをダウンロードしています: + + + + Resolving deltas: + デルタを解決しています: + + + + Updating files: + ファイルを更新しています: + + + + MapETA + + + eta + 予定到着時間 + + + + min + + + + + hr + 時間 + + + + km + キロメートル + + + + mi + マイル + + + + MapInstructions + + + km + キロメートル + + + + m + メートル + + + + mi + マイル + + + + ft + フィート + + + + MapPanel + + + Current Destination + 現在の目的地 + + + + CLEAR + 削除 + + + + Recent Destinations + 最近の目的地 + + + + Try the Navigation Beta + β版ナビゲーションを試す + + + + Get turn-by-turn directions displayed and more with a comma +prime subscription. Sign up now: https://connect.comma.ai + より詳細な案内情報を得ることができます。 +詳しくはこちら:https://connect.comma.ai + + + + No home +location set + 自宅の住所はまだ +設定されていません + + + + No work +location set + 職場の住所はまだ +設定されていません + + + + no recent destinations + 最近の目的地履歴がありません + + + + MapWindow + + + Map Loading + マップを読み込んでいます + + + + Waiting for GPS + GPS信号を探しています + + + + MultiOptionDialog + + + Select + 選択 + + + + Cancel + キャンセル + + + + Networking + + + Advanced + 詳細 + + + + Enter password + パスワードを入力 + + + + + for "%1" + ネットワーク名:%1 + + + + Wrong password + パスワードが間違っています + + + + NvgWindow + + + km/h + km/h + + + + mph + mph + + + + + MAX + 最高速度 + + + + + SPEED + 速度 + + + + + LIMIT + 制限速度 + + + + OffroadHome + + + UPDATE + 更新 + + + + ALERTS + 警告 + + + + ALERT + 警告 + + + + PairingPopup + + + Pair your device to your comma account + デバイスと comma アカウントを連携する + + + + Go to https://connect.comma.ai on your phone + モバイルデバイスで「connect.comma.ai」にアクセスして + + + + Click "add new device" and scan the QR code on the right + 「新しいデバイスを追加」を押すと、右側のQRコードをスキャンしてください + + + + Bookmark connect.comma.ai to your home screen to use it like an app + 「connect.comma.ai」をホーム画面に追加して、アプリのように使うことができます + + + + PrimeAdWidget + + + Upgrade Now + 今すぐアップグレート + + + + Become a comma prime member at connect.comma.ai + connect.comma.ai でプライム会員に登録できます + + + + PRIME FEATURES: + 特典: + + + + Remote access + リモートアクセス + + + + 1 year of storage + 一年間の保存期間 + + + + Developer perks + 開発者向け特典 + + + + PrimeUserWidget + + + ✓ SUBSCRIBED + ✓ 入会しました + + + + comma prime + comma prime + + + + CONNECT.COMMA.AI + CONNECT.COMMA.AI + + + + COMMA POINTS + COMMA POINTS + + + + QObject + + + Reboot + 再起動 + + + + Exit + 閉じる + + + + dashcam + ドライブレコーダー + + + + openpilot + openpilot + + + + %n minute(s) ago + + %n 分前 + + + + + %n hour(s) ago + + %n 時間前 + + + + + %n day(s) ago + + %n 日前 + + + + + Reset + + + Reset failed. Reboot to try again. + 初期化に失敗しました。再起動後に再試行してください。 + + + + Are you sure you want to reset your device? + 初期化してもよろしいですか? + + + + Resetting device... + デバイスが初期化されます... + + + + System Reset + システムを初期化 + + + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + システムの初期化をリクエストしました。「確認」ボタンを押すとデバイスが初期化されます。「キャンセル」ボタンを押すと起動を続行します。 + + + + Cancel + キャンセル + + + + Reboot + 再起動 + + + + Confirm + 確認 + + + + Unable to mount data partition. Press confirm to reset your device. + 「data」パーティションをマウントできません。「確認」ボタンを押すとデバイスが初期化されます。 + + + + RichTextDialog + + + Ok + OK + + + + SettingsWindow + + + × + × + + + + Device + デバイス + + + + + Network + ネットワーク + + + + Toggles + 切り替え + + + + Software + ソフトウェア + + + + Navigation + ナビゲーション + + + + Setup + + + WARNING: Low Voltage + 警告:低電圧 + + + + Power your device in a car with a harness or proceed at your own risk. + 自己責任でハーネスから電源を供給してください。 + + + + Power off + 電源を切る + + + + + + Continue + 続ける + + + + Getting Started + はじめに + + + + Before we get on the road, let’s finish installation and cover some details. + その前に、インストールを完了し、いくつかの詳細を説明します。 + + + + Connect to Wi-Fi + Wi-Fi に接続 + + + + + Back + 戻る + + + + Continue without Wi-Fi + Wi-Fi に未接続で続行 + + + + Waiting for internet + インターネット接続を待機中 + + + + Choose Software to Install + インストールするソフトウェアを選びます + + + + Dashcam + ドライブレコーダー + + + + Custom Software + カスタムソフトウェア + + + + Enter URL + URL を入力 + + + + for Custom Software + カスタムソフトウェア + + + + Downloading... + ダウンロード中... + + + + Download Failed + ダウンロード失敗 + + + + Ensure the entered URL is valid, and the device’s internet connection is good. + 入力された URL を確認し、デバイスがインターネットに接続されていることを確認してください。 + + + + Reboot device + デバイスを再起動 + + + + Start over + 最初からやり直す + + + + SetupWidget + + + Finish Setup + セットアップ完了 + + + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + デバイスを comma connect (connect.comma.ai)でペアリングし comma prime 特典を申請してください。 + + + + Pair device + デバイスをペアリング + + + + Sidebar + + + + CONNECT + 接続 + + + + OFFLINE + オフライン + + + + + ONLINE + オンライン + + + + ERROR + エラー + + + + + + TEMP + 温度 + + + + HIGH + 高温 + + + + GOOD + 最適 + + + + OK + OK + + + + VEHICLE + 車両 + + + + NO + NO + + + + PANDA + PANDA + + + + GPS + GPS + + + + SEARCH + 検索 + + + + -- + -- + + + + Wi-Fi + Wi-Fi + + + + ETH + ETH + + + + 2G + 2G + + + + 3G + 3G + + + + LTE + LTE + + + + 5G + 5G + + + + SoftwarePanel + + + Git Branch + Git ブランチ + + + + Git Commit + Git コミット + + + + OS Version + OS バージョン + + + + 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. + updater を実行する時にブランチを切り替えます。 + + + + Enter branch name + ブランチ名を入力 + + + + UNINSTALL + アンインストール + + + + Uninstall %1 + %1をアンインストール + + + + Are you sure you want to uninstall? + アンインストールしてもよろしいですか? + + + + failed to fetch update + 更新のダウンロードにエラーが発生しました + + + + + CHECK + 確認 + + + + SshControl + + + SSH Keys + SSH 鍵 + + + + Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. + 警告: これは、GitHub の設定にあるすべての公開鍵への SSH アクセスを許可するものです。自分以外の GitHub のユーザー名を入力しないでください。コンマのスタッフが GitHub のユーザー名を追加するようお願いすることはありません。 + + + + + ADD + 追加 + + + + Enter your GitHub username + GitHub のユーザー名を入力してください + + + + LOADING + ローディング + + + + REMOVE + 削除 + + + + Username '%1' has no keys on GitHub + ユーザー名 “%1” は GitHub に鍵がありません + + + + Request timed out + リクエストタイムアウト + + + + Username '%1' doesn't exist on GitHub + ユーザー名 '%1' は GitHub に存在しません + + + + SshToggle + + + Enable SSH + SSH を有効化 + + + + TermsPage + + + Terms & Conditions + 利用規約 + + + + Decline + 拒否 + + + + Scroll to accept + スクロールして同意 + + + + Agree + 同意 + + + + TogglesPanel + + + Enable openpilot + openpilot を有効化 + + + + Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + アダプティブクルーズコントロールとレーンキーピングドライバーアシスト(openpilotシステム)。この機能を使用するには、常に注意が必要です。この設定を変更すると、車の電源が切れたときに有効になります。 + + + + Enable Lane Departure Warnings + 車線逸脱警報機能を有効化 + + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 時速31マイル(50km)を超えるスピードで走行中、方向指示器を作動させずに検出された車線ライン上に車両が触れた場合、車線に戻るアラートを受信します。 + + + + Use Metric System + メートル法を有効化 + + + + Display speed in km/h instead of mph. + 速度は mph ではなく km/h で表示されます。 + + + + Record and Upload Driver Camera + 車内カメラの録画とアップロード + + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 車内カメラの映像をアップロードし、ドライバー監視システムのアルゴリズムの向上に役立てます。 + + + + Disengage On Accelerator Pedal + アクセル踏むと openpilot をキャンセル + + + + When enabled, pressing the accelerator pedal will disengage openpilot. + 有効な場合は、アクセルを踏むと openpilot をキャンセルします。 + + + + Show ETA in 24h Format + 24時間表示 + + + + Use 24h format instead of am/pm + AM/PM の代わりに24時間形式を使用します + + + + Show Map on Left Side of UI + ディスプレイの左側にマップを表示 + + + + Show map on left side when in split screen view. + 分割画面表示の場合、ディスプレイの左側にマップを表示します。 + + + + openpilot Longitudinal Control + openpilot 縦方向制御 + + + + openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! + openpilot は、車のレーダーを無効化し、アクセルとブレーキの制御を引き継ぎます。注意:AEB を無効化にします! + + + + Updater + + + Update Required + 更新が必要です + + + + An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. + オペレーティングシステムのアップデートが必要です。Wi-Fi に接続することで、最速のアップデートを体験できます。ダウンロードサイズは約 1GB です。 + + + + Connect to Wi-Fi + Wi-Fi に接続 + + + + Install + インストール + + + + Back + 戻る + + + + Loading... + 読み込み中... + + + + Reboot + 再起動 + + + + Update failed + 更新失敗 + + + + WifiUI + + + + Scanning for networks... + ネットワークをスキャン中... + + + + CONNECTING... + 接続中... + + + + FORGET + 削除 + + + + Forget Wi-Fi Network "%1"? + Wi-Fiネットワーク%1を削除してもよろしいですか? + + + diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts new file mode 100644 index 000000000..a67b957e8 --- /dev/null +++ b/selfdrive/ui/translations/main_ko.ts @@ -0,0 +1,1303 @@ + + + + + AbstractAlert + + + Close + 닫기 + + + + Snooze Update + 업데이트 일시중지 + + + + Reboot and Update + 업데이트 및 재부팅 + + + + AdvancedNetworking + + + Back + 뒤로 + + + + Enable Tethering + 테더링 사용 + + + + Tethering Password + 테더링 비밀번호 + + + + + EDIT + 편집 + + + + Enter new tethering password + 새 테더링 비밀번호를 입력하세요 + + + + IP Address + IP 주소 + + + + Enable Roaming + 로밍 사용 + + + + APN Setting + APN 설정 + + + + Enter APN + APN 입력 + + + + leave blank for automatic configuration + 자동설정을 하려면 공백으로 두세요 + + + + ConfirmationDialog + + + + Ok + 확인 + + + + Cancel + 취소 + + + + DeclinePage + + + You must accept the Terms and Conditions in order to use openpilot. + openpilot을 사용하려면 이용 약관에 동의해야 합니다. + + + + Back + 뒤로 + + + + Decline, uninstall %1 + 거절, %1 제거 + + + + DevicePanel + + + Dongle ID + Dongle ID + + + + N/A + N/A + + + + Serial + Serial + + + + Driver Camera + 운전자 카메라 + + + + PREVIEW + 미리보기 + + + + Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) + 운전자 모니터링이 좋은 가시성을 갖도록 운전자를 향한 카메라를 미리 봅니다. (차량연결은 해제되어있어야 합니다) + + + + Reset Calibration + 캘리브레이션 재설정 + + + + RESET + 재설정 + + + + Are you sure you want to reset calibration? + 캘리브레이션을 재설정하시겠습니까? + + + + Review Training Guide + 트레이닝 가이드 다시보기 + + + + REVIEW + 다시보기 + + + + Review the rules, features, and limitations of openpilot + openpilot의 규칙, 기능 및 제한 다시보기 + + + + Are you sure you want to review the training guide? + 트레이닝 가이드를 다시보시겠습니까? + + + + Regulatory + 규제 + + + + VIEW + 보기 + + + + Change Language + 언어 변경 + + + + CHANGE + 변경 + + + + Select a language + 언어를 선택하세요 + + + + Reboot + 재부팅 + + + + Power Off + 전원 종료 + + + + openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. + openpilot은 장치를 좌측 또는 우측은 4° 이내, 위쪽 5° 또는 아래쪽은 8° 이내로 설치해야 합니다. openpilot은 지속적으로 보정되므로 리셋이 거의 필요하지 않습니다. + + + + Your device is pointed %1° %2 and %3° %4. + 사용자의 장치가 %1° %2 및 %3° %4를 가리키고 있습니다. + + + + down + 아래로 + + + + up + 위로 + + + + left + 좌측으로 + + + + right + 우측으로 + + + + Are you sure you want to reboot? + 재부팅 하시겠습니까? + + + + Disengage to Reboot + 재부팅 하려면 해제하세요 + + + + Are you sure you want to power off? + 전원을 종료하시겠습니까? + + + + Disengage to Power Off + 전원을 종료하려면 해제하세요 + + + + DriveStats + + + Drives + 주행 + + + + Hours + 시간 + + + + ALL TIME + 전체 + + + + PAST WEEK + 지난주 + + + + KM + Km + + + + Miles + Miles + + + + DriverViewScene + + + camera starting + 카메라 시작중 + + + + InputDialog + + + Cancel + 취소 + + + + Need at least %n character(s)! + + 최소 %n 자가 필요합니다! + + + + + Installer + + + Installing... + 설치중... + + + + Receiving objects: + 수신중: + + + + Resolving deltas: + 델타병합: + + + + Updating files: + 파일갱신: + + + + MapETA + + + eta + 도착 + + + + min + + + + + hr + 시간 + + + + km + km + + + + mi + mi + + + + MapInstructions + + + km + km + + + + m + m + + + + mi + mi + + + + ft + ft + + + + MapPanel + + + Current Destination + 현재 목적지 + + + + CLEAR + 삭제 + + + + Recent Destinations + 최근 목적지 + + + + Try the Navigation Beta + 네비게이션(베타)를 사용해보세요 + + + + Get turn-by-turn directions displayed and more with a comma +prime subscription. Sign up now: https://connect.comma.ai + 자세한 경로안내를 원하시면 comma prime을 구독하세요. +등록:https://connect.comma.ai + + + + No home +location set + 집 +설정되지않음 + + + + No work +location set + 회사 +설정되지않음 + + + + no recent destinations + 최근 목적지 없음 + + + + MapWindow + + + Map Loading + 지도 로딩 + + + + Waiting for GPS + GPS를 기다리는 중 + + + + MultiOptionDialog + + + Select + 선택 + + + + Cancel + 취소 + + + + Networking + + + Advanced + 고급 설정 + + + + Enter password + 비밀번호를 입력하세요 + + + + + for "%1" + 하기위한 "%1" + + + + Wrong password + 비밀번호가 틀렸습니다 + + + + NvgWindow + + + km/h + km/h + + + + mph + mph + + + + + MAX + MAX + + + + + SPEED + SPEED + + + + + LIMIT + LIMIT + + + + OffroadHome + + + UPDATE + 업데이트 + + + + ALERTS + 알림 + + + + ALERT + 알림 + + + + PairingPopup + + + Pair your device to your comma account + 장치를 콤마 계정과 페어링합니다 + + + + Go to https://connect.comma.ai on your phone + https://connect.comma.ai에 접속하세요 + + + + Click "add new device" and scan the QR code on the right + "새 장치 추가"를 클릭하고 오른쪽 QR 코드를 검색합니다 + + + + Bookmark connect.comma.ai to your home screen to use it like an app + connect.comma.ai을 앱처럼 사용하려면 홈 화면에 바로가기를 만드십시오 + + + + PrimeAdWidget + + + Upgrade Now + 지금 업그레이드 + + + + Become a comma prime member at connect.comma.ai + connect.comma.ai에서 comma prime에 가입합니다 + + + + PRIME FEATURES: + PRIME 기능: + + + + Remote access + 원격 접속 + + + + 1 year of storage + 1년간 저장 + + + + Developer perks + 개발자 혜택 + + + + PrimeUserWidget + + + ✓ SUBSCRIBED + ✓ 구독함 + + + + comma prime + comma prime + + + + CONNECT.COMMA.AI + CONNECT.COMMA.AI + + + + COMMA POINTS + COMMA POINTS + + + + QObject + + + Reboot + 재부팅 + + + + Exit + 종료 + + + + dashcam + dashcam + + + + openpilot + openpilot + + + + %n minute(s) ago + + %n 분전 + + + + + %n hour(s) ago + + %n 시간전 + + + + + %n day(s) ago + + %n 일전 + + + + + Reset + + + Reset failed. Reboot to try again. + 초기화 실패. 재부팅후 다시 시도하세요. + + + + Are you sure you want to reset your device? + 장치를 초기화 하시겠습니까? + + + + Resetting device... + 장치 초기화중... + + + + System Reset + 장치 초기화 + + + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + 장치를 초기화 합니다. 확인버튼을 누르면 모든 내용과 설정이 초기화됩니다. 취소를 누르면 다시 부팅합니다. + + + + Cancel + 취소 + + + + Reboot + 재부팅 + + + + Confirm + 확인 + + + + Unable to mount data partition. Press confirm to reset your device. + 데이터 파티션을 마운트할 수 없습니다. 확인 버튼을 눌러 장치를 리셋합니다. + + + + RichTextDialog + + + Ok + 확인 + + + + SettingsWindow + + + × + × + + + + Device + 장치 + + + + + Network + 네트워크 + + + + Toggles + 토글 + + + + Software + 소프트웨어 + + + + Navigation + 네비게이션 + + + + Setup + + + WARNING: Low Voltage + 경고: 전압이 낮습니다 + + + + Power your device in a car with a harness or proceed at your own risk. + 하네스 보드에 차량의 전원을 연결하세요. + + + + Power off + 전원 종료 + + + + + + Continue + 계속 + + + + Getting Started + 설정 시작 + + + + Before we get on the road, let’s finish installation and cover some details. + 출발하기 전에 설정을 완료하고 몇 가지 세부 사항을 살펴보겠습니다. + + + + Connect to Wi-Fi + wifi 연결 + + + + + Back + 뒤로 + + + + Continue without Wi-Fi + wifi 없이 계속 + + + + Waiting for internet + 네트워크 접속을 기다립니다 + + + + Choose Software to Install + 설치할 소프트웨어를 선택하세요 + + + + Dashcam + Dashcam + + + + Custom Software + Custom Software + + + + Enter URL + URL 입력 + + + + for Custom Software + for Custom Software + + + + Downloading... + 다운로드중... + + + + Download Failed + 다운로드 실패 + + + + Ensure the entered URL is valid, and the device’s internet connection is good. + 입력된 URL이 유효하고 장치의 인터넷 연결이 잘 되어 있는지 확인합니다. + + + + Reboot device + 재부팅 + + + + Start over + 다시 시작 + + + + SetupWidget + + + Finish Setup + 설정 완료 + + + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + 장치를 (connect.comma.ai)에서 페어링하고 comma prime 오퍼를 청구합니다. + + + + Pair device + 장치 페어링 + + + + Sidebar + + + + CONNECT + 연결 + + + + OFFLINE + 오프라인 + + + + + ONLINE + 온라인 + + + + ERROR + 오류 + + + + + + TEMP + 온도 + + + + HIGH + 높음 + + + + GOOD + 좋음 + + + + OK + 경고 + + + + VEHICLE + 차량 + + + + NO + NO + + + + PANDA + PANDA + + + + GPS + GPS + + + + SEARCH + 검색중 + + + + -- + -- + + + + Wi-Fi + Wi-Fi + + + + ETH + 이더넷 + + + + 2G + 2G + + + + 3G + 3G + + + + LTE + LTE + + + + 5G + 5G + + + + SoftwarePanel + + + Git Branch + Git 브렌치 + + + + Git Commit + Git 커밋 + + + + OS Version + OS 버전 + + + + 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 + 제거 + + + + Uninstall %1 + %1 제거 + + + + Are you sure you want to uninstall? + 제거하시겠습니까? + + + + failed to fetch update + 업데이트를 가져올수없습니다 + + + + + CHECK + 확인 + + + + SshControl + + + SSH Keys + SSH 키 + + + + Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. + 경고:이렇게 하면 GitHub 설정의 모든 공용 키에 대한 SSH 액세스 권한이 부여됩니다. 자신의 사용자 이름이 아닌 GitHub 사용자 이름을 입력하지 마십시오. comma 직원은 GitHub 사용자 이름을 추가하도록 요청하지 않습니다. + + + + + ADD + 추가 + + + + Enter your GitHub username + GitHub 사용자 ID + + + + LOADING + 로딩 + + + + REMOVE + 제거 + + + + Username '%1' has no keys on GitHub + '%1'의 키가 GitHub에 없습니다 + + + + Request timed out + 요청 시간 초과 + + + + Username '%1' doesn't exist on GitHub + '%1'은 GitHub에 없습니다 + + + + SshToggle + + + Enable SSH + SSH 사용 + + + + TermsPage + + + Terms & Conditions + 약관 + + + + Decline + 거절 + + + + Scroll to accept + 허용하려면 아래로 스크롤하세요 + + + + Agree + 동의 + + + + TogglesPanel + + + Enable openpilot + openpilot 사용 + + + + Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + 어댑티브 크루즈 컨트롤 및 차선 유지 운전자 보조를 위해 openpilot 시스템을 사용하십시오. 이 기능을 사용하려면 항상 주의를 기울여야 합니다. 이 설정을 변경하면 차량 전원이 꺼질 때 적용됩니다. + + + + Enable Lane Departure Warnings + 차선 이탈 경고 사용 + + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 차량이 50km/h(31mph) 이상의 속도로 주행하는 동안 방향 지시등이 활성화되지 않은 상태에서 감지된 차선 위를 주행할 경우 차선이탈 경고를 사용합니다. + + + + Use Metric System + 미터법 사용 + + + + Display speed in km/h instead of mph. + mph 대신 km/h로 속도를 표시합니다. + + + + Record and Upload Driver Camera + 운전자 카메라 녹화 및 업로드 + + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 운전자 카메라에서 데이터를 업로드하고 운전자 모니터링 알고리즘을 개선합니다. + + + + Disengage On Accelerator Pedal + 가속페달 조작시 해제 + + + + When enabled, pressing the accelerator pedal will disengage openpilot. + 활성화된 경우 가속 페달을 누르면 openpilot이 해제됩니다. + + + + Show ETA in 24h Format + 24시간 형식으로 도착예정시간 표시 + + + + Use 24h format instead of am/pm + 오전/오후 대신 24시간 형식 사용 + + + + Show Map on Left Side of UI + UI 왼쪽에 지도 표시 + + + + Show map on left side when in split screen view. + 분할 화면 보기에서 지도를 왼쪽에 표시합니다. + + + + openpilot Longitudinal Control + openpilot Longitudinal Control + + + + openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! + openpilot은 차량'의 레이더를 무력화시키고 가속페달과 브레이크의 제어를 인계받을 것이다. 경고: AEB를 비활성화합니다! + + + + Updater + + + Update Required + 업데이트 필요 + + + + An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. + OS 업데이트가 필요합니다. 장치를 wifi에 연결하여 가장 빠른 업데이트 경험을 제공합니다. 다운로드 크기는 약 1GB입니다. + + + + Connect to Wi-Fi + wifi 연결 + + + + Install + 설치 + + + + Back + 뒤로 + + + + Loading... + 로딩중... + + + + Reboot + 재부팅 + + + + Update failed + 업데이트 실패 + + + + WifiUI + + + + Scanning for networks... + 네트워크 검색 중... + + + + CONNECTING... + 연결중... + + + + FORGET + 저장안함 + + + + Forget Wi-Fi Network "%1"? + wifi 네트워크 저장안함 "%1"? + + + diff --git a/selfdrive/ui/translations/main_pt.ts b/selfdrive/ui/translations/main_pt.ts new file mode 100644 index 000000000..912afa38a --- /dev/null +++ b/selfdrive/ui/translations/main_pt.ts @@ -0,0 +1,1307 @@ + + + + + AbstractAlert + + + Close + Fechar + + + + Snooze Update + Adiar Atualização + + + + Reboot and Update + Reiniciar e Atualizar + + + + AdvancedNetworking + + + Back + Voltar + + + + Enable Tethering + Ativar Theter + + + + Tethering Password + Senha Thetering + + + + + EDIT + EDITAR + + + + Enter new tethering password + Insira nova senha thetering + + + + IP Address + IP Endereço + + + + Enable Roaming + Ativar Roaming + + + + APN Setting + APN Config + + + + Enter APN + Insira APN + + + + leave blank for automatic configuration + deixe em branco para configuração automática + + + + ConfirmationDialog + + + + Ok + OK + + + + Cancel + Cancelar + + + + DeclinePage + + + You must accept the Terms and Conditions in order to use openpilot. + Você precisa aceitar os Termos e Condições para utilizar openpilot. + + + + Back + Voltar + + + + Decline, uninstall %1 + Rejeitar, desintalar %1 + + + + DevicePanel + + + Dongle ID + Dongle ID + + + + N/A + N/A + + + + Serial + Serial + + + + Driver Camera + Câmera Motorista + + + + PREVIEW + PREVISUAL + + + + Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) + Pré-visualizar a câmera voltada para o motorista para garantir que monitor tem uma boa visibilidade (veículo precisa estar desligado) + + + + Reset Calibration + Limpar Calibragem + + + + RESET + RESET + + + + Are you sure you want to reset calibration? + Tem certeza que quer limpar calibragem? + + + + Review Training Guide + Revisar o Treinamento + + + + REVIEW + REVISAR + + + + Review the rules, features, and limitations of openpilot + Revisar regras, features e limitações do openpilot + + + + Are you sure you want to review the training guide? + Tem certeza que quer rever o treinamento? + + + + Regulatory + Regulatório + + + + VIEW + VER + + + + Change Language + Mudar Língua + + + + CHANGE + MUDAR + + + + Select a language + Selecione uma linguagem + + + + Reboot + Reiniciar + + + + Power Off + Desligar + + + + openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. + o openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 8° para baixo. o openpilot está continuamente calibrando, a redefinição raramente é necessária. + + + + Your device is pointed %1° %2 and %3° %4. + Seu dispositivo está montado %1° %2 e %3° %4. + + + + down + baixo + + + + up + cima + + + + left + esquerda + + + + right + direita + + + + Are you sure you want to reboot? + Tem certeza que quer reiniciar? + + + + Disengage to Reboot + Desacione para Reiniciar + + + + Are you sure you want to power off? + Tem certeza que quer desligar? + + + + Disengage to Power Off + Desacione para Desligar + + + + DriveStats + + + Drives + Dirigidas + + + + Hours + Horas + + + + ALL TIME + TOTAL + + + + PAST WEEK + SEMANA PASSADA + + + + KM + KM + + + + Miles + Milhas + + + + DriverViewScene + + + camera starting + camera iniciando + + + + InputDialog + + + Cancel + Cancelar + + + + Need at least %n character(s)! + + Necessita no mínimo %n caractere! + Necessita no mínimo %n caracteres! + + + + + Installer + + + Installing... + Instalando... + + + + Receiving objects: + Recebendo objetos: + + + + Resolving deltas: + Resolvendo deltas: + + + + Updating files: + Atualizando arquivos: + + + + MapETA + + + eta + eta + + + + min + min + + + + hr + hr + + + + km + km + + + + mi + mi + + + + MapInstructions + + + km + km + + + + m + m + + + + mi + milha + + + + ft + pés + + + + MapPanel + + + Current Destination + Destino Atual + + + + CLEAR + LIMPAR + + + + Recent Destinations + Destinos Recentes + + + + Try the Navigation Beta + Experimente a Navegação Beta + + + + Get turn-by-turn directions displayed and more with a comma +prime subscription. Sign up now: https://connect.comma.ai + Obtenha instruções passo a passo exibidas e muito mais com +uma assinatura prime Increva-se agora:https://connect.comma.ai + + + + No home +location set + Sem local +residência definido + + + + No work +location set + Sem local +trabalho definido + + + + no recent destinations + sem destinos recentes + + + + MapWindow + + + Map Loading + Carregando Mapa + + + + Waiting for GPS + Esperando por GPS + + + + MultiOptionDialog + + + Select + Selecione + + + + Cancel + Cancelar + + + + Networking + + + Advanced + Avançado + + + + Enter password + Insira a senha + + + + + for "%1" + para "%1" + + + + Wrong password + Senha incorreta + + + + NvgWindow + + + km/h + km/h + + + + mph + mph + + + + + MAX + LIMITE + + + + + SPEED + MAX + + + + + LIMIT + VELO + + + + OffroadHome + + + UPDATE + ATUALIZAÇÃO + + + + ALERTS + ALERTAS + + + + ALERT + ALERTA + + + + PairingPopup + + + Pair your device to your comma account + Pareie seu dispositivo a sua conta comma + + + + Go to https://connect.comma.ai on your phone + navegue até https://connect.comma.ai no seu telefone + + + + Click "add new device" and scan the QR code on the right + Clique "add new device" e escaneie o QR code a seguir + + + + Bookmark connect.comma.ai to your home screen to use it like an app + Salve connect.comma.ai como sua página inicial para utilizar com um app + + + + PrimeAdWidget + + + Upgrade Now + Atualizar Agora + + + + Become a comma prime member at connect.comma.ai + Torne-se um membro comma prime em connect.comma.ai + + + + PRIME FEATURES: + PRIME FEATURES: + + + + Remote access + Acesso remoto + + + + 1 year of storage + 1 ano de armazenamento + + + + Developer perks + Benefícios para desenvolvedor + + + + PrimeUserWidget + + + ✓ SUBSCRIBED + ✓ INSCRITO + + + + comma prime + comma prime + + + + CONNECT.COMMA.AI + CONNECT.COMMA.AI + + + + COMMA POINTS + PONTOS COMMA + + + + QObject + + + Reboot + Reiniciar + + + + Exit + Sair + + + + dashcam + dashcam + + + + openpilot + openpilot + + + + %n minute(s) ago + + há %n minuto + há %n minutos + + + + + %n hour(s) ago + + há %n hora + há %n horas + + + + + %n day(s) ago + + há %n dia + há %n dias + + + + + Reset + + + Reset failed. Reboot to try again. + Reset falhou. Reinicie para tentar novamente. + + + + Are you sure you want to reset your device? + Tem certeza que quer resetar seu dispositivo? + + + + Resetting device... + Resetando dispositivo... + + + + System Reset + Resetar Sistema + + + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + Solicitado reset do sistema. Confirme para apagar todo conteúdo e configurações. Aperte cancelar para continuar boot. + + + + Cancel + Cancelar + + + + Reboot + Reiniciar + + + + Confirm + Confirmar + + + + Unable to mount data partition. Press confirm to reset your device. + Não foi possível montar a partição de dados. Pressione confirmar para resetar seu dispositivo. + + + + RichTextDialog + + + Ok + Ok + + + + SettingsWindow + + + × + × + + + + Device + Dispositivo + + + + + Network + Rede + + + + Toggles + Ajustes + + + + Software + Software + + + + Navigation + Navegação + + + + Setup + + + WARNING: Low Voltage + ALERTA: Baixa Voltagem + + + + Power your device in a car with a harness or proceed at your own risk. + Ligue seu dispositivo em um carro com um chicote ou prossiga por sua conta e risco. + + + + Power off + Desligar + + + + + + Continue + Continuar + + + + Getting Started + Começando + + + + Before we get on the road, let’s finish installation and cover some details. + Antes de pegarmos a estrada, vamos terminar a instalação e cobrir alguns detalhes. + + + + Connect to Wi-Fi + Conectar ao Wi-Fi + + + + + Back + Voltar + + + + Continue without Wi-Fi + Continuar sem Wi-Fi + + + + Waiting for internet + Esperando pela internet + + + + Choose Software to Install + Escolher Software para Instalar + + + + Dashcam + Dashcam + + + + Custom Software + Sofware Customizado + + + + Enter URL + Preencher URL + + + + for Custom Software + para o Software Customizado + + + + Downloading... + Baixando... + + + + Download Failed + Download Falhou + + + + Ensure the entered URL is valid, and the device’s internet connection is good. + Garanta que a URL inserida é valida, e uma boa conexão à internet. + + + + Reboot device + Reiniciar Dispositivo + + + + Start over + Inicializar + + + + SetupWidget + + + Finish Setup + Terminar Configuração + + + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + Pareie seu dispositivo com comma connect (connect.comma.ai) e reivindique sua oferta de comma prime. + + + + Pair device + Parear dispositivo + + + + Sidebar + + + + CONNECT + CONEXÃO + + + + OFFLINE + DESCONEC + + + + + ONLINE + CONECTADO + + + + ERROR + ERRO + + + + + + TEMP + TEMP + + + + HIGH + ALTA + + + + GOOD + BOA + + + + OK + OK + + + + VEHICLE + VEÍCULO + + + + NO + SEM + + + + PANDA + PANDA + + + + GPS + GPS + + + + SEARCH + PROCURA + + + + -- + -- + + + + Wi-Fi + Wi-Fi + + + + ETH + ETH + + + + 2G + 2G + + + + 3G + 3G + + + + LTE + LTE + + + + 5G + 5G + + + + SoftwarePanel + + + Git Branch + Ramo Git + + + + Git Commit + Commit Git + + + + OS Version + Versão do Sistema + + + + Version + Versão + + + + Last Update Check + Verificação da última atualização + + + + The last time openpilot successfully checked for an update. The updater only runs while the car is off. + A última vez que o openpilot verificou com sucesso uma atualização. O atualizador só funciona com o carro desligado. + + + + Check for Update + Verifique atualizações + + + + CHECKING + VERIFICANDO + + + + Switch Branch + Trocar Ramo + + + + ENTER + INSERIR + + + + + The new branch will be pulled the next time the updater runs. + O novo ramo será aplicado na próxima execução do atualizador. + + + + Enter branch name + Inserir o nome do ramo + + + + UNINSTALL + DESINSTALAR + + + + Uninstall %1 + Desintalando %1 + + + + Are you sure you want to uninstall? + Tem certeza que quer desinstalar? + + + + failed to fetch update + falha ao buscar atualização + + + + + CHECK + VERIFICAR + + + + SshControl + + + SSH Keys + Chave SSH + + + + Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. + Aviso: isso concede acesso SSH a todas as chaves públicas nas configurações do GitHub. Nunca insira um nome de usuário do GitHub que não seja o seu. Um funcionário da comma NUNCA pedirá que você adicione seu nome de usuário do GitHub. + + + + + ADD + ADICIONAR + + + + Enter your GitHub username + Insira seu nome de usuário do GitHub + + + + LOADING + CARREGANDO + + + + REMOVE + REMOVER + + + + Username '%1' has no keys on GitHub + Usuário "%1” não possui chaves no GitHub + + + + Request timed out + A solicitação expirou + + + + Username '%1' doesn't exist on GitHub + Usuário '%1' não existe no GitHub + + + + SshToggle + + + Enable SSH + Habilitar SSH + + + + TermsPage + + + Terms & Conditions + Termos & Condições + + + + Decline + Declinar + + + + Scroll to accept + Role para aceitar + + + + Agree + Concordo + + + + TogglesPanel + + + Enable openpilot + Ativar openpilot + + + + Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + Use o sistema openpilot para controle de cruzeiro adaptativo e assistência ao motorista de manutenção de faixa. Sua atenção é necessária o tempo todo para usar esse recurso. A alteração desta configuração tem efeito quando o carro é desligado. + + + + Enable Lane Departure Warnings + Ativar Avisos de Saída de Faixa + + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + Receba alertas para voltar para a pista se o seu veículo sair da faixa e a seta não tiver sido acionada previamente quando em velocidades superiores a 50 km/h. + + + + Use Metric System + Usar Sistema Métrico + + + + Display speed in km/h instead of mph. + Exibir velocidade em km/h invés de mph. + + + + Record and Upload Driver Camera + Gravar e Upload Câmera Motorista + + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor. + + + + Disengage On Accelerator Pedal + Desacionar Com Pedal Do Acelerador + + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Quando ativado, pressionar o pedal do acelerador desacionará o openpilot. + + + + Show ETA in 24h Format + Mostrar ETA em formato 24h + + + + Use 24h format instead of am/pm + Use o formato 24h em vez de am/pm + + + + Show Map on Left Side of UI + Exibir Mapa no Lado Esquerdo + + + + Show map on left side when in split screen view. + Exibir mapa do lado esquerdo quando a tela for dividida. + + + + openpilot Longitudinal Control + openpilot Controle Longitudinal + + + + openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! + openpilot desativará o radar do carro e assumirá o controle do acelerador e freios. Atenção: isso desativa AEB! + + + + Updater + + + Update Required + Atualização Necessária + + + + An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. + Uma atualização do sistema operacional é necessária. Conecte seu dispositivo ao Wi-Fi para a experiência de atualização mais rápida. O tamanho do download é de aproximadamente 1GB. + + + + Connect to Wi-Fi + Conecte-se ao Wi-Fi + + + + Install + Instalar + + + + Back + Voltar + + + + Loading... + Carregando... + + + + Reboot + Reiniciar + + + + Update failed + Falha na atualização + + + + WifiUI + + + + Scanning for networks... + Procurando redes... + + + + CONNECTING... + CONECTANDO... + + + + FORGET + ESQUECER + + + + Forget Wi-Fi Network "%1"? + Esquecer Rede Wi-Fi "%1"? + + + diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts new file mode 100644 index 000000000..1ed96422b --- /dev/null +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -0,0 +1,1301 @@ + + + + + AbstractAlert + + + Close + 关闭 + + + + Snooze Update + 暂停更新 + + + + Reboot and Update + 重启并更新 + + + + AdvancedNetworking + + + Back + 返回 + + + + Enable Tethering + 启用WiFi热点 + + + + Tethering Password + WiFi热点密码 + + + + + EDIT + 编辑 + + + + Enter new tethering password + 输入新的WiFi热点密码 + + + + IP Address + IP地址 + + + + Enable Roaming + 启用数据漫游 + + + + APN Setting + APN设置 + + + + Enter APN + 输入APN + + + + leave blank for automatic configuration + 留空以自动配置 + + + + ConfirmationDialog + + + + Ok + 好的 + + + + Cancel + 取消 + + + + DeclinePage + + + You must accept the Terms and Conditions in order to use openpilot. + 您必须接受条款和条件以使用openpilot。 + + + + Back + 返回 + + + + Decline, uninstall %1 + 拒绝并卸载%1 + + + + DevicePanel + + + Dongle ID + 设备ID(Dongle ID) + + + + N/A + N/A + + + + Serial + 序列号 + + + + Driver Camera + 驾驶员摄像头 + + + + PREVIEW + 预览 + + + + Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) + 打开并预览驾驶员摄像头,以确保驾驶员监控具有良好视野。仅熄火时可用。 + + + + Reset Calibration + 重置设备校准 + + + + RESET + 重置 + + + + Are you sure you want to reset calibration? + 您确定要重置设备校准吗? + + + + Review Training Guide + 新手指南 + + + + REVIEW + 查看 + + + + Review the rules, features, and limitations of openpilot + 查看openpilot的使用规则,以及其功能和限制。 + + + + Are you sure you want to review the training guide? + 您确定要查看新手指南吗? + + + + Regulatory + 监管信息 + + + + VIEW + 查看 + + + + Change Language + 切换语言 + + + + CHANGE + 切换 + + + + Select a language + 选择语言 + + + + Reboot + 重启 + + + + Power Off + 关机 + + + + openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. + openpilot要求设备安装的偏航角在左4°和右4°之间,俯仰角在上5°和下8°之间。一般来说,openpilot会持续更新校准,很少需要重置。 + + + + Your device is pointed %1° %2 and %3° %4. + 您的设备校准为%1° %2、%3° %4。 + + + + down + 朝下 + + + + up + 朝上 + + + + left + 朝左 + + + + right + 朝右 + + + + Are you sure you want to reboot? + 您确定要重新启动吗? + + + + Disengage to Reboot + 取消openpilot以重新启动 + + + + Are you sure you want to power off? + 您确定要关机吗? + + + + Disengage to Power Off + 取消openpilot以关机 + + + + DriveStats + + + Drives + 旅程数 + + + + Hours + 小时 + + + + ALL TIME + 全部 + + + + PAST WEEK + 过去一周 + + + + KM + 公里 + + + + Miles + 英里 + + + + DriverViewScene + + + camera starting + 正在启动相机 + + + + InputDialog + + + Cancel + 取消 + + + + Need at least %n character(s)! + + 至少需要 %n 个字符! + + + + + Installer + + + Installing... + 正在安装…… + + + + Receiving objects: + 正在接收: + + + + Resolving deltas: + 正在处理: + + + + Updating files: + 正在更新文件: + + + + MapETA + + + eta + 埃塔 + + + + min + 分钟 + + + + hr + 小时 + + + + km + km + + + + mi + mi + + + + MapInstructions + + + km + km + + + + m + m + + + + mi + mi + + + + ft + ft + + + + MapPanel + + + Current Destination + 当前目的地 + + + + CLEAR + 清空 + + + + Recent Destinations + 最近目的地 + + + + Try the Navigation Beta + 试用导航测试版 + + + + Get turn-by-turn directions displayed and more with a comma +prime subscription. Sign up now: https://connect.comma.ai + 订阅comma prime以获取导航。 +立即注册:https://connect.comma.ai + + + + No home +location set + 家:未设定 + + + + No work +location set + 工作:未设定 + + + + no recent destinations + 无最近目的地 + + + + MapWindow + + + Map Loading + 地图加载中 + + + + Waiting for GPS + 等待 GPS + + + + MultiOptionDialog + + + Select + 选择 + + + + Cancel + 取消 + + + + Networking + + + Advanced + 高级 + + + + Enter password + 输入密码 + + + + + for "%1" + 网络名称:"%1" + + + + Wrong password + 密码错误 + + + + NvgWindow + + + km/h + km/h + + + + mph + mph + + + + + MAX + 最高定速 + + + + + SPEED + SPEED + + + + + LIMIT + LIMIT + + + + OffroadHome + + + UPDATE + 更新 + + + + ALERTS + 警报 + + + + ALERT + 警报 + + + + PairingPopup + + + Pair your device to your comma account + 将您的设备与comma账号配对 + + + + Go to https://connect.comma.ai on your phone + 在手机上访问 https://connect.comma.ai + + + + Click "add new device" and scan the QR code on the right + 点击“添加新设备”,扫描右侧二维码 + + + + Bookmark connect.comma.ai to your home screen to use it like an app + 将 connect.comma.ai 收藏到您的主屏幕,以便像应用程序一样使用它 + + + + PrimeAdWidget + + + Upgrade Now + 现在升级 + + + + Become a comma prime member at connect.comma.ai + 打开connect.comma.ai以注册comma prime会员 + + + + PRIME FEATURES: + comma prime特权: + + + + Remote access + 远程访问 + + + + 1 year of storage + 1年数据存储 + + + + Developer perks + 开发者福利 + + + + PrimeUserWidget + + + ✓ SUBSCRIBED + ✓ 已订阅 + + + + comma prime + comma prime + + + + CONNECT.COMMA.AI + CONNECT.COMMA.AI + + + + COMMA POINTS + COMMA POINTS点数 + + + + QObject + + + Reboot + 重启 + + + + Exit + 退出 + + + + dashcam + 行车记录仪 + + + + openpilot + openpilot + + + + %n minute(s) ago + + %n 分钟前 + + + + + %n hour(s) ago + + %n 小时前 + + + + + %n day(s) ago + + %n 天前 + + + + + Reset + + + Reset failed. Reboot to try again. + 重置失败。 重新启动以重试。 + + + + Are you sure you want to reset your device? + 您确定要重置您的设备吗? + + + + Resetting device... + 正在重置设备…… + + + + System Reset + 恢复出厂设置 + + + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + 已触发系统重置:确认以删除所有内容和设置。取消以正常启动设备。 + + + + Cancel + 取消 + + + + Reboot + 重启 + + + + Confirm + 确认 + + + + Unable to mount data partition. Press confirm to reset your device. + 无法挂载数据分区。 确认以重置您的设备。 + + + + RichTextDialog + + + Ok + 好的 + + + + SettingsWindow + + + × + × + + + + Device + 设备 + + + + + Network + 网络 + + + + Toggles + 设定 + + + + Software + 软件 + + + + Navigation + 导航 + + + + Setup + + + WARNING: Low Voltage + 警告:低电压 + + + + Power your device in a car with a harness or proceed at your own risk. + 请使用car harness线束为您的设备供电,或自行承担风险。 + + + + Power off + 关机 + + + + + + Continue + 继续 + + + + Getting Started + 开始设置 + + + + Before we get on the road, let’s finish installation and cover some details. + 开始旅程之前,让我们完成安装并介绍一些细节。 + + + + Connect to Wi-Fi + 连接到WiFi + + + + + Back + 返回 + + + + Continue without Wi-Fi + 不连接WiFi并继续 + + + + Waiting for internet + 等待网络连接 + + + + Choose Software to Install + 选择要安装的软件 + + + + Dashcam + Dashcam(行车记录仪) + + + + Custom Software + 自定义软件 + + + + Enter URL + 输入网址 + + + + for Custom Software + 以下载自定义软件 + + + + Downloading... + 正在下载…… + + + + Download Failed + 下载失败 + + + + Ensure the entered URL is valid, and the device’s internet connection is good. + 请确保互联网连接良好且输入的URL有效。 + + + + Reboot device + 重启设备 + + + + Start over + 重来 + + + + SetupWidget + + + Finish Setup + 完成设置 + + + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + 将您的设备与comma connect (connect.comma.ai)配对并领取您的comma prime优惠。 + + + + Pair device + 配对设备 + + + + Sidebar + + + + CONNECT + CONNECT + + + + OFFLINE + 离线 + + + + + ONLINE + 在线 + + + + ERROR + 连接出错 + + + + + + TEMP + 设备温度 + + + + HIGH + 过热 + + + + GOOD + 良好 + + + + OK + 一般 + + + + VEHICLE + 车辆连接 + + + + NO + + + + + PANDA + PANDA + + + + GPS + GPS + + + + SEARCH + 搜索中 + + + + -- + -- + + + + Wi-Fi + Wi-Fi + + + + ETH + 以太网 + + + + 2G + 2G + + + + 3G + 3G + + + + LTE + LTE + + + + 5G + 5G + + + + 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. + 上一次成功检查更新的时间。更新程序仅在汽车熄火时运行。 + + + + Check for Update + 检查更新 + + + + CHECKING + 正在检查更新 + + + + Switch Branch + 切换分支 + + + + ENTER + 输入 + + + + + The new branch will be pulled the next time the updater runs. + 分支将在更新服务下次启动时自动切换。 + + + + Enter branch name + 输入分支名称 + + + + UNINSTALL + 卸载 + + + + Uninstall %1 + 卸载 %1 + + + + Are you sure you want to uninstall? + 您确定要卸载吗? + + + + failed to fetch update + 获取更新失败 + + + + + CHECK + 查看 + + + + SshControl + + + SSH Keys + SSH密钥 + + + + Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. + 警告:这将授予SSH访问权限给您GitHub设置中的所有公钥。切勿输入您自己以外的GitHub用户名。comma员工永远不会要求您添加他们的GitHub用户名。 + + + + + ADD + 添加 + + + + Enter your GitHub username + 输入您的GitHub用户名 + + + + LOADING + 正在加载 + + + + REMOVE + 删除 + + + + Username '%1' has no keys on GitHub + 用户名“%1”在GitHub上没有密钥 + + + + Request timed out + 请求超时 + + + + Username '%1' doesn't exist on GitHub + GitHub上不存在用户名“%1” + + + + SshToggle + + + Enable SSH + 启用SSH + + + + TermsPage + + + Terms & Conditions + 条款和条件 + + + + Decline + 拒绝 + + + + Scroll to accept + 滑动以接受 + + + + Agree + 同意 + + + + TogglesPanel + + + Enable openpilot + 启用openpilot + + + + Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + 使用openpilot进行自适应巡航和车道保持辅助。使用此功能时您必须时刻保持注意力。该设置的更改在熄火时生效。 + + + + Enable Lane Departure Warnings + 启用车道偏离警告 + + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 车速超过31mph(50km/h)时,若检测到车辆越过车道线且未打转向灯,系统将发出警告以提醒您返回车道。 + + + + Use Metric System + 使用公制单位 + + + + Display speed in km/h instead of mph. + 显示车速时,以km/h代替mph。 + + + + Record and Upload Driver Camera + 录制并上传驾驶员摄像头 + + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 上传驾驶员摄像头的数据,帮助改进驾驶员监控算法。 + + + + Disengage On Accelerator Pedal + 踩油门时取消控制 + + + + When enabled, pressing the accelerator pedal will disengage openpilot. + 启用后,踩下油门踏板将取消openpilot。 + + + + Show ETA in 24h Format + 以24小时格式显示预计到达时间 + + + + Use 24h format instead of am/pm + 使用24小时制代替am/pm + + + + Show Map on Left Side of UI + 在介面左侧显示地图 + + + + Show map on left side when in split screen view. + 在分屏模式中,将地图置于屏幕左侧。 + + + + openpilot Longitudinal Control + openpilot纵向控制 + + + + openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! + openpilot将禁用车辆的雷达并接管油门和刹车的控制。警告:AEB将被禁用! + + + + Updater + + + Update Required + 需要更新 + + + + An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. + 操作系统需要更新。请将您的设备连接到WiFi以获取更快的更新体验。下载大小约为1GB。 + + + + Connect to Wi-Fi + 连接到WiFi + + + + Install + 安装 + + + + Back + 返回 + + + + Loading... + 正在加载…… + + + + Reboot + 重启 + + + + Update failed + 更新失败 + + + + WifiUI + + + + Scanning for networks... + 正在扫描网络…… + + + + CONNECTING... + 正在连接…… + + + + FORGET + 忘记 + + + + Forget Wi-Fi Network "%1"? + 忘记WiFi网络 "%1"? + + + diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts new file mode 100644 index 000000000..4f5c37764 --- /dev/null +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -0,0 +1,1303 @@ + + + + + AbstractAlert + + + Close + 關閉 + + + + Snooze Update + 暫停更新 + + + + Reboot and Update + 重啟並更新 + + + + AdvancedNetworking + + + Back + 回上頁 + + + + Enable Tethering + 啟用網路分享 + + + + Tethering Password + 網路分享密碼 + + + + + EDIT + 編輯 + + + + Enter new tethering password + 輸入新的網路分享密碼 + + + + IP Address + IP 地址 + + + + Enable Roaming + 啟用漫遊 + + + + APN Setting + APN 設置 + + + + Enter APN + 輸入 APN + + + + leave blank for automatic configuration + 留空白將自動配置 + + + + ConfirmationDialog + + + + Ok + 確定 + + + + Cancel + 取消 + + + + DeclinePage + + + You must accept the Terms and Conditions in order to use openpilot. + 您必須先接受條款和條件才能使用 openpilot。 + + + + Back + 回上頁 + + + + Decline, uninstall %1 + 拒絕並卸載 %1 + + + + DevicePanel + + + Dongle ID + Dongle ID + + + + N/A + 無法使用 + + + + Serial + 序號 + + + + Driver Camera + 駕駛員攝像頭 + + + + PREVIEW + 預覽 + + + + Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) + 預覽駕駛員監控鏡頭畫面,以確保其具有良好視野。(僅在熄火時可用) + + + + Reset Calibration + 重置校準 + + + + RESET + 重置 + + + + Are you sure you want to reset calibration? + 您確定要重置校準嗎? + + + + Review Training Guide + 觀看使用教學 + + + + REVIEW + 觀看 + + + + Review the rules, features, and limitations of openpilot + 觀看 openpilot 的使用規則、功能和限制 + + + + Are you sure you want to review the training guide? + 您確定要觀看使用教學嗎? + + + + Regulatory + 法規/監管 + + + + VIEW + 觀看 + + + + Change Language + 更改語言 + + + + CHANGE + 更改 + + + + Select a language + 選擇語言 + + + + Reboot + 重新啟動 + + + + Power Off + 關機 + + + + openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. + openpilot 需要將裝置固定在左右偏差 4° 以內,朝上偏差 5° 以内或朝下偏差 8° 以内。鏡頭在後台會持續自動校準,很少有需要重置的情况。 + + + + Your device is pointed %1° %2 and %3° %4. + 你的設備目前朝%2 %1° 以及朝%4 %3° 。 + + + + down + + + + + up + + + + + left + + + + + right + + + + + Are you sure you want to reboot? + 您確定要重新啟動嗎? + + + + Disengage to Reboot + 請先取消控車才能重新啟動 + + + + Are you sure you want to power off? + 您確定您要關機嗎? + + + + Disengage to Power Off + 請先取消控車才能關機 + + + + DriveStats + + + Drives + 旅程 + + + + Hours + 小時 + + + + ALL TIME + 總共 + + + + PAST WEEK + 上周 + + + + KM + 公里 + + + + Miles + 英里 + + + + DriverViewScene + + + camera starting + 開啟相機中 + + + + InputDialog + + + Cancel + 取消 + + + + Need at least %n character(s)! + + 需要至少 %n 個字元! + + + + + Installer + + + Installing... + 安裝中… + + + + Receiving objects: + 接收對象: + + + + Resolving deltas: + 分析差異: + + + + Updating files: + 更新檔案: + + + + MapETA + + + eta + 抵達 + + + + min + 分鐘 + + + + hr + 小時 + + + + km + km + + + + mi + mi + + + + MapInstructions + + + km + km + + + + m + m + + + + mi + mi + + + + ft + ft + + + + MapPanel + + + Current Destination + 當前目的地 + + + + CLEAR + 清除 + + + + Recent Destinations + 最近目的地 + + + + Try the Navigation Beta + 試用導航功能 + + + + Get turn-by-turn directions displayed and more with a comma +prime subscription. Sign up now: https://connect.comma.ai + 成為 comma 高級會員來使用導航功能 +立即註冊:https://connect.comma.ai + + + + No home +location set + 未設定 +住家位置 + + + + No work +location set + 未設定 +工作位置 + + + + no recent destinations + 沒有最近的導航記錄 + + + + MapWindow + + + Map Loading + 地圖加載中 + + + + Waiting for GPS + 等待 GPS + + + + MultiOptionDialog + + + Select + 選擇 + + + + Cancel + 取消 + + + + Networking + + + Advanced + 進階 + + + + Enter password + 輸入密碼 + + + + + for "%1" + 給 "%1" + + + + Wrong password + 密碼錯誤 + + + + NvgWindow + + + km/h + km/h + + + + mph + mph + + + + + MAX + 最高 + + + + + SPEED + 速度 + + + + + LIMIT + 速限 + + + + OffroadHome + + + UPDATE + 更新 + + + + ALERTS + 提醒 + + + + ALERT + 提醒 + + + + PairingPopup + + + Pair your device to your comma account + 將設備與您的 comma 帳號配對 + + + + Go to https://connect.comma.ai on your phone + 用手機連至 https://connect.comma.ai + + + + Click "add new device" and scan the QR code on the right + 點選 "add new device" 後掃描右邊的二維碼 + + + + Bookmark connect.comma.ai to your home screen to use it like an app + 將 connect.comma.ai 加入您的主屏幕,以便像手機 App 一樣使用它 + + + + PrimeAdWidget + + + Upgrade Now + 馬上升級 + + + + Become a comma prime member at connect.comma.ai + 成為 connect.comma.ai 的高級會員 + + + + PRIME FEATURES: + 高級會員特點: + + + + Remote access + 遠程訪問 + + + + 1 year of storage + 一年的雲端行車記錄 + + + + Developer perks + 開發者福利 + + + + PrimeUserWidget + + + ✓ SUBSCRIBED + ✓ 已訂閱 + + + + comma prime + comma 高級會員 + + + + CONNECT.COMMA.AI + CONNECT.COMMA.AI + + + + COMMA POINTS + COMMA 積分 + + + + QObject + + + Reboot + 重新啟動 + + + + Exit + 離開 + + + + dashcam + 行車記錄器 + + + + openpilot + openpilot + + + + %n minute(s) ago + + %n 分鐘前 + + + + + %n hour(s) ago + + %n 小時前 + + + + + %n day(s) ago + + %n 天前 + + + + + Reset + + + Reset failed. Reboot to try again. + 重置失敗。請重新啟動後再試。 + + + + Are you sure you want to reset your device? + 您確定要重置你的設備嗎? + + + + Resetting device... + 重置設備中… + + + + System Reset + 系統重置 + + + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + 系統重置已觸發。請按確認刪除所有內容和設置。按取消恢復啟動。 + + + + Cancel + 取消 + + + + Reboot + 重新啟動 + + + + Confirm + 確認 + + + + Unable to mount data partition. Press confirm to reset your device. + 無法掛載數據分區。請按確認重置您的設備。 + + + + RichTextDialog + + + Ok + 確定 + + + + SettingsWindow + + + × + × + + + + Device + 設備 + + + + + Network + 網路 + + + + Toggles + 設定 + + + + Software + 軟體 + + + + Navigation + 導航 + + + + Setup + + + WARNING: Low Voltage + 警告:電壓過低 + + + + Power your device in a car with a harness or proceed at your own risk. + 請使用車上 harness 提供的電源,若繼續的話您需要自擔風險。 + + + + Power off + 關機 + + + + + + Continue + 繼續 + + + + Getting Started + 入門 + + + + Before we get on the road, let’s finish installation and cover some details. + 在我們上路之前,讓我們完成安裝並介紹一些細節。 + + + + Connect to Wi-Fi + 連接到無線網絡 + + + + + Back + 回上頁 + + + + Continue without Wi-Fi + 在沒有 Wi-Fi 的情況下繼續 + + + + Waiting for internet + 連接至網路中 + + + + Choose Software to Install + 選擇要安裝的軟體 + + + + Dashcam + 行車記錄器 + + + + Custom Software + 定制的軟體 + + + + Enter URL + 輸入網址 + + + + for Custom Software + 定制的軟體 + + + + Downloading... + 下載中… + + + + Download Failed + 下載失敗 + + + + Ensure the entered URL is valid, and the device’s internet connection is good. + 請確定您輸入的是有效的安裝網址,並且確定設備的網路連線狀態良好。 + + + + Reboot device + 重新啟動 + + + + Start over + 重新開始 + + + + SetupWidget + + + Finish Setup + 完成設置 + + + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + 將您的設備與 comma connect (connect.comma.ai) 配對並領取您的 comma 高級會員優惠。 + + + + Pair device + 配對設備 + + + + Sidebar + + + + CONNECT + 雲端服務 + + + + OFFLINE + 已離線 + + + + + ONLINE + 已連線 + + + + ERROR + 錯誤 + + + + + + TEMP + 溫度 + + + + HIGH + 偏高 + + + + GOOD + 正常 + + + + OK + 一般 + + + + VEHICLE + 車輛通訊 + + + + NO + 未連線 + + + + PANDA + 車輛通訊 + + + + GPS + GPS + + + + SEARCH + 車輛通訊 + + + + -- + -- + + + + Wi-Fi + + + + + ETH + + + + + 2G + + + + + 3G + + + + + LTE + + + + + 5G + + + + + SoftwarePanel + + + Git Branch + Git 分支 + + + + Git Commit + Git 提交 + + + + OS Version + 系統版本 + + + + Version + 版本 + + + + Last Update Check + 上次檢查時間 + + + + The last time openpilot successfully checked for an update. The updater only runs while the car is off. + 上次成功檢查更新的時間。更新系統只會在車子熄火時執行。 + + + + Check for Update + 檢查更新 + + + + CHECKING + 檢查中 + + + + Switch Branch + 切換分支 + + + + ENTER + 切換 + + + + + The new branch will be pulled the next time the updater runs. + 新的分支將會在下次檢查更新時切換過去。 + + + + Enter branch name + 輸入分支名稱 + + + + UNINSTALL + 卸載 + + + + Uninstall %1 + 卸載 %1 + + + + Are you sure you want to uninstall? + 您確定您要卸載嗎? + + + + failed to fetch update + 下載更新失敗 + + + + + CHECK + 檢查 + + + + SshControl + + + SSH Keys + SSH 密鑰 + + + + Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. + 警告:這將授權給 GitHub 帳號中所有公鑰 SSH 訪問權限。切勿輸入非您自己的 GitHub 用戶名。comma 員工「永遠不會」要求您添加他們的 GitHub 用戶名。 + + + + + ADD + 新增 + + + + Enter your GitHub username + 請輸入您 GitHub 的用戶名 + + + + LOADING + 載入中 + + + + REMOVE + 移除 + + + + Username '%1' has no keys on GitHub + GitHub 用戶 '%1' 沒有設定任何密鑰 + + + + Request timed out + 請求超時 + + + + Username '%1' doesn't exist on GitHub + GitHub 用戶 '%1' 不存在 + + + + SshToggle + + + Enable SSH + 啟用 SSH 服務 + + + + TermsPage + + + Terms & Conditions + 條款和條件 + + + + Decline + 拒絕 + + + + Scroll to accept + 滑動至頁尾接受條款 + + + + Agree + 接受 + + + + TogglesPanel + + + Enable openpilot + 啟用 openpilot + + + + Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + 使用 openpilot 的主動式巡航和車道保持功能,開啟後您需要持續集中注意力,設定變更在重新啟動車輛後生效。 + + + + Enable Lane Departure Warnings + 啟用車道偏離警告 + + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 車速在時速 50 公里 (31 英里) 以上且未打方向燈的情況下,如果偵測到車輛駛出目前車道線時,發出車道偏離警告。 + + + + Use Metric System + 使用公制單位 + + + + Display speed in km/h instead of mph. + 啟用後,速度單位顯示將從 mp/h 改為 km/h。 + + + + Record and Upload Driver Camera + 記錄並上傳駕駛監控影像 + + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 上傳駕駛監控的錄像來協助我們提升駕駛監控的準確率。 + + + + Disengage On Accelerator Pedal + 油門取消控車 + + + + When enabled, pressing the accelerator pedal will disengage openpilot. + 啟用後,踩踏油門將會取消 openpilot 控制。 + + + + Show ETA in 24h Format + 預計到達時間單位改用 24 小時制 + + + + Use 24h format instead of am/pm + 使用 24 小時制。(預設值為 12 小時制) + + + + Show Map on Left Side of UI + 將地圖顯示在畫面的左側 + + + + Show map on left side when in split screen view. + 進入分割畫面後,地圖將會顯示在畫面的左側。 + + + + openpilot Longitudinal Control + openpilot 縱向控制 + + + + openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! + openpilot 將會關閉雷達訊號並接管油門和剎車的控制。注意:這也會關閉自動緊急煞車 (AEB) 系統! + + + + Updater + + + Update Required + 系統更新 + + + + An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. + 設備的操作系統需要更新。請將您的設備連接到 Wi-Fi 以獲得最快的更新體驗。下載大小約為 1GB。 + + + + Connect to Wi-Fi + 連接到無線網絡 + + + + Install + 安裝 + + + + Back + 回上頁 + + + + Loading... + 載入中… + + + + Reboot + 重新啟動 + + + + Update failed + 更新失敗 + + + + WifiUI + + + + Scanning for networks... + 掃描無線網路中... + + + + CONNECTING... + 連線中... + + + + FORGET + 清除 + + + + Forget Wi-Fi Network "%1"? + 清除 Wi-Fi 網路 "%1"? + + + diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 4d1e1ab74..317ef497a 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -55,10 +55,13 @@ static void update_leads(UIState *s, const cereal::RadarState::Reader &radar_sta } static void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTData::Reader &line, - float y_off, float z_off, line_vertices_data *pvd, int max_idx, bool allow_invert=true) { + float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); - std::vector left_points, right_points; + QPolygonF left_points, right_points; + left_points.reserve(max_idx + 1); + right_points.reserve(max_idx + 1); + for (int i = 0; i <= max_idx; i++) { QPointF left, right; bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off, &left); @@ -69,19 +72,10 @@ static void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTDa continue; } left_points.push_back(left); - right_points.push_back(right); + right_points.push_front(right); } } - - pvd->cnt = 2 * left_points.size(); - assert(left_points.size() == right_points.size()); - assert(pvd->cnt <= std::size(pvd->v)); - - for (int left_idx = 0; left_idx < left_points.size(); left_idx++){ - int right_idx = 2 * left_points.size() - left_idx - 1; - pvd->v[left_idx] = left_points[left_idx]; - pvd->v[right_idx] = right_points[left_idx]; - } + *pvd = left_points + right_points; } static void update_model(UIState *s, const cereal::ModelDataV2::Reader &model) { @@ -140,6 +134,7 @@ static void update_state(UIState *s) { scene.view_from_calib.v[i*3 + j] = view_from_calib(i,j); } } + scene.calibration_valid = sm["liveCalibration"].getLiveCalibration().getCalStatus() == 1; } if (s->worldObjectsVisible()) { if (sm.updated("modelV2")) { @@ -197,7 +192,9 @@ static void update_state(UIState *s) { } void ui_update_params(UIState *s) { - s->scene.is_metric = Params().getBool("IsMetric"); + auto params = Params(); + s->scene.is_metric = params.getBool("IsMetric"); + s->scene.map_on_left = params.getBool("NavSettingLeftSide"); } void UIState::updateStatus() { @@ -232,7 +229,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", "pandaStates", "carParams", "driverMonitoringState", "sensorEvents", "carState", "liveLocationKalman", - "wideRoadCameraState", "managerState", "navInstruction", "navRoute", + "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements", }); Params params; @@ -251,7 +248,7 @@ void UIState::update() { updateStatus(); if (sm->frame % UI_FREQ == 0) { - watchdog_kick(); + watchdog_kick(nanos_since_boot()); } emit uiUpdate(*this); } diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index cbf3921e4..16f78cdef 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "cereal/messaging/messaging.h" @@ -22,10 +23,7 @@ const int footer_h = 280; const int UI_FREQ = 20; // Hz typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert; -// TODO: this is also hardcoded in common/transformations/camera.py -// TODO: choose based on frame input size -const float y_offset = 150.0; -const float ZOOM = 2912.8; +const mat3 DEFAULT_CALIBRATION = {{ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 }}; struct Alert { QString text1; @@ -87,27 +85,23 @@ const QColor bg_colors [] = { [STATUS_ALERT] = QColor(0xC9, 0x22, 0x31, 0xf1), }; -typedef struct { - QPointF v[TRAJECTORY_SIZE * 2]; - int cnt; -} line_vertices_data; - typedef struct UIScene { - mat3 view_from_calib; + bool calibration_valid = false; + mat3 view_from_calib = DEFAULT_CALIBRATION; cereal::PandaState::PandaType pandaType; // modelV2 float lane_line_probs[4]; float road_edge_stds[2]; - line_vertices_data track_vertices; - line_vertices_data lane_line_vertices[4]; - line_vertices_data road_edge_vertices[2]; + QPolygonF track_vertices; + QPolygonF lane_line_vertices[4]; + QPolygonF road_edge_vertices[2]; // lead QPointF lead_vertices[2]; float light_sensor, accel_sensor, gyro_sensor; - bool started, ignition, is_metric, longitudinal_control; + bool started, ignition, is_metric, map_on_left, longitudinal_control; uint64_t started_frame; } UIScene; diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py new file mode 100755 index 000000000..e509168ad --- /dev/null +++ b/selfdrive/ui/update_translations.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import argparse +import json +import os + +from common.basedir import BASEDIR + +UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") +TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") +LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") + + +def update_translations(vanish=False, plural_only=None, translations_dir=TRANSLATIONS_DIR): + if plural_only is None: + plural_only = [] + + with open(LANGUAGES_FILE, "r") as f: + translation_files = json.load(f) + + for file in translation_files.values(): + tr_file = os.path.join(translations_dir, f"{file}.ts") + args = f"lupdate -recursive {UI_DIR} -ts {tr_file}" + if vanish: + args += " -no-obsolete" + if file in plural_only: + args += " -pluralonly" + ret = os.system(args) + assert ret == 0 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Update translation files for UI", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("--vanish", action="store_true", help="Remove translations with source text no longer found") + parser.add_argument("--plural-only", type=str, nargs="*", default=["main_en"], help="Translation codes to only create plural translations for (ie. the base language)") + args = parser.parse_args() + + update_translations(args.vanish, args.plural_only) diff --git a/selfdrive/ui/watch3.cc b/selfdrive/ui/watch3.cc index 00d23ea97..d6b5cc67a 100644 --- a/selfdrive/ui/watch3.cc +++ b/selfdrive/ui/watch3.cc @@ -19,6 +19,7 @@ int main(int argc, char *argv[]) { { QHBoxLayout *hlayout = new QHBoxLayout(); layout->addLayout(hlayout); + hlayout->addWidget(new CameraViewWidget("navd", VISION_STREAM_MAP, false)); hlayout->addWidget(new CameraViewWidget("camerad", VISION_STREAM_ROAD, false)); } diff --git a/selfdrive/updated.py b/selfdrive/updated.py index bdec383f5..e8905962b 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -314,18 +314,26 @@ def fetch_update(wait_helper: WaitTimeHelper) -> bool: new_version: bool = cur_hash != upstream_hash git_fetch_result = check_git_fetch_result(git_fetch_output) + new_branch = Params().get("SwitchToBranch", encoding='utf8') + if new_branch is not None: + new_version = True + cloudlog.info(f"comparing {cur_hash} to {upstream_hash}") if new_version or git_fetch_result: cloudlog.info("Running update") if new_version: cloudlog.info("git reset in progress") - r = [ - run(["git", "reset", "--hard", "@{u}"], OVERLAY_MERGED, low_priority=True), - run(["git", "clean", "-xdf"], OVERLAY_MERGED, low_priority=True ), - run(["git", "submodule", "init"], OVERLAY_MERGED, low_priority=True), - run(["git", "submodule", "update"], OVERLAY_MERGED, low_priority=True), + cmds = [ + ["git", "reset", "--hard", "@{u}"], + ["git", "clean", "-xdf"], + ["git", "submodule", "init"], + ["git", "submodule", "update"], ] + if new_branch is not None: + cloudlog.info(f"switching to branch {repr(new_branch)}") + cmds.insert(0, ["git", "checkout", "-f", new_branch]) + r = [run(cmd, OVERLAY_MERGED, low_priority=True) for cmd in cmds] cloudlog.info("git reset success: %s", '\n'.join(r)) if AGNOS: diff --git a/selfdrive/camerad/SConscript b/system/camerad/SConscript similarity index 89% rename from selfdrive/camerad/SConscript rename to system/camerad/SConscript index b181fbab7..25e366210 100644 --- a/selfdrive/camerad/SConscript +++ b/system/camerad/SConscript @@ -10,7 +10,6 @@ if arch == "larch64": env.Program('camerad', [ 'main.cc', 'cameras/camera_common.cc', - 'transforms/rgb_to_yuv.cc', 'imgproc/utils.cc', cameras, ], LIBS=libs) @@ -19,5 +18,4 @@ if GetOption("test"): env.Program('test/ae_gray_test', [ 'test/ae_gray_test.cc', 'cameras/camera_common.cc', - 'transforms/rgb_to_yuv.cc', ], LIBS=libs) diff --git a/selfdrive/camerad/__init__.py b/system/camerad/__init__.py similarity index 100% rename from selfdrive/camerad/__init__.py rename to system/camerad/__init__.py diff --git a/selfdrive/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc similarity index 82% rename from selfdrive/camerad/cameras/camera_common.cc rename to system/camerad/cameras/camera_common.cc index a94cfedf1..1d4ecd526 100644 --- a/selfdrive/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -1,4 +1,4 @@ -#include "selfdrive/camerad/cameras/camera_common.h" +#include "system/camerad/cameras/camera_common.h" #include @@ -10,7 +10,7 @@ #include "libyuv.h" #include -#include "selfdrive/camerad/imgproc/utils.h" +#include "system/camerad/imgproc/utils.h" #include "common/clutil.h" #include "common/modeldata.h" #include "common/swaglog.h" @@ -20,9 +20,9 @@ #ifdef QCOM2 #include "CL/cl_ext_qcom.h" -#include "selfdrive/camerad/cameras/camera_qcom2.h" +#include "system/camerad/cameras/camera_qcom2.h" #else -#include "selfdrive/camerad/test/camera_test.h" +#include "system/camerad/test/camera_test.h" #endif ExitHandler do_exit; @@ -32,15 +32,14 @@ public: Debayer(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s, int buf_width, int uv_offset) { char args[4096]; const CameraInfo *ci = &s->ci; - hdr_ = ci->hdr; snprintf(args, sizeof(args), "-cl-fast-relaxed-math -cl-denorms-are-zero " "-DFRAME_WIDTH=%d -DFRAME_HEIGHT=%d -DFRAME_STRIDE=%d -DFRAME_OFFSET=%d " "-DRGB_WIDTH=%d -DRGB_HEIGHT=%d -DRGB_STRIDE=%d -DYUV_STRIDE=%d -DUV_OFFSET=%d " - "-DBAYER_FLIP=%d -DHDR=%d -DCAM_NUM=%d%s", + "-DCAM_NUM=%d%s", ci->frame_width, ci->frame_height, ci->frame_stride, ci->frame_offset, b->rgb_width, b->rgb_height, b->rgb_stride, buf_width, uv_offset, - ci->bayer_flip, ci->hdr, s->camera_num, s->camera_num==1 ? " -DVIGNETTING" : ""); + s->camera_num, s->camera_num==1 ? " -DVIGNETTING" : ""); const char *cl_file = "cameras/real_debayer.cl"; cl_program prg_debayer = cl_program_from_file(context, device_id, cl_file, args); krnl_ = CL_CHECK_ERR(clCreateKernel(prg_debayer, "debayer10", &err)); @@ -63,14 +62,11 @@ public: private: cl_kernel krnl_; - bool hdr_; }; -void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType init_rgb_type, VisionStreamType init_yuv_type, release_cb init_release_callback) { +void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType init_yuv_type) { vipc_server = v; - this->rgb_type = init_rgb_type; this->yuv_type = init_yuv_type; - this->release_callback = init_release_callback; const CameraInfo *ci = &s->ci; camera_state = s; @@ -90,11 +86,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, rgb_width = ci->frame_width; rgb_height = ci->frame_height; - yuv_transform = get_model_yuv_transform(ci->bayer); - - vipc_server->create_buffers(rgb_type, UI_BUF_COUNT, true, rgb_width, rgb_height); - rgb_stride = vipc_server->get_buffer(rgb_type)->stride; - LOGD("created %d UI vipc buffers with size %dx%d", UI_BUF_COUNT, rgb_width, rgb_height); + yuv_transform = get_model_yuv_transform(); int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, rgb_width); int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, rgb_height); @@ -105,10 +97,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, vipc_server->create_buffers_with_sizes(yuv_type, YUV_BUFFER_COUNT, false, rgb_width, rgb_height, nv12_size, nv12_width, nv12_uv_offset); LOGD("created %d YUV vipc buffers with size %dx%d", YUV_BUFFER_COUNT, nv12_width, nv12_height); - if (ci->bayer) { - debayer = new Debayer(device_id, context, this, s, nv12_width, nv12_uv_offset); - } - rgb2yuv = std::make_unique(context, device_id, rgb_width, rgb_height, rgb_stride); + debayer = new Debayer(device_id, context, this, s, nv12_width, nv12_uv_offset); #ifdef __APPLE__ q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); @@ -127,7 +116,7 @@ CameraBuf::~CameraBuf() { } bool CameraBuf::acquire() { - if (!safe_queue.try_pop(cur_buf_idx, 1)) return false; + if (!safe_queue.try_pop(cur_buf_idx, 50)) return false; if (camera_bufs_metadata[cur_buf_idx].frame_id == -1) { LOGE("no frame data? wtf"); @@ -136,7 +125,6 @@ bool CameraBuf::acquire() { } cur_frame_data = camera_bufs_metadata[cur_buf_idx]; - cur_rgb_buf = vipc_server->get_buffer(rgb_type); cur_yuv_buf = vipc_server->get_buffer(yuv_type); cl_mem camrabuf_cl = camera_bufs[cur_buf_idx].buf_cl; cl_event event; @@ -145,12 +133,7 @@ bool CameraBuf::acquire() { cur_camera_buf = &camera_bufs[cur_buf_idx]; - if (debayer) { - debayer->queue(q, camrabuf_cl, cur_yuv_buf->buf_cl, rgb_width, rgb_height, &event); - } else { - assert(rgb_stride == camera_state->ci.frame_stride); - rgb2yuv->queue(q, camrabuf_cl, cur_rgb_buf->buf_cl); - } + debayer->queue(q, camrabuf_cl, cur_yuv_buf->buf_cl, rgb_width, rgb_height, &event); clWaitForEvents(1, &event); CL_CHECK(clReleaseEvent(event)); @@ -169,9 +152,7 @@ bool CameraBuf::acquire() { } void CameraBuf::release() { - if (release_callback) { - release_callback((void*)camera_state, cur_buf_idx); - } + // Empty } void CameraBuf::queue(size_t buf_idx) { @@ -196,32 +177,6 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setProcessingTime(frame_data.processing_time); } -kj::Array get_frame_image(const CameraBuf *b) { - static const int x_min = util::getenv("XMIN", 0); - static const int y_min = util::getenv("YMIN", 0); - static const int env_xmax = util::getenv("XMAX", -1); - static const int env_ymax = util::getenv("YMAX", -1); - static const int scale = util::getenv("SCALE", 1); - - assert(b->cur_rgb_buf); - - const int x_max = env_xmax != -1 ? env_xmax : b->rgb_width - 1; - const int y_max = env_ymax != -1 ? env_ymax : b->rgb_height - 1; - const int new_width = (x_max - x_min + 1) / scale; - const int new_height = (y_max - y_min + 1) / scale; - const uint8_t *dat = (const uint8_t *)b->cur_rgb_buf->addr; - - kj::Array frame_image = kj::heapArray(new_width*new_height*3); - uint8_t *resized_dat = frame_image.begin(); - int goff = x_min*3 + y_min*b->rgb_stride; - for (int r=0;rrgb_stride*scale+c*3*scale], 3*sizeof(uint8_t)); - } - } - return kj::mv(frame_image); -} - kj::Array get_raw_frame_image(const CameraBuf *b) { const uint8_t *dat = (const uint8_t *)b->cur_camera_buf->addr; diff --git a/selfdrive/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h similarity index 80% rename from selfdrive/camerad/cameras/camera_common.h rename to system/camerad/cameras/camera_common.h index e9c7ccd75..2a5605255 100644 --- a/selfdrive/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -9,7 +9,6 @@ #include "cereal/visionipc/visionbuf.h" #include "cereal/visionipc/visionipc.h" #include "cereal/visionipc/visionipc_server.h" -#include "selfdrive/camerad/transforms/rgb_to_yuv.h" #include "common/mat.h" #include "common/queue.h" #include "common/swaglog.h" @@ -27,7 +26,6 @@ #define CAMERA_ID_IMX390 9 #define CAMERA_ID_MAX 10 -const int UI_BUF_COUNT = 4; const int YUV_BUFFER_COUNT = 40; enum CameraType { @@ -36,11 +34,6 @@ enum CameraType { WideRoadCam }; -// TODO: remove these once all the internal tools are moved to vipc -const bool env_send_driver = getenv("SEND_DRIVER") != NULL; -const bool env_send_road = getenv("SEND_ROAD") != NULL; -const bool env_send_wide_road = getenv("SEND_WIDE_ROAD") != NULL; - // for debugging const bool env_disable_road = getenv("DISABLE_ROAD") != NULL; const bool env_disable_wide_road = getenv("DISABLE_WIDE_ROAD") != NULL; @@ -48,14 +41,9 @@ const bool env_disable_driver = getenv("DISABLE_DRIVER") != NULL; const bool env_debug_frames = getenv("DEBUG_FRAMES") != NULL; const bool env_log_raw_frames = getenv("LOG_RAW_FRAMES") != NULL; -typedef void (*release_cb)(void *cookie, int buf_idx); - typedef struct CameraInfo { uint32_t frame_width, frame_height; uint32_t frame_stride; - bool bayer; - int bayer_flip; - bool hdr; uint32_t frame_offset = 0; uint32_t extra_height = 0; int registers_offset = -1; @@ -85,11 +73,6 @@ typedef struct FrameMetadata { float processing_time; } FrameMetadata; -typedef struct CameraExpInfo { - int op_id; - float grey_frac; -} CameraExpInfo; - struct MultiCameraState; struct CameraState; class Debayer; @@ -99,16 +82,14 @@ private: VisionIpcServer *vipc_server; CameraState *camera_state; Debayer *debayer = nullptr; - std::unique_ptr rgb2yuv; - VisionStreamType rgb_type, yuv_type; + VisionStreamType yuv_type; int cur_buf_idx; SafeQueue safe_queue; int frame_buf_count; - release_cb release_callback; public: cl_command_queue q; @@ -124,7 +105,7 @@ public: CameraBuf() = default; ~CameraBuf(); - void init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType rgb_type, VisionStreamType yuv_type, release_cb release_callback=nullptr); + void init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType yuv_type); bool acquire(); void release(); void queue(size_t buf_idx); @@ -133,7 +114,6 @@ public: typedef void (*process_thread_cb)(MultiCameraState *s, CameraState *c, int cnt); void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &frame_data); -kj::Array get_frame_image(const CameraBuf *b); kj::Array get_raw_frame_image(const CameraBuf *b); float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip); std::thread start_process_thread(MultiCameraState *cameras, CameraState *cs, process_thread_cb callback); diff --git a/selfdrive/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc similarity index 98% rename from selfdrive/camerad/cameras/camera_qcom2.cc rename to system/camerad/cameras/camera_qcom2.cc index c1ffc1275..31af284d5 100644 --- a/selfdrive/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -1,4 +1,4 @@ -#include "selfdrive/camerad/cameras/camera_qcom2.h" +#include "system/camerad/cameras/camera_qcom2.h" #include #include @@ -20,7 +20,7 @@ #include "media/cam_sensor_cmn_header.h" #include "media/cam_sync.h" #include "common/swaglog.h" -#include "selfdrive/camerad/cameras/sensor2_i2c.h" +#include "system/camerad/cameras/sensor2_i2c.h" // For debugging: // echo "4294967295" > /sys/module/cam_debug_util/parameters/debug_mdl @@ -46,19 +46,11 @@ CameraInfo cameras_supported[CAMERA_ID_MAX] = { .registers_offset = 0, .frame_offset = AR0231_REGISTERS_HEIGHT, .stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT, - - .bayer = true, - .bayer_flip = 1, - .hdr = false, }, [CAMERA_ID_IMX390] = { .frame_width = FRAME_WIDTH, .frame_height = FRAME_HEIGHT, .frame_stride = FRAME_STRIDE, - - .bayer = true, - .bayer_flip = 1, - .hdr = false, }, }; @@ -614,7 +606,7 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) { // ******************* camera ******************* -void CameraState::camera_init(MultiCameraState *multi_cam_state_, VisionIpcServer * v, int camera_id_, int camera_num_, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type, bool enabled_) { +void CameraState::camera_init(MultiCameraState *multi_cam_state_, VisionIpcServer * v, int camera_id_, int camera_num_, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type, bool enabled_) { multi_cam_state = multi_cam_state_; camera_id = camera_id_; camera_num = camera_num_; @@ -638,7 +630,7 @@ void CameraState::camera_init(MultiCameraState *multi_cam_state_, VisionIpcServe exposure_time = 5; cur_ev[0] = cur_ev[1] = cur_ev[2] = (dc_gain_enabled ? DC_GAIN : 1) * sensor_analog_gains[gain_idx] * exposure_time; - buf.init(device_id, ctx, this, v, FRAME_BUF_COUNT, rgb_type, yuv_type); + buf.init(device_id, ctx, this, v, FRAME_BUF_COUNT, yuv_type); } void CameraState::camera_open() { @@ -833,9 +825,9 @@ void CameraState::camera_open() { } void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { - s->driver_cam.camera_init(s, v, CAMERA_ID_AR0231, 2, 20, device_id, ctx, VISION_STREAM_RGB_DRIVER, VISION_STREAM_DRIVER, !env_disable_driver); - s->road_cam.camera_init(s, v, CAMERA_ID_AR0231, 1, 20, device_id, ctx, VISION_STREAM_RGB_ROAD, VISION_STREAM_ROAD, !env_disable_road); - s->wide_road_cam.camera_init(s, v, CAMERA_ID_AR0231, 0, 20, device_id, ctx, VISION_STREAM_RGB_WIDE_ROAD, VISION_STREAM_WIDE_ROAD, !env_disable_wide_road); + s->driver_cam.camera_init(s, v, CAMERA_ID_AR0231, 2, 20, device_id, ctx, VISION_STREAM_DRIVER, !env_disable_driver); + s->road_cam.camera_init(s, v, CAMERA_ID_AR0231, 1, 20, device_id, ctx, VISION_STREAM_ROAD, !env_disable_road); + s->wide_road_cam.camera_init(s, v, CAMERA_ID_AR0231, 0, 20, device_id, ctx, VISION_STREAM_WIDE_ROAD, !env_disable_wide_road); s->pm = new PubMaster({"roadCameraState", "driverCameraState", "wideRoadCameraState", "thumbnail"}); } @@ -1233,9 +1225,7 @@ static void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) auto framed = msg.initEvent().initDriverCameraState(); framed.setFrameType(cereal::FrameData::FrameType::FRONT); fill_frame_data(framed, c->buf.cur_frame_data); - if (env_send_driver) { - framed.setImage(get_frame_image(&c->buf)); - } + if (c->camera_id == CAMERA_ID_AR0231) { ar0231_process_registers(s, c, framed); } @@ -1248,9 +1238,7 @@ void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { MessageBuilder msg; auto framed = c == &s->road_cam ? msg.initEvent().initRoadCameraState() : msg.initEvent().initWideRoadCameraState(); fill_frame_data(framed, b->cur_frame_data); - if ((c == &s->road_cam && env_send_road) || (c == &s->wide_road_cam && env_send_wide_road)) { - framed.setImage(get_frame_image(b)); - } else if (env_log_raw_frames && c == &s->road_cam && cnt % 100 == 5) { // no overlap with qlog decimation + if (env_log_raw_frames && c == &s->road_cam && cnt % 100 == 5) { // no overlap with qlog decimation framed.setImage(get_raw_frame_image(b)); } LOGT(c->buf.cur_frame_data.frame_id, "%s: Image set", c == &s->road_cam ? "RoadCamera" : "WideRoadCamera"); diff --git a/selfdrive/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h similarity index 94% rename from selfdrive/camerad/cameras/camera_qcom2.h rename to system/camerad/cameras/camera_qcom2.h index 88766a68e..03d3d1a82 100644 --- a/selfdrive/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -6,7 +6,7 @@ #include -#include "selfdrive/camerad/cameras/camera_common.h" +#include "system/camerad/cameras/camera_common.h" #include "common/util.h" #define FRAME_BUF_COUNT 4 @@ -55,7 +55,7 @@ public: void sensors_start(); void camera_open(); - void camera_init(MultiCameraState *multi_cam_state, VisionIpcServer * v, int camera_id, int camera_num, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type, bool enabled); + void camera_init(MultiCameraState *multi_cam_state, VisionIpcServer * v, int camera_id, int camera_num, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type, bool enabled); void camera_close(); std::map ar0231_parse_registers(uint8_t *data, std::initializer_list addrs); diff --git a/selfdrive/camerad/cameras/real_debayer.cl b/system/camerad/cameras/real_debayer.cl similarity index 100% rename from selfdrive/camerad/cameras/real_debayer.cl rename to system/camerad/cameras/real_debayer.cl diff --git a/selfdrive/camerad/cameras/sensor2_i2c.h b/system/camerad/cameras/sensor2_i2c.h similarity index 100% rename from selfdrive/camerad/cameras/sensor2_i2c.h rename to system/camerad/cameras/sensor2_i2c.h diff --git a/selfdrive/camerad/imgproc/conv.cl b/system/camerad/imgproc/conv.cl similarity index 100% rename from selfdrive/camerad/imgproc/conv.cl rename to system/camerad/imgproc/conv.cl diff --git a/selfdrive/camerad/imgproc/pool.cl b/system/camerad/imgproc/pool.cl similarity index 100% rename from selfdrive/camerad/imgproc/pool.cl rename to system/camerad/imgproc/pool.cl diff --git a/selfdrive/camerad/imgproc/utils.cc b/system/camerad/imgproc/utils.cc similarity index 98% rename from selfdrive/camerad/imgproc/utils.cc rename to system/camerad/imgproc/utils.cc index a88b8f4bb..a7bbeb9e8 100644 --- a/selfdrive/camerad/imgproc/utils.cc +++ b/system/camerad/imgproc/utils.cc @@ -1,4 +1,4 @@ -#include "selfdrive/camerad/imgproc/utils.h" +#include "system/camerad/imgproc/utils.h" #include #include diff --git a/selfdrive/camerad/imgproc/utils.h b/system/camerad/imgproc/utils.h similarity index 100% rename from selfdrive/camerad/imgproc/utils.h rename to system/camerad/imgproc/utils.h diff --git a/selfdrive/camerad/include/media/cam_cpas.h b/system/camerad/include/media/cam_cpas.h similarity index 100% rename from selfdrive/camerad/include/media/cam_cpas.h rename to system/camerad/include/media/cam_cpas.h diff --git a/selfdrive/camerad/include/media/cam_defs.h b/system/camerad/include/media/cam_defs.h similarity index 100% rename from selfdrive/camerad/include/media/cam_defs.h rename to system/camerad/include/media/cam_defs.h diff --git a/selfdrive/camerad/include/media/cam_fd.h b/system/camerad/include/media/cam_fd.h similarity index 100% rename from selfdrive/camerad/include/media/cam_fd.h rename to system/camerad/include/media/cam_fd.h diff --git a/selfdrive/camerad/include/media/cam_icp.h b/system/camerad/include/media/cam_icp.h similarity index 100% rename from selfdrive/camerad/include/media/cam_icp.h rename to system/camerad/include/media/cam_icp.h diff --git a/selfdrive/camerad/include/media/cam_isp.h b/system/camerad/include/media/cam_isp.h similarity index 100% rename from selfdrive/camerad/include/media/cam_isp.h rename to system/camerad/include/media/cam_isp.h diff --git a/selfdrive/camerad/include/media/cam_isp_ife.h b/system/camerad/include/media/cam_isp_ife.h similarity index 100% rename from selfdrive/camerad/include/media/cam_isp_ife.h rename to system/camerad/include/media/cam_isp_ife.h diff --git a/selfdrive/camerad/include/media/cam_isp_vfe.h b/system/camerad/include/media/cam_isp_vfe.h similarity index 100% rename from selfdrive/camerad/include/media/cam_isp_vfe.h rename to system/camerad/include/media/cam_isp_vfe.h diff --git a/selfdrive/camerad/include/media/cam_jpeg.h b/system/camerad/include/media/cam_jpeg.h similarity index 100% rename from selfdrive/camerad/include/media/cam_jpeg.h rename to system/camerad/include/media/cam_jpeg.h diff --git a/selfdrive/camerad/include/media/cam_lrme.h b/system/camerad/include/media/cam_lrme.h similarity index 100% rename from selfdrive/camerad/include/media/cam_lrme.h rename to system/camerad/include/media/cam_lrme.h diff --git a/selfdrive/camerad/include/media/cam_req_mgr.h b/system/camerad/include/media/cam_req_mgr.h similarity index 100% rename from selfdrive/camerad/include/media/cam_req_mgr.h rename to system/camerad/include/media/cam_req_mgr.h diff --git a/selfdrive/camerad/include/media/cam_sensor.h b/system/camerad/include/media/cam_sensor.h similarity index 100% rename from selfdrive/camerad/include/media/cam_sensor.h rename to system/camerad/include/media/cam_sensor.h diff --git a/selfdrive/camerad/include/media/cam_sensor_cmn_header.h b/system/camerad/include/media/cam_sensor_cmn_header.h similarity index 100% rename from selfdrive/camerad/include/media/cam_sensor_cmn_header.h rename to system/camerad/include/media/cam_sensor_cmn_header.h diff --git a/selfdrive/camerad/include/media/cam_sync.h b/system/camerad/include/media/cam_sync.h similarity index 100% rename from selfdrive/camerad/include/media/cam_sync.h rename to system/camerad/include/media/cam_sync.h diff --git a/selfdrive/camerad/include/msm_cam_sensor.h b/system/camerad/include/msm_cam_sensor.h similarity index 100% rename from selfdrive/camerad/include/msm_cam_sensor.h rename to system/camerad/include/msm_cam_sensor.h diff --git a/selfdrive/camerad/include/msm_camsensor_sdk.h b/system/camerad/include/msm_camsensor_sdk.h similarity index 100% rename from selfdrive/camerad/include/msm_camsensor_sdk.h rename to system/camerad/include/msm_camsensor_sdk.h diff --git a/selfdrive/camerad/include/msmb_camera.h b/system/camerad/include/msmb_camera.h similarity index 100% rename from selfdrive/camerad/include/msmb_camera.h rename to system/camerad/include/msmb_camera.h diff --git a/selfdrive/camerad/include/msmb_isp.h b/system/camerad/include/msmb_isp.h similarity index 100% rename from selfdrive/camerad/include/msmb_isp.h rename to system/camerad/include/msmb_isp.h diff --git a/selfdrive/camerad/include/msmb_ispif.h b/system/camerad/include/msmb_ispif.h similarity index 100% rename from selfdrive/camerad/include/msmb_ispif.h rename to system/camerad/include/msmb_ispif.h diff --git a/selfdrive/camerad/main.cc b/system/camerad/main.cc similarity index 89% rename from selfdrive/camerad/main.cc rename to system/camerad/main.cc index 32899a854..c1f38f222 100644 --- a/selfdrive/camerad/main.cc +++ b/system/camerad/main.cc @@ -1,4 +1,4 @@ -#include "selfdrive/camerad/cameras/camera_common.h" +#include "system/camerad/cameras/camera_common.h" #include diff --git a/selfdrive/camerad/snapshot/__init__.py b/system/camerad/snapshot/__init__.py similarity index 100% rename from selfdrive/camerad/snapshot/__init__.py rename to system/camerad/snapshot/__init__.py diff --git a/selfdrive/camerad/snapshot/snapshot.py b/system/camerad/snapshot/snapshot.py similarity index 97% rename from selfdrive/camerad/snapshot/snapshot.py rename to system/camerad/snapshot/snapshot.py index fa88849b6..946c220a7 100755 --- a/selfdrive/camerad/snapshot/snapshot.py +++ b/system/camerad/snapshot/snapshot.py @@ -13,7 +13,7 @@ from system.hardware import PC from selfdrive.controls.lib.alertmanager import set_offroad_alert from selfdrive.manager.process_config import managed_processes -LM_THRESH = 120 # defined in selfdrive/camerad/imgproc/utils.h +LM_THRESH = 120 # defined in system/camerad/imgproc/utils.h VISION_STREAMS = { "roadCameraState": VisionStreamType.VISION_STREAM_ROAD, @@ -39,7 +39,7 @@ def yuv_to_rgb(y, u, v): [0.00000, -0.39465, 2.03211], [1.13983, -0.58060, 0.00000], ]) - rgb = np.dot(yuv, m) + rgb = np.dot(yuv, m).clip(0, 255) return rgb.astype(np.uint8) diff --git a/selfdrive/camerad/test/.gitignore b/system/camerad/test/.gitignore similarity index 100% rename from selfdrive/camerad/test/.gitignore rename to system/camerad/test/.gitignore diff --git a/selfdrive/camerad/test/ae_gray_test.cc b/system/camerad/test/ae_gray_test.cc similarity index 97% rename from selfdrive/camerad/test/ae_gray_test.cc rename to system/camerad/test/ae_gray_test.cc index 69ef2ac6d..aabd7534e 100644 --- a/selfdrive/camerad/test/ae_gray_test.cc +++ b/system/camerad/test/ae_gray_test.cc @@ -8,7 +8,7 @@ #include #include "common/util.h" -#include "selfdrive/camerad/cameras/camera_common.h" +#include "system/camerad/cameras/camera_common.h" int main() { // set up fake camerabuf diff --git a/selfdrive/camerad/test/ae_gray_test.h b/system/camerad/test/ae_gray_test.h similarity index 100% rename from selfdrive/camerad/test/ae_gray_test.h rename to system/camerad/test/ae_gray_test.h diff --git a/selfdrive/camerad/test/camera_test.h b/system/camerad/test/camera_test.h similarity index 100% rename from selfdrive/camerad/test/camera_test.h rename to system/camerad/test/camera_test.h diff --git a/selfdrive/camerad/test/check_skips.py b/system/camerad/test/check_skips.py similarity index 100% rename from selfdrive/camerad/test/check_skips.py rename to system/camerad/test/check_skips.py diff --git a/selfdrive/camerad/test/frame_test.py b/system/camerad/test/frame_test.py similarity index 100% rename from selfdrive/camerad/test/frame_test.py rename to system/camerad/test/frame_test.py diff --git a/selfdrive/camerad/test/get_thumbnails_for_segment.py b/system/camerad/test/get_thumbnails_for_segment.py similarity index 100% rename from selfdrive/camerad/test/get_thumbnails_for_segment.py rename to system/camerad/test/get_thumbnails_for_segment.py diff --git a/selfdrive/camerad/test/stress_restart.sh b/system/camerad/test/stress_restart.sh similarity index 100% rename from selfdrive/camerad/test/stress_restart.sh rename to system/camerad/test/stress_restart.sh diff --git a/selfdrive/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py similarity index 100% rename from selfdrive/camerad/test/test_camerad.py rename to system/camerad/test/test_camerad.py diff --git a/selfdrive/camerad/test/test_exposure.py b/system/camerad/test/test_exposure.py similarity index 96% rename from selfdrive/camerad/test/test_exposure.py rename to system/camerad/test/test_exposure.py index f42d0bfbe..8cce7e7ff 100755 --- a/selfdrive/camerad/test/test_exposure.py +++ b/system/camerad/test/test_exposure.py @@ -4,7 +4,7 @@ import unittest import numpy as np from selfdrive.test.helpers import with_processes -from selfdrive.camerad.snapshot.snapshot import get_snapshots +from system.camerad.snapshot.snapshot import get_snapshots from system.hardware import TICI diff --git a/selfdrive/camerad/transforms/rgb_to_yuv.cl b/system/camerad/transforms/rgb_to_yuv.cl similarity index 100% rename from selfdrive/camerad/transforms/rgb_to_yuv.cl rename to system/camerad/transforms/rgb_to_yuv.cl diff --git a/system/hardware/base.py b/system/hardware/base.py index 8684386f6..c9c02f3a5 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -86,6 +86,10 @@ class HardwareBase(ABC): def get_current_power_draw(self): pass + @abstractmethod + def get_som_power_draw(self): + pass + @abstractmethod def shutdown(self): pass @@ -140,3 +144,6 @@ class HardwareBase(ABC): def recover_internal_panda(self): pass + + def get_modem_data_usage(self): + return -1, -1 diff --git a/system/hardware/pc/hardware.py b/system/hardware/pc/hardware.py index b2c4a4343..60d14e4a6 100644 --- a/system/hardware/pc/hardware.py +++ b/system/hardware/pc/hardware.py @@ -55,6 +55,9 @@ class Pc(HardwareBase): def get_current_power_draw(self): return 0 + + def get_som_power_draw(self): + return 0 def shutdown(self): print("SHUTDOWN!") diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 004a0f9df..7ccea95ee 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -1,9 +1,9 @@ [ { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-bb71f49294150c4233b89e2a10768a5a3de003203ecd02e3f845821b35cd409f.img.xz", - "hash": "bb71f49294150c4233b89e2a10768a5a3de003203ecd02e3f845821b35cd409f", - "hash_raw": "bb71f49294150c4233b89e2a10768a5a3de003203ecd02e3f845821b35cd409f", + "url": "https://commadist.azureedge.net/agnosupdate/boot-243ddbb9e2256aa7af7fed0daf8cff4017a3c838c759373a634b8539f271bfb8.img.xz", + "hash": "243ddbb9e2256aa7af7fed0daf8cff4017a3c838c759373a634b8539f271bfb8", + "hash_raw": "243ddbb9e2256aa7af7fed0daf8cff4017a3c838c759373a634b8539f271bfb8", "size": 14780416, "sparse": false, "full_check": true, @@ -41,9 +41,9 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-11fdbc9e8a9cd27f98346d7e1039bc5b3032d0e892ff95fa1258673ff1809bca.img.xz", - "hash": "45b4719a9e580617cf840036b24fb0dcd32491edd9654d8d74c28d91ff362d36", - "hash_raw": "11fdbc9e8a9cd27f98346d7e1039bc5b3032d0e892ff95fa1258673ff1809bca", + "url": "https://commadist.azureedge.net/agnosupdate/system-59622eddd068d49f2e9df69ef5115e3f205ad369539690a5b240c8c93796dd13.img.xz", + "hash": "44da205d17b44b2be7c94854a6bb3efb2928ec9a9889fe62af8b322d2295b74f", + "hash_raw": "59622eddd068d49f2e9df69ef5115e3f205ad369539690a5b240c8c93796dd13", "size": 10737418240, "sparse": true, "full_check": false, diff --git a/system/hardware/tici/agnos.py b/system/hardware/tici/agnos.py index e664654c6..ca2498a00 100755 --- a/system/hardware/tici/agnos.py +++ b/system/hardware/tici/agnos.py @@ -1,15 +1,19 @@ #!/usr/bin/env python3 +import hashlib import json import lzma -import hashlib -import requests +import os import struct import subprocess import time -import os -from typing import Dict, Generator, Union +from typing import Dict, Generator, List, Tuple, Union + +import requests + +import system.hardware.tici.casync as casync SPARSE_CHUNK_FMT = struct.Struct('H2xI4x') +CAIBX_URL = "https://commadist.azureedge.net/agnosupdate/" class StreamingDecompressor: @@ -74,6 +78,7 @@ def unsparsify(f: StreamingDecompressor) -> Generator[bytes, None, None]: else: raise Exception("Unhandled sparse chunk type") + # noop wrapper with same API as unsparsify() for non sparse images def noop(f: StreamingDecompressor) -> Generator[bytes, None, None]: while not f.eof: @@ -99,28 +104,37 @@ def get_partition_path(target_slot_number: int, partition: dict) -> str: return path -def verify_partition(target_slot_number: int, partition: Dict[str, Union[str, int]]) -> bool: - full_check = partition['full_check'] +def get_raw_hash(path: str, partition_size: int) -> str: + raw_hash = hashlib.sha256() + pos, chunk_size = 0, 1024 * 1024 + + with open(path, 'rb+') as out: + while pos < partition_size: + n = min(chunk_size, partition_size - pos) + raw_hash.update(out.read(n)) + pos += n + + return raw_hash.hexdigest().lower() + + +def verify_partition(target_slot_number: int, partition: Dict[str, Union[str, int]], force_full_check: bool = False) -> bool: + full_check = partition['full_check'] or force_full_check path = get_partition_path(target_slot_number, partition) + if not isinstance(partition['size'], int): return False + partition_size: int = partition['size'] if not isinstance(partition['hash_raw'], str): return False - partition_hash: str = partition['hash_raw'] - with open(path, 'rb+') as out: - if full_check: - raw_hash = hashlib.sha256() - pos, chunk_size = 0, 1024 * 1024 - while pos < partition_size: - n = min(chunk_size, partition_size - pos) - raw_hash.update(out.read(n)) - pos += n + partition_hash: str = partition['hash_raw'] - return raw_hash.hexdigest().lower() == partition_hash.lower() - else: + if full_check: + return get_raw_hash(path, partition_size) == partition_hash.lower() + else: + with open(path, 'rb+') as out: out.seek(partition_size) return out.read(64) == partition_hash.lower().encode() @@ -135,21 +149,10 @@ def clear_partition_hash(target_slot_number: int, partition: dict) -> None: os.sync() -def flash_partition(target_slot_number: int, partition: dict, cloudlog): - cloudlog.info(f"Downloading and writing {partition['name']}") - - if verify_partition(target_slot_number, partition): - cloudlog.info(f"Already flashed {partition['name']}") - return - +def extract_compressed_image(target_slot_number: int, partition: dict, cloudlog): + path = get_partition_path(target_slot_number, partition) downloader = StreamingDecompressor(partition['url']) - # Clear hash before flashing in case we get interrupted - full_check = partition['full_check'] - if not full_check: - clear_partition_hash(target_slot_number, partition) - - path = get_partition_path(target_slot_number, partition) with open(path, 'wb+') as out: # Flash partition last_p = 0 @@ -172,9 +175,76 @@ def flash_partition(target_slot_number: int, partition: dict, cloudlog): if out.tell() != partition['size']: raise Exception("Uncompressed size mismatch") - # Write hash after successfull flash os.sync() - if not full_check: + + +def extract_casync_image(target_slot_number: int, partition: dict, cloudlog): + path = get_partition_path(target_slot_number, partition) + seed_path = path[:-1] + ('b' if path[-1] == 'a' else 'a') + + target = casync.parse_caibx(partition['casync_caibx']) + + sources: List[Tuple[str, casync.ChunkReader, casync.ChunkDict]] = [] + + # First source is the current partition. + try: + raw_hash = get_raw_hash(seed_path, partition['size']) + caibx_url = f"{CAIBX_URL}{partition['name']}-{raw_hash}.caibx" + + try: + cloudlog.info(f"casync fetching {caibx_url}") + sources += [('seed', casync.FileChunkReader(seed_path), casync.build_chunk_dict(casync.parse_caibx(caibx_url)))] + except requests.RequestException: + cloudlog.error(f"casync failed to load {caibx_url}") + except Exception: + cloudlog.exception("casync failed to hash seed partition") + + # Second source is the target partition, this allows for resuming + sources += [('target', casync.FileChunkReader(path), casync.build_chunk_dict(target))] + + # Finally we add the remote source to download any missing chunks + sources += [('remote', casync.RemoteChunkReader(partition['casync_store']), casync.build_chunk_dict(target))] + + last_p = 0 + + def progress(cur): + nonlocal last_p + p = int(cur / partition['size'] * 100) + if p != last_p: + last_p = p + print(f"Installing {partition['name']}: {p}", flush=True) + + stats = casync.extract(target, sources, path, progress) + cloudlog.error(f'casync done {json.dumps(stats)}') + + os.sync() + if not verify_partition(target_slot_number, partition, force_full_check=True): + raise Exception(f"Raw hash mismatch '{partition['hash_raw'].lower()}'") + + +def flash_partition(target_slot_number: int, partition: dict, cloudlog, standalone=False): + cloudlog.info(f"Downloading and writing {partition['name']}") + + if verify_partition(target_slot_number, partition): + cloudlog.info(f"Already flashed {partition['name']}") + return + + # Clear hash before flashing in case we get interrupted + full_check = partition['full_check'] + if not full_check: + clear_partition_hash(target_slot_number, partition) + + path = get_partition_path(target_slot_number, partition) + + if ('casync_caibx' in partition) and not standalone: + extract_casync_image(target_slot_number, partition, cloudlog) + else: + extract_compressed_image(target_slot_number, partition, cloudlog) + + # Write hash after successfull flash + if not full_check: + with open(path, 'wb+') as out: + out.seek(partition['size']) out.write(partition['hash_raw'].lower().encode()) @@ -193,7 +263,7 @@ def swap(manifest_path: str, target_slot_number: int, cloudlog) -> None: cloudlog.error(f"Swap failed {out}") -def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog) -> None: +def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog, standalone=False) -> None: update = json.load(open(manifest_path)) cloudlog.info(f"Target slot {target_slot_number}") @@ -206,7 +276,7 @@ def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog) -> for retries in range(10): try: - flash_partition(target_slot_number, partition, cloudlog) + flash_partition(target_slot_number, partition, cloudlog, standalone) success = True break @@ -228,8 +298,8 @@ def verify_agnos_update(manifest_path: str, target_slot_number: int) -> bool: if __name__ == "__main__": - import logging import argparse + import logging parser = argparse.ArgumentParser(description="Flash and verify AGNOS update", formatter_class=argparse.ArgumentDefaultsHelpFormatter) @@ -250,9 +320,9 @@ if __name__ == "__main__": elif args.swap: while not verify_agnos_update(args.manifest, target_slot_number): logging.error("Verification failed. Flashing AGNOS") - flash_agnos_update(args.manifest, target_slot_number, logging) + flash_agnos_update(args.manifest, target_slot_number, logging, standalone=True) logging.warning(f"Verification succeeded. Swapping to slot {target_slot_number}") swap(args.manifest, target_slot_number, logging) else: - flash_agnos_update(args.manifest, target_slot_number, logging) + flash_agnos_update(args.manifest, target_slot_number, logging, standalone=True) diff --git a/system/hardware/tici/casync.py b/system/hardware/tici/casync.py new file mode 100755 index 000000000..8ae42fa71 --- /dev/null +++ b/system/hardware/tici/casync.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +import io +import lzma +import os +import struct +import sys +import time +from abc import ABC, abstractmethod +from collections import defaultdict, namedtuple +from typing import Callable, Dict, List, Optional, Tuple + +import requests +from Crypto.Hash import SHA512 + +CA_FORMAT_INDEX = 0x96824d9c7b129ff9 +CA_FORMAT_TABLE = 0xe75b9e112f17417d +CA_FORMAT_TABLE_TAIL_MARKER = 0xe75b9e112f17417 +FLAGS = 0xb000000000000000 + +CA_HEADER_LEN = 48 +CA_TABLE_HEADER_LEN = 16 +CA_TABLE_ENTRY_LEN = 40 +CA_TABLE_MIN_LEN = CA_TABLE_HEADER_LEN + CA_TABLE_ENTRY_LEN + +CHUNK_DOWNLOAD_TIMEOUT = 60 +CHUNK_DOWNLOAD_RETRIES = 3 + +CAIBX_DOWNLOAD_TIMEOUT = 120 + +Chunk = namedtuple('Chunk', ['sha', 'offset', 'length']) +ChunkDict = Dict[bytes, Chunk] + + +class ChunkReader(ABC): + @abstractmethod + def read(self, chunk: Chunk) -> bytes: + ... + + +class FileChunkReader(ChunkReader): + """Reads chunks from a local file""" + def __init__(self, fn: str) -> None: + super().__init__() + self.f = open(fn, 'rb') + + def __del__(self): + self.f.close() + + def read(self, chunk: Chunk) -> bytes: + self.f.seek(chunk.offset) + return self.f.read(chunk.length) + + +class RemoteChunkReader(ChunkReader): + """Reads lzma compressed chunks from a remote store""" + + def __init__(self, url: str) -> None: + super().__init__() + self.url = url + self.session = requests.Session() + + def read(self, chunk: Chunk) -> bytes: + sha_hex = chunk.sha.hex() + url = os.path.join(self.url, sha_hex[:4], sha_hex + ".cacnk") + + if os.path.isfile(url): + with open(url, 'rb') as f: + contents = f.read() + else: + for i in range(CHUNK_DOWNLOAD_RETRIES): + try: + resp = self.session.get(url, timeout=CHUNK_DOWNLOAD_TIMEOUT) + break + except Exception: + if i == CHUNK_DOWNLOAD_RETRIES - 1: + raise + time.sleep(CHUNK_DOWNLOAD_TIMEOUT) + + resp.raise_for_status() + contents = resp.content + + decompressor = lzma.LZMADecompressor(format=lzma.FORMAT_AUTO) + return decompressor.decompress(contents) + + +def parse_caibx(caibx_path: str) -> List[Chunk]: + """Parses the chunks from a caibx file. Can handle both local and remote files. + Returns a list of chunks with hash, offset and length""" + if os.path.isfile(caibx_path): + caibx = open(caibx_path, 'rb') + else: + resp = requests.get(caibx_path, timeout=CAIBX_DOWNLOAD_TIMEOUT) + resp.raise_for_status() + caibx = io.BytesIO(resp.content) + + caibx.seek(0, os.SEEK_END) + caibx_len = caibx.tell() + caibx.seek(0, os.SEEK_SET) + + # Parse header + length, magic, flags, min_size, _, max_size = struct.unpack("= min_size + + chunks.append(Chunk(sha, offset, length)) + offset = new_offset + + caibx.close() + return chunks + + +def build_chunk_dict(chunks: List[Chunk]) -> ChunkDict: + """Turn a list of chunks into a dict for faster lookups based on hash. + Keep first chunk since it's more likely to be already downloaded.""" + r = {} + for c in chunks: + if c.sha not in r: + r[c.sha] = c + return r + + +def extract(target: List[Chunk], + sources: List[Tuple[str, ChunkReader, ChunkDict]], + out_path: str, + progress: Optional[Callable[[int], None]] = None): + stats: Dict[str, int] = defaultdict(int) + + mode = 'rb+' if os.path.exists(out_path) else 'wb' + with open(out_path, mode) as out: + for cur_chunk in target: + + # Find source for desired chunk + for name, chunk_reader, store_chunks in sources: + if cur_chunk.sha in store_chunks: + bts = chunk_reader.read(store_chunks[cur_chunk.sha]) + + # Check length + if len(bts) != cur_chunk.length: + continue + + # Check hash + if SHA512.new(bts, truncate="256").digest() != cur_chunk.sha: + continue + + # Write to output + out.seek(cur_chunk.offset) + out.write(bts) + + stats[name] += cur_chunk.length + + if progress is not None: + progress(sum(stats.values())) + + break + else: + raise RuntimeError("Desired chunk not found in provided stores") + + return stats + + +def print_stats(stats: Dict[str, int]): + total_bytes = sum(stats.values()) + print(f"Total size: {total_bytes / 1024 / 1024:.2f} MB") + for name, total in stats.items(): + print(f" {name}: {total / 1024 / 1024:.2f} MB ({total / total_bytes * 100:.1f}%)") + + +def extract_simple(caibx_path, out_path, store_path): + # (name, callback, chunks) + target = parse_caibx(caibx_path) + sources = [ + # (store_path, RemoteChunkReader(store_path), build_chunk_dict(target)), + (store_path, FileChunkReader(store_path), build_chunk_dict(target)), + ] + + return extract(target, sources, out_path) + + +if __name__ == "__main__": + caibx = sys.argv[1] + out = sys.argv[2] + store = sys.argv[3] + + stats = extract_simple(caibx, out, store) + print_stats(stats) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index a69d3eb74..c3f8c7f82 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -18,6 +18,7 @@ NM = 'org.freedesktop.NetworkManager' NM_CON_ACT = NM + '.Connection.Active' NM_DEV = NM + '.Device' NM_DEV_WL = NM + '.Device.Wireless' +NM_DEV_STATS = NM + '.Device.Statistics' NM_AP = NM + '.AccessPoint' DBUS_PROPS = 'org.freedesktop.DBus.Properties' @@ -49,6 +50,7 @@ class NMMetered(IntEnum): NM_METERED_GUESS_NO = 4 TIMEOUT = 0.1 +REFRESH_RATE_MS = 1000 NetworkType = log.DeviceState.NetworkType NetworkStrength = log.DeviceState.NetworkStrength @@ -143,6 +145,10 @@ class Tici(HardwareBase): wlan_path = self.nm.GetDeviceByIpIface('wlan0', dbus_interface=NM, timeout=TIMEOUT) return self.bus.get_object(NM, wlan_path) + def get_wwan(self): + wwan_path = self.nm.GetDeviceByIpIface('wwan0', dbus_interface=NM, timeout=TIMEOUT) + return self.bus.get_object(NM, wwan_path) + def get_sim_info(self): modem = self.get_modem() sim_path = modem.Get(MM_MODEM, 'Sim', dbus_interface=DBUS_PROPS, timeout=TIMEOUT) @@ -362,6 +368,9 @@ class Tici(HardwareBase): def get_current_power_draw(self): return (self.read_param_file("/sys/class/hwmon/hwmon1/power1_input", int) / 1e6) + def get_som_power_draw(self): + return (self.read_param_file("/sys/class/power_supply/bms/voltage_now", int) * self.read_param_file("/sys/class/power_supply/bms/current_now", int) / 1e12) + def shutdown(self): # Note that for this to work and have the device stay powered off, the panda needs to be in UsbPowerMode::CLIENT! os.system("sudo poweroff") @@ -501,6 +510,22 @@ class Tici(HardwareBase): return r + def get_modem_data_usage(self): + try: + wwan = self.get_wwan() + + # Ensure refresh rate is set so values don't go stale + refresh_rate = wwan.Get(NM_DEV_STATS, 'RefreshRateMs', dbus_interface=DBUS_PROPS, timeout=TIMEOUT) + if refresh_rate != REFRESH_RATE_MS: + u = type(refresh_rate) + wwan.Set(NM_DEV_STATS, 'RefreshRateMs', u(REFRESH_RATE_MS), dbus_interface=DBUS_PROPS, timeout=TIMEOUT) + + tx = wwan.Get(NM_DEV_STATS, 'TxBytes', dbus_interface=DBUS_PROPS, timeout=TIMEOUT) + rx = wwan.Get(NM_DEV_STATS, 'RxBytes', dbus_interface=DBUS_PROPS, timeout=TIMEOUT) + return int(tx), int(rx) + except Exception: + return -1, -1 + def reset_internal_panda(self): gpio_init(GPIO.STM_RST_N, True) @@ -508,7 +533,6 @@ class Tici(HardwareBase): time.sleep(2) gpio_set(GPIO.STM_RST_N, 0) - def recover_internal_panda(self): gpio_init(GPIO.STM_RST_N, True) gpio_init(GPIO.STM_BOOT0, True) diff --git a/system/hardware/tici/power_monitor.py b/system/hardware/tici/power_monitor.py index 07362c839..f9d1e3cc4 100755 --- a/system/hardware/tici/power_monitor.py +++ b/system/hardware/tici/power_monitor.py @@ -1,15 +1,17 @@ #!/usr/bin/env python3 import sys import time +import datetime import numpy as np from typing import List from common.realtime import Ratekeeper +from common.filter_simple import FirstOrderFilter -def average(avg, sample): - # Weighted avg between existing value and new sample - return ((avg[0] * avg[1] + sample) / (avg[1] + 1), avg[1] + 1) +def read_power(): + with open("/sys/bus/i2c/devices/0-0040/hwmon/hwmon1/power1_input") as f: + return int(f.read()) / 1e6 def sample_power(seconds=5) -> List[float]: rate = 123 @@ -17,8 +19,7 @@ def sample_power(seconds=5) -> List[float]: pwrs = [] for _ in range(rate*seconds): - with open("/sys/bus/i2c/devices/0-0040/hwmon/hwmon1/power1_input") as f: - pwrs.append(int(f.read()) / 1e6) + pwrs.append(read_power()) rk.keep_time() return pwrs @@ -27,47 +28,30 @@ def get_power(seconds=5): return np.mean(pwrs) -if __name__ == '__main__': - - sample_time = None +if __name__ == "__main__": + duration = None if len(sys.argv) > 1: - sample_time = int(sys.argv[1]) - - start_time = time.monotonic() - try: - voltage_average = (0, 0) # average, count - current_average = (0, 0) - power_average = (0, 0) - while sample_time is None or time.monotonic() - start_time < sample_time: - with open("/sys/bus/i2c/devices/0-0040/hwmon/hwmon1/in1_input") as f: - voltage_total = int(f.read()) / 1000. - - with open("/sys/bus/i2c/devices/0-0040/hwmon/hwmon1/curr1_input") as f: - current_total = int(f.read()) + duration = int(sys.argv[1]) - # SOM measurements are questionable - #with open("/sys/class/power_supply/bms/voltage_now") as f: - # voltage = int(f.read()) / 1e6 # volts - #with open("/sys/class/power_supply/bms/current_now") as f: - # current = int(f.read()) / 1e3 # ma - - power_total = voltage_total*current_total + rate = 23 + rk = Ratekeeper(rate, print_delay_threshold=None) + fltr = FirstOrderFilter(0, 5, 1. / rate, initialized=False) - # compute averages - voltage_average = average(voltage_average, voltage_total) - current_average = average(current_average, current_total) - power_average = average(power_average, power_total) + measurements = [] + start_time = time.monotonic() - print(f"now: {power_total:.2f} mW, avg: {power_average[0]:.2f} mW") - time.sleep(0.25) + try: + while duration is None or time.monotonic() - start_time < duration: + fltr.update(read_power()) + if rk.frame % rate == 0: + measurements.append(fltr.x) + t = datetime.timedelta(seconds=time.monotonic() - start_time) + avg = sum(measurements) / len(measurements) + print(f"Now: {fltr.x:.2f} W, Avg: {avg:.2f} W over {t}") + rk.keep_time() except KeyboardInterrupt: pass - finally: - stop_time = time.monotonic() - print("\n----------------------Average-----------------------------------") - voltage = voltage_average[0] - current = current_average[0] - power = power_average[0] - print(f"{voltage:.2f} volts {current:12.2f} ma {power:12.2f} mW {power_total:12.2f} mW") - print(f" {stop_time - start_time:.2f} Seconds {voltage_average[1]} samples") - print("----------------------------------------------------------------") + + t = datetime.timedelta(seconds=time.monotonic() - start_time) + avg = sum(measurements) / len(measurements) + print(f"\nAverage power: {avg:.2f}W over {t}") diff --git a/system/hardware/tici/test_power_draw.py b/system/hardware/tici/test_power_draw.py index b6b5c735f..4b380372b 100755 --- a/system/hardware/tici/test_power_draw.py +++ b/system/hardware/tici/test_power_draw.py @@ -21,7 +21,7 @@ class Proc: PROCS = [ Proc('camerad', 2.15), Proc('modeld', 1.0, atol=0.15), - Proc('dmonitoringmodeld', 0.25), + Proc('dmonitoringmodeld', 0.35), Proc('encoderd', 0.23), ] @@ -37,6 +37,9 @@ class TestPowerDraw(unittest.TestCase): HARDWARE.initialize_hardware() HARDWARE.set_power_save(False) + # wait a bit for power save to disable + time.sleep(5) + def tearDown(self): manager_cleanup() diff --git a/system/hardware/tici/tests/__init__.py b/system/hardware/tici/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/system/hardware/tici/tests/compare_casync_manifest.py b/system/hardware/tici/tests/compare_casync_manifest.py new file mode 100755 index 000000000..5e5fa2455 --- /dev/null +++ b/system/hardware/tici/tests/compare_casync_manifest.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +import argparse +import collections +import multiprocessing +import os +from typing import Dict, List + +import requests +from tqdm import tqdm + +import system.hardware.tici.casync as casync + + +def get_chunk_download_size(chunk): + sha = chunk.sha.hex() + path = os.path.join(remote_url, sha[:4], sha + ".cacnk") + if os.path.isfile(path): + return os.path.getsize(path) + else: + r = requests.head(path) + r.raise_for_status() + return int(r.headers['content-length']) + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Compute overlap between two casync manifests') + parser.add_argument('frm') + parser.add_argument('to') + args = parser.parse_args() + + frm = casync.parse_caibx(args.frm) + to = casync.parse_caibx(args.to) + remote_url = args.to.replace('.caibx', '') + + most_common = collections.Counter(t.sha for t in to).most_common(1)[0][0] + + frm_dict = casync.build_chunk_dict(frm) + + # Get content-length for each chunk + with multiprocessing.Pool() as pool: + szs = list(tqdm(pool.imap(get_chunk_download_size, to), total=len(to))) + chunk_sizes = {t.sha: sz for (t, sz) in zip(to, szs)} + + sources: Dict[str, List[int]] = { + 'seed': [], + 'remote_uncompressed': [], + 'remote_compressed': [], + } + + for chunk in to: + # Assume most common chunk is the zero chunk + if chunk.sha == most_common: + continue + + if chunk.sha in frm_dict: + sources['seed'].append(chunk.length) + else: + sources['remote_uncompressed'].append(chunk.length) + sources['remote_compressed'].append(chunk_sizes[chunk.sha]) + + print() + print("Update statistics (excluding zeros)") + print() + print("Download only with no seed:") + print(f" Remote (uncompressed)\t\t{sum(sources['seed'] + sources['remote_uncompressed']) / 1000 / 1000:.2f} MB\tn = {len(to)}") + print(f" Remote (compressed download)\t{sum(chunk_sizes.values()) / 1000 / 1000:.2f} MB\tn = {len(to)}") + print() + print("Upgrade with seed partition:") + print(f" Seed (uncompressed)\t\t{sum(sources['seed']) / 1000 / 1000:.2f} MB\t\t\t\tn = {len(sources['seed'])}") + sz, n = sum(sources['remote_uncompressed']), len(sources['remote_uncompressed']) + print(f" Remote (uncompressed)\t\t{sz / 1000 / 1000:.2f} MB\t(avg {sz / 1000 / 1000 / n:4f} MB)\tn = {n}") + sz, n = sum(sources['remote_compressed']), len(sources['remote_compressed']) + print(f" Remote (compressed download)\t{sz / 1000 / 1000:.2f} MB\t(avg {sz / 1000 / 1000 / n:4f} MB)\tn = {n}") diff --git a/system/hardware/tici/tests/test_casync.py b/system/hardware/tici/tests/test_casync.py new file mode 100755 index 000000000..8724575ad --- /dev/null +++ b/system/hardware/tici/tests/test_casync.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +import os +import unittest +import tempfile +import subprocess + +import system.hardware.tici.casync as casync + +# dd if=/dev/zero of=/tmp/img.raw bs=1M count=2 +# sudo losetup -f /tmp/img.raw +# losetup -a | grep img.raw +LOOPBACK = os.environ.get('LOOPBACK', None) + + +class TestCasync(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.tmpdir = tempfile.TemporaryDirectory() + + # Build example contents + chunk_a = [i % 256 for i in range(1024)] * 512 + chunk_b = [(256 - i) % 256 for i in range(1024)] * 512 + zeroes = [0] * (1024 * 128) + contents = chunk_a + chunk_b + zeroes + chunk_a + + cls.contents = bytes(contents) + + # Write to file + cls.orig_fn = os.path.join(cls.tmpdir.name, 'orig.bin') + with open(cls.orig_fn, 'wb') as f: + f.write(cls.contents) + + # Create casync files + cls.manifest_fn = os.path.join(cls.tmpdir.name, 'orig.caibx') + cls.store_fn = os.path.join(cls.tmpdir.name, 'store') + subprocess.check_output(["casync", "make", "--compression=xz", "--store", cls.store_fn, cls.manifest_fn, cls.orig_fn]) + + target = casync.parse_caibx(cls.manifest_fn) + hashes = [c.sha.hex() for c in target] + + # Ensure we have chunk reuse + assert len(hashes) > len(set(hashes)) + + def setUp(self): + # Clear target_lo + if LOOPBACK is not None: + self.target_lo = LOOPBACK + with open(self.target_lo, 'wb') as f: + f.write(b"0" * len(self.contents)) + + self.target_fn = os.path.join(self.tmpdir.name, next(tempfile._get_candidate_names())) + self.seed_fn = os.path.join(self.tmpdir.name, next(tempfile._get_candidate_names())) + + def tearDown(self): + for fn in [self.target_fn, self.seed_fn]: + try: + os.unlink(fn) + except FileNotFoundError: + pass + + def test_simple_extract(self): + target = casync.parse_caibx(self.manifest_fn) + + sources = [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + stats = casync.extract(target, sources, self.target_fn) + + with open(self.target_fn, 'rb') as target_f: + self.assertEqual(target_f.read(), self.contents) + + self.assertEqual(stats['remote'], len(self.contents)) + + def test_seed(self): + target = casync.parse_caibx(self.manifest_fn) + + # Populate seed with half of the target contents + with open(self.seed_fn, 'wb') as seed_f: + seed_f.write(self.contents[:len(self.contents) // 2]) + + sources = [('seed', casync.FileChunkReader(self.seed_fn), casync.build_chunk_dict(target))] + sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + stats = casync.extract(target, sources, self.target_fn) + + with open(self.target_fn, 'rb') as target_f: + self.assertEqual(target_f.read(), self.contents) + + self.assertGreater(stats['seed'], 0) + self.assertLess(stats['remote'], len(self.contents)) + + def test_already_done(self): + """Test that an already flashed target doesn't download any chunks""" + target = casync.parse_caibx(self.manifest_fn) + + with open(self.target_fn, 'wb') as f: + f.write(self.contents) + + sources = [('target', casync.FileChunkReader(self.target_fn), casync.build_chunk_dict(target))] + sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + + stats = casync.extract(target, sources, self.target_fn) + + with open(self.target_fn, 'rb') as f: + self.assertEqual(f.read(), self.contents) + + self.assertEqual(stats['target'], len(self.contents)) + + def test_chunk_reuse(self): + """Test that chunks that are reused are only downloaded once""" + target = casync.parse_caibx(self.manifest_fn) + + # Ensure target exists + with open(self.target_fn, 'wb'): + pass + + sources = [('target', casync.FileChunkReader(self.target_fn), casync.build_chunk_dict(target))] + sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + + stats = casync.extract(target, sources, self.target_fn) + + with open(self.target_fn, 'rb') as f: + self.assertEqual(f.read(), self.contents) + + self.assertLess(stats['remote'], len(self.contents)) + + @unittest.skipUnless(LOOPBACK, "requires loopback device") + def test_lo_simple_extract(self): + target = casync.parse_caibx(self.manifest_fn) + sources = [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + + stats = casync.extract(target, sources, self.target_lo) + + with open(self.target_lo, 'rb') as target_f: + self.assertEqual(target_f.read(len(self.contents)), self.contents) + + self.assertEqual(stats['remote'], len(self.contents)) + + @unittest.skipUnless(LOOPBACK, "requires loopback device") + def test_lo_chunk_reuse(self): + """Test that chunks that are reused are only downloaded once""" + target = casync.parse_caibx(self.manifest_fn) + + sources = [('target', casync.FileChunkReader(self.target_lo), casync.build_chunk_dict(target))] + sources += [('remote', casync.RemoteChunkReader(self.store_fn), casync.build_chunk_dict(target))] + + stats = casync.extract(target, sources, self.target_lo) + + with open(self.target_lo, 'rb') as f: + self.assertEqual(f.read(len(self.contents)), self.contents) + + self.assertLess(stats['remote'], len(self.contents)) + + +if __name__ == "__main__": + unittest.main() diff --git a/third_party/qt5/larch64/bin/lrelease b/third_party/qt5/larch64/bin/lrelease new file mode 100755 index 000000000..5c0195d29 Binary files /dev/null and b/third_party/qt5/larch64/bin/lrelease differ diff --git a/third_party/qt5/larch64/bin/lupdate b/third_party/qt5/larch64/bin/lupdate new file mode 100755 index 000000000..67b7ea5cb Binary files /dev/null and b/third_party/qt5/larch64/bin/lupdate differ diff --git a/tools/CTF.md b/tools/CTF.md index 396e7575a..a14a41d55 100644 --- a/tools/CTF.md +++ b/tools/CTF.md @@ -12,7 +12,7 @@ Welcome to the first part of the comma CTF! getting started ```bash # start the route reply -cd selfdrive/ui/replay +cd tools/replay ./replay '0c7f0c7f0c7f0c7f|2021-10-13--13-00-00' --dcam --ecam # start the UI in another terminal diff --git a/tools/camerastream/compressed_vipc.py b/tools/camerastream/compressed_vipc.py index 290610e45..d321d6fd2 100755 --- a/tools/camerastream/compressed_vipc.py +++ b/tools/camerastream/compressed_vipc.py @@ -73,6 +73,10 @@ def decoder(addr, sock_name, vipc_server, vst, nvidia): continue assert len(frames) == 1 img_yuv = frames[0].to_ndarray(format=av.video.format.VideoFormat('yuv420p')).flatten() + uv_offset = H*W + y = img_yuv[:uv_offset] + uv = img_yuv[uv_offset:].reshape(2, -1).ravel('F') + img_yuv = np.hstack((y, uv)) vipc_server.send(vst, img_yuv.data, cnt, int(time_q[0]*1e9), int(time.monotonic()*1e9)) cnt += 1 diff --git a/tools/lib/bootlog.py b/tools/lib/bootlog.py index 4e7112040..351537082 100644 --- a/tools/lib/bootlog.py +++ b/tools/lib/bootlog.py @@ -35,6 +35,9 @@ class Bootlog: def datetime(self) -> datetime.datetime: return timestamp_to_datetime(self._timestamp) + def __str__(self): + return f"{self._dongle_id}|{self._timestamp}" + def __eq__(self, b) -> bool: if not isinstance(b, Bootlog): return False diff --git a/tools/plotjuggler/README.md b/tools/plotjuggler/README.md index e120edf2f..25fcb5931 100644 --- a/tools/plotjuggler/README.md +++ b/tools/plotjuggler/README.md @@ -12,7 +12,8 @@ Once you've cloned and are in openpilot, this command will download PlotJuggler ``` $ ./juggle.py -h -usage: juggle.py [-h] [--demo] [--qlog] [--can] [--stream] [--layout [LAYOUT]] [--install] [--dbc DBC] [route_or_segment_name] [segment_count] +usage: juggle.py [-h] [--demo] [--qlog] [--ci] [--can] [--stream] [--layout [LAYOUT]] [--install] [--dbc DBC] + [route_or_segment_name] [segment_count] A helper to run PlotJuggler on openpilot routes @@ -25,12 +26,14 @@ optional arguments: -h, --help show this help message and exit --demo Use the demo route instead of providing one (default: False) --qlog Use qlogs (default: False) + --ci Download data from openpilot CI bucket (default: False) --can Parse CAN data (default: False) --stream Start PlotJuggler in streaming mode (default: False) --layout [LAYOUT] Run PlotJuggler with a pre-defined layout (default: None) --install Install or update PlotJuggler + plugins (default: False) - --dbc DBC Set the DBC name to load for parsing CAN data. If not set, the DBC will be - automatically inferred from the logs. (default: None) + --dbc DBC Set the DBC name to load for parsing CAN data. If not set, the DBC will be automatically + inferred from the logs. (default: None) + ``` Examples using route name: diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 96ced2834..a25a5640d 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -12,6 +12,7 @@ import argparse from common.basedir import BASEDIR from selfdrive.test.process_replay.compare_logs import save_log +from selfdrive.test.openpilotci import get_url from tools.lib.logreader import LogReader from tools.lib.route import Route, SegmentName from urllib.parse import urlparse, parse_qs @@ -22,7 +23,8 @@ DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" RELEASES_URL="https://github.com/commaai/PlotJuggler/releases/download/latest" INSTALL_DIR = os.path.join(juggle_dir, "bin") PLOTJUGGLER_BIN = os.path.join(juggle_dir, "bin/plotjuggler") - +MINIMUM_PLOTJUGGLER_VERSION = (3, 5, 2) +MAX_STREAMING_BUFFER_SIZE = 1000 def install(): m = f"{platform.system()}-{platform.machine()}" @@ -45,18 +47,24 @@ def install(): tar.extractall(path=INSTALL_DIR) +def get_plotjuggler_version(): + out = subprocess.check_output([PLOTJUGGLER_BIN, "-v"], encoding="utf-8").strip() + version = out.split(" ")[1] + return tuple(map(int, version.split("."))) + + def load_segment(segment_name): if segment_name is None: return [] try: return list(LogReader(segment_name)) - except ValueError as e: + except (AssertionError, ValueError) as e: print(f"Error parsing {segment_name}: {e}") return [] -def start_juggler(fn=None, dbc=None, layout=None): +def start_juggler(fn=None, dbc=None, layout=None, route_or_segment_name=None): env = os.environ.copy() env["BASEDIR"] = BASEDIR env["PATH"] = f"{INSTALL_DIR}:{os.getenv('PATH', '')}" @@ -68,12 +76,14 @@ def start_juggler(fn=None, dbc=None, layout=None): extra_args += f" -d {fn}" if layout is not None: extra_args += f" -l {layout}" + if route_or_segment_name is not None: + extra_args += f" --window_title \"{route_or_segment_name}\"" - cmd = f'{PLOTJUGGLER_BIN} --plugin_folders {INSTALL_DIR}{extra_args}' + cmd = f'{PLOTJUGGLER_BIN} --buffer_size {MAX_STREAMING_BUFFER_SIZE} --plugin_folders {INSTALL_DIR}{extra_args}' subprocess.call(cmd, shell=True, env=env, cwd=juggle_dir) -def juggle_route(route_or_segment_name, segment_count, qlog, can, layout, dbc=None): +def juggle_route(route_or_segment_name, segment_count, qlog, can, layout, dbc=None, ci=False): segment_start = 0 if 'cabana' in route_or_segment_name: query = parse_qs(urlparse(route_or_segment_name).query) @@ -81,6 +91,11 @@ def juggle_route(route_or_segment_name, segment_count, qlog, can, layout, dbc=No if route_or_segment_name.startswith(("http://", "https://")) or os.path.isfile(route_or_segment_name): logs = [route_or_segment_name] + elif ci: + route_or_segment_name = SegmentName(route_or_segment_name, allow_route_name=True) + route = route_or_segment_name.route_name.canonical_name + segment_start = max(route_or_segment_name.segment_num, 0) + logs = [get_url(route, i) for i in range(100)] # Assume there not more than 100 segments else: route_or_segment_name = SegmentName(route_or_segment_name, allow_route_name=True) segment_start = max(route_or_segment_name.segment_num, 0) @@ -123,7 +138,7 @@ def juggle_route(route_or_segment_name, segment_count, qlog, can, layout, dbc=No with tempfile.NamedTemporaryFile(suffix='.rlog', dir=juggle_dir) as tmp: save_log(tmp.name, all_data, compress=False) del all_data - start_juggler(tmp.name, dbc, layout) + start_juggler(tmp.name, dbc, layout, route_or_segment_name) if __name__ == "__main__": @@ -132,6 +147,7 @@ if __name__ == "__main__": parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") parser.add_argument("--qlog", action="store_true", help="Use qlogs") + parser.add_argument("--ci", action="store_true", help="Download data from openpilot CI bucket") parser.add_argument("--can", action="store_true", help="Parse CAN data") parser.add_argument("--stream", action="store_true", help="Start PlotJuggler in streaming mode") parser.add_argument("--layout", nargs='?', help="Run PlotJuggler with a pre-defined layout") @@ -152,9 +168,13 @@ if __name__ == "__main__": if not os.path.exists(PLOTJUGGLER_BIN): print("PlotJuggler is missing. Downloading...") install() + + if get_plotjuggler_version() < MINIMUM_PLOTJUGGLER_VERSION: + print("PlotJuggler is out of date. Installing update...") + install() if args.stream: start_juggler(layout=args.layout) else: route_or_segment_name = DEMO_ROUTE if args.demo else args.route_or_segment_name.strip() - juggle_route(route_or_segment_name, args.segment_count, args.qlog, args.can, args.layout, args.dbc) + juggle_route(route_or_segment_name, args.segment_count, args.qlog, args.can, args.layout, args.dbc, args.ci) diff --git a/tools/plotjuggler/layouts/max-torque-debug.xml b/tools/plotjuggler/layouts/max-torque-debug.xml new file mode 100644 index 000000000..05aa208e2 --- /dev/null +++ b/tools/plotjuggler/layouts/max-torque-debug.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if (v3 == 0 and v4 == 1) then + return (value * v1 ^ 2) - (v2 * 9.81) +end +return 0 + /controlsState/curvature + + /carState/vEgo + /liveParameters/roll + /carState/steeringPressed + /carControl/latActive + + + + + return (value * v1 ^ 2) - (v2 * 9.81) + /controlsState/desiredCurvature + + /carState/vEgo + /liveParameters/roll + + + + + return (value * v1 ^ 2) - (v2 * 9.81) + /controlsState/curvature + + /carState/vEgo + /liveParameters/roll + + + + + + + diff --git a/tools/replay/.gitignore b/tools/replay/.gitignore new file mode 100644 index 000000000..83f0e99a8 --- /dev/null +++ b/tools/replay/.gitignore @@ -0,0 +1,5 @@ +moc_* +*.moc + +replay +tests/test_replay diff --git a/tools/replay/README.md b/tools/replay/README.md index 759a448d7..2d0b702bd 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -6,15 +6,15 @@ ```bash # Log in via browser to have access to non-public routes -python lib/auth.py +python tools/lib/auth.py # Start a replay -selfdrive/ui/replay/replay +tools/replay/replay # Example: -# selfdrive/ui/replay/replay '4cf7a6ad03080c90|2021-09-29--13-46-36' +# tools/replay/replay '4cf7a6ad03080c90|2021-09-29--13-46-36' # or use --demo to replay the default demo route: -# selfdrive/ui/replay/replay --demo +# tools/replay/replay --demo # watch the replay with the normal openpilot UI cd selfdrive/ui && ./ui @@ -24,9 +24,10 @@ python replay/ui.py ``` ## usage + ``` bash -$ selfdrive/ui/replay/replay -h -Usage: selfdrive/ui/replay/replay [options] route +$ tools/replay/replay -h +Usage: tools/replay/replay [options] route Mock openpilot components by publishing logged messages. Options: @@ -51,7 +52,7 @@ simply replay a route using the `--dcam` and `--ecam` flags: ```bash # start a replay -cd selfdrive/ui/replay && ./replay --demo --dcam --ecam +cd tools/replay && ./replay --demo --dcam --ecam # then start watch3 cd selfdrive/ui && ./watch3 @@ -70,5 +71,5 @@ In order to replay specific route: MOCK=1 selfdrive/boardd/tests/boardd_old.py # In another terminal: -selfdrive/ui/replay/replay +tools/replay/replay ``` diff --git a/tools/replay/SConscript b/tools/replay/SConscript new file mode 100644 index 000000000..d3967708f --- /dev/null +++ b/tools/replay/SConscript @@ -0,0 +1,25 @@ +import os +Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', + 'cereal', 'transformations') + +base_frameworks = qt_env['FRAMEWORKS'] +base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', + 'capnp', 'kj', 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] + +if arch == "Darwin": + base_frameworks.append('OpenCL') +else: + base_libs.append('OpenCL') + +qt_libs = ['qt_util'] + base_libs +if arch in ['x86_64', 'Darwin'] or GetOption('extras'): + qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] + + replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc"] + + replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=qt_libs, FRAMEWORKS=base_frameworks) + 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]) diff --git a/selfdrive/ui/replay/camera.cc b/tools/replay/camera.cc similarity index 96% rename from selfdrive/ui/replay/camera.cc rename to tools/replay/camera.cc index 2e8b68a41..87afe63a2 100644 --- a/selfdrive/ui/replay/camera.cc +++ b/tools/replay/camera.cc @@ -1,5 +1,5 @@ -#include "selfdrive/ui/replay/camera.h" -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/camera.h" +#include "tools/replay/util.h" #include diff --git a/selfdrive/ui/replay/camera.h b/tools/replay/camera.h similarity index 92% rename from selfdrive/ui/replay/camera.h rename to tools/replay/camera.h index 7f078511c..66d33142f 100644 --- a/selfdrive/ui/replay/camera.h +++ b/tools/replay/camera.h @@ -3,8 +3,8 @@ #include #include "cereal/visionipc/visionipc_server.h" #include "common/queue.h" -#include "selfdrive/ui/replay/framereader.h" -#include "selfdrive/ui/replay/logreader.h" +#include "tools/replay/framereader.h" +#include "tools/replay/logreader.h" class CameraServer { public: diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index b212abc43..0b8b4fe0a 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -2,6 +2,7 @@ import os import time import threading +import multiprocessing from tqdm import tqdm os.environ['FILEREADER_CACHE'] = '1' @@ -9,7 +10,7 @@ os.environ['FILEREADER_CACHE'] = '1' from common.basedir import BASEDIR from common.realtime import config_realtime_process, Ratekeeper, DT_CTRL from selfdrive.boardd.boardd import can_capnp_to_can_list -from tools.lib.logreader import LogReader +from tools.plotjuggler.juggle import load_segment from panda import Panda try: @@ -94,11 +95,10 @@ if __name__ == "__main__": ROUTE = "77611a1fac303767/2020-03-24--09-50-38" REPLAY_SEGS = list(range(10, 16)) # route has 82 segments available CAN_MSGS = [] - for i in tqdm(REPLAY_SEGS): - log_url = f"https://commadataci.blob.core.windows.net/openpilotci/{ROUTE}/{i}/rlog.bz2" - lr = LogReader(log_url) - CAN_MSGS += [can_capnp_to_can_list(m.can) for m in lr if m.which() == 'can'] - + logs = [f"https://commadataci.blob.core.windows.net/openpilotci/{ROUTE}/{i}/rlog.bz2" for i in REPLAY_SEGS] + with multiprocessing.Pool(24) as pool: + for lr in tqdm(pool.map(load_segment, logs)): + CAN_MSGS += [can_capnp_to_can_list(m.can) for m in lr if m.which() == 'can'] # set both to cycle ignition IGN_ON = int(os.getenv("ON", "0")) diff --git a/selfdrive/ui/replay/consoleui.cc b/tools/replay/consoleui.cc similarity index 99% rename from selfdrive/ui/replay/consoleui.cc rename to tools/replay/consoleui.cc index 05eb09c9e..e4a3146a6 100644 --- a/selfdrive/ui/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/replay/consoleui.h" +#include "tools/replay/consoleui.h" #include #include diff --git a/selfdrive/ui/replay/consoleui.h b/tools/replay/consoleui.h similarity index 96% rename from selfdrive/ui/replay/consoleui.h rename to tools/replay/consoleui.h index bce1146d4..20e07524d 100644 --- a/selfdrive/ui/replay/consoleui.h +++ b/tools/replay/consoleui.h @@ -7,7 +7,7 @@ #include #include -#include "selfdrive/ui/replay/replay.h" +#include "tools/replay/replay.h" #include class ConsoleUI : public QObject { diff --git a/selfdrive/ui/replay/filereader.cc b/tools/replay/filereader.cc similarity index 94% rename from selfdrive/ui/replay/filereader.cc rename to tools/replay/filereader.cc index 5f862bec3..88879067c 100644 --- a/selfdrive/ui/replay/filereader.cc +++ b/tools/replay/filereader.cc @@ -1,9 +1,9 @@ -#include "selfdrive/ui/replay/filereader.h" +#include "tools/replay/filereader.h" #include #include "common/util.h" -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/util.h" std::string cacheFilePath(const std::string &url) { static std::string cache_path = [] { diff --git a/selfdrive/ui/replay/filereader.h b/tools/replay/filereader.h similarity index 100% rename from selfdrive/ui/replay/filereader.h rename to tools/replay/filereader.h diff --git a/selfdrive/ui/replay/framereader.cc b/tools/replay/framereader.cc similarity index 95% rename from selfdrive/ui/replay/framereader.cc rename to tools/replay/framereader.cc index 8453407ce..ecde9ad5d 100644 --- a/selfdrive/ui/replay/framereader.cc +++ b/tools/replay/framereader.cc @@ -1,5 +1,5 @@ -#include "selfdrive/ui/replay/framereader.h" -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/framereader.h" +#include "tools/replay/util.h" #include #include "libyuv.h" @@ -117,8 +117,6 @@ bool FrameReader::load(const std::byte *data, size_t size, bool no_hw_decoder, s if (has_hw_decoder && !no_hw_decoder) { if (!initHardwareDecoder(HW_DEVICE_TYPE)) { rWarning("No device with hardware decoder found. fallback to CPU decoding."); - } else { - nv12toyuv_buffer.resize(getYUVSize()); } } @@ -227,22 +225,21 @@ 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; if (hw_pix_fmt == HW_PIX_FMT) { - uint8_t *y = yuv ? yuv : nv12toyuv_buffer.data(); - uint8_t *uv = y + width * height; 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); } } else { - uint8_t *y = yuv ? yuv : nv12toyuv_buffer.data(); - uint8_t *uv = y + width * height; libyuv::I420ToNV12(f->data[0], f->linesize[0], f->data[1], f->linesize[1], f->data[2], f->linesize[2], y, width, - uv, width / 2, + uv, width, width, height); } return true; diff --git a/selfdrive/ui/replay/framereader.h b/tools/replay/framereader.h similarity index 94% rename from selfdrive/ui/replay/framereader.h rename to tools/replay/framereader.h index 443636e27..e50b61d7f 100644 --- a/selfdrive/ui/replay/framereader.h +++ b/tools/replay/framereader.h @@ -4,7 +4,7 @@ #include #include -#include "selfdrive/ui/replay/filereader.h" +#include "tools/replay/filereader.h" extern "C" { #include @@ -46,7 +46,6 @@ private: AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE; AVBufferRef *hw_device_ctx = nullptr; - std::vector nv12toyuv_buffer; int prev_idx = -1; inline static std::atomic has_hw_decoder = true; }; diff --git a/selfdrive/ui/replay/logreader.cc b/tools/replay/logreader.cc similarity index 86% rename from selfdrive/ui/replay/logreader.cc rename to tools/replay/logreader.cc index 579fe5064..9b7a07a83 100644 --- a/selfdrive/ui/replay/logreader.cc +++ b/tools/replay/logreader.cc @@ -1,7 +1,7 @@ -#include "selfdrive/ui/replay/logreader.h" +#include "tools/replay/logreader.h" #include -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/util.h" Event::Event(const kj::ArrayPtr &amsg, bool frame) : reader(amsg), frame(frame) { words = kj::ArrayPtr(amsg.begin(), reader.getEnd()); @@ -47,22 +47,22 @@ LogReader::~LogReader() { } bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache, int chunk_size, int retries) { - FileReader f(local_cache, chunk_size, retries); - std::string data = f.read(url, abort); - if (data.empty()) return false; + raw_ = FileReader(local_cache, chunk_size, retries).read(url, abort); + if (raw_.empty()) return false; - return load((std::byte*)data.data(), data.size(), abort); + if (url.find(".bz2") != std::string::npos) { + raw_ = decompressBZ2(raw_, abort); + if (raw_.empty()) return false; + } + return parse(abort); } bool LogReader::load(const std::byte *data, size_t size, std::atomic *abort) { - raw_ = decompressBZ2(data, size, abort); - if (raw_.empty()) { - if (!(abort && *abort)) { - rWarning("failed to decompress log"); - } - return false; - } + raw_.assign((const char *)data, size); + return parse(abort); +} +bool LogReader::parse(std::atomic *abort) { try { kj::ArrayPtr words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word)); while (words.size() > 0 && !(abort && *abort)) { diff --git a/selfdrive/ui/replay/logreader.h b/tools/replay/logreader.h similarity index 93% rename from selfdrive/ui/replay/logreader.h rename to tools/replay/logreader.h index 7ada20605..bd666d0a7 100644 --- a/selfdrive/ui/replay/logreader.h +++ b/tools/replay/logreader.h @@ -6,8 +6,8 @@ #endif #include "cereal/gen/cpp/log.capnp.h" -#include "selfdrive/camerad/cameras/camera_common.h" -#include "selfdrive/ui/replay/filereader.h" +#include "system/camerad/cameras/camera_common.h" +#include "tools/replay/filereader.h" const CameraType ALL_CAMERAS[] = {RoadCam, DriverCam, WideRoadCam}; const int MAX_CAMERAS = std::size(ALL_CAMERAS); @@ -52,10 +52,10 @@ public: ~LogReader(); bool load(const std::string &url, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); bool load(const std::byte *data, size_t size, std::atomic *abort = nullptr); - std::vector events; private: + bool parse(std::atomic *abort); std::string raw_; #ifdef HAS_MEMORY_RESOURCE std::pmr::monotonic_buffer_resource *mbr_ = nullptr; diff --git a/selfdrive/ui/replay/main.cc b/tools/replay/main.cc similarity index 96% rename from selfdrive/ui/replay/main.cc rename to tools/replay/main.cc index e09587023..d3d689487 100644 --- a/selfdrive/ui/replay/main.cc +++ b/tools/replay/main.cc @@ -1,8 +1,8 @@ #include #include -#include "selfdrive/ui/replay/consoleui.h" -#include "selfdrive/ui/replay/replay.h" +#include "tools/replay/consoleui.h" +#include "tools/replay/replay.h" const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; diff --git a/selfdrive/ui/replay/replay.cc b/tools/replay/replay.cc similarity index 98% rename from selfdrive/ui/replay/replay.cc rename to tools/replay/replay.cc index 5eb7469c9..c886a7e18 100644 --- a/selfdrive/ui/replay/replay.cc +++ b/tools/replay/replay.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/replay/replay.h" +#include "tools/replay/replay.h" #include #include @@ -8,7 +8,7 @@ #include "common/params.h" #include "common/timing.h" #include "system/hardware/hw.h" -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/util.h" Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *sm_, uint32_t flags, QString data_dir, QObject *parent) : sm(sm_), flags_(flags), QObject(parent) { @@ -360,7 +360,8 @@ void Replay::stream() { setCurrentSegment(toSeconds(cur_mono_time_) / 60); // migration for pandaState -> pandaStates to keep UI working for old segments - if (cur_which == cereal::Event::Which::PANDA_STATE_D_E_P_R_E_C_A_T_E_D) { + if (cur_which == cereal::Event::Which::PANDA_STATE_D_E_P_R_E_C_A_T_E_D && + sockets_[cereal::Event::Which::PANDA_STATES] != nullptr) { MessageBuilder msg; auto ps = msg.initEvent().initPandaStates(1); ps[0].setIgnitionLine(true); diff --git a/selfdrive/ui/replay/replay.h b/tools/replay/replay.h similarity index 97% rename from selfdrive/ui/replay/replay.h rename to tools/replay/replay.h index c89a835f6..13269d3ec 100644 --- a/selfdrive/ui/replay/replay.h +++ b/tools/replay/replay.h @@ -4,8 +4,8 @@ #include -#include "selfdrive/ui/replay/camera.h" -#include "selfdrive/ui/replay/route.h" +#include "tools/replay/camera.h" +#include "tools/replay/route.h" // one segment uses about 100M of memory constexpr int FORWARD_SEGS = 5; diff --git a/selfdrive/ui/replay/route.cc b/tools/replay/route.cc similarity index 95% rename from selfdrive/ui/replay/route.cc rename to tools/replay/route.cc index 3d595141c..c91b27ae8 100644 --- a/selfdrive/ui/replay/route.cc +++ b/tools/replay/route.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/replay/route.h" +#include "tools/replay/route.h" #include #include @@ -11,8 +11,8 @@ #include "system/hardware/hw.h" #include "selfdrive/ui/qt/api.h" -#include "selfdrive/ui/replay/replay.h" -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/replay.h" +#include "tools/replay/util.h" Route::Route(const QString &route, const QString &data_dir) : data_dir_(data_dir) { route_ = parseRoute(route); @@ -82,9 +82,9 @@ void Route::addFileToSegment(int n, const QString &file) { const int pos = name.lastIndexOf("--"); name = pos != -1 ? name.mid(pos + 2) : name; - if (name == "rlog.bz2") { + if (name == "rlog.bz2" || name == "rlog") { segments_[n].rlog = file; - } else if (name == "qlog.bz2") { + } else if (name == "qlog.bz2" || name == "qlog") { segments_[n].qlog = file; } else if (name == "fcamera.hevc") { segments_[n].road_cam = file; diff --git a/selfdrive/ui/replay/route.h b/tools/replay/route.h similarity index 92% rename from selfdrive/ui/replay/route.h rename to tools/replay/route.h index ac8fba75c..6ca9c3b88 100644 --- a/selfdrive/ui/replay/route.h +++ b/tools/replay/route.h @@ -2,9 +2,9 @@ #include -#include "selfdrive/ui/replay/framereader.h" -#include "selfdrive/ui/replay/logreader.h" -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/framereader.h" +#include "tools/replay/logreader.h" +#include "tools/replay/util.h" struct RouteIdentifier { QString dongle_id; diff --git a/selfdrive/ui/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc similarity index 98% rename from selfdrive/ui/replay/tests/test_replay.cc rename to tools/replay/tests/test_replay.cc index bf984f5f3..d6482c3ca 100644 --- a/selfdrive/ui/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -6,8 +6,8 @@ #include "catch2/catch.hpp" #include "common/util.h" -#include "selfdrive/ui/replay/replay.h" -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/replay.h" +#include "tools/replay/util.h" const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; @@ -71,6 +71,7 @@ TEST_CASE("LogReader") { FileReader reader(true); std::string corrupt_content = reader.read(TEST_RLOG_URL); corrupt_content.resize(corrupt_content.length() / 2); + corrupt_content = decompressBZ2(corrupt_content); LogReader log; REQUIRE(log.load((std::byte *)corrupt_content.data(), corrupt_content.size())); REQUIRE(log.events.size() > 0); diff --git a/selfdrive/ui/replay/tests/test_runner.cc b/tools/replay/tests/test_runner.cc similarity index 100% rename from selfdrive/ui/replay/tests/test_runner.cc rename to tools/replay/tests/test_runner.cc diff --git a/selfdrive/ui/replay/util.cc b/tools/replay/util.cc similarity index 99% rename from selfdrive/ui/replay/util.cc rename to tools/replay/util.cc index 099219ccd..a6ebbec61 100644 --- a/selfdrive/ui/replay/util.cc +++ b/tools/replay/util.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/util.h" #include #include diff --git a/selfdrive/ui/replay/util.h b/tools/replay/util.h similarity index 100% rename from selfdrive/ui/replay/util.h rename to tools/replay/util.h diff --git a/tools/serial/connect.sh b/tools/serial/connect.sh index 037152019..a073310b0 100755 --- a/tools/serial/connect.sh +++ b/tools/serial/connect.sh @@ -1,8 +1,8 @@ #!/bin/bash while true; do - if ls /dev/ttyUSB* 2> /dev/null; then - sudo screen /dev/ttyUSB* 115200 + if ls /dev/serial/by-id/usb-FTDI_FT230X* 2> /dev/null; then + sudo screen /dev/serial/by-id/usb-FTDI_FT230X* 115200 fi sleep 0.005 done diff --git a/tools/sim/Dockerfile.sim b/tools/sim/Dockerfile.sim index 0d6e8e584..3692d48e0 100644 --- a/tools/sim/Dockerfile.sim +++ b/tools/sim/Dockerfile.sim @@ -18,6 +18,7 @@ COPY ./third_party $HOME/openpilot/third_party COPY ./pyextra $HOME/openpilot/pyextra COPY ./site_scons $HOME/openpilot/site_scons COPY ./rednose $HOME/openpilot/rednose +COPY ./laika $HOME/openpilot/laika COPY ./common $HOME/openpilot/common COPY ./opendbc $HOME/openpilot/opendbc COPY ./cereal $HOME/openpilot/cereal diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index ae65e8deb..fa4ce2b41 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -81,7 +81,7 @@ class Camerad: cl_arg = f" -DHEIGHT={H} -DWIDTH={W} -DRGB_STRIDE={W * 3} -DUV_WIDTH={W // 2} -DUV_HEIGHT={H // 2} -DRGB_SIZE={W * H} -DCL_DEBUG " # TODO: move rgb_to_yuv.cl to local dir once the frame stream camera is removed - kernel_fn = os.path.join(BASEDIR, "selfdrive", "camerad", "transforms", "rgb_to_yuv.cl") + kernel_fn = os.path.join(BASEDIR, "system", "camerad", "transforms", "rgb_to_yuv.cl") with open(kernel_fn) as f: prg = cl.Program(self.ctx, f.read()).build(cl_arg) self.krnl = prg.rgb_to_yuv @@ -198,12 +198,12 @@ def gps_callback(gps, vehicle_state): def fake_driver_monitoring(exit_event: threading.Event): - pm = messaging.PubMaster(['driverState', 'driverMonitoringState']) + pm = messaging.PubMaster(['driverStateV2', 'driverMonitoringState']) while not exit_event.is_set(): # dmonitoringmodeld output - dat = messaging.new_message('driverState') - dat.driverState.faceProb = 1.0 - pm.send('driverState', dat) + dat = messaging.new_message('driverStateV2') + dat.driverStateV2.leftDriverData.faceProb = 1.0 + pm.send('driverStateV2', dat) # dmonitoringd output dat = messaging.new_message('driverMonitoringState') @@ -304,6 +304,7 @@ class CarlaBridge: 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.''' diff --git a/tools/sim/lib/can.py b/tools/sim/lib/can.py index 2b1048fef..af4339b2e 100755 --- a/tools/sim/lib/can.py +++ b/tools/sim/lib/can.py @@ -33,48 +33,48 @@ def can_function(pm, speed, angle, idx, cruise_button, is_engaged): # *** powertrain bus *** speed = speed * 3.6 # convert m/s to kph - msg.append(packer.make_can_msg("ENGINE_DATA", 0, {"XMISSION_SPEED": speed}, idx)) + 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 - }, -1)) + })) - msg.append(packer.make_can_msg("SCM_BUTTONS", 0, {"CRUISE_BUTTONS": cruise_button}, idx)) + 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}, -1)[2][:-1]) + 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, -1)) - - msg.append(packer.make_can_msg("GEARBOX", 0, {"GEAR": 4, "GEAR_SHIFTER": 8}, idx)) - msg.append(packer.make_can_msg("GAS_PEDAL_2", 0, {}, idx)) - msg.append(packer.make_can_msg("SEATBELT_STATUS", 0, {"SEATBELT_DRIVER_LATCHED": 1}, idx)) - msg.append(packer.make_can_msg("STEER_STATUS", 0, {}, idx)) - msg.append(packer.make_can_msg("STEERING_SENSORS", 0, {"STEER_ANGLE": angle}, idx)) - msg.append(packer.make_can_msg("VSA_STATUS", 0, {}, idx)) - msg.append(packer.make_can_msg("STANDSTILL", 0, {"WHEELS_MOVING": 1 if speed >= 1.0 else 0}, idx)) - msg.append(packer.make_can_msg("STEER_MOTOR_TORQUE", 0, {}, idx)) - msg.append(packer.make_can_msg("EPB_STATUS", 0, {}, idx)) - msg.append(packer.make_can_msg("DOORS_STATUS", 0, {}, idx)) - msg.append(packer.make_can_msg("CRUISE_PARAMS", 0, {}, idx)) - msg.append(packer.make_can_msg("CRUISE", 0, {}, idx)) - msg.append(packer.make_can_msg("SCM_FEEDBACK", 0, {"MAIN_ON": 1}, idx)) - msg.append(packer.make_can_msg("POWERTRAIN_DATA", 0, {"ACC_STATUS": int(is_engaged)}, idx)) + 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, {}, idx)) - msg.append(packer.make_can_msg("ACC_HUD", 2, {}, idx)) - msg.append(packer.make_can_msg("BRAKE_COMMAND", 2, {}, idx)) + msg.append(packer.make_can_msg("STEERING_CONTROL", 2, {})) + msg.append(packer.make_can_msg("ACC_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}, -1)) + 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}, -1)) + msg.append(rpacker.make_can_msg("TRACK_%d" % i, 1, {"LONG_DIST": 255.5})) pm.send('can', can_list_to_can_capnp(msg)) diff --git a/tools/sim/lib/replay.sh b/tools/sim/lib/replay.sh deleted file mode 100755 index e65a4b69a..000000000 --- a/tools/sim/lib/replay.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -cd ~/openpilot/tools/nui - -# vision, boardd, sensorsd -ALLOW=frame,can,ubloxRaw,health,sensorEvents,gpsNMEA,gpsLocationExternal ./nui "02ec6bea180a4d36/2019-10-25--10-18-09" diff --git a/tools/sim/start_carla.sh b/tools/sim/start_carla.sh index 67ced7eb2..7ead6699f 100755 --- a/tools/sim/start_carla.sh +++ b/tools/sim/start_carla.sh @@ -22,6 +22,7 @@ if [[ "$DETACH" ]]; then EXTRA_ARGS="-d" fi +docker kill carla_sim || true docker run \ --name carla_sim \ --rm \ diff --git a/tools/ssh/README.md b/tools/ssh/README.md index 66f030de5..29d334932 100644 --- a/tools/ssh/README.md +++ b/tools/ssh/README.md @@ -22,3 +22,52 @@ The public keys are only fetched from your GitHub account once. In order to upda The `id_rsa` key in this directory only works while your device is in the setup state with no software installed. After installation, that default key will be removed. See the [community wiki](https://github.com/commaai/openpilot/wiki/SSH) for more detailed instructions and information. + +# Connecting to ssh.comma.ai +SSH into your comma device from anywhere with `ssh.comma.ai`. + +## Setup + +With software version 0.6.1 or newer, enter your GitHub username on your device under Developer Settings. Your GitHub authorized public keys will become your authorized SSH keys for `ssh.comma.ai`. You can add any additional keys in `/system/comma/home/.ssh/authorized_keys.persist`. + +Requires [comma SIM with comma prime](https://comma.ai/shop) activated with comma connect, available on iOS and Android. comma two and EON ship with a pre-inserted comma SIM. + +## Recommended .ssh/config + +With the below ssh configuration, you can type `ssh comma-{dongleid}` to connect to your device through `ssh.comma.ai`. For example, `ssh comma-ffffffffffffffff`. + +``` +Host comma-* + Port 22 + User comma + IdentityFile ~/.ssh/my_github_key + ProxyCommand ssh %h@ssh.comma.ai -W %h:%p +Host ssh.comma.ai + Hostname ssh.comma.ai + Port 22 + IdentityFile ~/.ssh/my_github_key +``` + +## One-off connection + +``` +ssh -i ~/.ssh/my_github_key -o ProxyCommand="ssh -i ~/.ssh/my_github_key -W %h:%p -p %p %h@ssh.comma.ai" comma@ffffffffffffffff +``` +(Replace `ffffffffffffffff` with your dongle_id) + +## ssh.comma.ai host key fingerprint + +``` +Host key fingerprint is SHA256:X22GOmfjGb9J04IA2+egtdaJ7vW9Fbtmpz9/x8/W1X4 ++---[RSA 4096]----+ +| | +| | +| . | +| + o | +| S = + +..| +| + @ = .=| +| . B @ ++=| +| o * B XE| +| .o o OB/| ++----[SHA256]-----+ +``` diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index 5084247cb..7abfb6358 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -71,7 +71,7 @@ if __name__ == "__main__": active = sm['controlsState'].active steer = sm['carControl'].actuatorsOutput.steer standstill = sm['carState'].standstill - steer_limited = sm['carState'].steeringRateLimited + steer_limited = abs(sm['carControl'].actuators.steer - sm['carControl'].actuatorsOutput.steer) > 1e-2 overriding = sm['carState'].steeringPressed changing_lanes = sm['lateralPlan'].laneChangeState != 0 d_path_points = sm['lateralPlan'].dPathPoints diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh index 60d41a477..863b85371 100755 --- a/tools/ubuntu_setup.sh +++ b/tools/ubuntu_setup.sh @@ -14,6 +14,7 @@ function install_ubuntu_common_requirements() { autoconf \ build-essential \ ca-certificates \ + casync \ clang \ cmake \ make \ @@ -59,6 +60,7 @@ function install_ubuntu_common_requirements() { qtmultimedia5-dev \ qtlocation5-dev \ qtpositioning5-dev \ + qttools5-dev-tools \ libqt5sql5-sqlite \ libqt5svg5-dev \ libqt5x11extras5-dev \ @@ -68,7 +70,7 @@ function install_ubuntu_common_requirements() { } # Install Ubuntu 22.04 LTS packages -function install_ubuntu_latest_requirements() { +function install_ubuntu_jammy_requirements() { install_ubuntu_common_requirements sudo apt-get install -y --no-install-recommends \ @@ -80,7 +82,7 @@ function install_ubuntu_latest_requirements() { } # Install Ubuntu 20.04 packages -function install_ubuntu_lts_requirements() { +function install_ubuntu_focal_requirements() { install_ubuntu_common_requirements sudo apt-get install -y --no-install-recommends \ @@ -92,12 +94,12 @@ function install_ubuntu_lts_requirements() { # Detect OS using /etc/os-release file if [ -f "/etc/os-release" ]; then source /etc/os-release - case "$ID $VERSION_ID" in - "ubuntu 22.04") - install_ubuntu_latest_requirements + case "$VERSION_CODENAME" in + "jammy") + install_ubuntu_jammy_requirements ;; - "ubuntu 20.04") - install_ubuntu_lts_requirements + "focal") + install_ubuntu_focal_requirements ;; *) echo "$ID $VERSION_ID is unsupported. This setup script is written for Ubuntu 20.04." @@ -106,7 +108,11 @@ if [ -f "/etc/os-release" ]; then if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi - install_ubuntu_lts_requirements + if [ "$UBUNTU_CODENAME" = "jammy" ]; then + install_ubuntu_jammy_requirements + else + install_ubuntu_focal_requirements + fi esac else echo "No /etc/os-release in the system" diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 48e5accb2..1e07bdbbe 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -23,14 +23,14 @@ git clone https://github.com/commaai/openpilot.git ``` cd ~/openpilot ``` -- check out selfdrive/camerad/cameras/camera_webcam.cc lines 72 and 146 before building if any camera is upside down +- check out system/camerad/cameras/camera_webcam.cc lines 72 and 146 before building if any camera is upside down ``` USE_WEBCAM=1 scons -j$(nproc) ``` ## Connect the hardware - Connect the road facing camera first, then the driver facing camera -- (default indexes are 1 and 2; can be modified in selfdrive/camerad/cameras/camera_webcam.cc) +- (default indexes are 1 and 2; can be modified in system/camerad/cameras/camera_webcam.cc) - Connect your computer to panda ## GO diff --git a/tools/zookeeper/power_monitor.py b/tools/zookeeper/power_monitor.py index d3bdd6679..fa1f442bb 100755 --- a/tools/zookeeper/power_monitor.py +++ b/tools/zookeeper/power_monitor.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import sys import time +import datetime from common.realtime import Ratekeeper from common.filter_simple import FirstOrderFilter @@ -19,12 +20,21 @@ if __name__ == "__main__": rk = Ratekeeper(rate, print_delay_threshold=None) fltr = FirstOrderFilter(0, 5, 1. / rate, initialized=False) + measurements = [] + start_time = time.monotonic() + try: - start_time = time.monotonic() while duration is None or time.monotonic() - start_time < duration: fltr.update(z.read_power()) if rk.frame % rate == 0: - print(f"{fltr.x:.2f} W") + measurements.append(fltr.x) + t = datetime.timedelta(seconds=time.monotonic() - start_time) + avg = sum(measurements) / len(measurements) + print(f"Now: {fltr.x:.2f} W, Avg: {avg:.2f} W over {t}") rk.keep_time() except KeyboardInterrupt: pass + + t = datetime.timedelta(seconds=time.monotonic() - start_time) + avg = sum(measurements) / len(measurements) + print(f"\nAverage power: {avg:.2f}W over {t}")