diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2dfa9d0297..99d42c36e3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -51,4 +51,4 @@ jobs: - name: Push master-ci run: | unset TARGET_DIR - BRANCH=master-ci release/build_devel.sh + BRANCH=__nightly release/build_devel.sh diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index b4b0eb614c..95f8320d62 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -5,7 +5,33 @@ on: - cron: "0 14 * * 1" # every Monday at 2am UTC (6am PST) workflow_dispatch: +env: + BASE_IMAGE: openpilot-base + BUILD: selfdrive/test/docker_build.sh base + RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c + jobs: + update_translations: + runs-on: ubuntu-latest + if: github.repository == 'commaai/openpilot' + steps: + - uses: actions/checkout@v4 + - uses: ./.github/workflows/setup-with-retry + - name: Update translations + run: | + ${{ env.RUN }} "python3 selfdrive/ui/update_translations.py --vanish" + - name: Create Pull Request + uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83 + with: + author: Vehicle Researcher + commit-message: "Update translations" + title: "[bot] Update translations" + body: "Automatic PR from repo-maintenance -> update_translations" + branch: "update-translations" + base: "master" + delete-branch: true + labels: bot + package_updates: name: package_updates runs-on: ubuntu-latest diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml index 8a01753687..2ded4a2d2a 100644 --- a/.github/workflows/ui_preview.yaml +++ b/.github/workflows/ui_preview.yaml @@ -86,7 +86,7 @@ jobs: run: >- sudo apt-get install -y imagemagick - scenes="homescreen settings_device settings_network settings_network_advanced settings_software settings_sunnylink settings_toggles settings_sunnypilot settings_sunnypilot_mads settings_trips settings_vehicle settings_developer offroad_alert update_available prime onroad onroad_disengaged onroad_override onroad_sidebar onroad_wide onroad_wide_sidebar onroad_alert_small onroad_alert_mid onroad_alert_full driver_camera body keyboard keyboard_uppercase" + scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device') A=($scenes) DIFF="" diff --git a/.gitignore b/.gitignore index 2d9e7a374f..0bb5fc38e0 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ a.out *.pyxbldc *.vcd *.qm +*_pyx.cpp config.json clcache compile_commands.json diff --git a/Jenkinsfile b/Jenkinsfile index 330967c657..b1a0746ea3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -166,7 +166,7 @@ node { env.GIT_BRANCH = checkout(scm).GIT_BRANCH env.GIT_COMMIT = checkout(scm).GIT_COMMIT - def excludeBranches = ['master-ci', 'devel', 'devel-staging', 'release3', 'release3-staging', + def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging', 'testing-closet*', 'hotfix-*'] def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*') @@ -183,7 +183,7 @@ node { ]) } - if (env.BRANCH_NAME == 'master-ci') { + if (env.BRANCH_NAME == '__nightly') { parallel ( 'nightly': { deviceStage("build nightly", "tici-needs-can", [], [ @@ -203,8 +203,6 @@ node { // tici tests 'onroad tests': { deviceStage("onroad", "tici-needs-can", ["UNSAFE=1"], [ - // TODO: ideally, this test runs in master-ci, but it takes 5+m to build it - //["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"], step("build openpilot", "cd system/manager && ./build.py"), step("check dirty", "release/check-dirty.sh"), step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]), @@ -240,7 +238,6 @@ node { step("test exposure", "pytest system/camerad/test/test_exposure.py"), ]) }, - /* 'camerad OS04C10': { deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), @@ -248,7 +245,6 @@ node { step("test exposure", "pytest system/camerad/test/test_exposure.py"), ]) }, - */ 'sensord': { deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), diff --git a/RELEASES.md b/RELEASES.md index 37e4a01b20..8a5638bc84 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -12,6 +12,7 @@ Version 0.9.8 (2025-02-28) * More GPU time for bigger driving models * Power draw reduced 0.5W, which means your device runs cooler * Added toggle to enable driver monitoring even when openpilot is not engaged +* Localizer rewritten to remove GPS dependency at runtime * Firehose Mode for maximizing your training data uploads * Enable openpilot longitudinal control for Ford Q3 vehicles * New Toyota TSS2 longitudinal tune diff --git a/cereal/log.capnp b/cereal/log.capnp index 49697687f8..059d3ecbb7 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -151,6 +151,10 @@ struct InitData { gitBranch @11 :Text; gitRemote @13 :Text; + # this is source commit for prebuilt branches + gitSrcCommit @23 :Text; + gitSrcCommitDate @24 :Text; + androidProperties @16 :Map(Text, Text); pandaInfo @8 :PandaInfo; diff --git a/common/clutil.cc b/common/clutil.cc index ee43ef36d5..f8381a7e09 100644 --- a/common/clutil.cc +++ b/common/clutil.cc @@ -96,109 +96,3 @@ cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const } return prg; } - -cl_program cl_program_from_binary(cl_context ctx, cl_device_id device_id, const uint8_t* binary, size_t length, const char* args) { - cl_program prg = CL_CHECK_ERR(clCreateProgramWithBinary(ctx, 1, &device_id, &length, &binary, NULL, &err)); - if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { - cl_print_build_errors(prg, device_id); - assert(0); - } - return prg; -} - -// Given a cl code and return a string representation -#define CL_ERR_TO_STR(err) case err: return #err -const char* cl_get_error_string(int err) { - switch (err) { - CL_ERR_TO_STR(CL_SUCCESS); - CL_ERR_TO_STR(CL_DEVICE_NOT_FOUND); - CL_ERR_TO_STR(CL_DEVICE_NOT_AVAILABLE); - CL_ERR_TO_STR(CL_COMPILER_NOT_AVAILABLE); - CL_ERR_TO_STR(CL_MEM_OBJECT_ALLOCATION_FAILURE); - CL_ERR_TO_STR(CL_OUT_OF_RESOURCES); - CL_ERR_TO_STR(CL_OUT_OF_HOST_MEMORY); - CL_ERR_TO_STR(CL_PROFILING_INFO_NOT_AVAILABLE); - CL_ERR_TO_STR(CL_MEM_COPY_OVERLAP); - CL_ERR_TO_STR(CL_IMAGE_FORMAT_MISMATCH); - CL_ERR_TO_STR(CL_IMAGE_FORMAT_NOT_SUPPORTED); - CL_ERR_TO_STR(CL_MAP_FAILURE); - CL_ERR_TO_STR(CL_MISALIGNED_SUB_BUFFER_OFFSET); - CL_ERR_TO_STR(CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST); - CL_ERR_TO_STR(CL_COMPILE_PROGRAM_FAILURE); - CL_ERR_TO_STR(CL_LINKER_NOT_AVAILABLE); - CL_ERR_TO_STR(CL_LINK_PROGRAM_FAILURE); - CL_ERR_TO_STR(CL_DEVICE_PARTITION_FAILED); - CL_ERR_TO_STR(CL_KERNEL_ARG_INFO_NOT_AVAILABLE); - CL_ERR_TO_STR(CL_INVALID_VALUE); - CL_ERR_TO_STR(CL_INVALID_DEVICE_TYPE); - CL_ERR_TO_STR(CL_INVALID_PLATFORM); - CL_ERR_TO_STR(CL_INVALID_DEVICE); - CL_ERR_TO_STR(CL_INVALID_CONTEXT); - CL_ERR_TO_STR(CL_INVALID_QUEUE_PROPERTIES); - CL_ERR_TO_STR(CL_INVALID_COMMAND_QUEUE); - CL_ERR_TO_STR(CL_INVALID_HOST_PTR); - CL_ERR_TO_STR(CL_INVALID_MEM_OBJECT); - CL_ERR_TO_STR(CL_INVALID_IMAGE_FORMAT_DESCRIPTOR); - CL_ERR_TO_STR(CL_INVALID_IMAGE_SIZE); - CL_ERR_TO_STR(CL_INVALID_SAMPLER); - CL_ERR_TO_STR(CL_INVALID_BINARY); - CL_ERR_TO_STR(CL_INVALID_BUILD_OPTIONS); - CL_ERR_TO_STR(CL_INVALID_PROGRAM); - CL_ERR_TO_STR(CL_INVALID_PROGRAM_EXECUTABLE); - CL_ERR_TO_STR(CL_INVALID_KERNEL_NAME); - CL_ERR_TO_STR(CL_INVALID_KERNEL_DEFINITION); - CL_ERR_TO_STR(CL_INVALID_KERNEL); - CL_ERR_TO_STR(CL_INVALID_ARG_INDEX); - CL_ERR_TO_STR(CL_INVALID_ARG_VALUE); - CL_ERR_TO_STR(CL_INVALID_ARG_SIZE); - CL_ERR_TO_STR(CL_INVALID_KERNEL_ARGS); - CL_ERR_TO_STR(CL_INVALID_WORK_DIMENSION); - CL_ERR_TO_STR(CL_INVALID_WORK_GROUP_SIZE); - CL_ERR_TO_STR(CL_INVALID_WORK_ITEM_SIZE); - CL_ERR_TO_STR(CL_INVALID_GLOBAL_OFFSET); - CL_ERR_TO_STR(CL_INVALID_EVENT_WAIT_LIST); - CL_ERR_TO_STR(CL_INVALID_EVENT); - CL_ERR_TO_STR(CL_INVALID_OPERATION); - CL_ERR_TO_STR(CL_INVALID_GL_OBJECT); - CL_ERR_TO_STR(CL_INVALID_BUFFER_SIZE); - CL_ERR_TO_STR(CL_INVALID_MIP_LEVEL); - CL_ERR_TO_STR(CL_INVALID_GLOBAL_WORK_SIZE); - CL_ERR_TO_STR(CL_INVALID_PROPERTY); - CL_ERR_TO_STR(CL_INVALID_IMAGE_DESCRIPTOR); - CL_ERR_TO_STR(CL_INVALID_COMPILER_OPTIONS); - CL_ERR_TO_STR(CL_INVALID_LINKER_OPTIONS); - CL_ERR_TO_STR(CL_INVALID_DEVICE_PARTITION_COUNT); - case -69: return "CL_INVALID_PIPE_SIZE"; - case -70: return "CL_INVALID_DEVICE_QUEUE"; - case -71: return "CL_INVALID_SPEC_ID"; - case -72: return "CL_MAX_SIZE_RESTRICTION_EXCEEDED"; - case -1002: return "CL_INVALID_D3D10_DEVICE_KHR"; - case -1003: return "CL_INVALID_D3D10_RESOURCE_KHR"; - case -1004: return "CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR"; - case -1005: return "CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR"; - case -1006: return "CL_INVALID_D3D11_DEVICE_KHR"; - case -1007: return "CL_INVALID_D3D11_RESOURCE_KHR"; - case -1008: return "CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR"; - case -1009: return "CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR"; - case -1010: return "CL_INVALID_DX9_MEDIA_ADAPTER_KHR"; - case -1011: return "CL_INVALID_DX9_MEDIA_SURFACE_KHR"; - case -1012: return "CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR"; - case -1013: return "CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR"; - case -1093: return "CL_INVALID_EGL_OBJECT_KHR"; - case -1092: return "CL_EGL_RESOURCE_NOT_ACQUIRED_KHR"; - case -1001: return "CL_PLATFORM_NOT_FOUND_KHR"; - case -1057: return "CL_DEVICE_PARTITION_FAILED_EXT"; - case -1058: return "CL_INVALID_PARTITION_COUNT_EXT"; - case -1059: return "CL_INVALID_PARTITION_NAME_EXT"; - case -1094: return "CL_INVALID_ACCELERATOR_INTEL"; - case -1095: return "CL_INVALID_ACCELERATOR_TYPE_INTEL"; - case -1096: return "CL_INVALID_ACCELERATOR_DESCRIPTOR_INTEL"; - case -1097: return "CL_ACCELERATOR_TYPE_NOT_SUPPORTED_INTEL"; - case -1000: return "CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR"; - case -1098: return "CL_INVALID_VA_API_MEDIA_ADAPTER_INTEL"; - case -1099: return "CL_INVALID_VA_API_MEDIA_SURFACE_INTEL"; - case -1100: return "CL_VA_API_MEDIA_SURFACE_ALREADY_ACQUIRED_INTEL"; - case -1101: return "CL_VA_API_MEDIA_SURFACE_NOT_ACQUIRED_INTEL"; - default: return "CL_UNKNOWN_ERROR"; - } -} diff --git a/common/clutil.h b/common/clutil.h index c8bd2cd38f..b364e79d45 100644 --- a/common/clutil.h +++ b/common/clutil.h @@ -25,6 +25,4 @@ cl_device_id cl_get_device_id(cl_device_type device_type); cl_context cl_create_context(cl_device_id device_id); void cl_release_context(cl_context context); cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); -cl_program cl_program_from_binary(cl_context ctx, cl_device_id device_id, const uint8_t* binary, size_t length, const char* args = nullptr); cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); -const char* cl_get_error_string(int err); diff --git a/common/numpy_fast.py b/common/numpy_fast.py deleted file mode 100644 index 878c0005c8..0000000000 --- a/common/numpy_fast.py +++ /dev/null @@ -1,19 +0,0 @@ -def clip(x, lo, hi): - return max(lo, min(hi, x)) - -def interp(x, xp, fp): - N = len(xp) - - def get_interp(xv): - hi = 0 - while hi < N and xv > xp[hi]: - hi += 1 - low = hi - 1 - return fp[-1] if hi == N and xv > xp[low] else ( - fp[0] if hi == 0 else - (xv - xp[low]) * (fp[hi] - fp[low]) / (xp[hi] - xp[low]) + fp[low]) - - return [get_interp(v) for v in x] if hasattr(x, '__iter__') else get_interp(x) - -def mean(x): - return sum(x) / len(x) diff --git a/common/params_keys.h b/common/params_keys.h index 904b9491a2..307f7ada46 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -8,6 +8,7 @@ inline static std::unordered_map keys = { {"AdbEnabled", PERSISTENT}, {"AlwaysOnDM", PERSISTENT}, {"ApiCache_Device", PERSISTENT}, + {"ApiCache_FirehoseStats", PERSISTENT}, {"AssistNowToken", PERSISTENT}, {"AthenadPid", PERSISTENT}, {"AthenadUploadQueue", PERSISTENT}, @@ -36,7 +37,6 @@ inline static std::unordered_map keys = { {"ExperimentalLongitudinalEnabled", PERSISTENT | DEVELOPMENT_ONLY | BACKUP}, {"ExperimentalMode", PERSISTENT | BACKUP}, {"ExperimentalModeConfirmed", PERSISTENT | BACKUP}, - {"FirehoseMode", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"FirmwareQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ForcePowerDown", PERSISTENT}, {"GitBranch", PERSISTENT}, diff --git a/common/util.cc b/common/util.cc index 096df85634..a853faed04 100644 --- a/common/util.cc +++ b/common/util.cc @@ -257,7 +257,6 @@ bool ends_with(const std::string& s, const std::string& suffix) { std::string strip(const std::string &str) { auto should_trim = [](unsigned char ch) { - // trim whitespace or a null character return std::isspace(ch) || ch == '\0'; }; diff --git a/conftest.py b/conftest.py index baa352094e..b62a88c188 100644 --- a/conftest.py +++ b/conftest.py @@ -78,8 +78,10 @@ def openpilot_class_fixture(): @pytest.fixture(scope="function") -def tici_setup_fixture(openpilot_function_fixture): +def tici_setup_fixture(request, openpilot_function_fixture): """Ensure a consistent state for tests on-device. Needs the openpilot function fixture to run first.""" + if 'skip_tici_setup' in request.keywords: + return HARDWARE.initialize_hardware() HARDWARE.set_power_save(False) os.system("pkill -9 -f athena") diff --git a/docs/CARS.md b/docs/CARS.md index fa32aec550..a3b1fe6351 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -312,7 +312,7 @@ A supported vehicle is one that just works when you install a comma device. All |Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| ### Footnotes -1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
+1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `nightly-dev`.
2By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
3Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
diff --git a/docs/contributing/roadmap.md b/docs/contributing/roadmap.md index ce50ad5577..7086d85b85 100644 --- a/docs/contributing/roadmap.md +++ b/docs/contributing/roadmap.md @@ -16,7 +16,7 @@ a [learned simulator](https://youtu.be/EqQNZXqzFSI). * Always-on driver monitoring (behind a toggle) * GPS removed from the driving stack * 100KB qlogs -* `master-ci` pushed after 1000 hours of hardware-in-the-loop testing +* `nightly` pushed after 1000 hours of hardware-in-the-loop testing * Car interface code moved into [opendbc](https://github.com/commaai/opendbc) * openpilot on PC for Linux x86, Linux arm64, and Mac (Apple Silicon) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 442a811b16..66f57c2cd1 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -1,13 +1,9 @@ #!/usr/bin/env bash -if [ -z "$BASEDIR" ]; then - BASEDIR="/data/openpilot" -fi - -source "$BASEDIR/launch_env.sh" - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +source "$DIR/launch_env.sh" + function agnos_init { # TODO: move this to agnos sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta diff --git a/launch_env.sh b/launch_env.sh index 3300423055..049b9da279 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="11.8" + export AGNOS_VERSION="11.10" fi export STAGING_ROOT="/data/safe_staging" diff --git a/opendbc_repo b/opendbc_repo index a98ee4ec96..4b2472a6df 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit a98ee4ec96cdfbd7ef81ee3672c07af0343153a0 +Subproject commit 4b2472a6df1bfe6fdce328a47c2190b2a35dd279 diff --git a/panda b/panda index 0bacdb1268..5389b01bdb 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 0bacdb126818d5126e7807231a7056c7aa7991fd +Subproject commit 5389b01bdb6265826adcf858d0b719644db4f87e diff --git a/pyproject.toml b/pyproject.toml index a71ada13f8..ecf2db5789 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,6 +143,7 @@ asyncio_default_fixture_loop_scope = "function" markers = [ "slow: tests that take awhile to run and can be skipped with -m 'not slow'", "tici: tests that are only meant to run on the C3/C3X", + "skip_tici_setup: mark test to skip tici setup fixture" ] testpaths = [ "common", @@ -216,7 +217,7 @@ lint.select = [ "E", "F", "W", "PIE", "C4", "ISC", "A", "B", "NPY", # numpy "UP", # pyupgrade - "TRY302", "TRY400", "TRY401", # try/excepts + "TRY203", "TRY400", "TRY401", # try/excepts "RUF008", "RUF100", "TID251", "PLR1704", diff --git a/release/build_devel.sh b/release/build_devel.sh index c6d04396fe..05dcaafcaa 100755 --- a/release/build_devel.sh +++ b/release/build_devel.sh @@ -19,15 +19,15 @@ cd $TARGET_DIR cp -r $SOURCE_DIR/.git $TARGET_DIR pre-commit uninstall || true -echo "[-] bringing master-ci and devel in sync T=$SECONDS" +echo "[-] bringing __nightly and devel in sync T=$SECONDS" cd $TARGET_DIR -git fetch --depth 1 origin master-ci +git fetch --depth 1 origin __nightly git fetch --depth 1 origin devel -git checkout -f --track origin/master-ci -git reset --hard master-ci -git checkout master-ci +git checkout -f --track origin/__nightly +git reset --hard __nightly +git checkout __nightly git reset --hard origin/devel git clean -xdff git lfs uninstall @@ -51,9 +51,13 @@ rm -f panda/board/obj/panda.bin.signed # include source commit hash and build date in commit GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD) +GIT_COMMIT_DATE=$(git --git-dir=$SOURCE_DIR/.git show --no-patch --format='%ct %ci' HEAD) DATETIME=$(date '+%Y-%m-%dT%H:%M:%S') VERSION=$(cat $SOURCE_DIR/common/version.h | awk -F\" '{print $2}') +echo -n "$GIT_HASH" > git_src_commit +echo -n "$GIT_COMMIT_DATE" > git_src_commit_date + echo "[-] committing version $VERSION T=$SECONDS" git add -f . git status @@ -81,7 +85,7 @@ fi if [ ! -z "$BRANCH" ]; then echo "[-] Pushing to $BRANCH T=$SECONDS" - git push -f origin master-ci:$BRANCH + git push -f origin __nightly:$BRANCH fi echo "[-] done T=$SECONDS, ready at $TARGET_DIR" diff --git a/release/build_release.sh b/release/build_release.sh index ff28179eeb..a582dbab9f 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -93,14 +93,9 @@ git add -f . git commit --amend -m "openpilot v$VERSION" # Run tests -TEST_FILES="tools/" -cd $SOURCE_DIR -cp -pR -n --parents $TEST_FILES $BUILD_DIR/ cd $BUILD_DIR RELEASE=1 pytest -n0 -s selfdrive/test/test_onroad.py -#system/manager/test/test_manager.py -pytest selfdrive/car/tests/test_car_interfaces.py -rm -rf $TEST_FILES +#pytest selfdrive/car/tests/test_car_interfaces.py if [ ! -z "$RELEASE_BRANCH" ]; then echo "[-] pushing release T=$SECONDS" diff --git a/selfdrive/assets/offroad/tc.html b/selfdrive/assets/offroad/tc.html deleted file mode 100644 index f88daf08f2..0000000000 --- a/selfdrive/assets/offroad/tc.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - openpilot Terms of Service - - - - -

The Terms and Conditions below are effective for all users

-

Last Updated on October 18, 2019

-

Please read these Terms of Use (“Terms”) carefully before using openpilot which is open-sourced software developed by Comma.ai, Inc., a corporation organized under the laws of Delaware (“comma,” “us,” “we,” or “our”).

-

Before using and by accessing openpilot, you indicate that you have read, understood, and agree to these Terms. These Terms apply to all users and others who access or use openpilot. If others use openpilot through your user account or vehicle, you are responsible to ensure that they only use openpilot when it is safe to do so, and in compliance with these Terms and with applicable law. If you disagree with any part of the Terms, you should not access or use openpilot.

-

Communications

-

You agree that comma may contact you by email or telephone in connection with openpilot or for other business purposes. You may opt out of receiving email messages at any time by contacting us at support@comma.ai.

-

We collect, use, and share information from and about you and your vehicle in connection with openpilot. You consent to comma accessing the systems associated with openpilot, without additional notice or consent, for the purposes of providing openpilot, data collection, software updates, safety and cybersecurity, suspension or removal of your account, and as disclosed in the Privacy Policy (available at https://connect.comma.ai/privacy).

-

Safety

-

openpilot performs the functions of Adaptive Cruise Control (ACC) and Lane Keeping Assist System (LKAS) designed for use in compatible motor vehicles. While using openpilot, it is your responsibility to obey all laws, traffic rules, and traffic regulations governing your vehicle and its operation. Access to and use of openpilot is at your own risk and responsibility, and openpilot should be accessed and/or used only when you can do so safely.

-

openpilot does not make your vehicle “autonomous” or capable of operation without the active monitoring of a licensed driver. It is designed to assist a licensed driver. A licensed driver must pay attention to the road, remain aware of navigation at all times, and be prepared to take immediate action. Failure to do so can cause damage, injury, or death.

-

Supported Locations and Models

-

openpilot is compatible only with particular makes and models of vehicles. For a complete list of currently supported vehicles, visit https://comma.ai. openpilot will not function properly when installed in an incompatible vehicle. openpilot is compatible only within the geographical boundaries of the United States of America.

-

Indemnification

-

To the maximum extent allowable by law, you agree to defend, indemnify and hold harmless comma, and its employees, partners, suppliers, contractors, investors, agents, officers, directors, and affiliates, from and against any and all claims, damages, causes of action, penalties, interest, demands, obligations, losses, liabilities, costs or debt, additional taxes, and expenses (including but not limited to attorneys’ fees), resulting from or arising out of (i) your use and access of, or inability to use or access, openpilot, (ii) your breach of these Terms, (iii) the inaccuracy of any information, representation or warranty made by you, (iv) activities of anyone other than you in connection with openpilot conducted through your comma device or account, (v) any other of your activities under or in connection with these Terms or openpilot.

-

Limitation of Liability

-

In no event shall comma, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from (i) your access to or use of or inability to access or use of the Software; or (ii) any conduct or content of any third party on the Software whether based on warranty, contract, tort (including negligence) or any other legal theory, whether or not we have been informed of the possibility of such damage, and even if a remedy set forth herein is found to have failed of its essential purpose.

-

No Warranty or Obligations to Maintain or Service

-

comma provides openpilot without representations, conditions, or warranties of any kind. openpilot is provided on an “AS IS” and “AS AVAILABLE” basis, including with all faults and errors as may occur. To the extent permitted by law and unless prohibited by law, comma on behalf of itself and all persons and parties acting by, through, or for comma, explicitly disclaims all warranties or conditions, express, implied, or collateral, including any implied warranties of merchantability, satisfactory quality, and fitness for a particular purpose in respect of openpilot.

-

To the extent permitted by law, comma does not warrant the operation, performance, or availability of openpilot under all conditions. comma is not responsible for any failures caused by server errors, misdirected or redirected transmissions, failed internet connections, interruptions or failures in the transmission of data, any computer virus, or any acts or omissions of third parties that damage the network or impair wireless service.

-

We undertake reasonable measures to preserve and secure information collected through our openpilot. However, no data collection, transmission or storage system is 100% secure, and there is always a risk that your information may be intercepted without our consent. In using openpilot, you acknowledge that comma is not responsible for intercepted information, and you hereby release us from any and all claims arising out of or related to the use of intercepted information in any unauthorized manner.

-

By providing openpilot, comma does not transfer or license its intellectual property or grant rights in its brand names, nor does comma make representations with respect to third-party intellectual property rights.

-

We are not obligated to provide any maintenance or support for openpilot, technical or otherwise. If we voluntarily provide any maintenance or support for openpilot, we may stop any such maintenance, support, or services at any time in our sole discretion.

-

Modification of Software

-

In no event shall comma, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable if you choose to modify the software.

-

Changes

-

We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material we will provide at least 15 days’ notice prior to any new terms taking effect. What constitutes a material change will be determined at our sole discretion.

-

By continuing to access or use our Software after any revisions become effective, you agree to be bound by the revised terms. If you do not agree to the new terms, you are no longer authorized to use the Software.

-

Contact Us

-

If you have any questions about these Terms, please contact us at support@comma.ai.

- - diff --git a/selfdrive/car/CARS_template.md b/selfdrive/car/CARS_template.md index c7500ffad0..463683fd3c 100644 --- a/selfdrive/car/CARS_template.md +++ b/selfdrive/car/CARS_template.md @@ -12,11 +12,11 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# {{all_car_docs | length}} Supported Cars +# {{all_car_docs | selectattr('support_type', 'eq', SupportType.UPSTREAM) | list | length}} Supported Cars |{{Column | map(attribute='value') | join('|') | replace(hardware_col_name, wide_hardware_col_name)}}| |---|---|---|{% for _ in range((Column | length) - 3) %}{{':---:|'}}{% endfor +%} -{% for car_docs in all_car_docs %} +{% for car_docs in all_car_docs | selectattr('support_type', 'eq', SupportType.UPSTREAM) %} |{% for column in Column %}{{car_docs.get_column(column, star_icon, video_icon, footnote_tag)}}|{% endfor %} {% endfor %} diff --git a/selfdrive/car/cruise.py b/selfdrive/car/cruise.py index ba726edb85..d696566ba5 100644 --- a/selfdrive/car/cruise.py +++ b/selfdrive/car/cruise.py @@ -56,6 +56,9 @@ class VCruiseHelper: if CS.cruiseState.speed == 0: self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_cluster_kph = V_CRUISE_UNSET + elif CS.cruiseState.speed == -1: + self.v_cruise_kph = -1 + self.v_cruise_cluster_kph = -1 else: self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_cluster_kph = V_CRUISE_UNSET diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 8a3c18eac6..3504bbb2b4 100644 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -270,6 +270,9 @@ class TestCarModelBase(unittest.TestCase): def test_panda_safety_tx_cases(self, data=None): """Asserts we can tx common messages""" + if self.CP.dashcamOnly: + self.skipTest("no need to check panda safety for dashcamOnly") + if self.CP.notCar: self.skipTest("Skipping test for notCar") diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 40280b8d17..357f40c61f 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -45,7 +45,7 @@ class Controls: poll='selfdriveState') self.pm = messaging.PubMaster(['carControl', 'controlsState'] + ['carControlSP']) - self.steer_limited = False + self.steer_limited_by_controls = False self.desired_curvature = 0.0 self.pose_calibrator = PoseCalibrator() @@ -121,11 +121,11 @@ class Controls: actuators.accel = float(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits)) # Steering PID loop and lateral MPC - self.desired_curvature = clip_curvature(CS.vEgo, self.desired_curvature, model_v2.action.desiredCurvature) - actuators.curvature = float(self.desired_curvature) + self.desired_curvature, curvature_limited = clip_curvature(CS.vEgo, self.desired_curvature, model_v2.action.desiredCurvature, lp.roll) + actuators.curvature = self.desired_curvature steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, - self.steer_limited, self.desired_curvature, - self.calibrated_pose) # TODO what if not available + self.steer_limited_by_controls, self.desired_curvature, + self.calibrated_pose, curvature_limited) # TODO what if not available actuators.torque = float(steer) actuators.steeringAngleDeg = float(steeringAngleDeg) # Ensure no NaNs/Infs @@ -176,10 +176,10 @@ class Controls: if self.sm['selfdriveState'].active: CO = self.sm['carOutput'] if self.CP.steerControlType == car.CarParams.SteerControlType.angle: - self.steer_limited = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \ - STEER_ANGLE_SATURATION_THRESHOLD + self.steer_limited_by_controls = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \ + STEER_ANGLE_SATURATION_THRESHOLD else: - self.steer_limited = abs(CC.actuators.torque - CO.actuatorsOutput.torque) > 1e-2 + self.steer_limited_by_controls = abs(CC.actuators.torque - CO.actuatorsOutput.torque) > 1e-2 # TODO: both controlsState and carControl valids should be set by # sm.all_checks(), but this creates a circular dependency @@ -195,7 +195,7 @@ class Controls: cs.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan'] cs.lateralPlanMonoTime = self.sm.logMonoTime['modelV2'] - cs.desiredCurvature = float(self.desired_curvature) + cs.desiredCurvature = self.desired_curvature cs.longControlState = self.LoC.long_control_state cs.upAccelCmd = float(self.LoC.pid.p) cs.uiAccelCmd = float(self.LoC.pid.i) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index dcb0093a18..41384abae8 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -1,5 +1,6 @@ import numpy as np from cereal import log +from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from openpilot.common.realtime import DT_CTRL MIN_SPEED = 1.0 @@ -7,20 +8,33 @@ CONTROL_N = 17 CAR_ROTATION_RADIUS = 0.0 # This is a turn radius smaller than most cars can achieve MAX_CURVATURE = 0.2 +MAX_VEL_ERR = 5.0 # m/s # EU guidelines -MAX_LATERAL_JERK = 5.0 -MAX_VEL_ERR = 5.0 +MAX_LATERAL_JERK = 5.0 # m/s^3 +MAX_LATERAL_ACCEL_NO_ROLL = 3.0 # m/s^2 -def clip_curvature(v_ego, prev_curvature, new_curvature): - new_curvature = np.clip(new_curvature, -MAX_CURVATURE, MAX_CURVATURE) - v_ego = max(MIN_SPEED, v_ego) - max_curvature_rate = MAX_LATERAL_JERK / (v_ego**2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755 - safe_desired_curvature = np.clip(new_curvature, - prev_curvature - max_curvature_rate * DT_CTRL, - prev_curvature + max_curvature_rate * DT_CTRL) - return safe_desired_curvature +def clamp(val, min_val, max_val): + clamped_val = float(np.clip(val, min_val, max_val)) + return clamped_val, clamped_val != val + + +def clip_curvature(v_ego, prev_curvature, new_curvature, roll): + # This function respects ISO lateral jerk and acceleration limits + a max curvature + v_ego = max(v_ego, MIN_SPEED) + max_curvature_rate = MAX_LATERAL_JERK / (v_ego ** 2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755 + new_curvature = np.clip(new_curvature, + prev_curvature - max_curvature_rate * DT_CTRL, + prev_curvature + max_curvature_rate * DT_CTRL) + + roll_compensation = roll * ACCELERATION_DUE_TO_GRAVITY + max_lat_accel = MAX_LATERAL_ACCEL_NO_ROLL + roll_compensation + min_lat_accel = -MAX_LATERAL_ACCEL_NO_ROLL + roll_compensation + new_curvature, limited_accel = clamp(new_curvature, min_lat_accel / v_ego ** 2, max_lat_accel / v_ego ** 2) + + new_curvature, limited_max_curv = clamp(new_curvature, -MAX_CURVATURE, MAX_CURVATURE) + return float(new_curvature), limited_accel or limited_max_curv def get_speed_error(modelV2: log.ModelDataV2, v_ego: float) -> float: diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index f84f70be4f..dcf0003430 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -17,14 +17,15 @@ class LatControl(ABC): self.steer_max = 1.0 @abstractmethod - def update(self, active, CS, VM, params, steer_limited, desired_curvature, calibrated_pose): + def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited): pass def reset(self): self.sat_count = 0. - def _check_saturation(self, saturated, CS, steer_limited): - if saturated and CS.vEgo > self.sat_check_min_speed and not steer_limited and not CS.steeringPressed: + def _check_saturation(self, saturated, CS, steer_limited_by_controls, curvature_limited): + # Saturated only if control output is not being limited by car torque/angle rate limits + if (saturated or curvature_limited) and CS.vEgo > self.sat_check_min_speed and not steer_limited_by_controls 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 4162bd62dc..1b249a3d1a 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -11,7 +11,7 @@ class LatControlAngle(LatControl): super().__init__(CP, CI) self.sat_check_min_speed = 5. - def update(self, active, CS, VM, params, steer_limited, desired_curvature, calibrated_pose): + def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited): angle_log = log.ControlsState.LateralAngleState.new_message() if not active: @@ -23,7 +23,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 = bool(self._check_saturation(angle_control_saturated, CS, False)) + angle_log.saturated = bool(self._check_saturation(angle_control_saturated, CS, False, curvature_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_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index dedc97a964..1f1199565e 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, steer_limited, desired_curvature, calibrated_pose): + def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited): 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 = float(self.pid.i) pid_log.f = float(self.pid.f) pid_log.output = float(output_steer) - pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited)) + pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited_by_controls, curvature_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 f677b7f897..3aef57baca 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -37,7 +37,7 @@ class LatControlTorque(LatControl): self.torque_params.latAccelOffset = latAccelOffset self.torque_params.friction = friction - def update(self, active, CS, VM, params, steer_limited, desired_curvature, calibrated_pose): + def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited): pid_log = log.ControlsState.LateralTorqueState.new_message() if not active: output_torque = 0.0 @@ -73,7 +73,7 @@ class LatControlTorque(LatControl): desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, friction_compensation=True, gravity_adjusted=True) - freeze_integrator = steer_limited or CS.steeringPressed or CS.vEgo < 5 + freeze_integrator = steer_limited_by_controls or CS.steeringPressed or CS.vEgo < 5 output_torque = self.pid.update(pid_log.error, feedforward=ff, speed=CS.vEgo, @@ -87,7 +87,7 @@ class LatControlTorque(LatControl): pid_log.output = float(-output_torque) pid_log.actualLateralAccel = float(actual_lateral_accel) pid_log.desiredLateralAccel = float(desired_lateral_accel) - pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited)) + pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_controls, curvature_limited)) # TODO left is positive in this convention return -output_torque, 0.0, pid_log diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py index 61d85e4f99..81edb15a98 100644 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -34,7 +34,15 @@ class TestLatControl: lp = generate_livePose() pose = Pose.from_live_pose(lp.livePose) + # Saturate for curvature limited and controller limited for _ in range(1000): - _, _, lac_log = controller.update(True, CS, VM, params, False, 1, pose) - + _, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, True) + assert lac_log.saturated + + for _ in range(1000): + _, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, False) + assert not lac_log.saturated + + for _ in range(1000): + _, _, lac_log = controller.update(True, CS, VM, params, False, 1, pose, False) assert lac_log.saturated diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index f23c285908..1216b7299f 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -65,8 +65,7 @@ if __name__ == "__main__": CP = msg.carParams car_fw = [fw for fw in CP.carFw if not fw.logging] if len(car_fw) == 0: - print("no fw") - break + print("WARNING: no fw") live_fingerprint = CP.carFingerprint live_fingerprint = MIGRATION.get(live_fingerprint, live_fingerprint) @@ -98,7 +97,7 @@ if __name__ == "__main__": print("New style (exact):", exact_matches) print("New style (fuzzy):", fuzzy_matches) - padding = max([len(fw.brand or UNKNOWN_BRAND) for fw in car_fw]) + padding = max([len(fw.brand or UNKNOWN_BRAND) for fw in car_fw] + [0]) for version in sorted(car_fw, key=lambda fw: fw.brand): subaddr = None if version.subAddress == 0 else hex(version.subAddress) print(f" Brand: {version.brand or UNKNOWN_BRAND:{padding}}, bus: {version.bus} - " + diff --git a/selfdrive/locationd/locationd.py b/selfdrive/locationd/locationd.py index 0216b69767..80789b8886 100755 --- a/selfdrive/locationd/locationd.py +++ b/selfdrive/locationd/locationd.py @@ -147,13 +147,13 @@ class LocationEstimator: self.car_speed = abs(msg.vEgo) elif which == "liveCalibration": + # Note that we use this message during calibration if len(msg.rpyCalib) > 0: calib = np.array(msg.rpyCalib) if calib.min() < -CALIB_RPY_SANITY_CHECK or calib.max() > CALIB_RPY_SANITY_CHECK: return HandleLogResult.INPUT_INVALID self.device_from_calib = rot_from_euler(calib) - self.calibrated = msg.calStatus == log.LiveCalibrationData.Status.calibrated elif which == "cameraOdometry": if not self._validate_timestamp(t): diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 24bf6619cf..258fc97e5a 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -46,18 +46,25 @@ class ParamsLearner: self.yaw_rate_std = 0.0 self.roll = 0.0 self.steering_angle = 0.0 - self.roll_valid = False def handle_log(self, t, which, msg): if which == 'livePose': device_pose = Pose.from_live_pose(msg) calibrated_pose = self.calibrator.build_calibrated_pose(device_pose) - self.yaw_rate, self.yaw_rate_std = calibrated_pose.angular_velocity.z, calibrated_pose.angular_velocity.z_std + + yaw_rate_valid = msg.angularVelocityDevice.valid and self.calibrator.calib_valid + 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 yaw_rate_valid: + self.yaw_rate, self.yaw_rate_std = calibrated_pose.angular_velocity.z, calibrated_pose.angular_velocity.z_std + else: + # This is done to bound the yaw rate estimate when localizer values are invalid or calibrating + self.yaw_rate, self.yaw_rate_std = 0.0, np.radians(1) localizer_roll, localizer_roll_std = device_pose.orientation.x, device_pose.orientation.x_std localizer_roll_std = np.radians(1) if np.isnan(localizer_roll_std) else localizer_roll_std - self.roll_valid = (localizer_roll_std < ROLL_STD_MAX) and (ROLL_MIN < localizer_roll < ROLL_MAX) and msg.sensorsOK - if self.roll_valid: + roll_valid = (localizer_roll_std < ROLL_STD_MAX) and (ROLL_MIN < localizer_roll < ROLL_MAX) and msg.sensorsOK + if roll_valid: roll = localizer_roll # Experimentally found multiplier of 2 to be best trade-off between stability and accuracy or similar? roll_std = 2 * localizer_roll_std @@ -67,18 +74,12 @@ class ParamsLearner: roll_std = np.radians(10.0) self.roll = np.clip(roll, self.roll - ROLL_MAX_DELTA, self.roll + ROLL_MAX_DELTA) - yaw_rate_valid = msg.angularVelocityDevice.valid and self.calibrator.calib_valid - 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: - - if yaw_rate_valid: - self.kf.predict_and_observe(t, - ObservationKind.ROAD_FRAME_YAW_RATE, - np.array([[-self.yaw_rate]]), - np.array([np.atleast_2d(self.yaw_rate_std**2)])) + self.kf.predict_and_observe(t, + ObservationKind.ROAD_FRAME_YAW_RATE, + np.array([[-self.yaw_rate]]), + np.array([np.atleast_2d(self.yaw_rate_std**2)])) self.kf.predict_and_observe(t, ObservationKind.ROAD_ROLL, diff --git a/selfdrive/modeld/.gitignore b/selfdrive/modeld/.gitignore deleted file mode 100644 index 742d3d1205..0000000000 --- a/selfdrive/modeld/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*_pyx.cpp diff --git a/selfdrive/modeld/constants.py b/selfdrive/modeld/constants.py index 2bb7b8100c..5ca0a86bc8 100644 --- a/selfdrive/modeld/constants.py +++ b/selfdrive/modeld/constants.py @@ -14,8 +14,14 @@ class ModelConstants: # model inputs constants MODEL_FREQ = 20 + HISTORY_FREQ = 5 + HISTORY_LEN_SECONDS = 5 + TEMPORAL_SKIP = MODEL_FREQ // HISTORY_FREQ + FULL_HISTORY_BUFFER_LEN = MODEL_FREQ * HISTORY_LEN_SECONDS + INPUT_HISTORY_BUFFER_LEN = HISTORY_FREQ * HISTORY_LEN_SECONDS + FEATURE_LEN = 512 - FULL_HISTORY_BUFFER_LEN = 100 + DESIRE_LEN = 8 TRAFFIC_CONVENTION_LEN = 2 LAT_PLANNER_STATE_LEN = 4 diff --git a/selfdrive/modeld/dmonitoringmodeld b/selfdrive/modeld/dmonitoringmodeld deleted file mode 100755 index 90b43800fe..0000000000 --- a/selfdrive/modeld/dmonitoringmodeld +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -exec "$DIR/dmonitoringmodeld.py" "$@" diff --git a/selfdrive/modeld/modeld b/selfdrive/modeld/modeld deleted file mode 100755 index 5ba4688554..0000000000 --- a/selfdrive/modeld/modeld +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -exec "$DIR/modeld.py" "$@" diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index ee825d158f..1e557942fe 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -56,16 +56,24 @@ class ModelState: prev_desire: np.ndarray # for tracking the rising edge of the pulse def __init__(self, context: CLContext): - self.frames = {'input_imgs': DrivingModelFrame(context), 'big_input_imgs': DrivingModelFrame(context)} + self.frames = { + 'input_imgs': DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP), + 'big_input_imgs': DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP) + } self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) + self.full_features_buffer = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32) + self.full_desire = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32) + self.full_prev_desired_curv = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.PREV_DESIRED_CURV_LEN), dtype=np.float32) + self.temporal_idxs = slice(-1-(ModelConstants.TEMPORAL_SKIP*(ModelConstants.INPUT_HISTORY_BUFFER_LEN-1)), None, ModelConstants.TEMPORAL_SKIP) + # policy inputs self.numpy_inputs = { - 'desire': np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32), + 'desire': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32), 'traffic_convention': np.zeros((1, ModelConstants.TRAFFIC_CONVENTION_LEN), dtype=np.float32), 'lateral_control_params': np.zeros((1, ModelConstants.LATERAL_CONTROL_PARAMS_LEN), dtype=np.float32), - 'prev_desired_curv': np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.PREV_DESIRED_CURV_LEN), dtype=np.float32), - 'features_buffer': np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32), + 'prev_desired_curv': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.PREV_DESIRED_CURV_LEN), dtype=np.float32), + 'features_buffer': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32), } with open(VISION_METADATA_PATH, 'rb') as f: @@ -104,8 +112,9 @@ class ModelState: new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0) self.prev_desire[:] = inputs['desire'] - self.numpy_inputs['desire'][0,:-1] = self.numpy_inputs['desire'][0,1:] - self.numpy_inputs['desire'][0,-1] = new_desire + self.full_desire[0,:-1] = self.full_desire[0,1:] + self.full_desire[0,-1] = new_desire + self.numpy_inputs['desire'][:] = self.full_desire.reshape((1,ModelConstants.INPUT_HISTORY_BUFFER_LEN,ModelConstants.TEMPORAL_SKIP,-1)).max(axis=2) self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention'] self.numpy_inputs['lateral_control_params'][:] = inputs['lateral_control_params'] @@ -128,15 +137,17 @@ class ModelState: self.vision_output = self.vision_run(**self.vision_inputs).numpy().flatten() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) - self.numpy_inputs['features_buffer'][0,:-1] = self.numpy_inputs['features_buffer'][0,1:] - self.numpy_inputs['features_buffer'][0,-1] = vision_outputs_dict['hidden_state'][0, :] + self.full_features_buffer[0,:-1] = self.full_features_buffer[0,1:] + self.full_features_buffer[0,-1] = vision_outputs_dict['hidden_state'][0, :] + self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[0, self.temporal_idxs] self.policy_output = self.policy_run(**self.policy_inputs).numpy().flatten() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) # TODO model only uses last value now - self.numpy_inputs['prev_desired_curv'][0,:-1] = self.numpy_inputs['prev_desired_curv'][0,1:] - self.numpy_inputs['prev_desired_curv'][0,-1,:] = policy_outputs_dict['desired_curvature'][0, :] + self.full_prev_desired_curv[0,:-1] = self.full_prev_desired_curv[0,1:] + self.full_prev_desired_curv[0,-1,:] = policy_outputs_dict['desired_curvature'][0, :] + self.numpy_inputs['prev_desired_curv'][:] = self.full_prev_desired_curv[0, self.temporal_idxs] combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc index 9973d18588..99155d9f1e 100644 --- a/selfdrive/modeld/models/commonmodel.cc +++ b/selfdrive/modeld/models/commonmodel.cc @@ -5,11 +5,12 @@ #include "common/clutil.h" -DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { +DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) { input_frames = std::make_unique(buf_size); + temporal_skip = _temporal_skip; input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, 2*frame_size_bytes, NULL, &err)); - region.origin = 1 * frame_size_bytes; + img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err)); + region.origin = temporal_skip * frame_size_bytes; region.size = frame_size_bytes; last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); @@ -20,7 +21,7 @@ DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context) cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - for (int i = 0; i < 1; i++) { + for (int i = 0; i < temporal_skip; i++) { CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); } loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h index 14409943e4..176d7eb6dc 100644 --- a/selfdrive/modeld/models/commonmodel.h +++ b/selfdrive/modeld/models/commonmodel.h @@ -64,20 +64,21 @@ protected: class DrivingModelFrame : public ModelFrame { public: - DrivingModelFrame(cl_device_id device_id, cl_context context); + DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip); ~DrivingModelFrame(); cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); const int MODEL_WIDTH = 512; const int MODEL_HEIGHT = 256; const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const int buf_size = MODEL_FRAME_SIZE * 2; + const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); private: LoadYUVState loadyuv; cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; cl_buffer_region region; + int temporal_skip; }; class MonitoringModelFrame : public ModelFrame { diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd index b4f08b12aa..4ac64d9172 100644 --- a/selfdrive/modeld/models/commonmodel.pxd +++ b/selfdrive/modeld/models/commonmodel.pxd @@ -20,7 +20,7 @@ cdef extern from "selfdrive/modeld/models/commonmodel.h": cppclass DrivingModelFrame: int buf_size - DrivingModelFrame(cl_device_id, cl_context) + DrivingModelFrame(cl_device_id, cl_context, int) cppclass MonitoringModelFrame: int buf_size diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx index 7b3a5bb342..5b7d11bc71 100644 --- a/selfdrive/modeld/models/commonmodel_pyx.pyx +++ b/selfdrive/modeld/models/commonmodel_pyx.pyx @@ -59,8 +59,8 @@ cdef class ModelFrame: cdef class DrivingModelFrame(ModelFrame): cdef cppDrivingModelFrame * _frame - def __cinit__(self, CLContext context): - self._frame = new cppDrivingModelFrame(context.device_id, context.context) + def __cinit__(self, CLContext context, int temporal_skip): + self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip) self.frame = (self._frame) self.buf_size = self._frame.buf_size diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index f804b4ec31..3601bbb5da 100644 --- a/selfdrive/modeld/models/driving_policy.onnx +++ b/selfdrive/modeld/models/driving_policy.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cae3285c876804e649b14adadcfb8be79a9bd5a1b928113e37f1f08e25e9688 -size 16581121 +oid sha256:98f0121ccb6f850077b04cc91bd33d370fc6cbdc2bd35f1ab55628a15a813f36 +size 15966721 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 06c87d8755..aee6d8f1bf 100644 --- a/selfdrive/modeld/models/driving_vision.onnx +++ b/selfdrive/modeld/models/driving_vision.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29bbf79f9dfd7048c0013bb81e86d9b2979275b95ea1ed8a86d1a86a88695240 +oid sha256:897f80d0388250f99bba69b6a8434560cc0fd83157cbeb0bc134c67fe4e64624 size 34882971 diff --git a/selfdrive/modeld/tests/dmon_lag/repro.cc b/selfdrive/modeld/tests/dmon_lag/repro.cc deleted file mode 100644 index c4c1c65cbe..0000000000 --- a/selfdrive/modeld/tests/dmon_lag/repro.cc +++ /dev/null @@ -1,101 +0,0 @@ -// clang++ -O2 repro.cc && ./a.out - -#include -#include -#include - -#include -#include -#include -#include -#include - -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; -} - -#define MODEL_WIDTH 320 -#define MODEL_HEIGHT 640 - -// null function still breaks it -#define input_lambda(x) x - -// this is copied from models/dmonitoring.cc, and is the code that triggers the issue -void inner(uint8_t *resized_buf, float *net_input_buf) { - int resized_width = MODEL_WIDTH; - int resized_height = MODEL_HEIGHT; - - // 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[(c*MODEL_HEIGHT/2) + r] = input_lambda(resized_buf[(2*r*resized_width) + (2*c)]); - // Y_ur - net_input_buf[(c*MODEL_HEIGHT/2) + r + (2*(MODEL_WIDTH/2)*(MODEL_HEIGHT/2))] = input_lambda(resized_buf[(2*r*resized_width) + (2*c+1)]); - // Y_dl - net_input_buf[(c*MODEL_HEIGHT/2) + r + ((MODEL_WIDTH/2)*(MODEL_HEIGHT/2))] = input_lambda(resized_buf[(2*r*resized_width+1) + (2*c)]); - // Y_dr - net_input_buf[(c*MODEL_HEIGHT/2) + r + (3*(MODEL_WIDTH/2)*(MODEL_HEIGHT/2))] = input_lambda(resized_buf[(2*r*resized_width+1) + (2*c+1)]); - // U - net_input_buf[(c*MODEL_HEIGHT/2) + r + (4*(MODEL_WIDTH/2)*(MODEL_HEIGHT/2))] = input_lambda(resized_buf[(resized_width*resized_height) + (r*resized_width/2) + c]); - // V - net_input_buf[(c*MODEL_HEIGHT/2) + r + (5*(MODEL_WIDTH/2)*(MODEL_HEIGHT/2))] = input_lambda(resized_buf[(resized_width*resized_height) + ((resized_width/2)*(resized_height/2)) + (r*resized_width/2) + c]); - } - } -} - -float trial() { - int resized_width = MODEL_WIDTH; - int resized_height = MODEL_HEIGHT; - - int yuv_buf_len = (MODEL_WIDTH/2) * (MODEL_HEIGHT/2) * 6; // Y|u|v -> y|y|y|y|u|v - - // allocate the buffers - uint8_t *resized_buf = (uint8_t*)malloc(resized_width*resized_height*3/2); - float *net_input_buf = (float*)malloc(yuv_buf_len*sizeof(float)); - printf("allocate -- %p 0x%x -- %p 0x%lx\n", resized_buf, resized_width*resized_height*3/2, net_input_buf, yuv_buf_len*sizeof(float)); - - // test for bad buffers - static int CNT = 20; - float avg = 0.0; - for (int i = 0; i < CNT; i++) { - double s4 = millis_since_boot(); - inner(resized_buf, net_input_buf); - double s5 = millis_since_boot(); - avg += s5-s4; - } - avg /= CNT; - - // once it's bad, it's reliably bad - if (avg > 10) { - printf("HIT %f\n", avg); - printf("BAD\n"); - - for (int i = 0; i < 200; i++) { - double s4 = millis_since_boot(); - inner(resized_buf, net_input_buf); - double s5 = millis_since_boot(); - printf("%.2f ", s5-s4); - } - printf("\n"); - - exit(0); - } - - // don't free so we get a different buffer each time - //free(resized_buf); - //free(net_input_buf); - - return avg; -} - -int main() { - while (true) { - float ret = trial(); - printf("got %f\n", ret); - } -} - diff --git a/selfdrive/modeld/tests/tf_test/build.sh b/selfdrive/modeld/tests/tf_test/build.sh deleted file mode 100755 index df1d24761e..0000000000 --- a/selfdrive/modeld/tests/tf_test/build.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -clang++ -I /home/batman/one/external/tensorflow/include/ -L /home/batman/one/external/tensorflow/lib -Wl,-rpath=/home/batman/one/external/tensorflow/lib main.cc -ltensorflow diff --git a/selfdrive/modeld/tests/tf_test/main.cc b/selfdrive/modeld/tests/tf_test/main.cc deleted file mode 100644 index b00f7f95e8..0000000000 --- a/selfdrive/modeld/tests/tf_test/main.cc +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include -#include -#include "tensorflow/c/c_api.h" - -void* read_file(const char* path, size_t* out_len) { - FILE* f = fopen(path, "r"); - if (!f) { - return NULL; - } - fseek(f, 0, SEEK_END); - long f_len = ftell(f); - rewind(f); - - char* buf = (char*)calloc(f_len, 1); - assert(buf); - - size_t num_read = fread(buf, f_len, 1, f); - fclose(f); - - if (num_read != 1) { - free(buf); - return NULL; - } - - if (out_len) { - *out_len = f_len; - } - - return buf; -} - -static void DeallocateBuffer(void* data, size_t) { - free(data); -} - -int main(int argc, char* argv[]) { - TF_Buffer* buf; - TF_Graph* graph; - TF_Status* status; - char *path = argv[1]; - - // load model - { - size_t model_size; - char tmp[1024]; - snprintf(tmp, sizeof(tmp), "%s.pb", path); - printf("loading model %s\n", tmp); - uint8_t *model_data = (uint8_t *)read_file(tmp, &model_size); - buf = TF_NewBuffer(); - buf->data = model_data; - buf->length = model_size; - buf->data_deallocator = DeallocateBuffer; - printf("loaded model of size %d\n", model_size); - } - - // import graph - status = TF_NewStatus(); - graph = TF_NewGraph(); - TF_ImportGraphDefOptions *opts = TF_NewImportGraphDefOptions(); - TF_GraphImportGraphDef(graph, buf, opts, status); - TF_DeleteImportGraphDefOptions(opts); - TF_DeleteBuffer(buf); - if (TF_GetCode(status) != TF_OK) { - printf("FAIL: %s\n", TF_Message(status)); - } else { - printf("SUCCESS\n"); - } -} diff --git a/selfdrive/modeld/tests/tf_test/pb_loader.py b/selfdrive/modeld/tests/tf_test/pb_loader.py deleted file mode 100755 index 3e476628eb..0000000000 --- a/selfdrive/modeld/tests/tf_test/pb_loader.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -import sys -import tensorflow as tf - -with open(sys.argv[1], "rb") as f: - graph_def = tf.compat.v1.GraphDef() - graph_def.ParseFromString(f.read()) - #tf.io.write_graph(graph_def, '', sys.argv[1]+".try") diff --git a/selfdrive/modeld/tests/timing/benchmark.py b/selfdrive/modeld/tests/timing/benchmark.py deleted file mode 100755 index c629ec2fff..0000000000 --- a/selfdrive/modeld/tests/timing/benchmark.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -# type: ignore - -import os -import time -import numpy as np - -import cereal.messaging as messaging -from openpilot.system.manager.process_config import managed_processes - - -N = int(os.getenv("N", "5")) -TIME = int(os.getenv("TIME", "30")) - -if __name__ == "__main__": - sock = messaging.sub_sock('modelV2', conflate=False, timeout=1000) - - execution_times = [] - - for _ in range(N): - os.environ['LOGPRINT'] = 'debug' - managed_processes['modeld'].start() - time.sleep(5) - - t = [] - start = time.monotonic() - while time.monotonic() - start < TIME: - msgs = messaging.drain_sock(sock, wait_for_one=True) - for m in msgs: - t.append(m.modelV2.modelExecutionTime) - - execution_times.append(np.array(t[10:]) * 1000) - managed_processes['modeld'].stop() - - print("\n\n") - print(f"ran modeld {N} times for {TIME}s each") - for _, t in enumerate(execution_times): - print(f"\tavg: {sum(t)/len(t):0.2f}ms, min: {min(t):0.2f}ms, max: {max(t):0.2f}ms") - print("\n\n") diff --git a/selfdrive/pandad/pandad_api_impl.pyx b/selfdrive/pandad/pandad_api_impl.pyx index e571c4aa58..6683b843ae 100644 --- a/selfdrive/pandad/pandad_api_impl.pyx +++ b/selfdrive/pandad/pandad_api_impl.pyx @@ -17,14 +17,17 @@ cdef extern from "opendbc/can/common.h": vector[CanFrame] frames cdef extern from "can_list_to_can_capnp.cc": - void can_list_to_can_capnp_cpp(const vector[CanFrame] &can_list, string &out, bool sendcan, bool valid) + void can_list_to_can_capnp_cpp(const vector[CanFrame] &can_list, string &out, bool sendcan, bool valid) nogil void can_capnp_to_can_list_cpp(const vector[string] &strings, vector[CanData] &can_data, bool sendcan) def can_list_to_can_capnp(can_msgs, msgtype='can', valid=True): cdef CanFrame *f cdef vector[CanFrame] can_list + cdef uint32_t cpp_can_msgs_len = len(can_msgs) + + with nogil: + can_list.reserve(cpp_can_msgs_len) - can_list.reserve(len(can_msgs)) for can_msg in can_msgs: f = &(can_list.emplace_back()) f.address = can_msg[0] @@ -32,7 +35,10 @@ def can_list_to_can_capnp(can_msgs, msgtype='can', valid=True): f.src = can_msg[2] cdef string out - can_list_to_can_capnp_cpp(can_list, out, msgtype == 'sendcan', valid) + cdef bool is_sendcan = (msgtype == 'sendcan') + cdef bool is_valid = valid + with nogil: + can_list_to_can_capnp_cpp(can_list, out, is_sendcan, is_valid) return out def can_capnp_to_list(strings, msgtype='can'): diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index ba9c2dcda2..b38b411db9 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -31,6 +31,7 @@ from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP REPLAY = "REPLAY" in os.environ SIMULATION = "SIMULATION" in os.environ TESTING_CLOSET = "TESTING_CLOSET" in os.environ +IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd"} LONGITUDINAL_PERSONALITY_MAP = {v: k for k, v in log.LongitudinalPersonality.schema.enumerants.items()} ThermalStatus = log.DeviceState.ThermalStatus @@ -283,7 +284,7 @@ class SelfdriveD(CruiseHelper): if not_running != self.not_running_prev: cloudlog.event("process_not_running", not_running=not_running, error=True) self.not_running_prev = not_running - if self.sm.recv_frame['managerState'] and not_running: + if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES): self.events.add(EventName.processNotRunning) else: if not SIMULATION and not self.rk.lagging: @@ -352,11 +353,11 @@ class SelfdriveD(CruiseHelper): if lac.active and not recent_steer_pressed and not self.CP.notCar: clipped_speed = max(CS.vEgo, MIN_LATERAL_CONTROL_SPEED) actual_lateral_accel = controlstate.curvature * (clipped_speed**2) - desired_lateral_accel = controlstate.desiredCurvature * (clipped_speed**2) + desired_lateral_accel = self.sm['modelV2'].action.desiredCurvature * (clipped_speed**2) undershooting = abs(desired_lateral_accel) / abs(1e-3 + actual_lateral_accel) > 1.2 turning = abs(desired_lateral_accel) > 1.0 - good_speed = CS.vEgo > 5 - if undershooting and turning and good_speed and lac.saturated: + # TODO: lac.saturated includes speed and other checks, should be pulled out + if undershooting and turning and lac.saturated: self.events.add(EventName.steerSaturated) # Check for FCW diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 061da31de3..2d42d31619 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -358,6 +358,7 @@ def migrate_cameraStates(msgs): new_msg = messaging.new_message(msg.which()) new_camera_state = getattr(new_msg, new_msg.which()) + new_camera_state.sensor = camera_state.sensor new_camera_state.frameId = encode_id new_camera_state.encodeId = encode_id # timestampSof was added later so it might be missing on some old segments diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 50027574ba..36398af8ca 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -19,8 +19,8 @@ from openpilot.tools.lib.framereader import FrameReader, NumpyFrameReader from openpilot.tools.lib.logreader import LogReader, save_log from openpilot.tools.lib.github_utils import GithubUtils -TEST_ROUTE = "2f4452b03ccb98f0|2022-12-03--13-45-30" -SEGMENT = 6 +TEST_ROUTE = "8494c69d3c710e81|000001d4--2648a9a404" +SEGMENT = 4 MAX_FRAMES = 100 if PC else 400 NO_MODEL = "NO_MODEL" in os.environ @@ -66,6 +66,8 @@ def generate_report(proposed, master, tmp, commit): (lambda x: get_idx_if_non_empty(x.action.desiredCurvature), "desiredCurvature"), (lambda x: get_idx_if_non_empty(x.leadsV3[0].x, 0), "leadsV3.x"), (lambda x: get_idx_if_non_empty(x.laneLines[1].y, 0), "laneLines.y"), + (lambda x: get_idx_if_non_empty(x.meta.desireState, 3), "desireState.laneChangeLeft"), + (lambda x: get_idx_if_non_empty(x.meta.desireState, 4), "desireState.laneChangeRight"), (lambda x: get_idx_if_non_empty(x.meta.disengagePredictions.gasPressProbs, 1), "gasPressProbs") ], "modelV2") DriverStateV2_Plots = zl([ @@ -143,7 +145,8 @@ def trim_logs_to_max_frames(logs, max_frames, frs_types, include_all_types): def model_replay(lr, frs): # modeld is using frame pairs - modeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"roadCameraState", "wideRoadCameraState"}, {"roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}) + modeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"roadCameraState", "wideRoadCameraState"}, + {"roadEncodeIdx", "wideRoadEncodeIdx", "carParams", "carState", "carControl"}) dmodeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"driverCameraState"}, {"driverEncodeIdx", "carParams"}) if not SEND_EXTRA_INPUTS: diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index c82c401738..d1a0cc0827 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -571,7 +571,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="modeld", - pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState", "carState"], + pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState", "carState", "carControl"], subs=["modelV2", "drivingModelData", "cameraOdometry"], ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime", "drivingModelData.frameDropPerc", "drivingModelData.modelExecutionTime"], should_recv_callback=ModeldCameraSyncRcvCallback(), diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 3c63f00300..54df53fd91 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -d343af55506b14a4011bfe43b2a49c7e3c387d75 \ No newline at end of file +465979047295dad5da3a552db9227ed776a26a79 \ No newline at end of file diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index e87b8347e1..4d00613f0a 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -12,10 +12,9 @@ from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, FAKE check_openpilot_enabled, check_most_messages_valid, get_custom_params_from_lr from openpilot.selfdrive.test.process_replay.vision_meta import DRIVER_CAMERA_FRAME_SIZES from openpilot.selfdrive.test.update_ci_routes import upload_route -from openpilot.tools.lib.route import Route from openpilot.tools.lib.framereader import FrameReader, BaseFrameReader, FrameType from openpilot.tools.lib.logreader import LogReader, LogIterable, save_log - +from openpilot.tools.lib.openpilotci import get_url class DummyFrameReader(BaseFrameReader): def __init__(self, w: int, h: int, frame_count: int, pix_val: int): @@ -55,45 +54,28 @@ def regen_segment( def setup_data_readers( - route: str, sidx: int, use_route_meta: bool, - needs_driver_cam: bool = True, needs_road_cam: bool = True, dummy_driver_cam: bool = False + route: str, sidx: int, needs_driver_cam: bool = True, needs_road_cam: bool = True, dummy_driver_cam: bool = False ) -> tuple[LogReader, dict[str, Any]]: - if use_route_meta: - r = Route(route) - lr = LogReader(r.log_paths()[sidx]) - frs = {} - if needs_road_cam and len(r.camera_paths()) > sidx and r.camera_paths()[sidx] is not None: - frs['roadCameraState'] = FrameReader(r.camera_paths()[sidx]) - if needs_road_cam and len(r.ecamera_paths()) > sidx and r.ecamera_paths()[sidx] is not None: - frs['wideRoadCameraState'] = FrameReader(r.ecamera_paths()[sidx]) - if needs_driver_cam: - if dummy_driver_cam: - frs['driverCameraState'] = DummyFrameReader.zero_dcamera() - elif len(r.dcamera_paths()) > sidx and r.dcamera_paths()[sidx] is not None: - device_type = next(str(msg.initData.deviceType) for msg in lr if msg.which() == "initData") - assert device_type != "neo", "Driver camera not supported on neo segments. Use dummy dcamera." - frs['driverCameraState'] = FrameReader(r.dcamera_paths()[sidx]) - else: - lr = LogReader(f"{route}/{sidx}/r") - frs = {} - if needs_road_cam: - frs['roadCameraState'] = FrameReader(f"cd:/{route.replace('|', '/')}/{sidx}/fcamera.hevc") - if next((True for m in lr if m.which() == "wideRoadCameraState"), False): - frs['wideRoadCameraState'] = FrameReader(f"cd:/{route.replace('|', '/')}/{sidx}/ecamera.hevc") - if needs_driver_cam: - if dummy_driver_cam: - frs['driverCameraState'] = DummyFrameReader.zero_dcamera() - else: - device_type = next(str(msg.initData.deviceType) for msg in lr if msg.which() == "initData") - assert device_type != "neo", "Driver camera not supported on neo segments. Use dummy dcamera." - frs['driverCameraState'] = FrameReader(f"cd:/{route.replace('|', '/')}/{sidx}/dcamera.hevc") + lr = LogReader(f"{route}/{sidx}/r") + frs = {} + if needs_road_cam: + frs['roadCameraState'] = FrameReader(get_url(route, str(sidx), "fcamera.hevc")) + if next((True for m in lr if m.which() == "wideRoadCameraState"), False): + frs['wideRoadCameraState'] = FrameReader(get_url(route, str(sidx), "ecamera.hevc")) + if needs_driver_cam: + if dummy_driver_cam: + frs['driverCameraState'] = DummyFrameReader.zero_dcamera() + else: + device_type = next(str(msg.initData.deviceType) for msg in lr if msg.which() == "initData") + assert device_type != "neo", "Driver camera not supported on neo segments. Use dummy dcamera." + frs['driverCameraState'] = FrameReader(get_url(route, str(sidx), "dcamera.hevc")) return lr, frs def regen_and_save( route: str, sidx: int, processes: str | Iterable[str] = "all", outdir: str = FAKEDATA, - upload: bool = False, use_route_meta: bool = False, disable_tqdm: bool = False, dummy_driver_cam: bool = False + upload: bool = False, disable_tqdm: bool = False, dummy_driver_cam: bool = False ) -> str: if not isinstance(processes, str) and not hasattr(processes, "__iter__"): raise ValueError("whitelist_proc must be a string or iterable") @@ -110,7 +92,7 @@ def regen_and_save( replayed_processes = CONFIGS all_vision_pubs = {pub for cfg in replayed_processes for pub in cfg.vision_pubs} - lr, frs = setup_data_readers(route, sidx, use_route_meta, + lr, frs = setup_data_readers(route, sidx, needs_driver_cam="driverCameraState" in all_vision_pubs, needs_road_cam="roadCameraState" in all_vision_pubs or "wideRoadCameraState" in all_vision_pubs, dummy_driver_cam=dummy_driver_cam) diff --git a/selfdrive/test/process_replay/regen_all.py b/selfdrive/test/process_replay/regen_all.py index 656a5b89e1..78a90b420c 100755 --- a/selfdrive/test/process_replay/regen_all.py +++ b/selfdrive/test/process_replay/regen_all.py @@ -17,7 +17,7 @@ def regen_job(segment, upload, disable_tqdm): 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=upload, use_route_meta=False, + relr = regen_and_save(sn.route_name.canonical_name, sn.segment_num, upload=upload, outdir=os.path.join(FAKEDATA, fake_dongle_id), disable_tqdm=disable_tqdm, dummy_driver_cam=True) relr = '|'.join(relr.split('/')[-2:]) return f' ("{segment[0]}", "{relr}"), ' diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 2c4ed4996d..9b9118b7f3 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -44,22 +44,22 @@ source_segments = [ segments = [ ("BODY", "regenA67A128BCD8|2024-08-30--02-36-22--0"), - ("HYUNDAI", "regen9CBD921E93E|2024-08-30--02-38-51--0"), + ("HYUNDAI", "regenCCD47FEBC0C|2025-03-04--03-21-48--0"), ("HYUNDAI2", "regen306779F6870|2024-10-03--04-03-23--0"), - ("TOYOTA", "regen1CA7A48E6F7|2024-08-30--02-45-08--0"), + ("TOYOTA", "regen4A5115B248D|2025-03-04--03-21-43--0"), ("TOYOTA2", "regen6E484EDAB96|2024-08-30--02-47-37--0"), ("TOYOTA3", "regen4CE950B0267|2024-08-30--02-51-30--0"), - ("HONDA", "regenC8F0D6ADC5C|2024-08-30--02-54-01--0"), + ("HONDA", "regenB8CABEC09CC|2025-03-04--03-32-55--0"), ("HONDA2", "regen4B38A7428CD|2024-08-30--02-56-31--0"), ("CHRYSLER", "regenF3DBBA9E8DF|2024-08-30--02-59-03--0"), ("RAM", "regenDB02684E00A|2024-08-30--03-02-54--0"), - ("SUBARU", "regenAA1FF48CF1F|2024-08-30--03-06-45--0"), + ("SUBARU", "regen5E3347D0A0F|2025-03-04--03-23-55--0"), ("GM", "regen720F2BA4CF6|2024-08-30--03-09-15--0"), ("GM2", "regen9ADBECBCD1C|2024-08-30--03-13-04--0"), ("NISSAN", "regen58464878D07|2024-08-30--03-15-31--0"), ("VOLKSWAGEN", "regenED976DEB757|2024-08-30--03-18-02--0"), ("MAZDA", "regenACF84CCF482|2024-08-30--03-21-55--0"), - ("FORD", "regen756F8230C21|2024-11-07--00-08-24--0"), + ("FORD", "regenA75209BD115|2025-03-04--03-23-53--0"), ("RIVIAN", "bc095dc92e101734|000000db--ee9fe46e57--1"), ] diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 23d1687770..9756010614 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -1,8 +1,6 @@ import math import json import os -import pathlib -import psutil import pytest import shutil import subprocess @@ -12,7 +10,7 @@ from collections import Counter, defaultdict from pathlib import Path from tabulate import tabulate -from cereal import car, log +from cereal import log import cereal.messaging as messaging from cereal.services import SERVICE_LIST from openpilot.common.basedir import BASEDIR @@ -23,6 +21,7 @@ from openpilot.selfdrive.test.helpers import set_params_enabled, release_only from openpilot.system.hardware import HARDWARE from openpilot.system.hardware.hw import Paths from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.log_time_series import msgs_to_time_series """ CPU usage budget @@ -112,6 +111,7 @@ def cputime_total(ct): @pytest.mark.tici +@pytest.mark.skip_tici_setup class TestOnroad: @classmethod @@ -119,7 +119,8 @@ class TestOnroad: if "DEBUG" in os.environ: segs = filter(lambda x: os.path.exists(os.path.join(x, "rlog.zst")), Path(Paths.log_root()).iterdir()) segs = sorted(segs, key=lambda x: x.stat().st_mtime) - cls.lr = list(LogReader(os.path.join(segs[-3], "rlog.zst"))) + cls.lr = list(LogReader(os.path.join(segs[-1], "rlog.zst"))) + cls.ts = msgs_to_time_series(cls.lr) return # setup env @@ -140,44 +141,31 @@ class TestOnroad: proc = subprocess.Popen(["python", manager_path]) sm = messaging.SubMaster(['carState']) - with Timeout(150, "controls didn't start"): - while sm.recv_frame['carState'] < 0: + with Timeout(30, "controls didn't start"): + while not sm.seen['carState']: sm.update(1000) - route = None - cls.segments = [] - with Timeout(300, "timed out waiting for logs"): - while route is None: - route = params.get("CurrentRoute", encoding="utf-8") - time.sleep(0.01) + route = params.get("CurrentRoute", encoding="utf-8") + assert route is not None - # test car params caching - params.put("CarParamsCache", car.CarParams().to_bytes()) - - while len(cls.segments) < 1: - segs = set() - if Path(Paths.log_root()).exists(): - segs = set(Path(Paths.log_root()).glob(f"{route}--*")) - cls.segments = sorted(segs, key=lambda s: int(str(s).rsplit('--')[-1])) - time.sleep(0.01) + segs = list(Path(Paths.log_root()).glob(f"{route}--*")) + assert len(segs) == 1 time.sleep(TEST_DURATION) - finally: - cls.gpu_procs = {psutil.Process(int(f.name)).name() for f in pathlib.Path('/sys/devices/virtual/kgsl/kgsl/proc/').iterdir() if f.is_dir()} - if proc is not None: proc.terminate() if proc.wait(60) is None: proc.kill() - cls.lrs = [list(LogReader(os.path.join(str(s), "rlog.zst"))) for s in cls.segments] - - cls.lr = list(LogReader(os.path.join(str(cls.segments[0]), "rlog.zst"))) - cls.log_path = cls.segments[0] + cls.lr = list(LogReader(os.path.join(str(segs[0]), "rlog.zst"))) + st = time.monotonic() + cls.ts = msgs_to_time_series(cls.lr) + print("msgs to time series", time.monotonic() - st) + log_path = segs[0] cls.log_sizes = {} - for f in cls.log_path.iterdir(): + for f in log_path.iterdir(): assert f.is_file() cls.log_sizes[f] = f.stat().st_size / 1e6 @@ -198,7 +186,7 @@ class TestOnroad: assert len(msgs) >= math.floor(SERVICE_LIST[s].frequency*int(TEST_DURATION*0.8)) def test_manager_starting_time(self): - st = self.msgs['managerState'][0].logMonoTime / 1e9 + st = self.ts['managerState']['t'][0] assert (st - self.manager_st) < 10, f"manager.py took {st - self.manager_st}s to publish the first 'managerState' msg" def test_cloudlog_size(self): @@ -226,7 +214,7 @@ class TestOnroad: result += "-------------- UI Draw Timing ------------------\n" result += "------------------------------------------------\n" - ts = [m.uiDebug.drawTimeMillis for m in self.msgs['uiDebug']] + ts = self.ts['uiDebug']['drawTimeMillis'] result += f"min {min(ts):.2f}ms\n" result += f"max {max(ts):.2f}ms\n" result += f"std {np.std(ts):.2f}ms\n" @@ -309,9 +297,6 @@ class TestOnroad: assert np.max(np.diff(mems)) <= 4, "Max memory increase too high" assert np.average(np.diff(mems)) <= 1, "Average memory increase too high" - def test_gpu_usage(self): - assert self.gpu_procs == {"weston", "ui", "camerad", "selfdrive.modeld.modeld", "selfdrive.modeld.dmonitoringmodeld"} - def test_camera_frame_timings(self, subtests): # test timing within a single camera result = "\n" @@ -319,7 +304,7 @@ class TestOnroad: result += "----------------- SOF Timing ------------------\n" result += "------------------------------------------------\n" for name in ['roadCameraState', 'wideRoadCameraState', 'driverCameraState']: - ts = [getattr(m, m.which()).timestampSof for m in self.lr if name in m.which()] + ts = self.ts[name]['timestampSof'] d_ms = np.diff(ts) / 1e6 d50 = np.abs(d_ms-50) result += f"{name} sof delta vs 50ms: min {min(d50):.2f}ms\n" @@ -338,35 +323,51 @@ class TestOnroad: # sanity checks within a single cam for cam in cams: with subtests.test(test="frame_skips", camera=cam): - cam_log = [getattr(x, x.which()) for x in self.msgs[cam]] - assert set(np.diff([x.frameId for x in cam_log])) == {1, }, "Frame ID skips" + assert set(np.diff(self.ts[cam]['frameId'])) == {1, }, "Frame ID skips" # EOF > SOF - eof_sof_diff = np.array([x.timestampEof - x.timestampSof for x in cam_log]) + eof_sof_diff = self.ts[cam]['timestampEof'] - self.ts[cam]['timestampSof'] assert np.all(eof_sof_diff > 0) assert np.all(eof_sof_diff < 50*1e6) - fid = {c: [getattr(m, m.which()).frameId for m in self.msgs[c]] for c in cams} - first_fid = [min(x) for x in fid.values()] + first_fid = {c: min(self.ts[c]['frameId']) for c in cams} if cam.endswith('CameraState'): # camerad guarantees that all cams start on frame ID 0 # (note loggerd also needs to start up fast enough to catch it) - assert set(first_fid) == {0, }, "Cameras don't start on frame ID 0" + assert set(first_fid.values()) == {0, }, "Cameras don't start on frame ID 0" else: # encoder guarantees all cams start on the same frame ID - assert len(set(first_fid)) == 1, "Cameras don't start on same frame ID" + assert len(set(first_fid.values())) == 1, "Cameras don't start on same frame ID" # we don't do a full segment rotation, so these might not match exactly - last_fid = [max(x) for x in fid.values()] - assert max(last_fid) - min(last_fid) < 10 + last_fid = {c: max(self.ts[c]['frameId']) for c in cams} + assert max(last_fid.values()) - min(last_fid.values()) < 10 - start, end = min(first_fid), min(last_fid) - all_ts = [[getattr(m, m.which()).timestampSof for m in self.msgs[c]] for c in cams] + start, end = min(first_fid.values()), min(last_fid.values()) for i in range(end-start): - ts = [round(x[i]/1e6, 1) for x in all_ts] - diff = max(ts) - min(ts) + ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams} + diff = (max(ts.values()) - min(ts.values())) assert diff < 2, f"Cameras not synced properly: frame_id={start+i}, {diff=:.1f}ms, {ts=}" + def test_camera_encoder_matches(self, subtests): + # sanity check that the frame metadata is consistent with the encoded frames + pairs = [('roadCameraState', 'roadEncodeIdx'), + ('wideRoadCameraState', 'wideRoadEncodeIdx'), + ('driverCameraState', 'driverEncodeIdx')] + for cam, enc in pairs: + with subtests.test(camera=cam, encoder=enc): + cam_frames = {fid: (sof, eof) for fid, sof, eof in zip( + self.ts[cam]['frameId'], + self.ts[cam]['timestampSof'], + self.ts[cam]['timestampEof'], + strict=True, + )} + for i, fid in enumerate(self.ts[enc]['frameId']): + cam_sof, cam_eof = cam_frames[fid] + enc_sof, enc_eof = self.ts[enc]['timestampSof'][i], self.ts[enc]['timestampEof'][i] + assert enc_sof == cam_sof, f"SOF mismatch: frameId={fid}, enc_sof={enc_sof}, cam_sof={cam_sof}" + assert enc_eof == cam_eof, f"EOF mismatch: frameId={fid}, enc_eof={enc_eof}, cam_eof={cam_eof}" + def test_mpc_execution_timings(self): result = "\n" result += "------------------------------------------------\n" @@ -438,12 +439,7 @@ class TestOnroad: @release_only def test_startup(self): - startup_alert = None - for msg in self.lrs[0]: - # can't use onroadEvents because the first msg can be dropped while loggerd is starting up - if msg.which() == "selfdriveState": - startup_alert = msg.selfdriveState.alertText1 - break + startup_alert = self.ts['selfdriveState']['alertText1'][0] expected = EVENTS[log.OnroadEvent.EventName.startup][ET.PERMANENT].alert_text_1 assert startup_alert == expected, "wrong startup alert" diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 12b1b023f9..8b14bec41b 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -47,12 +47,9 @@ translation_sources = [f"#selfdrive/ui/translations/{l}.ts" for l in languages.v translation_targets = [src.replace(".ts", ".qm") for src in translation_sources] lrelease_bin = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease' -lupdate = qt_env.Command(translation_sources + ["translations/alerts_generated.h"], qt_src + widgets_src, "selfdrive/ui/update_translations.py") lrelease = qt_env.Command(translation_targets, translation_sources, f"{lrelease_bin} $SOURCES") -qt_env.Depends(lrelease, lupdate) qt_env.NoClean(translation_sources) qt_env.Precious(translation_sources) -qt_env.NoCache(lupdate) # create qrc file for compiled translations to include with assets translations_assets_src = "#selfdrive/assets/translations_assets.qrc" diff --git a/selfdrive/ui/qt/offroad/firehose.cc b/selfdrive/ui/qt/offroad/firehose.cc index ef542535a3..726b71c8d7 100644 --- a/selfdrive/ui/qt/offroad/firehose.cc +++ b/selfdrive/ui/qt/offroad/firehose.cc @@ -8,6 +8,9 @@ #include #include #include +#include +#include +#include FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent) { layout = new QVBoxLayout(this); @@ -26,7 +29,7 @@ FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent) content_layout->setSpacing(20); // Top description - QLabel *description = new QLabel(tr("openpilot learns to drive by watching humans, like you, drive.\n\nFirehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode.")); + QLabel *description = new QLabel(tr("openpilot learns to drive by watching humans, like you, drive.\n\nFirehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode.")); description->setStyleSheet("font-size: 45px; padding-bottom: 20px;"); description->setWordWrap(true); content_layout->addWidget(description); @@ -38,41 +41,16 @@ FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent) line->setStyleSheet("background-color: #444444; margin-top: 5px; margin-bottom: 5px;"); content_layout->addWidget(line); - enable_firehose = new ParamControl("FirehoseMode", tr("Enable Firehose Mode"), "", ""); + toggle_label = new QLabel(tr("Firehose Mode: ACTIVE")); + toggle_label->setStyleSheet("font-size: 60px; font-weight: bold; color: white;"); + content_layout->addWidget(toggle_label); - content_layout->addWidget(enable_firehose); - - // Create progress bar container - progress_container = new QFrame(); - progress_container->hide(); - QHBoxLayout *progress_layout = new QHBoxLayout(progress_container); - progress_layout->setContentsMargins(10, 0, 10, 10); - progress_layout->setSpacing(20); - - progress_bar = new QProgressBar(); - progress_bar->setRange(0, 100); - progress_bar->setValue(0); - progress_bar->setTextVisible(false); - progress_bar->setStyleSheet(R"( - QProgressBar { - background-color: #444444; - border-radius: 10px; - height: 20px; - } - QProgressBar::chunk { - background-color: #3498db; - border-radius: 10px; - } - )"); - progress_bar->setFixedHeight(40); - - // Progress text - progress_text = new QLabel(tr("0%")); - progress_text->setStyleSheet("font-size: 40px; font-weight: bold; color: white;"); - - progress_layout->addWidget(progress_text); - - content_layout->addWidget(progress_container); + // Add contribution label + contribution_label = new QLabel(); + contribution_label->setStyleSheet("font-size: 52px; margin-top: 10px; margin-bottom: 10px;"); + contribution_label->setWordWrap(true); + contribution_label->hide(); + content_layout->addWidget(contribution_label); // Add a separator before detailed instructions QFrame *line2 = new QFrame(); @@ -83,22 +61,49 @@ FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent) // Detailed instructions at the bottom detailed_instructions = new QLabel(tr( - "Follow these steps to get your device ready:
" - "\t1. Bring your device inside and connect to a good USB-C adapter
" - "\t2. Connect to Wi-Fi
" - "\t3. Enable the toggle
" - "\t4. Leave it connected for at least 30 minutes
" + "For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.
" "
" - "The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness." - "

FAQ
" - "Does it matter how or where I drive? Nope, just drive as you normally would.
" - "What's a good USB-C adapter? Any fast phone or laptop charger should be fine.
" - "Do I need to be on Wi-Fi? Yes.
" - "Do I need to bring the device inside? No, you can enable once you're parked, however your uploads will be limited by your car's battery.
" + "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.
" + "

" + "Frequently Asked Questions

" + "Does it matter how or where I drive? Nope, just drive as you normally would.

" + "What's a good USB-C adapter? Any fast phone or laptop charger should be fine.

" + "Does it matter which software I run? Yes, only upstream openpilot (and particular forks) are able to be used for training." )); - detailed_instructions->setStyleSheet("font-size: 40px; padding: 20px; color: #E4E4E4;"); + detailed_instructions->setStyleSheet("font-size: 40px; color: #E4E4E4;"); detailed_instructions->setWordWrap(true); content_layout->addWidget(detailed_instructions); layout->addWidget(content, 1); + + // Set up the API request for firehose stats + const QString dongle_id = QString::fromStdString(Params().get("DongleId")); + firehose_stats = new RequestRepeater(this, CommaApi::BASE_URL + "/v1/devices/" + dongle_id + "/firehose_stats", + "ApiCache_FirehoseStats", 30, true); + QObject::connect(firehose_stats, &RequestRepeater::requestDone, [=](const QString &response, bool success) { + if (success) { + QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); + QJsonObject json = doc.object(); + int count = json["firehose"].toInt(); + contribution_label->setText(tr("%1 %2 of your driving are in the training dataset so far.").arg(count).arg(count == 1 ? "segment" : "segments")); + contribution_label->show(); + } + }); + + QObject::connect(uiState(), &UIState::uiUpdate, this, &FirehosePanel::refresh); +} + +void FirehosePanel::refresh() { + auto deviceState = (*uiState()->sm)["deviceState"].getDeviceState(); + auto networkType = deviceState.getNetworkType(); + bool networkMetered = deviceState.getNetworkMetered(); + + bool is_active = !networkMetered && (networkType != cereal::DeviceState::NetworkType::NONE); + if (is_active) { + toggle_label->setText(tr("ACTIVE")); + toggle_label->setStyleSheet("font-size: 60px; font-weight: bold; color: #2ecc71;"); + } else { + toggle_label->setText(tr("INACTIVE: connect to unmetered network")); + toggle_label->setStyleSheet("font-size: 60px;"); + } } diff --git a/selfdrive/ui/qt/offroad/firehose.h b/selfdrive/ui/qt/offroad/firehose.h index c43e28325e..0a5fd77ba3 100644 --- a/selfdrive/ui/qt/offroad/firehose.h +++ b/selfdrive/ui/qt/offroad/firehose.h @@ -2,9 +2,8 @@ #include #include -#include #include -#include "common/params.h" +#include "selfdrive/ui/qt/request_repeater.h" #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/ui.h" @@ -26,12 +25,13 @@ public: private: QVBoxLayout *layout; - - ParamControl *enable_firehose; - QFrame *progress_container; - QProgressBar *progress_bar; - QLabel *progress_text; + QLabel *detailed_instructions; - - void updateFirehoseState(bool enabled); + QLabel *contribution_label; + QLabel *toggle_label; + + RequestRepeater *firehose_stats; + +private slots: + void refresh(); }; diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index bae7e3bdf3..1390566671 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -4,7 +4,6 @@ #include #include -#include #include #include @@ -12,7 +11,6 @@ #include "common/params.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/input.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" TrainingGuide::TrainingGuide(QWidget *parent) : QFrame(parent) { setAttribute(Qt::WA_OpaquePaintEvent); @@ -85,29 +83,25 @@ void TrainingGuide::paintEvent(QPaintEvent *event) { } void TermsPage::showEvent(QShowEvent *event) { - // late init, building QML widget takes 200ms - if (layout()) { - return; - } - QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(45, 35, 45, 45); main_layout->setSpacing(0); - QLabel *title = new QLabel(tr("Terms & Conditions")); - title->setStyleSheet("font-size: 90px; font-weight: 600;"); - main_layout->addWidget(title); + QVBoxLayout *vlayout = new QVBoxLayout(); + vlayout->setContentsMargins(165, 165, 165, 0); + main_layout->addLayout(vlayout); - QLabel *text = new QLabel(this); - text->setTextFormat(Qt::RichText); - text->setWordWrap(true); - text->setText(QString::fromStdString(util::read_file("../assets/offroad/tc.html"))); - text->setStyleSheet("font-size:50px; font-weight: 200; color: #C9C9C9; background-color:#1B1B1B; padding:50px 50px;"); - ScrollView *scroll = new ScrollView(text, this); + QLabel *title = new QLabel(tr("Welcome to openpilot")); + title->setStyleSheet("font-size: 90px; font-weight: 500;"); + vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); - main_layout->addSpacing(30); - main_layout->addWidget(scroll); - main_layout->addSpacing(50); + vlayout->addSpacing(90); + QLabel *desc = new QLabel(tr("You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing.")); + desc->setWordWrap(true); + desc->setStyleSheet("font-size: 80px; font-weight: 300;"); + vlayout->addWidget(desc, 0); + + vlayout->addStretch(); QHBoxLayout* buttons = new QHBoxLayout; buttons->setMargin(0); @@ -118,8 +112,7 @@ void TermsPage::showEvent(QShowEvent *event) { buttons->addWidget(decline_btn); QObject::connect(decline_btn, &QPushButton::clicked, this, &TermsPage::declinedTerms); - accept_btn = new QPushButton(tr("Scroll to accept")); - accept_btn->setEnabled(false); + accept_btn = new QPushButton(tr("Agree")); accept_btn->setStyleSheet(R"( QPushButton { background-color: #465BEA; @@ -127,23 +120,9 @@ void TermsPage::showEvent(QShowEvent *event) { QPushButton:pressed { background-color: #3049F4; } - QPushButton:disabled { - background-color: #4F4F4F; - } )"); buttons->addWidget(accept_btn); QObject::connect(accept_btn, &QPushButton::clicked, this, &TermsPage::acceptedTerms); - QScrollBar *scroll_bar = scroll->verticalScrollBar(); - connect(scroll_bar, &QScrollBar::valueChanged, this, [this, scroll_bar](int value) { - if (value == scroll_bar->maximum()) { - enableAccept(); - } - }); -} - -void TermsPage::enableAccept() { - accept_btn->setText(tr("Agree")); - accept_btn->setEnabled(true); } void DeclinePage::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index 0f6dbb5063..0c6c58e4a7 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -65,9 +65,6 @@ class TermsPage : public QFrame { public: explicit TermsPage(QWidget *parent = 0) : QFrame(parent) {} -public slots: - void enableAccept(); - private: void showEvent(QShowEvent *event) override; diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 194b723fc9..9bc3fad3c9 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -54,7 +54,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { connect(targetBranchBtn, &ButtonControl::clicked, [=]() { auto current = params.get("GitBranch"); QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(","); - for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "nightly-dev", "master-ci", "master"}) { + for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "nightly-dev", "master"}) { auto i = branches.indexOf(b); if (i >= 0) { branches.removeAt(i); diff --git a/selfdrive/ui/qt/onroad/hud.cc b/selfdrive/ui/qt/onroad/hud.cc index c63962caf6..4cfa3d0e3c 100644 --- a/selfdrive/ui/qt/onroad/hud.cc +++ b/selfdrive/ui/qt/onroad/hud.cc @@ -26,6 +26,7 @@ void HudRenderer::updateState(const UIState &s) { // Handle older routes where vCruiseCluster is not set set_speed = car_state.getVCruiseCluster() == 0.0 ? controls_state.getVCruiseDEPRECATED() : car_state.getVCruiseCluster(); is_cruise_set = set_speed > 0 && set_speed != SET_SPEED_NA; + is_cruise_available = set_speed != -1; if (is_cruise_set && !is_metric) { set_speed *= KM_TO_MILE; @@ -47,7 +48,9 @@ void HudRenderer::draw(QPainter &p, const QRect &surface_rect) { p.fillRect(0, 0, surface_rect.width(), UI_HEADER_HEIGHT, bg); - drawSetSpeed(p, surface_rect); + if (is_cruise_available) { + drawSetSpeed(p, surface_rect); + } drawCurrentSpeed(p, surface_rect); p.restore(); diff --git a/selfdrive/ui/qt/onroad/hud.h b/selfdrive/ui/qt/onroad/hud.h index 893a9133fe..6638af8a05 100644 --- a/selfdrive/ui/qt/onroad/hud.h +++ b/selfdrive/ui/qt/onroad/hud.h @@ -25,6 +25,7 @@ protected: float speed = 0; float set_speed = 0; bool is_cruise_set = false; + bool is_cruise_available = true; bool is_metric = false; bool v_ego_cluster_seen = false; int status = STATUS_DISENGAGED; diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 3cd6ae5820..8b5fc8bd04 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -69,14 +69,13 @@ class Soundd: for sound in sound_list: filename, play_count, volume = sound_list[sound] - wavefile = wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r') + with wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r') as wavefile: + assert wavefile.getnchannels() == 1 + assert wavefile.getsampwidth() == 2 + assert wavefile.getframerate() == SAMPLE_RATE - assert wavefile.getnchannels() == 1 - assert wavefile.getsampwidth() == 2 - assert wavefile.getframerate() == SAMPLE_RATE - - length = wavefile.getnframes() - self.loaded_sounds[sound] = np.frombuffer(wavefile.readframes(length), dtype=np.int16).astype(np.float32) / (2**16/2) + length = wavefile.getnframes() + self.loaded_sounds[sound] = np.frombuffer(wavefile.readframes(length), dtype=np.int16).astype(np.float32) / (2**16/2) def get_sound_data(self, frames): # get "frames" worth of data from the current alert sound, looping when required diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 0967152fa4..09dd7c5d8b 100644 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -2,14 +2,12 @@ import pytest import json import os import re -import shutil -import tempfile import xml.etree.ElementTree as ET import string import requests from parameterized import parameterized_class -from openpilot.selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE, update_translations +from openpilot.selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE with open(LANGUAGES_FILE) as f: translation_files = json.load(f) @@ -34,16 +32,6 @@ class TestTranslations: assert os.path.exists(os.path.join(TRANSLATIONS_DIR, f"{self.file}.ts")), \ f"{self.name} has no XML translation file, run selfdrive/ui/update_translations.py" - def test_translations_updated(self): - with tempfile.TemporaryDirectory() as tmpdir: - shutil.copytree(TRANSLATIONS_DIR, tmpdir, dirs_exist_ok=True) - update_translations(translation_files=[self.file], translations_dir=tmpdir) - - cur_translations = self._read_translation_file(TRANSLATIONS_DIR, self.file) - new_translations = self._read_translation_file(tmpdir, self.file) - assert cur_translations == new_translations, \ - f"{self.file} ({self.name}) XML translation file out of date. Run selfdrive/ui/update_translations.py to update the translation files" - @pytest.mark.skip("Only test unfinished translations before going to release") def test_unfinished_translations(self): cur_translations = self._read_translation_file(TRANSLATIONS_DIR, self.file) diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index 101a3de437..a23dd1290c 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -421,22 +421,30 @@ 🔥 Firehose Mode 🔥 - - Enable Firehose Mode - - openpilot learns to drive by watching humans, like you, drive. -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode. +Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 0% - 5G {0%?} + Firehose Mode: ACTIVE + - Follow these steps to get your device ready:<br> 1. Bring your device inside and connect to a good USB-C adapter<br> 2. Connect to Wi-Fi<br> 3. Enable the toggle<br> 4. Leave it connected for at least 30 minutes<br><br>The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness.<br><br><b>FAQ</b><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><i>Do I need to be on Wi-Fi?</i> Yes.<br><i>Do I need to bring the device inside?</i> No, you can enable once you're parked, however your uploads will be limited by your car's battery.<br> + For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. + + + + <b>%1 %2</b> of your driving are in the training dataset so far. + + + + ACTIVE + + + + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network @@ -1451,22 +1459,22 @@ This may take up to a minute. TermsPage - - Terms & Conditions - الشروط والأحكام - Decline رفض - - Scroll to accept - قم بالتمرير للقبول - Agree أوافق + + Welcome to openpilot + + + + You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. + + TogglesPanel diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 2fcb2d4c84..9a578556bb 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -421,22 +421,30 @@ 🔥 Firehose Mode 🔥 - - Enable Firehose Mode - - openpilot learns to drive by watching humans, like you, drive. -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode. +Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 0% - 5G {0%?} + Firehose Mode: ACTIVE + - Follow these steps to get your device ready:<br> 1. Bring your device inside and connect to a good USB-C adapter<br> 2. Connect to Wi-Fi<br> 3. Enable the toggle<br> 4. Leave it connected for at least 30 minutes<br><br>The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness.<br><br><b>FAQ</b><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><i>Do I need to be on Wi-Fi?</i> Yes.<br><i>Do I need to bring the device inside?</i> No, you can enable once you're parked, however your uploads will be limited by your car's battery.<br> + For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. + + + + <b>%1 %2</b> of your driving are in the training dataset so far. + + + + ACTIVE + + + + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network @@ -1435,22 +1443,22 @@ This may take up to a minute. TermsPage - - Terms & Conditions - Benutzungsbedingungen - Decline Ablehnen - - Scroll to accept - Scrolle herunter um zu akzeptieren - Agree Zustimmen + + Welcome to openpilot + + + + You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. + + TogglesPanel diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts index e3086c7e01..429f5fbafc 100644 --- a/selfdrive/ui/translations/main_es.ts +++ b/selfdrive/ui/translations/main_es.ts @@ -421,22 +421,30 @@ 🔥 Firehose Mode 🔥 - - Enable Firehose Mode - - openpilot learns to drive by watching humans, like you, drive. -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode. +Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 0% - 5G {0%?} + Firehose Mode: ACTIVE + - Follow these steps to get your device ready:<br> 1. Bring your device inside and connect to a good USB-C adapter<br> 2. Connect to Wi-Fi<br> 3. Enable the toggle<br> 4. Leave it connected for at least 30 minutes<br><br>The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness.<br><br><b>FAQ</b><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><i>Do I need to be on Wi-Fi?</i> Yes.<br><i>Do I need to bring the device inside?</i> No, you can enable once you're parked, however your uploads will be limited by your car's battery.<br> + For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. + + + + <b>%1 %2</b> of your driving are in the training dataset so far. + + + + ACTIVE + + + + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network @@ -1435,22 +1443,22 @@ Esto puede tardar un minuto. TermsPage - - Terms & Conditions - Términos & Condiciones - Decline Rechazar - - Scroll to accept - Desliza para aceptar - Agree Aceptar + + Welcome to openpilot + + + + You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. + + TogglesPanel diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index f5fe64d8f4..a6f9006e01 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -421,22 +421,30 @@ 🔥 Firehose Mode 🔥 - - Enable Firehose Mode - - openpilot learns to drive by watching humans, like you, drive. -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode. +Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 0% - 5G {0%?} + Firehose Mode: ACTIVE + - Follow these steps to get your device ready:<br> 1. Bring your device inside and connect to a good USB-C adapter<br> 2. Connect to Wi-Fi<br> 3. Enable the toggle<br> 4. Leave it connected for at least 30 minutes<br><br>The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness.<br><br><b>FAQ</b><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><i>Do I need to be on Wi-Fi?</i> Yes.<br><i>Do I need to bring the device inside?</i> No, you can enable once you're parked, however your uploads will be limited by your car's battery.<br> + For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. + + + + <b>%1 %2</b> of your driving are in the training dataset so far. + + + + ACTIVE + + + + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network @@ -1435,22 +1443,22 @@ Cela peut prendre jusqu'à une minute. TermsPage - - Terms & Conditions - Termes & Conditions - Decline Refuser - - Scroll to accept - Faire défiler pour accepter - Agree Accepter + + Welcome to openpilot + + + + You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. + + TogglesPanel diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 84854192e6..b9e6c75386 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -419,24 +419,32 @@ FirehosePanel 🔥 Firehose Mode 🔥 - - - - Enable Firehose Mode - + 🔥 Firehoseモード 🔥 openpilot learns to drive by watching humans, like you, drive. -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode. +Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 0% - 5G {0%?} + Firehose Mode: ACTIVE + - Follow these steps to get your device ready:<br> 1. Bring your device inside and connect to a good USB-C adapter<br> 2. Connect to Wi-Fi<br> 3. Enable the toggle<br> 4. Leave it connected for at least 30 minutes<br><br>The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness.<br><br><b>FAQ</b><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><i>Do I need to be on Wi-Fi?</i> Yes.<br><i>Do I need to bring the device inside?</i> No, you can enable once you're parked, however your uploads will be limited by your car's battery.<br> + For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. + + + + <b>%1 %2</b> of your driving are in the training dataset so far. + + + + ACTIVE + + + + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network @@ -897,7 +905,7 @@ This may take up to a minute. Firehose - + データ学習 @@ -1431,22 +1439,22 @@ This may take up to a minute. TermsPage - - Terms & Conditions - 利用規約 - Decline 拒否 - - Scroll to accept - スクロールして同意 - Agree 同意 + + Welcome to openpilot + + + + You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. + + TogglesPanel @@ -1606,15 +1614,15 @@ This may take up to a minute. WiFiPromptWidget Open - + 開く Maximize your training data uploads to improve openpilot's driving models. - + openpilotの運転モデルを改善するために、大容量の学習データをアップロードして下さい。 <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - + <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehoseモード <span style='font-family: Noto Color Emoji;'>🔥</span> diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 875e97d903..9969bf8efe 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -121,27 +121,27 @@ Longitudinal Maneuver Mode - 롱컨 기동 모드 + 롱컨 제어 모드 openpilot Longitudinal Control (Alpha) - openpilot 가감속 제어 (알파) + openpilot 가감속 제어 (알파) WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - 경고: openpilot 가감속 제어 알파 기능으로 차량의 자동긴급제동(AEB)을 비활성화합니다. + 경고: openpilot 가감속 제어 알파 기능으로 차량의 자동긴급제동(AEB)을 비활성화합니다. On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - 이 차량은 openpilot 가감속 제어 대신 기본적으로 차량의 ACC로 가감속을 제어합니다. openpilot의 가감속 제어로 전환하려면 이 기능을 활성화하세요. openpilot 가감속 제어 알파를 활성화하는 경우 실험 모드 활성화를 권장합니다. + 이 차량은 openpilot 가감속 제어 대신 기본적으로 차량의 ACC로 가감속을 제어합니다. openpilot의 가감속 제어로 전환하려면 이 기능을 활성화하세요. openpilot 가감속 제어 알파를 활성화하는 경우 실험 모드 활성화를 권장합니다. Enable ADB - + ADB 사용 ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - + ADB (안드로이드 디버그 브릿지) USB 또는 네트워크를 통해 장치에 연결할 수 있습니다. 자세한 내용은 https://docs.comma.ai/how-to/connect-to-comma를 참조하세요. Hyundai: Enable Radar Tracks @@ -419,24 +419,32 @@ FirehosePanel 🔥 Firehose Mode 🔥 - - - - Enable Firehose Mode - + 🔥 파이어호스 모드 🔥 openpilot learns to drive by watching humans, like you, drive. -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode. +Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 0% - 5G {0%?} + Firehose Mode: ACTIVE + - Follow these steps to get your device ready:<br> 1. Bring your device inside and connect to a good USB-C adapter<br> 2. Connect to Wi-Fi<br> 3. Enable the toggle<br> 4. Leave it connected for at least 30 minutes<br><br>The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness.<br><br><b>FAQ</b><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><i>Do I need to be on Wi-Fi?</i> Yes.<br><i>Do I need to bring the device inside?</i> No, you can enable once you're parked, however your uploads will be limited by your car's battery.<br> + For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. + + + + <b>%1 %2</b> of your driving are in the training dataset so far. + + + + ACTIVE + + + + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network @@ -897,7 +905,7 @@ This may take up to a minute. Firehose - + 파이어호스 @@ -1431,22 +1439,22 @@ This may take up to a minute. TermsPage - - Terms & Conditions - 이용약관 - Decline 거절 - - Scroll to accept - 동의하려면 아래로 스크롤하세요 - Agree 동의 + + Welcome to openpilot + + + + You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. + + TogglesPanel @@ -1606,15 +1614,15 @@ This may take up to a minute. WiFiPromptWidget Open - + 열기 Maximize your training data uploads to improve openpilot's driving models. - + 훈련 데이터 업로드를 최대화하여 오픈파일럿의 주행 모델을 개선하세요. <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - + <span style='font-family: "Noto Color Emoji";'>🔥</span> 파이어호스 모드 <span style='font-family: Noto Color Emoji;'>🔥</span> diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index b9eaf7aeb4..3168f76d20 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -419,24 +419,32 @@ FirehosePanel 🔥 Firehose Mode 🔥 - - - - Enable Firehose Mode - + 🔥 Mode Firehose 🔥 openpilot learns to drive by watching humans, like you, drive. -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode. +Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 0% - 5G {0%?} + Firehose Mode: ACTIVE + - Follow these steps to get your device ready:<br> 1. Bring your device inside and connect to a good USB-C adapter<br> 2. Connect to Wi-Fi<br> 3. Enable the toggle<br> 4. Leave it connected for at least 30 minutes<br><br>The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness.<br><br><b>FAQ</b><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><i>Do I need to be on Wi-Fi?</i> Yes.<br><i>Do I need to bring the device inside?</i> No, you can enable once you're parked, however your uploads will be limited by your car's battery.<br> + For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. + + + + <b>%1 %2</b> of your driving are in the training dataset so far. + + + + ACTIVE + + + + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network @@ -901,7 +909,7 @@ Isso pode levar até um minuto. Firehose - + Firehose @@ -1435,22 +1443,22 @@ Isso pode levar até um minuto. TermsPage - - Terms & Conditions - Termos & Condições - Decline Declinar - - Scroll to accept - Role a tela para aceitar - Agree Concordo + + Welcome to openpilot + + + + You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. + + TogglesPanel @@ -1610,15 +1618,15 @@ Isso pode levar até um minuto. WiFiPromptWidget Open - + Abrir Maximize your training data uploads to improve openpilot's driving models. - + Maximize seus envios de dados de treinamento para melhorar os modelos de direção do openpilot. <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - + <span style='font-family: "Noto Color Emoji";'>🔥</span> Mode Firehose <span style='font-family: Noto Color Emoji;'>🔥</span> diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 0ee7de61f1..a3d3f347f1 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -421,22 +421,30 @@ 🔥 Firehose Mode 🔥 - - Enable Firehose Mode - - openpilot learns to drive by watching humans, like you, drive. -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode. +Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 0% - 5G {0%?} + Firehose Mode: ACTIVE + - Follow these steps to get your device ready:<br> 1. Bring your device inside and connect to a good USB-C adapter<br> 2. Connect to Wi-Fi<br> 3. Enable the toggle<br> 4. Leave it connected for at least 30 minutes<br><br>The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness.<br><br><b>FAQ</b><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><i>Do I need to be on Wi-Fi?</i> Yes.<br><i>Do I need to bring the device inside?</i> No, you can enable once you're parked, however your uploads will be limited by your car's battery.<br> + For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. + + + + <b>%1 %2</b> of your driving are in the training dataset so far. + + + + ACTIVE + + + + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network @@ -1431,22 +1439,22 @@ This may take up to a minute. TermsPage - - Terms & Conditions - ข้อตกลงและเงื่อนไข - Decline ปฏิเสธ - - Scroll to accept - เลื่อนเพื่อตอบรับข้อตกลง - Agree ยอมรับ + + Welcome to openpilot + + + + You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. + + TogglesPanel diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index 41c4304084..616bf71915 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -421,22 +421,30 @@ 🔥 Firehose Mode 🔥 - - Enable Firehose Mode - - openpilot learns to drive by watching humans, like you, drive. -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode. +Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 0% - 5G {0%?} + Firehose Mode: ACTIVE + - Follow these steps to get your device ready:<br> 1. Bring your device inside and connect to a good USB-C adapter<br> 2. Connect to Wi-Fi<br> 3. Enable the toggle<br> 4. Leave it connected for at least 30 minutes<br><br>The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness.<br><br><b>FAQ</b><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><i>Do I need to be on Wi-Fi?</i> Yes.<br><i>Do I need to bring the device inside?</i> No, you can enable once you're parked, however your uploads will be limited by your car's battery.<br> + For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. + + + + <b>%1 %2</b> of your driving are in the training dataset so far. + + + + ACTIVE + + + + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network @@ -1429,22 +1437,22 @@ This may take up to a minute. TermsPage - - Terms & Conditions - Şartlar ve Koşullar - Decline Reddet - - Scroll to accept - Kabul etmek için kaydırın - Agree Kabul et + + Welcome to openpilot + + + + You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. + + TogglesPanel diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 552d25f47b..e0facb278f 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -419,24 +419,32 @@ FirehosePanel 🔥 Firehose Mode 🔥 - - - - Enable Firehose Mode - + 🔥 训练数据上传模式 🔥 openpilot learns to drive by watching humans, like you, drive. -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode. +Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 0% - 5G {0%?} + Firehose Mode: ACTIVE + - Follow these steps to get your device ready:<br> 1. Bring your device inside and connect to a good USB-C adapter<br> 2. Connect to Wi-Fi<br> 3. Enable the toggle<br> 4. Leave it connected for at least 30 minutes<br><br>The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness.<br><br><b>FAQ</b><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><i>Do I need to be on Wi-Fi?</i> Yes.<br><i>Do I need to bring the device inside?</i> No, you can enable once you're parked, however your uploads will be limited by your car's battery.<br> + For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. + + + + <b>%1 %2</b> of your driving are in the training dataset so far. + + + + ACTIVE + + + + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network @@ -897,7 +905,7 @@ This may take up to a minute. Firehose - + 训练上传 @@ -1431,22 +1439,22 @@ This may take up to a minute. TermsPage - - Terms & Conditions - 条款和条件 - Decline 拒绝 - - Scroll to accept - 滑动以接受 - Agree 同意 + + Welcome to openpilot + + + + You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. + + TogglesPanel @@ -1606,15 +1614,15 @@ This may take up to a minute. WiFiPromptWidget Open - + 开启 Maximize your training data uploads to improve openpilot's driving models. - + 最大化您的训练数据上传,以改善 openpilot 的驾驶模型。 <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - + <span style='font-family: "Noto Color Emoji";'>🔥</span> 训练数据上传模式 <span style='font-family: Noto Color Emoji;'>🔥</span> diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 1b747b1b1c..8fecfe8bb8 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -419,24 +419,32 @@ FirehosePanel 🔥 Firehose Mode 🔥 - - - - Enable Firehose Mode - + 🔥 訓練資料上傳模式 🔥 openpilot learns to drive by watching humans, like you, drive. -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models with better Experimental Mode. +Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 0% - 5G {0%?} + Firehose Mode: ACTIVE + - Follow these steps to get your device ready:<br> 1. Bring your device inside and connect to a good USB-C adapter<br> 2. Connect to Wi-Fi<br> 3. Enable the toggle<br> 4. Leave it connected for at least 30 minutes<br><br>The toggle turns off once you restart your device. Repeat at least once a week for maximum effectiveness.<br><br><b>FAQ</b><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><i>Do I need to be on Wi-Fi?</i> Yes.<br><i>Do I need to bring the device inside?</i> No, you can enable once you're parked, however your uploads will be limited by your car's battery.<br> + For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. + + + + <b>%1 %2</b> of your driving are in the training dataset so far. + + + + ACTIVE + + + + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network @@ -897,7 +905,7 @@ This may take up to a minute. Firehose - + 訓練上傳 @@ -1431,22 +1439,22 @@ This may take up to a minute. TermsPage - - Terms & Conditions - 條款和條件 - Decline 拒絕 - - Scroll to accept - 滑動至頁尾接受條款 - Agree 接受 + + Welcome to openpilot + + + + You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. + + TogglesPanel @@ -1606,15 +1614,15 @@ This may take up to a minute. WiFiPromptWidget Open - + 開啟 Maximize your training data uploads to improve openpilot's driving models. - + 最大化您的訓練數據上傳,以改善 openpilot 的駕駛模型。 <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - + <span style='font-family: "Noto Color Emoji";'>🔥</span> 訓練資料上傳模式 <span style='font-family: Noto Color Emoji;'>🔥</span> diff --git a/system/athena/athenad.py b/system/athena/athenad.py index 3a4168a490..df728876d8 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -523,10 +523,6 @@ def getSshAuthorizedKeys() -> str: def getGithubUsername() -> str: return Params().get("GithubUsername", encoding='utf8') or '' -@dispatcher.add_method -def getFirehoseMode() -> bool: - return Params().get_bool("FirehoseMode") or False - @dispatcher.add_method def getSimInfo(): return HARDWARE.get_sim_info() diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index a08ebdecf9..f6c2681dea 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -15,7 +15,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera * const SensorInfo *sensor = cam->sensor.get(); // RAW frames from ISP - if (cam->output_type != ISP_IFE_PROCESSED) { + if (cam->cc.output_type != ISP_IFE_PROCESSED) { camera_bufs_raw = std::make_unique(frame_buf_count); const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index d969d092e9..f050070d63 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -55,7 +55,7 @@ public: float fl_pix = 0; std::unique_ptr pm; - CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config, config.stream_type == VISION_STREAM_DRIVER ? ISP_BPS_PROCESSED : ISP_IFE_PROCESSED) {}; + CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config) {}; ~CameraState(); void init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); @@ -268,7 +268,7 @@ void camerad_thread() { // *** per-cam init *** std::vector> cams; - for (const auto &config : {WIDE_ROAD_CAMERA_CONFIG, ROAD_CAMERA_CONFIG, DRIVER_CAMERA_CONFIG}) { + for (const auto &config : ALL_CAMERA_CONFIGS) { auto cam = std::make_unique(&m, config); cam->init(&v, device_id, ctx); cams.emplace_back(std::move(cam)); diff --git a/system/camerad/cameras/hw.h b/system/camerad/cameras/hw.h index bc40e01345..d299627ce9 100644 --- a/system/camerad/cameras/hw.h +++ b/system/camerad/cameras/hw.h @@ -6,6 +6,13 @@ #include "media/cam_isp_ife.h" + +typedef enum { + ISP_RAW_OUTPUT, // raw frame from sensor + ISP_IFE_PROCESSED, // fully processed image through the IFE + ISP_BPS_PROCESSED, // fully processed image through the BPS +} SpectraOutputType; + // For the comma 3/3X three camera platform struct CameraConfig { @@ -17,6 +24,7 @@ struct CameraConfig { bool enabled; uint32_t phy; bool vignetting_correction; + SpectraOutputType output_type; }; // NOTE: to be able to disable road and wide road, we still have to configure the sensor over i2c @@ -30,6 +38,7 @@ const CameraConfig WIDE_ROAD_CAMERA_CONFIG = { .enabled = !getenv("DISABLE_WIDE_ROAD"), .phy = CAM_ISP_IFE_IN_RES_PHY_0, .vignetting_correction = false, + .output_type = ISP_IFE_PROCESSED, }; const CameraConfig ROAD_CAMERA_CONFIG = { @@ -41,6 +50,7 @@ const CameraConfig ROAD_CAMERA_CONFIG = { .enabled = !getenv("DISABLE_ROAD"), .phy = CAM_ISP_IFE_IN_RES_PHY_1, .vignetting_correction = true, + .output_type = ISP_IFE_PROCESSED, }; const CameraConfig DRIVER_CAMERA_CONFIG = { @@ -52,6 +62,7 @@ const CameraConfig DRIVER_CAMERA_CONFIG = { .enabled = !getenv("DISABLE_DRIVER"), .phy = CAM_ISP_IFE_IN_RES_PHY_2, .vignetting_correction = false, + .output_type = ISP_BPS_PROCESSED, }; const CameraConfig ALL_CAMERA_CONFIGS[] = {WIDE_ROAD_CAMERA_CONFIG, ROAD_CAMERA_CONFIG, DRIVER_CAMERA_CONFIG}; diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 7e991755ce..713d5ba1cc 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -233,12 +233,11 @@ void SpectraMaster::init() { // *** SpectraCamera *** -SpectraCamera::SpectraCamera(SpectraMaster *master, const CameraConfig &config, SpectraOutputType out) +SpectraCamera::SpectraCamera(SpectraMaster *master, const CameraConfig &config) : m(master), enabled(config.enabled), - cc(config), - output_type(out) { - ife_buf_depth = (out == ISP_RAW_OUTPUT) ? 4 : VIPC_BUFFER_COUNT; + cc(config) { + ife_buf_depth = (cc.output_type == ISP_RAW_OUTPUT) ? 4 : VIPC_BUFFER_COUNT; assert(ife_buf_depth < MAX_IFE_BUFS); } @@ -249,13 +248,7 @@ SpectraCamera::~SpectraCamera() { } int SpectraCamera::clear_req_queue() { - struct cam_req_mgr_flush_info req_mgr_flush_request = {0}; - req_mgr_flush_request.session_hdl = session_handle; - req_mgr_flush_request.link_hdl = link_handle; - req_mgr_flush_request.flush_type = CAM_REQ_MGR_FLUSH_TYPE_ALL; - int ret = do_cam_control(m->video0_fd, CAM_REQ_MGR_FLUSH_REQ, &req_mgr_flush_request, sizeof(req_mgr_flush_request)); - LOGD("flushed all req: %d", ret); - + // for "non-realtime" BPS if (icp_dev_handle > 0) { struct cam_flush_dev_cmd cmd = { .session_handle = session_handle, @@ -264,8 +257,17 @@ int SpectraCamera::clear_req_queue() { }; int err = do_cam_control(m->icp_fd, CAM_FLUSH_REQ, &cmd, sizeof(cmd)); assert(err == 0); + LOGD("flushed bps: %d", err); } + // for "realtime" devices + struct cam_req_mgr_flush_info req_mgr_flush_request = {0}; + req_mgr_flush_request.session_hdl = session_handle; + req_mgr_flush_request.link_hdl = link_handle; + req_mgr_flush_request.flush_type = CAM_REQ_MGR_FLUSH_TYPE_ALL; + int ret = do_cam_control(m->video0_fd, CAM_REQ_MGR_FLUSH_REQ, &req_mgr_flush_request, sizeof(req_mgr_flush_request)); + LOGD("flushed all req: %d", ret); + for (int i = 0; i < MAX_IFE_BUFS; ++i) { destroySyncObjectAt(i); } @@ -287,7 +289,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c uv_height = VENUS_UV_SCANLINES(COLOR_FMT_NV12, sensor->frame_height); uv_offset = stride*y_height; yuv_size = uv_offset + stride*uv_height; - if (output_type != ISP_RAW_OUTPUT) { + if (cc.output_type != ISP_RAW_OUTPUT) { uv_offset = ALIGNED_SIZE(uv_offset, 0x1000); yuv_size = uv_offset + ALIGNED_SIZE(stride*uv_height, 0x1000); } @@ -296,21 +298,14 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c open = true; configISP(); - if (output_type == ISP_BPS_PROCESSED) configICP(); + if (cc.output_type == ISP_BPS_PROCESSED) configICP(); configCSIPHY(); linkDevices(); LOGD("camera init %d", cc.camera_num); buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); - enqueue_req_multi(1, ife_buf_depth); -} - -void SpectraCamera::enqueue_req_multi(uint64_t start, int n) { - for (uint64_t request_id = start; request_id < start + n; ++request_id) { - uint64_t idx = (request_id - 1) % ife_buf_depth; - enqueue_buffer(idx, request_id); - } + clearAndRequeue(1); } void SpectraCamera::sensors_start() { @@ -740,7 +735,7 @@ void SpectraCamera::config_ife(int idx, int request_id, bool init) { buf_desc[0].offset = ife_cmd.aligned_size()*idx; // stream of IFE register writes - bool is_raw = output_type != ISP_IFE_PROCESSED; + bool is_raw = cc.output_type != ISP_IFE_PROCESSED; if (!is_raw) { if (init) { buf_desc[0].length = build_initial_config((unsigned char*)ife_cmd.ptr + buf_desc[0].offset, cc, sensor.get(), patches); @@ -829,7 +824,7 @@ void SpectraCamera::config_ife(int idx, int request_id, bool init) { pkt->io_configs_offset = sizeof(struct cam_cmd_buf_desc)*pkt->num_cmd_buf; struct cam_buf_io_cfg *io_cfg = (struct cam_buf_io_cfg *)((char*)&pkt->payload + pkt->io_configs_offset); - if (output_type != ISP_IFE_PROCESSED) { + if (cc.output_type != ISP_IFE_PROCESSED) { io_cfg[0].mem_handle[0] = buf_handle_raw[idx]; io_cfg[0].planes[0] = (struct cam_plane_cfg){ .width = sensor->frame_width, @@ -900,60 +895,14 @@ void SpectraCamera::config_ife(int idx, int request_id, bool init) { assert(ret == 0); } -// Enqueue buffer for the given index and return true if the frame is ready -bool SpectraCamera::enqueue_buffer(int i, uint64_t request_id) { - int ret; - bool frame_ready = false; - - // Before queuing up a new frame, wait for the - // previous one in this slot (index) to come in. - if (sync_objs_ife[i]) { - // TODO: write a test to stress test w/ a low timeout and check camera frame ids match - - struct cam_sync_wait sync_wait = {0}; - - // *** Wait for IFE *** - // in RAW_OUTPUT mode, this is just the frame readout from the sensor - // in IFE_PROCESSED mode, this is both frame readout and image processing (~1ms) - sync_wait.sync_obj = sync_objs_ife[i]; - sync_wait.timeout_ms = 100; - if (stress_test("IFE sync")) { - sync_wait.timeout_ms = 1; - } - ret = do_sync_control(m->cam_sync_fd, CAM_SYNC_WAIT, &sync_wait, sizeof(sync_wait)); - if (ret != 0) { - LOGE("failed to wait for IFE sync: %d %d", ret, sync_wait.sync_obj); - } - - // *** Wait for BPS *** - if (ret == 0 && sync_objs_bps[i]) { - sync_wait.sync_obj = sync_objs_bps[i]; - sync_wait.timeout_ms = 50; // typically 7ms - if (stress_test("BPS sync")) { - sync_wait.timeout_ms = 1; - } - ret = do_sync_control(m->cam_sync_fd, CAM_SYNC_WAIT, &sync_wait, sizeof(sync_wait)); - if (ret != 0) { - LOGE("failed to wait for BPS sync: %d %d", ret, sync_wait.sync_obj); - } - } - - // all good, hand off frame - if (ret == 0) { - frame_ready = true; - } - - if (ret != 0) { - clear_req_queue(); - } - - destroySyncObjectAt(i); - } +void SpectraCamera::enqueue_frame(uint64_t request_id) { + int i = request_id % ife_buf_depth; + assert(sync_objs_ife[i] == 0); // create output fences struct cam_sync_info sync_create = {0}; strcpy(sync_create.name, "NodeOutputPortFence"); - ret = do_sync_control(m->cam_sync_fd, CAM_SYNC_CREATE, &sync_create, sizeof(sync_create)); + int ret = do_sync_control(m->cam_sync_fd, CAM_SYNC_CREATE, &sync_create, sizeof(sync_create)); if (ret != 0) { LOGE("failed to create fence: %d %d", ret, sync_create.sync_obj); } else { @@ -984,9 +933,7 @@ bool SpectraCamera::enqueue_buffer(int i, uint64_t request_id) { // submit request to IFE and BPS config_ife(i, request_id); - if (output_type == ISP_BPS_PROCESSED) config_bps(i, request_id); - - return frame_ready; + if (cc.output_type == ISP_BPS_PROCESSED) config_bps(i, request_id); } void SpectraCamera::destroySyncObjectAt(int index) { @@ -1019,7 +966,7 @@ void SpectraCamera::camera_map_bufs() { mem_mgr_map_cmd.mmu_hdls[1] = m->icp_device_iommu; } - if (output_type != ISP_IFE_PROCESSED) { + if (cc.output_type != ISP_IFE_PROCESSED) { // RAW bayer images mem_mgr_map_cmd.fd = buf.camera_bufs_raw[i].fd; ret = do_cam_control(m->video0_fd, CAM_REQ_MGR_MAP_BUF, &mem_mgr_map_cmd, sizeof(mem_mgr_map_cmd)); @@ -1028,7 +975,7 @@ void SpectraCamera::camera_map_bufs() { buf_handle_raw[i] = mem_mgr_map_cmd.out.buf_handle; } - if (output_type != ISP_RAW_OUTPUT) { + if (cc.output_type != ISP_RAW_OUTPUT) { // final processed images VisionBuf *vb = buf.vipc_server->get_buffer(buf.stream_type, i); mem_mgr_map_cmd.fd = vb->fd; @@ -1125,7 +1072,7 @@ void SpectraCamera::configISP() { }, }; - if (output_type != ISP_IFE_PROCESSED) { + if (cc.output_type != ISP_IFE_PROCESSED) { in_port_info.line_start = 0; in_port_info.line_stop = sensor->frame_height + sensor->extra_height - 1; in_port_info.height = sensor->frame_height + sensor->extra_height; @@ -1148,7 +1095,7 @@ void SpectraCamera::configISP() { // allocate IFE memory, then configure it ife_cmd.init(m, 67984, 0x20, false, m->device_iommu, m->cdm_iommu, ife_buf_depth); - if (output_type == ISP_IFE_PROCESSED) { + if (cc.output_type == ISP_IFE_PROCESSED) { assert(sensor->gamma_lut_rgb.size() == 64); ife_gamma_lut.init(m, sensor->gamma_lut_rgb.size()*sizeof(uint32_t), 0x20, false, m->device_iommu, m->cdm_iommu, 3); // 3 for RGB for (int i = 0; i < 3; i++) { @@ -1300,7 +1247,7 @@ void SpectraCamera::linkDevices() { ret = device_control(m->isp_fd, CAM_START_DEV, session_handle, isp_dev_handle); LOGD("start isp: %d", ret); assert(ret == 0); - if (output_type == ISP_BPS_PROCESSED) { + if (cc.output_type == ISP_BPS_PROCESSED) { ret = device_control(m->icp_fd, CAM_START_DEV, session_handle, icp_dev_handle); LOGD("start icp: %d", ret); assert(ret == 0); @@ -1315,7 +1262,7 @@ void SpectraCamera::camera_close() { // LOGD("stop sensor: %d", ret); int ret = device_control(m->isp_fd, CAM_STOP_DEV, session_handle, isp_dev_handle); LOGD("stop isp: %d", ret); - if (output_type == ISP_BPS_PROCESSED) { + if (cc.output_type == ISP_BPS_PROCESSED) { ret = device_control(m->icp_fd, CAM_STOP_DEV, session_handle, icp_dev_handle); LOGD("stop icp: %d", ret); } @@ -1344,7 +1291,7 @@ void SpectraCamera::camera_close() { LOGD("-- Release devices"); ret = device_control(m->isp_fd, CAM_RELEASE_DEV, session_handle, isp_dev_handle); LOGD("release isp: %d", ret); - if (output_type == ISP_BPS_PROCESSED) { + if (cc.output_type == ISP_BPS_PROCESSED) { ret = device_control(m->icp_fd, CAM_RELEASE_DEV, session_handle, icp_dev_handle); LOGD("release icp: %d", ret); } @@ -1371,90 +1318,129 @@ void SpectraCamera::camera_close() { LOGD("destroyed session %d: %d", cc.camera_num, ret); } -// Processes camera events and returns true if the frame is ready for further processing bool SpectraCamera::handle_camera_event(const cam_req_mgr_message *event_data) { - if (stress_test("skipping handling camera event")) { - LOGW("skipping event"); + /* + Handles camera SOF event. Returns true if the frame is valid for publishing. + */ + + uint64_t request_id = event_data->u.frame_msg.request_id; // ID from the camera request manager + uint64_t frame_id_raw = event_data->u.frame_msg.frame_id; // raw as opposed to our re-indexed frame ID + uint64_t timestamp = event_data->u.frame_msg.timestamp; // timestamped in the kernel's SOF IRQ callback + //LOGD("handle cam %d ts %lu req id %lu frame id %lu", cc.camera_num, timestamp, request_id, frame_id_raw); + + if (stress_test("skipping SOF event")) return false; + + if (!validateEvent(request_id, frame_id_raw)) { return false; } - if (!enabled) return false; + // Update tracking variables + if (request_id == request_id_last + 1) { + skip_expected = false; + } + frame_id_raw_last = frame_id_raw; + request_id_last = request_id; - // ID from the qcom camera request manager - uint64_t request_id = event_data->u.frame_msg.request_id; - - // raw as opposed to our re-indexed frame ID - uint64_t frame_id_raw = event_data->u.frame_msg.frame_id; - - if (request_id != 0) { // next ready - // check for skipped_last frames - if (frame_id_raw > frame_id_raw_last + 1 && !skipped_last) { - LOGE("camera %d realign", cc.camera_num); - clear_req_queue(); - enqueue_req_multi(request_id + 1, ife_buf_depth - 1); - skipped_last = true; - } else if (frame_id_raw == frame_id_raw_last + 1) { - skipped_last = false; - } - - // check for dropped requests - if (request_id > request_id_last + 1) { - LOGE("camera %d dropped requests %ld %ld", cc.camera_num, request_id, request_id_last); - enqueue_req_multi(request_id_last + 1 + ife_buf_depth, request_id - (request_id_last + 1)); - } - - // metas - frame_id_raw_last = frame_id_raw; - request_id_last = request_id; - - int buf_idx = (request_id - 1) % ife_buf_depth; - uint64_t timestamp = event_data->u.frame_msg.timestamp; // this is timestamped in the kernel's SOF IRQ callback - if (syncFirstFrame(cc.camera_num, request_id, frame_id_raw, timestamp)) { - // wait for this frame's EOF, then queue up the next one - if (enqueue_buffer(buf_idx, request_id + ife_buf_depth)) { - // Frame is ready - - // in IFE_PROCESSED mode, we can't know the true EOF, so recover it with sensor readout time - uint64_t timestamp_eof = timestamp + sensor->readout_time_ns; - - // Update buffer and frame data - buf.cur_buf_idx = buf_idx; - buf.cur_frame_data = { - .frame_id = (uint32_t)(frame_id_raw - camera_sync_data[cc.camera_num].frame_id_offset), - .request_id = (uint32_t)request_id, - .timestamp_sof = timestamp, - .timestamp_eof = timestamp_eof, - .processing_time = float((nanos_since_boot() - timestamp_eof) * 1e-9) - }; - return true; - } - // LOGW("camerad %d synced req %d fid %d, publishing ts %.2f cereal_frame_id %d", cc.camera_num, (int)request_id, (int)frame_id_raw, (double)(timestamp)*1e-6, meta_data.frame_id); - } else { - // Frames not yet synced - enqueue_req_multi(request_id + ife_buf_depth, 1); - // LOGW("camerad %d not synced req %d fid %d", cc.camera_num, (int)request_id, (int)frame_id_raw); - } - } else { // not ready - if (frame_id_raw > frame_id_raw_last + 10) { - LOGE("camera %d reset after half second of no response", cc.camera_num); - clear_req_queue(); - enqueue_req_multi(request_id_last + 1, ife_buf_depth); - frame_id_raw_last = frame_id_raw; - skipped_last = true; - } + // Wait until frame's fully read out and processed + if (!waitForFrameReady(request_id)) { + // Reset queue on sync failure to prevent frame tearing + LOGE("camera %d sync failure %ld %ld ", cc.camera_num, request_id, frame_id_raw); + clearAndRequeue(request_id + 1); + return false; } - return false; + int buf_idx = request_id % ife_buf_depth; + bool ret = processFrame(buf_idx, request_id, frame_id_raw, timestamp); + destroySyncObjectAt(buf_idx); + enqueue_frame(request_id + ife_buf_depth); // request next frame for this slot + return ret; +} + +bool SpectraCamera::validateEvent(uint64_t request_id, uint64_t frame_id_raw) { + // check if the request ID is even valid. this happens after queued + // requests are cleared. unclear if it happens any other time. + if (request_id == 0) { + if (invalid_request_count++ > ife_buf_depth+2) { + LOGE("camera %d reset after half second of invalid requests", cc.camera_num); + clearAndRequeue(request_id_last + 1); + invalid_request_count = 0; + } + return false; + } + invalid_request_count = 0; + + // check for skips in frame_id or request_id + if (!skip_expected) { + if (frame_id_raw != frame_id_raw_last + 1) { + LOGE("camera %d frame ID skipped, %lu -> %lu", cc.camera_num, frame_id_raw_last, frame_id_raw); + clearAndRequeue(request_id + 1); + return false; + } + + if (request_id != request_id_last + 1) { + LOGE("camera %d requests skipped %ld -> %ld", cc.camera_num, request_id_last, request_id); + clearAndRequeue(request_id_last + 1); + return false; + } + } + return true; +} + +void SpectraCamera::clearAndRequeue(uint64_t from_request_id) { + // clear everything, then queue up a fresh set of frames + LOGW("clearing and requeuing camera %d from %lu", cc.camera_num, from_request_id); + clear_req_queue(); + for (uint64_t id = from_request_id; id < from_request_id + ife_buf_depth; ++id) { + enqueue_frame(id); + } + skip_expected = true; +} + +bool SpectraCamera::waitForFrameReady(uint64_t request_id) { + int buf_idx = request_id % ife_buf_depth; + assert(sync_objs_ife[buf_idx]); + + auto waitForSync = [&](uint32_t sync_obj, int timeout_ms, const char *sync_type) { + struct cam_sync_wait sync_wait = {}; + sync_wait.sync_obj = sync_obj; + sync_wait.timeout_ms = stress_test(sync_type) ? 1 : timeout_ms; + return do_sync_control(m->cam_sync_fd, CAM_SYNC_WAIT, &sync_wait, sizeof(sync_wait)) == 0; + }; + + // wait for frame from IFE + // - in RAW_OUTPUT mode, this time is just the frame readout from the sensor + // - in IFE_PROCESSED mode, this time also includes image processing (~1ms) + bool success = waitForSync(sync_objs_ife[buf_idx], 100, "IFE sync"); + if (success && sync_objs_bps[buf_idx]) { + // BPS is typically 7ms + success = waitForSync(sync_objs_bps[buf_idx], 50, "BPS sync"); + } + return success; +} + +bool SpectraCamera::processFrame(int buf_idx, uint64_t request_id, uint64_t frame_id_raw, uint64_t timestamp) { + if (!syncFirstFrame(cc.camera_num, request_id, frame_id_raw, timestamp)) { + return false; + } + + // in IFE_PROCESSED mode, we can't know the true EOF, so recover it with sensor readout time + uint64_t timestamp_eof = timestamp + sensor->readout_time_ns; + + // Update buffer and frame data + buf.cur_buf_idx = buf_idx; + buf.cur_frame_data = { + .frame_id = (uint32_t)(frame_id_raw - camera_sync_data[cc.camera_num].frame_id_offset), + .request_id = (uint32_t)request_id, + .timestamp_sof = timestamp, + .timestamp_eof = timestamp_eof, + .processing_time = float((nanos_since_boot() - timestamp_eof) * 1e-9) + }; + return true; } bool SpectraCamera::syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp) { if (first_frame_synced) return true; - // OX and OS cameras require a few frames for the FSIN to sync up - if (request_id < 3) { - return false; - } - // Store the frame data for this camera camera_sync_data[camera_id] = SyncData{timestamp, raw_id + 1}; @@ -1468,7 +1454,7 @@ bool SpectraCamera::syncFirstFrame(int camera_id, uint64_t request_id, uint64_t for (const auto &[_, sync_data] : camera_sync_data) { uint64_t diff = std::max(timestamp, sync_data.timestamp) - std::min(timestamp, sync_data.timestamp); - if (diff > 0.5*1e6) { // within 0.5ms + if (diff > 0.2*1e6) { // milliseconds all_cams_synced = false; } } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index d9abf884c1..21f1970326 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -22,7 +22,6 @@ const int MIPI_SETTLE_CNT = 33; // Calculated by camera_freqs.py // For use with the Titan 170 ISP in the SDM845 // https://github.com/commaai/agnos-kernel-sdm845 - // CSLDeviceType/CSLPacketOpcodesIFE from camx // cam_packet_header.op_code = (device << 24) | (opcode); #define CSLDeviceTypeImageSensor (0x01 << 24) @@ -31,12 +30,6 @@ const int MIPI_SETTLE_CNT = 33; // Calculated by camera_freqs.py #define OpcodesIFEInitialConfig 0x0 #define OpcodesIFEUpdate 0x1 -typedef enum { - ISP_RAW_OUTPUT, // raw frame from sensor - ISP_IFE_PROCESSED, // fully processed image through the IFE - ISP_BPS_PROCESSED, // fully processed image through the BPS -} SpectraOutputType; - std::optional device_acquire(int fd, int32_t session_handle, void *data, uint32_t num_resources=1); int device_config(int fd, int32_t session_handle, int32_t dev_handle, uint64_t packet_handle); int device_control(int fd, int op_code, int session_handle, int dev_handle); @@ -117,7 +110,7 @@ public: class SpectraCamera { public: - SpectraCamera(SpectraMaster *master, const CameraConfig &config, SpectraOutputType out); + SpectraCamera(SpectraMaster *master, const CameraConfig &config); ~SpectraCamera(); void camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); @@ -128,8 +121,7 @@ public: void config_ife(int idx, int request_id, bool init=false); int clear_req_queue(); - bool enqueue_buffer(int i, uint64_t request_id); - void enqueue_req_multi(uint64_t start, int n); + void enqueue_frame(uint64_t request_id); int sensors_init(); void sensors_start(); @@ -190,15 +182,17 @@ public: int sync_objs_bps[MAX_IFE_BUFS] = {}; uint64_t request_id_last = 0; uint64_t frame_id_raw_last = 0; - int64_t frame_id_offset = 0; - bool skipped_last = true; - - SpectraOutputType output_type; + int invalid_request_count = 0; + bool skip_expected = true; CameraBuf buf; SpectraMaster *m; private: + void clearAndRequeue(uint64_t from_request_id); + bool validateEvent(uint64_t request_id, uint64_t frame_id_raw); + bool waitForFrameReady(uint64_t request_id); + bool processFrame(int buf_idx, uint64_t request_id, uint64_t frame_id_raw, uint64_t timestamp); static bool syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp); struct SyncData { uint64_t timestamp; @@ -208,11 +202,11 @@ private: inline static bool first_frame_synced = false; // a mode for stressing edge cases: realignment, sync failures, etc. - inline bool stress_test(const char* log, float prob=0.01) { - static bool enable = getenv("SPECTRA_STRESS_TEST") != nullptr; - bool triggered = enable && ((static_cast(rand()) / RAND_MAX) < prob); + inline bool stress_test(const char* log) { + static double prob = std::stod(util::getenv("SPECTRA_ERROR_PROB", "-1"));; + bool triggered = (prob > 0) && ((static_cast(rand()) / RAND_MAX) < prob); if (triggered) { - LOGE("stress test: %s", log); + LOGE("stress test (cam %d): %s", cc.camera_num, log); } return triggered; } diff --git a/system/camerad/sensors/os04c10.cc b/system/camerad/sensors/os04c10.cc index b3a7e39bfb..d008e1d07b 100644 --- a/system/camerad/sensors/os04c10.cc +++ b/system/camerad/sensors/os04c10.cc @@ -43,6 +43,9 @@ OS04C10::OS04C10() { frame_data_type = 0x2c; mclk_frequency = 24000000; // Hz + // TODO: this was set from logs. actually calculate it out + readout_time_ns = 11000000; + ev_scale = 150.0; dc_gain_factor = 1; dc_gain_min_weight = 1; // always on is fine diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index e88a7bf4bf..ab76985972 100644 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -64,7 +64,35 @@ class TestCamerad: laggy_frames = {k: v for k, v in diffs.items() if v > 1.1} assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" - @pytest.mark.skip("TODO: enable this") - def test_stress_test(self, logs): - os.environ['SPECTRA_STRESS_TEST'] = '1' - run_and_log(["camerad", ], CAMERAS, 5) + def test_sanity_checks(self, logs): + self._sanity_checks(logs) + + def _sanity_checks(self, ts): + for c in CAMERAS: + assert c in ts + assert len(ts[c]['t']) > 20 + + # not a valid request id + assert 0 not in ts[c]['requestId'] + + # should monotonically increase + assert np.all(np.diff(ts[c]['frameId']) >= 1) + assert np.all(np.diff(ts[c]['requestId']) >= 1) + + # EOF > SOF + assert np.all((ts[c]['timestampEof'] - ts[c]['timestampSof']) > 0) + + # logMonoTime > SOF + assert np.all((ts[c]['t'] - ts[c]['timestampSof']/1e9) > 1e-7) + assert np.all((ts[c]['t'] - ts[c]['timestampEof']/1e9) > 1e-7) + + def test_stress_test(self): + os.environ['SPECTRA_ERROR_PROB'] = '0.008' + logs = run_and_log(["camerad", ], CAMERAS, 10) + ts = msgs_to_time_series(logs) + + # we should see some jumps from introduced errors + assert np.max([ np.max(np.diff(ts[c]['frameId'])) for c in CAMERAS ]) > 1 + assert np.max([ np.max(np.diff(ts[c]['requestId'])) for c in CAMERAS ]) > 1 + + self._sanity_checks(ts) diff --git a/system/hardware/base.h b/system/hardware/base.h index 732f0f99e0..baf0f3c3da 100644 --- a/system/hardware/base.h +++ b/system/hardware/base.h @@ -34,8 +34,6 @@ public: static bool get_ssh_enabled() { return false; } static void set_ssh_enabled(bool enabled) {} - static void config_cpu_rendering(bool offscreen); - static bool PC() { return false; } static bool TICI() { return false; } static bool AGNOS() { return false; } diff --git a/system/hardware/pc/hardware.h b/system/hardware/pc/hardware.h index 5dea184ca6..978dd771c8 100644 --- a/system/hardware/pc/hardware.h +++ b/system/hardware/pc/hardware.h @@ -12,12 +12,4 @@ public: static bool PC() { return true; } static bool TICI() { return util::getenv("TICI", 0) == 1; } static bool AGNOS() { return util::getenv("TICI", 0) == 1; } - - static void config_cpu_rendering(bool offscreen) { - if (offscreen) { - setenv("QT_QPA_PLATFORM", "offscreen", 1); - } - setenv("__GLX_VENDOR_LIBRARY_NAME", "mesa", 1); - setenv("LP_NUM_THREADS", "0", 1); // disable threading so we stay on our assigned CPU - } }; diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 7844dcbf68..d42c8151ab 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-c7cba1ce64bf85384241fa063971855c5ffcb4d90a0d07bed4733d46b94d4170.img.xz", - "hash": "c7cba1ce64bf85384241fa063971855c5ffcb4d90a0d07bed4733d46b94d4170", - "hash_raw": "c7cba1ce64bf85384241fa063971855c5ffcb4d90a0d07bed4733d46b94d4170", + "url": "https://commadist.azureedge.net/agnosupdate/boot-bca7573652def58a0afc40bbdd550d63dc08ed2e925ace69032aef84bb9dc4ba.img.xz", + "hash": "bca7573652def58a0afc40bbdd550d63dc08ed2e925ace69032aef84bb9dc4ba", + "hash_raw": "bca7573652def58a0afc40bbdd550d63dc08ed2e925ace69032aef84bb9dc4ba", "size": 18475008, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "6a9a71bf01f2013f35bda9594cefe3cb4a3835402a6cb0e95306fe4decf261c5" + "ondevice_hash": "8907415564e8a242548e871b534dcd53240fe4e4517700c6c85b5637e365f0b0" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-cd03486f6c7333dee21f59af771f9992ea90a9a04271c1506a663d658f391625.img.xz", - "hash": "990ff7005a5bee8e759c96ddba23f1258f043fb038cf74083bf7d2d9c9a29e39", - "hash_raw": "cd03486f6c7333dee21f59af771f9992ea90a9a04271c1506a663d658f391625", + "url": "https://commadist.azureedge.net/agnosupdate/system-5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9.img.xz", + "hash": "e4872f4132111b7b28586d978dd01bb48ffa031e103d029ebede7613c1bc2aa6", + "hash_raw": "5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9", "size": 4404019200, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "d922fffe1b5f02898465a2d6625294abb70d22643ebf2d6a94f5d7512291d1a4", + "ondevice_hash": "4e5e680b4ac387ddc974b32dd3d5ec1d76282511eab974866b3b72399034985e", "alt": { - "hash": "cd03486f6c7333dee21f59af771f9992ea90a9a04271c1506a663d658f391625", - "url": "https://commadist.azureedge.net/agnosupdate/system-cd03486f6c7333dee21f59af771f9992ea90a9a04271c1506a663d658f391625.img", + "hash": "5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9", + "url": "https://commadist.azureedge.net/agnosupdate/system-5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9.img", "size": 4404019200 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index b21225e4ac..e90e8c2ec4 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -339,62 +339,62 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-c7cba1ce64bf85384241fa063971855c5ffcb4d90a0d07bed4733d46b94d4170.img.xz", - "hash": "c7cba1ce64bf85384241fa063971855c5ffcb4d90a0d07bed4733d46b94d4170", - "hash_raw": "c7cba1ce64bf85384241fa063971855c5ffcb4d90a0d07bed4733d46b94d4170", + "url": "https://commadist.azureedge.net/agnosupdate/boot-bca7573652def58a0afc40bbdd550d63dc08ed2e925ace69032aef84bb9dc4ba.img.xz", + "hash": "bca7573652def58a0afc40bbdd550d63dc08ed2e925ace69032aef84bb9dc4ba", + "hash_raw": "bca7573652def58a0afc40bbdd550d63dc08ed2e925ace69032aef84bb9dc4ba", "size": 18475008, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "6a9a71bf01f2013f35bda9594cefe3cb4a3835402a6cb0e95306fe4decf261c5" + "ondevice_hash": "8907415564e8a242548e871b534dcd53240fe4e4517700c6c85b5637e365f0b0" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-cd03486f6c7333dee21f59af771f9992ea90a9a04271c1506a663d658f391625.img.xz", - "hash": "990ff7005a5bee8e759c96ddba23f1258f043fb038cf74083bf7d2d9c9a29e39", - "hash_raw": "cd03486f6c7333dee21f59af771f9992ea90a9a04271c1506a663d658f391625", + "url": "https://commadist.azureedge.net/agnosupdate/system-5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9.img.xz", + "hash": "e4872f4132111b7b28586d978dd01bb48ffa031e103d029ebede7613c1bc2aa6", + "hash_raw": "5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9", "size": 4404019200, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "d922fffe1b5f02898465a2d6625294abb70d22643ebf2d6a94f5d7512291d1a4", + "ondevice_hash": "4e5e680b4ac387ddc974b32dd3d5ec1d76282511eab974866b3b72399034985e", "alt": { - "hash": "cd03486f6c7333dee21f59af771f9992ea90a9a04271c1506a663d658f391625", - "url": "https://commadist.azureedge.net/agnosupdate/system-cd03486f6c7333dee21f59af771f9992ea90a9a04271c1506a663d658f391625.img", + "hash": "5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9", + "url": "https://commadist.azureedge.net/agnosupdate/system-5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9.img", "size": 4404019200 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-554a22697b356cb150c2c803b4cb1de79403849e9be451c844d218d38b5bc236.img.xz", - "hash": "4f0a862e3aff4980e697ece63afaef6f9869d013ac2ca4c45193e41089ee4f5c", - "hash_raw": "554a22697b356cb150c2c803b4cb1de79403849e9be451c844d218d38b5bc236", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-16c037fa42ee99bc6ec92909efc8a8075a0e8a0232a7d90e39e7d40a7bd0ee8e.img.xz", + "hash": "c6fb215f2b297f7ff5b8f133bc5d687772b37f2fee42a44aa730e37a84a14e52", + "hash_raw": "16c037fa42ee99bc6ec92909efc8a8075a0e8a0232a7d90e39e7d40a7bd0ee8e", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "5d780092d51c569e6b8a28ab73f4029bcc3517698f496af5801f863c9865060f" + "ondevice_hash": "7199262f209abbb07be5eece505ac4d4c7ba8957f2d9ff7b1ac1ef2063461665" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-6045b9a3f1ae6e0ee09b95da039b697ab8d8447cdd0796fa31afa9c7d324ee80.img.xz", - "hash": "ba49fef48573e9befdfb6334181753855cb83d3e1d8c6d4d83d510ea0141ec3f", - "hash_raw": "6045b9a3f1ae6e0ee09b95da039b697ab8d8447cdd0796fa31afa9c7d324ee80", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-62c2c41470282b581ec1bbbe0375fb3b6c66df2f4bc3dc6c6fdf796f1797f136.img.xz", + "hash": "d66f894436fa11d4ff00f8a84e54d9e23a6492b0087f69bb958d2ab0bdc6dfba", + "hash_raw": "62c2c41470282b581ec1bbbe0375fb3b6c66df2f4bc3dc6c6fdf796f1797f136", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "19b63b184063c25ff2fc9f1c8d1c919c140c3e1295ebac6cb66e64c5cb609abe" + "ondevice_hash": "f1d3685618f6d1bde24ce6109284c5d30ece2f4fd015be67e8b52ef7e06067a4" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-498358c1e5347dc3f8c369523bd77b94ef73d6c6729b40f376209d0d32b356fe.img.xz", - "hash": "828d0911713af02de7fae4c583a2274783e344939f296c7c016866a8cf2cb63f", - "hash_raw": "498358c1e5347dc3f8c369523bd77b94ef73d6c6729b40f376209d0d32b356fe", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3e71d8804c90a6dff5048edb976149f9ea177efa139dea47cb585cef76b26f6e.img.xz", + "hash": "1b201ecbd0e1573777811bf18fa90cb080bfbccb34a6dcfd39b412632e7ca699", + "hash_raw": "3e71d8804c90a6dff5048edb976149f9ea177efa139dea47cb585cef76b26f6e", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "4beb60ffceff88b05daa014db50cb2f4e9acec3ed7ae1b89a8d3c84cfc3c1c92" + "ondevice_hash": "47c28e63209556442cd9d2fd06a6c0e7fcf35c8a4e4fbcc550b9c089429ad0e0" } ] \ No newline at end of file diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index 7d73f86ee9..179ef54a9b 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -109,11 +109,4 @@ public: static bool get_ssh_enabled() { return Params().getBool("SshEnabled"); } static void set_ssh_enabled(bool enabled) { Params().putBool("SshEnabled", enabled); } - - static void config_cpu_rendering(bool offscreen) { - if (offscreen) { - setenv("QT_QPA_PLATFORM", "eglfs", 1); // offscreen doesn't work with EGL/GLES - } - setenv("LP_NUM_THREADS", "0", 1); // disable threading so we stay on our assigned CPU - } }; diff --git a/system/loggerd/deleter.py b/system/loggerd/deleter.py index 2a2bd5fe11..eb8fd35f21 100755 --- a/system/loggerd/deleter.py +++ b/system/loggerd/deleter.py @@ -22,9 +22,9 @@ def has_preserve_xattr(d: str) -> bool: return getxattr(os.path.join(Paths.log_root(), d), PRESERVE_ATTR_NAME) == PRESERVE_ATTR_VALUE -def get_preserved_segments(dirs_by_creation: list[str]) -> list[str]: +def get_preserved_segments(dirs_by_creation: list[str]) -> set[str]: # skip deleting most recent N preserved segments (and their prior segment) - preserved = [] + preserved = set() for n, d in enumerate(filter(has_preserve_xattr, reversed(dirs_by_creation))): if n == PRESERVE_COUNT: break @@ -40,7 +40,7 @@ def get_preserved_segments(dirs_by_creation: list[str]) -> list[str]: # preserve segment and two prior for _seg_num in range(max(0, seg_num - 2), seg_num + 1): - preserved.append(f"{date_str}--{_seg_num}") + preserved.add(f"{date_str}--{_seg_num}") return preserved diff --git a/system/loggerd/logger.cc b/system/loggerd/logger.cc index 1461ceaca6..f07aee1596 100644 --- a/system/loggerd/logger.cc +++ b/system/loggerd/logger.cc @@ -50,6 +50,10 @@ kj::Array logger_build_init_data() { init.setPassive(false); init.setDongleId(params_map["DongleId"]); + // for prebuilt branches + init.setGitSrcCommit(util::read_file("../../git_src_commit")); + init.setGitSrcCommitDate(util::read_file("../../git_src_commit_date")); + auto lparams = init.initParams().initEntries(params_map.size()); int j = 0; for (auto& [key, value] : params_map) { diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index daf0cc112e..ea0178fe80 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -64,6 +64,58 @@ struct RemoteEncoder { bool seen_first_packet = false; }; +size_t write_encode_data(LoggerdState *s, cereal::Event::Reader event, RemoteEncoder &re, const EncoderInfo &encoder_info) { + auto edata = (event.*(encoder_info.get_encode_data_func))(); + auto idx = edata.getIdx(); + auto flags = idx.getFlags(); + + // if we aren't recording yet, try to start, since we are in the correct segment + if (!re.recording) { + if (flags & V4L2_BUF_FLAG_KEYFRAME) { + // only create on iframe + if (re.dropped_frames) { + // this should only happen for the first segment, maybe + LOGW("%s: dropped %d non iframe packets before init", encoder_info.publish_name, re.dropped_frames); + re.dropped_frames = 0; + } + // if we aren't actually recording, don't create the writer + if (encoder_info.record) { + assert(encoder_info.filename != NULL); + re.writer.reset(new VideoWriter(s->logger.segmentPath().c_str(), + encoder_info.filename, idx.getType() != cereal::EncodeIndex::Type::FULL_H_E_V_C, + edata.getWidth(), edata.getHeight(), encoder_info.fps, idx.getType())); + // write the header + auto header = edata.getHeader(); + re.writer->write((uint8_t *)header.begin(), header.size(), idx.getTimestampEof() / 1000, true, false); + } + re.recording = true; + } else { + // this is a sad case when we aren't recording, but don't have an iframe + // nothing we can do but drop the frame + ++re.dropped_frames; + return 0; + } + } + + // we have to be recording if we are here + assert(re.recording); + + // if we are actually writing the video file, do so + if (re.writer) { + auto data = edata.getData(); + re.writer->write((uint8_t *)data.begin(), data.size(), idx.getTimestampEof() / 1000, false, flags & V4L2_BUF_FLAG_KEYFRAME); + } + + // put it in log stream as the idx packet + MessageBuilder bmsg; + auto evt = bmsg.initEvent(event.getValid()); + evt.setLogMonoTime(event.getLogMonoTime()); + (evt.*(encoder_info.set_encode_idx_func))(idx); + auto new_msg = bmsg.toBytes(); + s->logger.write((uint8_t *)new_msg.begin(), new_msg.size(), true); // always in qlog? + return new_msg.size(); +} + int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct RemoteEncoder &re, const EncoderInfo &encoder_info) { int bytes_count = 0; @@ -72,7 +124,6 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct auto event = cmsg.getRoot(); auto edata = (event.*(encoder_info.get_encode_data_func))(); auto idx = edata.getIdx(); - auto flags = idx.getFlags(); // encoderd can have started long before loggerd if (!re.seen_first_packet) { @@ -95,61 +146,15 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct re.marked_ready_to_rotate = false; // we are in this segment now, process any queued messages before this one if (!re.q.empty()) { - for (auto &qmsg : re.q) { - bytes_count += handle_encoder_msg(s, qmsg, name, re, encoder_info); + for (auto qmsg : re.q) { + capnp::FlatArrayMessageReader reader({(capnp::word *)qmsg->getData(), qmsg->getSize() / sizeof(capnp::word)}); + bytes_count += write_encode_data(s, reader.getRoot(), re, encoder_info); + delete qmsg; } re.q.clear(); } } - - // if we aren't recording yet, try to start, since we are in the correct segment - if (!re.recording) { - if (flags & V4L2_BUF_FLAG_KEYFRAME) { - // only create on iframe - if (re.dropped_frames) { - // this should only happen for the first segment, maybe - LOGW("%s: dropped %d non iframe packets before init", name.c_str(), re.dropped_frames); - re.dropped_frames = 0; - } - // if we aren't actually recording, don't create the writer - if (encoder_info.record) { - assert(encoder_info.filename != NULL); - re.writer.reset(new VideoWriter(s->logger.segmentPath().c_str(), - encoder_info.filename, idx.getType() != cereal::EncodeIndex::Type::FULL_H_E_V_C, - edata.getWidth(), edata.getHeight(), encoder_info.fps, idx.getType())); - // write the header - auto header = edata.getHeader(); - re.writer->write((uint8_t *)header.begin(), header.size(), idx.getTimestampEof()/1000, true, false); - } - re.recording = true; - } else { - // this is a sad case when we aren't recording, but don't have an iframe - // nothing we can do but drop the frame - delete msg; - ++re.dropped_frames; - return bytes_count; - } - } - - // we have to be recording if we are here - assert(re.recording); - - // if we are actually writing the video file, do so - if (re.writer) { - auto data = edata.getData(); - re.writer->write((uint8_t *)data.begin(), data.size(), idx.getTimestampEof()/1000, false, flags & V4L2_BUF_FLAG_KEYFRAME); - } - - // put it in log stream as the idx packet - MessageBuilder bmsg; - auto evt = bmsg.initEvent(event.getValid()); - evt.setLogMonoTime(event.getLogMonoTime()); - (evt.*(encoder_info.set_encode_idx_func))(idx); - auto new_msg = bmsg.toBytes(); - s->logger.write((uint8_t *)new_msg.begin(), new_msg.size(), true); // always in qlog? - bytes_count += new_msg.size(); - - // free the message, we used it + bytes_count += write_encode_data(s, event, re, encoder_info); delete msg; } else if (offset_segment_num > s->logger.segment()) { // encoderd packet has a newer segment, this means encoderd has rolled over diff --git a/system/loggerd/tests/test_zstd_writer.cc b/system/loggerd/tests/test_zstd_writer.cc index f116bb2d5e..479e866a14 100644 --- a/system/loggerd/tests/test_zstd_writer.cc +++ b/system/loggerd/tests/test_zstd_writer.cc @@ -18,15 +18,21 @@ TEST_CASE("ZstdFileWriter writes and compresses data correctly in loops", "[Zstd // Step 1: Write compressed data to file in a loop { ZstdFileWriter writer(filename, LOG_COMPRESSION_LEVEL); + // Write various data sizes including edge cases + std::vector testSizes = {dataSize, 1, 0, dataSize * 2}; // Normal, minimal, empty, large for (int i = 0; i < iterations; ++i) { - std::string testData = util::random_string(dataSize); + size_t currentSize = testSizes[i % testSizes.size()]; + std::string testData = util::random_string(currentSize); totalTestData.append(testData); + writer.write((void *)testData.c_str(), testData.size()); } } // Step 2: Decompress the file and verify the data auto compressedContent = util::read_file(filename); + REQUIRE(compressedContent.size() > 0); + REQUIRE(compressedContent.size() < totalTestData.size()); std::string decompressedData = zstd_decompress(compressedContent); // Step 3: Verify that the decompressed data matches the original accumulated data diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 5d714fefd3..48136bfa14 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -105,10 +105,11 @@ procs = [ PythonProcess("micd", "system.micd", iscar), PythonProcess("timed", "system.timed", always_run, enabled=not PC), - # TODO Make python process once TG allows opening QCOM from child proc - NativeProcess("dmonitoringmodeld", "selfdrive/modeld", ["./dmonitoringmodeld"], driverview, enabled=(WEBCAM or not PC)), - # TODO Make python process once TG allows opening QCOM from child proc - NativeProcess("modeld", "selfdrive/modeld", ["./modeld"], and_(only_onroad, is_stock_model)), + # TODO: Make python process once TG allows opening QCOM from child pro + # https://github.com/tinygrad/tinygrad/blob/ac9c96dae1656dc220ee4acc39cef4dd449aa850/tinygrad/device.py#L26 + NativeProcess("modeld", "selfdrive/modeld", ["./modeld.py"], and_(only_onroad, is_stock_model)), + NativeProcess("dmonitoringmodeld", "selfdrive/modeld", ["./dmonitoringmodeld.py"], driverview, enabled=(WEBCAM or not PC)), + NativeProcess("sensord", "system/sensord", ["./sensord"], only_onroad, enabled=not PC), NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 22c6ed71f2..c7b7a36e5d 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -15,6 +15,7 @@ DEBUG_FPS = os.getenv("DEBUG_FPS") == '1' STRICT_MODE = os.getenv("STRICT_MODE") == '1' DEFAULT_TEXT_SIZE = 60 +DEFAULT_TEXT_COLOR = rl.Color(200, 200, 200, 255) FONT_DIR = os.path.join(BASEDIR, "selfdrive/assets/fonts") @@ -118,7 +119,7 @@ class GuiApplication: rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BORDER_WIDTH, 0) rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, DEFAULT_TEXT_SIZE) rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.BACKGROUND_COLOR, rl.color_to_int(rl.BLACK)) - rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(rl.Color(200, 200, 200, 255))) + rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(DEFAULT_TEXT_COLOR)) rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.BACKGROUND_COLOR, rl.color_to_int(rl.Color(30, 30, 30, 255))) rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BASE_COLOR_NORMAL, rl.color_to_int(rl.Color(50, 50, 50, 255))) diff --git a/system/ui/lib/button.py b/system/ui/lib/button.py index 9bcf3f7e01..034189275f 100644 --- a/system/ui/lib/button.py +++ b/system/ui/lib/button.py @@ -54,6 +54,7 @@ def gui_button( if button_style != ButtonStyle.TRANSPARENT: rl.draw_rectangle_rounded(rect, roundness, 20, bg_color) else: + rl.draw_rectangle_rounded(rect, roundness, 20, rl.BLACK) rl.draw_rectangle_rounded_lines_ex(rect, roundness, 20, 2, rl.WHITE) font = gui_app.font(font_weight) diff --git a/system/ui/lib/label.py b/system/ui/lib/label.py index 37b66582f9..ccfd89a2ec 100644 --- a/system/ui/lib/label.py +++ b/system/ui/lib/label.py @@ -1,11 +1,55 @@ import pyray as rl +from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_COLOR from openpilot.system.ui.lib.utils import GuiStyleContext -def gui_label(rect, text, font_size): + +def gui_label( + rect: rl.Rectangle, + text: str, + font_size: int = DEFAULT_TEXT_SIZE, + color: rl.Color = DEFAULT_TEXT_COLOR, + font_weight: FontWeight = FontWeight.NORMAL, + alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE +): + # Set font based on the provided weight + font = gui_app.font(font_weight) + + # Measure text size + text_size = rl.measure_text_ex(font, text, font_size, 0) + + # Calculate horizontal position based on alignment + text_x = rect.x + { + rl.GuiTextAlignment.TEXT_ALIGN_LEFT: 0, + rl.GuiTextAlignment.TEXT_ALIGN_CENTER: (rect.width - text_size.x) / 2, + rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: rect.width - text_size.x, + }.get(alignment, 0) + + # Calculate vertical position based on alignment + text_y = rect.y + { + rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP: 0, + rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE: (rect.height - text_size.y) / 2, + rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: rect.height - text_size.y, + }.get(alignment_vertical, 0) + + # Draw the text in the specified rectangle + rl.draw_text_ex(font, text, rl.Vector2(text_x, text_y), font_size, 0, color) + + +def gui_text_box( + rect: rl.Rectangle, + text: str, + font_size: int = DEFAULT_TEXT_SIZE, + color: rl.Color = DEFAULT_TEXT_COLOR, + alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP +): styles = [ + (rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(color)), (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, font_size), (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_LINE_SPACING, font_size), - (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_ALIGNMENT_VERTICAL, rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP), + (rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_ALIGNMENT, alignment), + (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_ALIGNMENT_VERTICAL, alignment_vertical), (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_WRAP_MODE, rl.GuiTextWrapMode.TEXT_WRAP_WORD) ] diff --git a/system/ui/reset.py b/system/ui/reset.py index a5a8a84b04..80a1c10ea8 100755 --- a/system/ui/reset.py +++ b/system/ui/reset.py @@ -7,7 +7,7 @@ from enum import IntEnum from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.button import gui_button, ButtonStyle -from openpilot.system.ui.lib.label import gui_label +from openpilot.system.ui.lib.label import gui_label, gui_text_box NVME = "/dev/nvme0n1" USERDATA = "/dev/disk/by-partlabel/userdata" @@ -51,13 +51,11 @@ class Reset: threading.Timer(0.1, self.do_reset).start() def render(self, rect: rl.Rectangle): - rl.gui_set_font(gui_app.font(FontWeight.BOLD)) - label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, rect.height) - gui_label(label_rect, "System Reset", 90) - rl.gui_set_font(gui_app.font(FontWeight.NORMAL)) + label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100) + gui_label(label_rect, "System Reset", 100, font_weight=FontWeight.BOLD) - label_rect.y += 150 - gui_label(label_rect, self.get_body_text(), 80) + text_rect = rl.Rectangle(rect.x + 140, rect.y + 140, rect.width - 280, rect.height - 90 - 100) + gui_text_box(text_rect, self.get_body_text(), 90) button_height = 160 button_spacing = 50 diff --git a/system/ui/text.py b/system/ui/text.py index 77a0d267e4..33299605d6 100755 --- a/system/ui/text.py +++ b/system/ui/text.py @@ -39,7 +39,7 @@ def main(): text_content = sys.argv[1] if len(sys.argv) > 1 else DEMO_TEXT - textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2 - BUTTON_SIZE.y - SPACING) + textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2) wrapped_lines = wrap_text(text_content, FONT_SIZE, textarea_rect.width - 20) content_rect = rl.Rectangle(0, 0, textarea_rect.width - 20, len(wrapped_lines) * LINE_HEIGHT) scroll_panel = GuiScrollPanel(show_vertical_scroll_bar=True) diff --git a/tinygrad_repo b/tinygrad_repo index 6f39c4d653..70266e9f94 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 6f39c4d653737c056540194605dc18a7273df280 +Subproject commit 70266e9f94d5a247ccbb2f3a46e72a2fbdaf7a8e diff --git a/tools/adb_shell.sh b/tools/adb_shell.sh index 985c5c0726..f757f7d4d0 100755 --- a/tools/adb_shell.sh +++ b/tools/adb_shell.sh @@ -1,7 +1,7 @@ #!/usr/bin/env expect spawn adb shell expect "#" -send "cd usr/comma\r" +send "cd data/openpilot\r" send "export TERM=xterm-256color\r" send "su comma\r" send "clear\r" diff --git a/tools/cabana/dbc/generate_dbc_json.py b/tools/cabana/dbc/generate_dbc_json.py index 5e928e6054..d002a50ef8 100755 --- a/tools/cabana/dbc/generate_dbc_json.py +++ b/tools/cabana/dbc/generate_dbc_json.py @@ -7,7 +7,7 @@ from opendbc.car.fingerprints import MIGRATION from opendbc.car.values import PLATFORMS -def generate_dbc_json() -> str: +def generate_dbc_dict() -> dict[str, str]: dbc_map = {} for platform in PLATFORMS.values(): if platform != "MOCK": @@ -24,7 +24,7 @@ def generate_dbc_json() -> str: if MIGRATION[m] in dbc_map: dbc_map[m] = dbc_map[MIGRATION[m]] - return json.dumps(dict(sorted(dbc_map.items())), indent=2) + return dbc_map if __name__ == "__main__": @@ -35,5 +35,5 @@ if __name__ == "__main__": args = parser.parse_args() with open(args.out, 'w') as f: - f.write(generate_dbc_json()) + f.write(json.dumps(dict(sorted(generate_dbc_dict().items())), indent=2)) print(f"Generated and written to {args.out}") diff --git a/tools/car_porting/README.md b/tools/car_porting/README.md index 68b0a57fb3..77492035ca 100644 --- a/tools/car_porting/README.md +++ b/tools/car_porting/README.md @@ -57,7 +57,21 @@ Traceback (most recent call last): AssertionError: 1 is not false : panda safety doesn't agree with openpilot: {'gasPressed': 116} ``` -### [tools/car_porting/examples/subaru_steer_temp_fault.ipynb](/tools/car_porting/examples/subaru_steer_temp_fault.ipynb) +## Jupyter notebooks + +To use these notebooks, install Jupyter within your [openpilot virtual environment](/tools/README.md). + +```bash +uv pip install jupyter ipykernel +``` + +Launching: + +```bash +jupyter notebook +``` + +### [examples/subaru_steer_temp_fault.ipynb](/tools/car_porting/examples/subaru_steer_temp_fault.ipynb) An example of searching through a database of segments for a specific condition, and plotting the results. @@ -65,7 +79,7 @@ An example of searching through a database of segments for a specific condition, *a plot of the steer_warning vs steering angle, where we can see it is clearly caused by a large steering angle change* -### [tools/car_porting/examples/subaru_long_accel.ipynb](/tools/car_porting/examples/subaru_long_accel.ipynb) +### [examples/subaru_long_accel.ipynb](/tools/car_porting/examples/subaru_long_accel.ipynb) An example of plotting the response of an actuator when it is active. @@ -73,7 +87,7 @@ An example of plotting the response of an actuator when it is active. *a plot of the brake_pressure vs acceleration, where we can see it is a fairly linear response.* -### [tools/car_porting/examples/ford_vin_fingerprint.ipynb](/tools/car_porting/examples/ford_vin_fingerprint.ipynb) +### [examples/ford_vin_fingerprint.ipynb](/tools/car_porting/examples/ford_vin_fingerprint.ipynb) In this example, we use the public comma car segments database to check if vin fingerprinting is feasible for ford. @@ -94,3 +108,22 @@ vin: 5LM5J7XC8MGXXXXXX real platform: FORD EXPLORER 6TH GEN determi vin: 3FTTW8E31PRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False vin: 3FTTW8E99NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False ``` + +### [examples/find_segments_with_message.ipynb](/tools/car_porting/examples/find_segments_with_message.ipynb) + +Searches for segments where a set of given CAN message IDs are present. In the example, we search for all messages +used for CAN-based ignition detection. + +``` +Match found: 46b21f1c5f7aa885/2024-01-23--15-19-34/20/s JEEP GRAND CHEROKEE V6 2018 ['VW CAN Ign'] +Match found: a63a23c3e628f288/2023-11-05--18-36-20/8/s JEEP GRAND CHEROKEE V6 2018 ['VW CAN Ign'] +Match found: ce31b7a998781ba8/2024-01-19--07-05-29/23/s JEEP GRAND CHEROKEE 2019 ['VW CAN Ign'] +Match found: e1dfba62a4e33f7b/2023-12-25--19-31-00/4/s JEEP GRAND CHEROKEE 2019 ['VW CAN Ign'] +Match found: e1dfba62a4e33f7b/2024-01-10--14-33-57/2/s JEEP GRAND CHEROKEE 2019 ['VW CAN Ign'] +Match found: ae679616266f4096/2023-12-05--15-43-46/4/s RAM HD 5TH GEN ['Tesla 3/Y CAN Ign'] +Match found: ae679616266f4096/2023-11-18--17-49-42/3/s RAM HD 5TH GEN ['Tesla 3/Y CAN Ign'] +Match found: ae679616266f4096/2024-01-03--21-57-09/25/s RAM HD 5TH GEN ['Tesla 3/Y CAN Ign'] +Match found: 6dae2984cc53cd7f/2023-12-10--11-53-15/17/s FORD BRONCO SPORT 1ST GEN ['Rivian CAN Ign'] +Match found: 6dae2984cc53cd7f/2023-12-03--17-31-17/29/s FORD BRONCO SPORT 1ST GEN ['Rivian CAN Ign'] +Match found: 6dae2984cc53cd7f/2023-11-27--23-29-07/1/s FORD BRONCO SPORT 1ST GEN ['Rivian CAN Ign'] +``` diff --git a/tools/car_porting/examples/find_segments_with_message.ipynb b/tools/car_porting/examples/find_segments_with_message.ipynb new file mode 100644 index 0000000000..4e688cc65b --- /dev/null +++ b/tools/car_porting/examples/find_segments_with_message.ipynb @@ -0,0 +1,232 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 85, + "id": "facb8edc-9924-491a-a4dd-fe6135b0c6c4", + "metadata": {}, + "outputs": [], + "source": [ + "# Import all cars from opendbc\n", + "\n", + "from opendbc.car import structs\n", + "from opendbc.car.values import PLATFORMS as TEST_PLATFORMS\n", + "\n", + "# Example: add additional platforms/segments to test outside of commaCarSegments\n", + "\n", + "EXTRA_SEGMENTS = {\n", + " # \"81dd9e9fe256c397/0000001f--97c42cf98d\", # Volkswagen ID.4 test route, new car port, not in public dataset\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "ed1c8aec-c274-4c61-b83d-711ea194bf86", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Searching 221 platforms\n", + "No segments available for DODGE_DURANGO\n", + "No segments available for FORD_RANGER_MK2\n", + "No segments available for HOLDEN_ASTRA\n", + "No segments available for CADILLAC_ATS\n", + "No segments available for CHEVROLET_MALIBU\n", + "No segments available for CADILLAC_XT4\n", + "No segments available for CHEVROLET_VOLT_2019\n", + "No segments available for CHEVROLET_TRAVERSE\n", + "No segments available for GMC_YUKON\n", + "No segments available for HONDA_ODYSSEY_CHN\n", + "No segments available for HYUNDAI_KONA_2022\n", + "No segments available for HYUNDAI_NEXO_1ST_GEN\n", + "No segments available for GENESIS_GV70_ELECTRIFIED_1ST_GEN\n", + "No segments available for GENESIS_G80_2ND_GEN_FL\n", + "No segments available for RIVIAN_R1_GEN1\n", + "No segments available for SUBARU_FORESTER_HYBRID\n", + "No segments available for TESLA_MODEL_3\n", + "No segments available for TESLA_MODEL_Y\n", + "No segments available for TOYOTA_RAV4_PRIME\n", + "No segments available for TOYOTA_SIENNA_4TH_GEN\n", + "No segments available for LEXUS_LC_TSS2\n", + "No segments available for VOLKSWAGEN_CADDY_MK3\n", + "No segments available for VOLKSWAGEN_CRAFTER_MK2\n", + "No segments available for VOLKSWAGEN_JETTA_MK6\n", + "Searching 577 segments\n" + ] + } + ], + "source": [ + "import random\n", + "\n", + "from openpilot.tools.lib.logreader import LogReader\n", + "from openpilot.tools.lib.comma_car_segments import get_comma_car_segments_database\n", + "\n", + "\n", + "MAX_SEGS_PER_PLATFORM = 3 # Increase this to search more segments\n", + "\n", + "database = get_comma_car_segments_database()\n", + "TEST_SEGMENTS = []\n", + "\n", + "print(f\"Searching {len(TEST_PLATFORMS)} platforms\")\n", + "\n", + "for platform in TEST_PLATFORMS:\n", + " if platform not in database:\n", + " print(f\"No segments available for {platform}\")\n", + " continue\n", + " \n", + " all_segments = database[platform]\n", + " NUM_SEGMENTS = min(len(all_segments), MAX_SEGS_PER_PLATFORM)\n", + " TEST_SEGMENTS.extend(random.sample(all_segments, NUM_SEGMENTS))\n", + "\n", + "TEST_SEGMENTS.extend(EXTRA_SEGMENTS)\n", + "\n", + "print(f\"Searching {len(TEST_SEGMENTS)} segments\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c75e8f2-4f5f-4f89-b8db-5223a6534a9f", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "27a243c33de44498b2b946190df44b23", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "segments searched: 0%| | 0/577 [00:00 0:\n", + " message_names = [MESSAGES_TO_FIND[message] for message in matched_messages]\n", + " print(f\"Match found: {segment:<45} {CP.carFingerprint:<38} {message_names}\")\n", + "\n", + " progress_bar.update()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7724dd97-f62e-4fd3-9f64-63d49be669d2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f393e00-8efd-40fb-a41e-d312531a83e8", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 66bb9fa404..f66782d6f3 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -13,6 +13,7 @@ from functools import partial from opendbc.car.fingerprints import MIGRATION from openpilot.common.basedir import BASEDIR from openpilot.common.swaglog import cloudlog +from openpilot.tools.cabana.dbc.generate_dbc_json import generate_dbc_dict from openpilot.tools.lib.logreader import LogReader, ReadMode, save_log from openpilot.selfdrive.test.process_replay.migration import migrate_all @@ -75,7 +76,7 @@ def start_juggler(fn=None, dbc=None, layout=None, route_or_segment_name=None, pl def process(can, lr): - return [d for d in lr if can or d.which() not in ['can', 'sendcan']] + return [d for d in lr if can or d.which() not in ['can', 'sendcan'] and not d.which().startswith('customReserved')] def juggle_route(route_or_segment_name, can, layout, dbc, should_migrate): @@ -90,11 +91,10 @@ def juggle_route(route_or_segment_name, can, layout, dbc, should_migrate): if dbc is None: try: CP = lr.first('carParams') - platform = CP.carFingerprint - DBC = __import__(f"opendbc.car.{CP.brand}.values", fromlist=['DBC']).DBC - dbc = DBC[MIGRATION.get(CP.carFingerprint, CP.carFingerprint)]['pt'] + platform = MIGRATION.get(CP.carFingerprint, CP.carFingerprint) + dbc = generate_dbc_dict()[platform] except Exception: - cloudlog.error("Failed to get DBC name from logs!") + cloudlog.exception("Failed to get DBC name from logs!") with tempfile.NamedTemporaryFile(suffix='.rlog', dir=juggle_dir) as tmp: save_log(tmp.name, all_data, compress=False) diff --git a/tools/plotjuggler/layouts/max-torque-debug.xml b/tools/plotjuggler/layouts/max-torque-debug.xml index 8cfd30599e..9a6693165e 100644 --- a/tools/plotjuggler/layouts/max-torque-debug.xml +++ b/tools/plotjuggler/layouts/max-torque-debug.xml @@ -24,7 +24,7 @@ - + diff --git a/tools/plotjuggler/layouts/torque-controller.xml b/tools/plotjuggler/layouts/torque-controller.xml index b4edfdcc18..5a0a7625e0 100644 --- a/tools/plotjuggler/layouts/torque-controller.xml +++ b/tools/plotjuggler/layouts/torque-controller.xml @@ -24,8 +24,8 @@ - - + + @@ -114,8 +114,8 @@ - - + + diff --git a/tools/replay/README.md b/tools/replay/README.md index 5e91aae943..f02090c58e 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -58,8 +58,8 @@ Mock openpilot components by publishing logged messages. Options: -h, --help Displays this help. - -a, --allow whitelist of services to send - -b, --block blacklist of services to send + -a, --allow whitelist of services to send (comma-separated) + -b, --block blacklist of services to send (comma-separated) -c, --cache cache segments in memory. default is 5 -s, --start start from -x playback . between 0.2 - 3 diff --git a/tools/replay/main.cc b/tools/replay/main.cc index 31493d1486..b33b7fa263 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -11,10 +11,10 @@ #include "tools/replay/util.h" const std::string helpText = -R"(Usage: replay [options] +R"(Usage: replay [options] [route] Options: - -a, --allow Whitelist of services to send - -b, --block Blacklist of services to send + -a, --allow Whitelist of services to send (comma-separated) + -b, --block Blacklist of services to send (comma-separated) -c, --cache Cache segments in memory. Default is 5 -s, --start Start from -x, --playback Playback diff --git a/tools/replay/ui.py b/tools/replay/ui.py index 34e22ec595..6e40579902 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -8,7 +8,6 @@ import numpy as np import pygame import cereal.messaging as messaging -from openpilot.common.numpy_fast import clip from openpilot.common.basedir import BASEDIR from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.tools.replay.lib.ui_helpers import (UP, @@ -152,11 +151,11 @@ def ui_thread(addr): plot_arr[-1, name_to_arr_idx['angle_steers_k']] = angle_steers_k plot_arr[-1, name_to_arr_idx['gas']] = sm['carState'].gas # TODO gas is deprecated - plot_arr[-1, name_to_arr_idx['computer_gas']] = clip(sm['carControl'].actuators.accel/4.0, 0.0, 1.0) + plot_arr[-1, name_to_arr_idx['computer_gas']] = np.clip(sm['carControl'].actuators.accel/4.0, 0.0, 1.0) plot_arr[-1, name_to_arr_idx['user_brake']] = sm['carState'].brake plot_arr[-1, name_to_arr_idx['steer_torque']] = sm['carControl'].actuators.torque * ANGLE_SCALE # TODO brake is deprecated - plot_arr[-1, name_to_arr_idx['computer_brake']] = clip(-sm['carControl'].actuators.accel/4.0, 0.0, 1.0) + plot_arr[-1, name_to_arr_idx['computer_brake']] = np.clip(-sm['carControl'].actuators.accel/4.0, 0.0, 1.0) plot_arr[-1, name_to_arr_idx['v_ego']] = sm['carState'].vEgo plot_arr[-1, name_to_arr_idx['v_cruise']] = sm['carState'].cruiseState.speed plot_arr[-1, name_to_arr_idx['a_ego']] = sm['carState'].aEgo diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index 5294ffcc49..a2f437819f 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -49,7 +49,7 @@ class SteeringAccuracyTool: active = sm['controlsState'].active steer = sm['carOutput'].actuatorsOutput.torque standstill = sm['carState'].standstill - steer_limited = abs(sm['carControl'].actuators.torque - sm['carControl'].actuatorsOutput.torque) > 1e-2 + steer_limited_by_controls = abs(sm['carControl'].actuators.torque - sm['carControl'].actuatorsOutput.torque) > 1e-2 overriding = sm['carState'].steeringPressed changing_lanes = sm['modelV2'].meta.laneChangeState != 0 model_points = sm['modelV2'].position.y @@ -77,7 +77,7 @@ class SteeringAccuracyTool: self.speed_group_stats[group][angle_abs]["steer"] += abs(steer) if len(model_points): self.speed_group_stats[group][angle_abs]["dpp"] += abs(model_points[0]) - if steer_limited: + if steer_limited_by_controls: self.speed_group_stats[group][angle_abs]["limited"] += 1 if control_state.saturated: self.speed_group_stats[group][angle_abs]["saturated"] += 1 diff --git a/uv.lock b/uv.lock index 3923154ec6..8da47bf004 100644 --- a/uv.lock +++ b/uv.lock @@ -12,11 +12,11 @@ resolution-markers = [ [[package]] name = "aiohappyeyeballs" -version = "2.4.6" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/07/508f9ebba367fc3370162e53a3cfd12f5652ad79f0e0bfdf9f9847c6f159/aiohappyeyeballs-2.4.6.tar.gz", hash = "sha256:9b05052f9042985d32ecbe4b59a77ae19c006a78f1344d7fdad69d28ded3d0b0", size = 21726 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/0c/458958007041f4b4de2d307e6b75d9e7554dad0baf26fe7a48b741aac126/aiohappyeyeballs-2.5.0.tar.gz", hash = "sha256:18fde6204a76deeabc97c48bdd01d5801cfda5d6b9c8bbeb1aaaee9d648ca191", size = 22494 } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/4c/03fb05f56551828ec67ceb3665e5dc51638042d204983a03b0a1541475b6/aiohappyeyeballs-2.4.6-py3-none-any.whl", hash = "sha256:147ec992cf873d74f5062644332c539fcd42956dc69453fe5204195e560517e1", size = 14543 }, + { url = "https://files.pythonhosted.org/packages/1b/9a/e4886864ce06e1579bd428208127fbdc0d62049c751e4e9e3b509c0059dc/aiohappyeyeballs-2.5.0-py3-none-any.whl", hash = "sha256:0850b580748c7071db98bffff6d4c94028d0d3035acc20fd721a0ce7e8cac35d", size = 15128 }, ] [[package]] @@ -628,7 +628,7 @@ wheels = [ [[package]] name = "gymnasium" -version = "1.1.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cloudpickle" }, @@ -636,9 +636,9 @@ dependencies = [ { name = "numpy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/74/db400493fbb86ff6b20ab46e798ac79a366a7db31ece785499e5e5bfd873/gymnasium-1.1.0.tar.gz", hash = "sha256:dedb5c8c83047d3927ef8b841fb4ebadaeaa43ab954e2e3aca7eadcf4226c5f2", size = 827655 } +sdist = { url = "https://files.pythonhosted.org/packages/90/69/70cd29e9fc4953d013b15981ee71d4c9ef4d8b2183e6ef2fe89756746dce/gymnasium-1.1.1.tar.gz", hash = "sha256:8bd9ea9bdef32c950a444ff36afc785e1d81051ec32d30435058953c20d2456d", size = 829326 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/2a/f931b9b7515c16e5285ecb0e6c2d773266a4f781332f24456e7c0d516c8d/gymnasium-1.1.0-py3-none-any.whl", hash = "sha256:8632bbb860512e565a46cd30fa8325ce8fbdac09b9c4f04171a772d6e95b232b", size = 965463 }, + { url = "https://files.pythonhosted.org/packages/f9/68/2bdc7b46b5f543dd865575f9d19716866bdb76e50dd33b71ed1a3dd8bb42/gymnasium-1.1.1-py3-none-any.whl", hash = "sha256:9c167ec0a2b388666e37f63b2849cd2552f7f5b71938574c637bb36487eb928a", size = 965410 }, ] [[package]] @@ -701,14 +701,14 @@ wheels = [ [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, ] [[package]] @@ -760,13 +760,13 @@ wheels = [ [[package]] name = "libusb1" -version = "3.2.0" +version = "3.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d9/b7/9e833af6cb52fa2aece1c6a1378667ca0172bead14f63ffccc3cb9862df3/libusb1-3.2.0.tar.gz", hash = "sha256:a11a6095e718cd49418a96329314da271cca6be7b4317a142724523371ac8961", size = 105601 } +sdist = { url = "https://files.pythonhosted.org/packages/69/c7/3c441fb0628b5afdfe68768f90f8d6ce74dc08e72691fb78d0875db06c48/libusb1-3.3.0.tar.gz", hash = "sha256:b642aa518689f8e053f61955cba274e72bc8d0794c65d7990745aaae90dfc8a1", size = 107253 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/35/d5bde9434fc734709960a94cc489d487184d0ab08e5867311d5a4c64fcea/libusb1-3.2.0-py3-none-any.whl", hash = "sha256:048d4fb79021ec05af667a32e51a911578fc63ab3ed48a4f4fe0f67da797f416", size = 65829 }, - { url = "https://files.pythonhosted.org/packages/a3/cd/ef05b2c67fff2d704aa0325993187ce6a6d04f40ba4be953766b47f5f949/libusb1-3.2.0-py3-none-win32.whl", hash = "sha256:5dc48d6f5207e184cb53278527e7d8e5f4051d8bc419bd15b564c37dc5cca268", size = 127781 }, - { url = "https://files.pythonhosted.org/packages/f9/82/8bcadf2794fa2d39ec100a4f3945db58c316d55c1a0e79ac2cf81c754282/libusb1-3.2.0-py3-none-win_amd64.whl", hash = "sha256:b13acc618263348c91bc4476fadada47be98b7924d6f60e79e3f1da67ac39ddc", size = 139410 }, + { url = "https://files.pythonhosted.org/packages/c7/48/b24b810b14595bd4680030f73a31119120069a8857f41554f00e00b57229/libusb1-3.3.0-py3-none-any.whl", hash = "sha256:7923ff2bbe43878e1c7cf733fde49cca844ecada29dbc436033bca6171cda41f", size = 67044 }, + { url = "https://files.pythonhosted.org/packages/3f/45/52980eedf3fb78711960a558465b3d998beed327fcd2b8483119527adc44/libusb1-3.3.0-py3-none-win32.whl", hash = "sha256:270f35b3a8faddd63d78de9b2341e49f6aed6083dceb4b04f47b95fd944e8d00", size = 128992 }, + { url = "https://files.pythonhosted.org/packages/af/d3/992bfd5bcd9d7a978563d3dabb11d0fce5cf7ceea73d9633fbadba93df4e/libusb1-3.3.0-py3-none-win_amd64.whl", hash = "sha256:61e2b4efdbacdd689bf4eab6b9afa2d6ee8a4bae20ffbf7c5785024d25fa3258", size = 140624 }, ] [[package]] @@ -902,7 +902,7 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.10.0" +version = "3.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, @@ -915,20 +915,20 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/dd/fa2e1a45fce2d09f4aea3cee169760e672c8262325aa5796c49d543dc7e6/matplotlib-3.10.0.tar.gz", hash = "sha256:b886d02a581b96704c9d1ffe55709e49b4d2d52709ccebc4be42db856e511278", size = 36686418 } +sdist = { url = "https://files.pythonhosted.org/packages/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/f1/e37f6c84d252867d7ddc418fff70fc661cfd363179263b08e52e8b748e30/matplotlib-3.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fd44fc75522f58612ec4a33958a7e5552562b7705b42ef1b4f8c0818e304a363", size = 8171677 }, - { url = "https://files.pythonhosted.org/packages/c7/8b/92e9da1f28310a1f6572b5c55097b0c0ceb5e27486d85fb73b54f5a9b939/matplotlib-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c58a9622d5dbeb668f407f35f4e6bfac34bb9ecdcc81680c04d0258169747997", size = 8044945 }, - { url = "https://files.pythonhosted.org/packages/c5/cb/49e83f0fd066937a5bd3bc5c5d63093703f3637b2824df8d856e0558beef/matplotlib-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:845d96568ec873be63f25fa80e9e7fae4be854a66a7e2f0c8ccc99e94a8bd4ef", size = 8458269 }, - { url = "https://files.pythonhosted.org/packages/b2/7d/2d873209536b9ee17340754118a2a17988bc18981b5b56e6715ee07373ac/matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5439f4c5a3e2e8eab18e2f8c3ef929772fd5641876db71f08127eed95ab64683", size = 8599369 }, - { url = "https://files.pythonhosted.org/packages/b8/03/57d6cbbe85c61fe4cbb7c94b54dce443d68c21961830833a1f34d056e5ea/matplotlib-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4673ff67a36152c48ddeaf1135e74ce0d4bce1bbf836ae40ed39c29edf7e2765", size = 9405992 }, - { url = "https://files.pythonhosted.org/packages/14/cf/e382598f98be11bf51dd0bc60eca44a517f6793e3dc8b9d53634a144620c/matplotlib-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e8632baebb058555ac0cde75db885c61f1212e47723d63921879806b40bec6a", size = 8034580 }, - { url = "https://files.pythonhosted.org/packages/44/c7/6b2d8cb7cc251d53c976799cacd3200add56351c175ba89ab9cbd7c1e68a/matplotlib-3.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4659665bc7c9b58f8c00317c3c2a299f7f258eeae5a5d56b4c64226fca2f7c59", size = 8172465 }, - { url = "https://files.pythonhosted.org/packages/42/2a/6d66d0fba41e13e9ca6512a0a51170f43e7e7ed3a8dfa036324100775612/matplotlib-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d44cb942af1693cced2604c33a9abcef6205601c445f6d0dc531d813af8a2f5a", size = 8043300 }, - { url = "https://files.pythonhosted.org/packages/90/60/2a60342b27b90a16bada939a85e29589902b41073f59668b904b15ea666c/matplotlib-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a994f29e968ca002b50982b27168addfd65f0105610b6be7fa515ca4b5307c95", size = 8448936 }, - { url = "https://files.pythonhosted.org/packages/a7/b2/d872fc3d753516870d520595ddd8ce4dd44fa797a240999f125f58521ad7/matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b0558bae37f154fffda54d779a592bc97ca8b4701f1c710055b609a3bac44c8", size = 8594151 }, - { url = "https://files.pythonhosted.org/packages/f4/bd/b2f60cf7f57d014ab33e4f74602a2b5bdc657976db8196bbc022185f6f9c/matplotlib-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:503feb23bd8c8acc75541548a1d709c059b7184cde26314896e10a9f14df5f12", size = 9400347 }, - { url = "https://files.pythonhosted.org/packages/9f/6e/264673e64001b99d747aff5a288eca82826c024437a3694e19aed1decf46/matplotlib-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:c40ba2eb08b3f5de88152c2333c58cee7edcead0a2a0d60fcafa116b17117adc", size = 8039144 }, + { url = "https://files.pythonhosted.org/packages/a5/14/a1b840075be247bb1834b22c1e1d558740b0f618fe3a823740181ca557a1/matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401", size = 8174669 }, + { url = "https://files.pythonhosted.org/packages/0a/e4/300b08e3e08f9c98b0d5635f42edabf2f7a1d634e64cb0318a71a44ff720/matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe", size = 8047996 }, + { url = "https://files.pythonhosted.org/packages/75/f9/8d99ff5a2498a5f1ccf919fb46fb945109623c6108216f10f96428f388bc/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd", size = 8461612 }, + { url = "https://files.pythonhosted.org/packages/40/b8/53fa08a5eaf78d3a7213fd6da1feec4bae14a81d9805e567013811ff0e85/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c", size = 8602258 }, + { url = "https://files.pythonhosted.org/packages/40/87/4397d2ce808467af86684a622dd112664553e81752ea8bf61bdd89d24a41/matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7", size = 9408896 }, + { url = "https://files.pythonhosted.org/packages/d7/68/0d03098b3feb786cbd494df0aac15b571effda7f7cbdec267e8a8d398c16/matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a", size = 8061281 }, + { url = "https://files.pythonhosted.org/packages/7c/1d/5e0dc3b59c034e43de16f94deb68f4ad8a96b3ea00f4b37c160b7474928e/matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107", size = 8175488 }, + { url = "https://files.pythonhosted.org/packages/7a/81/dae7e14042e74da658c3336ab9799128e09a1ee03964f2d89630b5d12106/matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be", size = 8046264 }, + { url = "https://files.pythonhosted.org/packages/21/c4/22516775dcde10fc9c9571d155f90710761b028fc44f660508106c363c97/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6", size = 8452048 }, + { url = "https://files.pythonhosted.org/packages/63/23/c0615001f67ce7c96b3051d856baedc0c818a2ed84570b9bf9bde200f85d/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d", size = 8597111 }, + { url = "https://files.pythonhosted.org/packages/ca/c0/a07939a82aed77770514348f4568177d7dadab9787ebc618a616fe3d665e/matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea", size = 9402771 }, + { url = "https://files.pythonhosted.org/packages/a6/b6/a9405484fb40746fdc6ae4502b16a9d6e53282ba5baaf9ebe2da579f68c4/matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c", size = 8063742 }, ] [[package]] @@ -1578,16 +1578,16 @@ wheels = [ [[package]] name = "protobuf" -version = "5.29.3" +version = "6.30.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/d1/e0a911544ca9993e0f17ce6d3cc0932752356c1b0a834397f28e63479344/protobuf-5.29.3.tar.gz", hash = "sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620", size = 424945 } +sdist = { url = "https://files.pythonhosted.org/packages/53/6a/2629bb3529e5bdfbd6c4608ff5c7d942cd4beae85793f84ba543aab2548a/protobuf-6.30.0.tar.gz", hash = "sha256:852b675d276a7d028f660da075af1841c768618f76b90af771a8e2c29e6f5965", size = 429239 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/7a/1e38f3cafa022f477ca0f57a1f49962f21ad25850c3ca0acd3b9d0091518/protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888", size = 422708 }, - { url = "https://files.pythonhosted.org/packages/61/fa/aae8e10512b83de633f2646506a6d835b151edf4b30d18d73afd01447253/protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a", size = 434508 }, - { url = "https://files.pythonhosted.org/packages/dd/04/3eaedc2ba17a088961d0e3bd396eac764450f431621b58a04ce898acd126/protobuf-5.29.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e", size = 417825 }, - { url = "https://files.pythonhosted.org/packages/4f/06/7c467744d23c3979ce250397e26d8ad8eeb2bea7b18ca12ad58313c1b8d5/protobuf-5.29.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84", size = 319573 }, - { url = "https://files.pythonhosted.org/packages/a8/45/2ebbde52ad2be18d3675b6bee50e68cd73c9e0654de77d595540b5129df8/protobuf-5.29.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f", size = 319672 }, - { url = "https://files.pythonhosted.org/packages/fd/b2/ab07b09e0f6d143dfb839693aa05765257bceaa13d03bf1a696b78323e7a/protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f", size = 172550 }, + { url = "https://files.pythonhosted.org/packages/aa/76/8b1cbf762d98b09fcb29bbc6eca97dc6e1cd865b97a49c443aa23f1a9f82/protobuf-6.30.0-cp310-abi3-win32.whl", hash = "sha256:7337d76d8efe65ee09ee566b47b5914c517190196f414e5418fa236dfd1aed3e", size = 419141 }, + { url = "https://files.pythonhosted.org/packages/57/50/2ea2fb4533321438f5106723c70c303529ba184540e619ebe75e790d402e/protobuf-6.30.0-cp310-abi3-win_amd64.whl", hash = "sha256:9b33d51cc95a7ec4f407004c8b744330b6911a37a782e2629c67e1e8ac41318f", size = 430995 }, + { url = "https://files.pythonhosted.org/packages/a1/7d/a7dfa7aa3deda114920b1ed57c0026e85a976e74658db2784a0443510252/protobuf-6.30.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:52d4bb6fe76005860e1d0b8bfa126f5c97c19cc82704961f60718f50be16942d", size = 417570 }, + { url = "https://files.pythonhosted.org/packages/11/87/a9c7b020c4072dc34e3a2a3cde69366ffc623afff0e7f10f4e5275aaec01/protobuf-6.30.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:7940ab4dfd60d514b2e1d3161549ea7aed5be37d53bafde16001ac470a3e202b", size = 317310 }, + { url = "https://files.pythonhosted.org/packages/95/66/424db2262723781dc94208ff9ce201df2f44f18a46fbff3c067812c6b5b9/protobuf-6.30.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:d79bf6a202a536b192b7e8d295d7eece0c86fbd9b583d147faf8cfeff46bf598", size = 316203 }, + { url = "https://files.pythonhosted.org/packages/51/6f/21c2b7de96c3051f847a4a88a12fdf015ed6b7d50fc131fb101a739bd7a5/protobuf-6.30.0-py3-none-any.whl", hash = "sha256:e5ef216ea061b262b8994cb6b7d6637a4fb27b3fb4d8e216a6040c0b93bd10d7", size = 167054 }, ] [[package]] @@ -4279,7 +4279,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.3.4" +version = "8.3.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -4287,9 +4287,9 @@ dependencies = [ { name = "packaging" }, { name = "pluggy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] [[package]] @@ -4678,36 +4678,36 @@ wheels = [ [[package]] name = "ruff" -version = "0.9.7" +version = "0.9.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/39/8b/a86c300359861b186f18359adf4437ac8e4c52e42daa9eedc731ef9d5b53/ruff-0.9.7.tar.gz", hash = "sha256:643757633417907510157b206e490c3aa11cab0c087c912f60e07fbafa87a4c6", size = 3669813 } +sdist = { url = "https://files.pythonhosted.org/packages/20/8e/fafaa6f15c332e73425d9c44ada85360501045d5ab0b81400076aff27cf6/ruff-0.9.10.tar.gz", hash = "sha256:9bacb735d7bada9cfb0f2c227d3658fc443d90a727b47f206fb33f52f3c0eac7", size = 3759776 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/f3/3a1d22973291226df4b4e2ff70196b926b6f910c488479adb0eeb42a0d7f/ruff-0.9.7-py3-none-linux_armv6l.whl", hash = "sha256:99d50def47305fe6f233eb8dabfd60047578ca87c9dcb235c9723ab1175180f4", size = 11774588 }, - { url = "https://files.pythonhosted.org/packages/8e/c9/b881f4157b9b884f2994fd08ee92ae3663fb24e34b0372ac3af999aa7fc6/ruff-0.9.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d59105ae9c44152c3d40a9c40d6331a7acd1cdf5ef404fbe31178a77b174ea66", size = 11746848 }, - { url = "https://files.pythonhosted.org/packages/14/89/2f546c133f73886ed50a3d449e6bf4af27d92d2f960a43a93d89353f0945/ruff-0.9.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f313b5800483770bd540cddac7c90fc46f895f427b7820f18fe1822697f1fec9", size = 11177525 }, - { url = "https://files.pythonhosted.org/packages/d7/93/6b98f2c12bf28ab9def59c50c9c49508519c5b5cfecca6de871cf01237f6/ruff-0.9.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042ae32b41343888f59c0a4148f103208bf6b21c90118d51dc93a68366f4e903", size = 11996580 }, - { url = "https://files.pythonhosted.org/packages/8e/3f/b3fcaf4f6d875e679ac2b71a72f6691a8128ea3cb7be07cbb249f477c061/ruff-0.9.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87862589373b33cc484b10831004e5e5ec47dc10d2b41ba770e837d4f429d721", size = 11525674 }, - { url = "https://files.pythonhosted.org/packages/f0/48/33fbf18defb74d624535d5d22adcb09a64c9bbabfa755bc666189a6b2210/ruff-0.9.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a17e1e01bee0926d351a1ee9bc15c445beae888f90069a6192a07a84af544b6b", size = 12739151 }, - { url = "https://files.pythonhosted.org/packages/63/b5/7e161080c5e19fa69495cbab7c00975ef8a90f3679caa6164921d7f52f4a/ruff-0.9.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7c1f880ac5b2cbebd58b8ebde57069a374865c73f3bf41f05fe7a179c1c8ef22", size = 13416128 }, - { url = "https://files.pythonhosted.org/packages/4e/c8/b5e7d61fb1c1b26f271ac301ff6d9de5e4d9a9a63f67d732fa8f200f0c88/ruff-0.9.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e63fc20143c291cab2841dbb8260e96bafbe1ba13fd3d60d28be2c71e312da49", size = 12870858 }, - { url = "https://files.pythonhosted.org/packages/da/cb/2a1a8e4e291a54d28259f8fc6a674cd5b8833e93852c7ef5de436d6ed729/ruff-0.9.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91ff963baed3e9a6a4eba2a02f4ca8eaa6eba1cc0521aec0987da8d62f53cbef", size = 14786046 }, - { url = "https://files.pythonhosted.org/packages/ca/6c/c8f8a313be1943f333f376d79724260da5701426c0905762e3ddb389e3f4/ruff-0.9.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88362e3227c82f63eaebf0b2eff5b88990280fb1ecf7105523883ba8c3aaf6fb", size = 12550834 }, - { url = "https://files.pythonhosted.org/packages/9d/ad/f70cf5e8e7c52a25e166bdc84c082163c9c6f82a073f654c321b4dff9660/ruff-0.9.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0372c5a90349f00212270421fe91874b866fd3626eb3b397ede06cd385f6f7e0", size = 11961307 }, - { url = "https://files.pythonhosted.org/packages/52/d5/4f303ea94a5f4f454daf4d02671b1fbfe2a318b5fcd009f957466f936c50/ruff-0.9.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d76b8ab60e99e6424cd9d3d923274a1324aefce04f8ea537136b8398bbae0a62", size = 11612039 }, - { url = "https://files.pythonhosted.org/packages/eb/c8/bd12a23a75603c704ce86723be0648ba3d4ecc2af07eecd2e9fa112f7e19/ruff-0.9.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c439bdfc8983e1336577f00e09a4e7a78944fe01e4ea7fe616d00c3ec69a3d0", size = 12168177 }, - { url = "https://files.pythonhosted.org/packages/cc/57/d648d4f73400fef047d62d464d1a14591f2e6b3d4a15e93e23a53c20705d/ruff-0.9.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:115d1f15e8fdd445a7b4dc9a30abae22de3f6bcabeb503964904471691ef7606", size = 12610122 }, - { url = "https://files.pythonhosted.org/packages/49/79/acbc1edd03ac0e2a04ae2593555dbc9990b34090a9729a0c4c0cf20fb595/ruff-0.9.7-py3-none-win32.whl", hash = "sha256:e9ece95b7de5923cbf38893f066ed2872be2f2f477ba94f826c8defdd6ec6b7d", size = 9988751 }, - { url = "https://files.pythonhosted.org/packages/6d/95/67153a838c6b6ba7a2401241fd8a00cd8c627a8e4a0491b8d853dedeffe0/ruff-0.9.7-py3-none-win_amd64.whl", hash = "sha256:3770fe52b9d691a15f0b87ada29c45324b2ace8f01200fb0c14845e499eb0c2c", size = 11002987 }, - { url = "https://files.pythonhosted.org/packages/63/6a/aca01554949f3a401991dc32fe22837baeaccb8a0d868256cbb26a029778/ruff-0.9.7-py3-none-win_arm64.whl", hash = "sha256:b075a700b2533feb7a01130ff656a4ec0d5f340bb540ad98759b8401c32c2037", size = 10177763 }, + { url = "https://files.pythonhosted.org/packages/73/b2/af7c2cc9e438cbc19fafeec4f20bfcd72165460fe75b2b6e9a0958c8c62b/ruff-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:eb4d25532cfd9fe461acc83498361ec2e2252795b4f40b17e80692814329e42d", size = 10049494 }, + { url = "https://files.pythonhosted.org/packages/6d/12/03f6dfa1b95ddd47e6969f0225d60d9d7437c91938a310835feb27927ca0/ruff-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:188a6638dab1aa9bb6228a7302387b2c9954e455fb25d6b4470cb0641d16759d", size = 10853584 }, + { url = "https://files.pythonhosted.org/packages/02/49/1c79e0906b6ff551fb0894168763f705bf980864739572b2815ecd3c9df0/ruff-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5284dcac6b9dbc2fcb71fdfc26a217b2ca4ede6ccd57476f52a587451ebe450d", size = 10155692 }, + { url = "https://files.pythonhosted.org/packages/5b/01/85e8082e41585e0e1ceb11e41c054e9e36fed45f4b210991052d8a75089f/ruff-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47678f39fa2a3da62724851107f438c8229a3470f533894b5568a39b40029c0c", size = 10369760 }, + { url = "https://files.pythonhosted.org/packages/a1/90/0bc60bd4e5db051f12445046d0c85cc2c617095c0904f1aa81067dc64aea/ruff-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99713a6e2766b7a17147b309e8c915b32b07a25c9efd12ada79f217c9c778b3e", size = 9912196 }, + { url = "https://files.pythonhosted.org/packages/66/ea/0b7e8c42b1ec608033c4d5a02939c82097ddcb0b3e393e4238584b7054ab/ruff-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524ee184d92f7c7304aa568e2db20f50c32d1d0caa235d8ddf10497566ea1a12", size = 11434985 }, + { url = "https://files.pythonhosted.org/packages/d5/86/3171d1eff893db4f91755175a6e1163c5887be1f1e2f4f6c0c59527c2bfd/ruff-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df92aeac30af821f9acf819fc01b4afc3dfb829d2782884f8739fb52a8119a16", size = 12155842 }, + { url = "https://files.pythonhosted.org/packages/89/9e/700ca289f172a38eb0bca752056d0a42637fa17b81649b9331786cb791d7/ruff-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de42e4edc296f520bb84954eb992a07a0ec5a02fecb834498415908469854a52", size = 11613804 }, + { url = "https://files.pythonhosted.org/packages/f2/92/648020b3b5db180f41a931a68b1c8575cca3e63cec86fd26807422a0dbad/ruff-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d257f95b65806104b6b1ffca0ea53f4ef98454036df65b1eda3693534813ecd1", size = 13823776 }, + { url = "https://files.pythonhosted.org/packages/5e/a6/cc472161cd04d30a09d5c90698696b70c169eeba2c41030344194242db45/ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c", size = 11302673 }, + { url = "https://files.pythonhosted.org/packages/6c/db/d31c361c4025b1b9102b4d032c70a69adb9ee6fde093f6c3bf29f831c85c/ruff-0.9.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d838b60007da7a39c046fcdd317293d10b845001f38bcb55ba766c3875b01e43", size = 10235358 }, + { url = "https://files.pythonhosted.org/packages/d1/86/d6374e24a14d4d93ebe120f45edd82ad7dcf3ef999ffc92b197d81cdc2a5/ruff-0.9.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ccaf903108b899beb8e09a63ffae5869057ab649c1e9231c05ae354ebc62066c", size = 9886177 }, + { url = "https://files.pythonhosted.org/packages/00/62/a61691f6eaaac1e945a1f3f59f1eea9a218513139d5b6c2b8f88b43b5b8f/ruff-0.9.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f9567d135265d46e59d62dc60c0bfad10e9a6822e231f5b24032dba5a55be6b5", size = 10864747 }, + { url = "https://files.pythonhosted.org/packages/ee/94/2c7065e1d92a8a8a46d46d9c3cf07b0aa7e0a1e0153d74baa5e6620b4102/ruff-0.9.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f202f0d93738c28a89f8ed9eaba01b7be339e5d8d642c994347eaa81c6d75b8", size = 11360441 }, + { url = "https://files.pythonhosted.org/packages/a7/8f/1f545ea6f9fcd7bf4368551fb91d2064d8f0577b3079bb3f0ae5779fb773/ruff-0.9.10-py3-none-win32.whl", hash = "sha256:bfb834e87c916521ce46b1788fbb8484966e5113c02df216680102e9eb960029", size = 10247401 }, + { url = "https://files.pythonhosted.org/packages/4f/18/fb703603ab108e5c165f52f5b86ee2aa9be43bb781703ec87c66a5f5d604/ruff-0.9.10-py3-none-win_amd64.whl", hash = "sha256:f2160eeef3031bf4b17df74e307d4c5fb689a6f3a26a2de3f7ef4044e3c484f1", size = 11366360 }, + { url = "https://files.pythonhosted.org/packages/35/85/338e603dc68e7d9994d5d84f24adbf69bae760ba5efd3e20f5ff2cec18da/ruff-0.9.10-py3-none-win_arm64.whl", hash = "sha256:5fd804c0327a5e5ea26615550e706942f348b197d5475ff34c19733aee4b2e69", size = 10436892 }, ] [[package]] name = "scons" -version = "4.8.1" +version = "4.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b9/76/a2c1293642f9a448f2d012cabf525be69ca5abf4af289bc0935ac1554ee8/scons-4.8.1.tar.gz", hash = "sha256:5b641357904d2f56f7bfdbb37e165ab996b6143c948b9df0efc7305f54949daa", size = 3244174 } +sdist = { url = "https://files.pythonhosted.org/packages/61/7e/79e07dc2eb8874580934cd0c834a8a78f15d5b0d6155a424f6c7b35441c3/scons-4.9.0.tar.gz", hash = "sha256:f1a5e161bf3d1411d780d65d7919654b9405555994621d3d68e42d62114b592a", size = 3251763 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/a7/823091100c88d7d992c8c608d82a88ed59e227d13f8ccb33ea7a96d43d51/SCons-4.8.1-py3-none-any.whl", hash = "sha256:a4c3b434330e2d7d975002fd6783284ba348bf394db94c8f83fdc5bf69cdb8d7", size = 4123564 }, + { url = "https://files.pythonhosted.org/packages/18/f5/77635f6c68ed742a23e1f52977267e191ea7c2aec89b9604a86487405da1/scons-4.9.0-py3-none-any.whl", hash = "sha256:c8cc309db0f91cffdc27c1374fa3d31e9421bcb31d210de071b0b11adc84739b", size = 4130637 }, ] [[package]] @@ -4919,14 +4919,14 @@ wheels = [ [[package]] name = "types-requests" -version = "2.32.0.20241016" +version = "2.32.0.20250306" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/3c/4f2a430c01a22abd49a583b6b944173e39e7d01b688190a5618bd59a2e22/types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95", size = 18065 } +sdist = { url = "https://files.pythonhosted.org/packages/09/1a/beaeff79ef9efd186566ba5f0d95b44ae21f6d31e9413bcfbef3489b6ae3/types_requests-2.32.0.20250306.tar.gz", hash = "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1", size = 23012 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/01/485b3026ff90e5190b5e24f1711522e06c79f4a56c8f4b95848ac072e20f/types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747", size = 15836 }, + { url = "https://files.pythonhosted.org/packages/99/26/645d89f56004aa0ba3b96fec27793e3c7e62b40982ee069e52568922b6db/types_requests-2.32.0.20250306-py3-none-any.whl", hash = "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b", size = 20673 }, ] [[package]]