diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c86c044772..1dd06f77e9 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 ab0f3c1bee..3bcb143825 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 7936192ca8..75e6249b76 100644 --- a/.github/workflows/ui_preview.yaml +++ b/.github/workflows/ui_preview.yaml @@ -84,7 +84,7 @@ jobs: run: >- sudo apt-get install -y imagemagick - scenes="homescreen settings_device settings_software settings_toggles 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 4562e47817..834b463083 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 67321c67cc..8a5638bc84 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,8 @@ -Version 0.9.8 (2025-02-27) +Version 0.9.9 (2025-04-XX) +======================== +* Tesla Model 3 and Y support thanks to lukasloetkolben! + +Version 0.9.8 (2025-02-28) ======================== * New driving model * Model now gates applying positive acceleration in Chill mode @@ -8,12 +12,11 @@ Version 0.9.8 (2025-02-27) * 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 -* FIREHOSE mode - * Allows you to maximize your training data uploads to improve the models +* 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 * Rivian R1S and R1T support thanks to lukasloetkolben! -* Tesla Model 3 and Y support thanks to lukasloetkolben! * Ford F-150, F-150 Hybrid, Mach-E, and Ranger support Version 0.9.7 (2024-06-13) diff --git a/cereal/log.capnp b/cereal/log.capnp index 1209f3fd95..78b511ca64 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; @@ -2238,6 +2242,11 @@ struct LiveParametersData { roll @14 :Float32; debugFilterState @16 :FilterState; + angleOffsetValid @17 :Bool = true; + angleOffsetAverageValid @18 :Bool = true; + steerRatioValid @19 :Bool = true; + stiffnessFactorValid @20 :Bool = true; + yawRateDEPRECATED @7 :Float32; filterStateDEPRECATED @15 :LiveLocationKalman.Measurement; @@ -2564,7 +2573,7 @@ struct Event { # DO change the name of the field and struct # DON'T change the ID (e.g. @107) - # DON'T change which struct it points to + # DON'T change which struct it points to customReserved0 @107 :Custom.CustomReserved0; customReserved1 @108 :Custom.CustomReserved1; customReserved2 @109 :Custom.CustomReserved2; 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 40a69ebd88..2b540b744c 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}, {"ExperimentalMode", PERSISTENT}, {"ExperimentalModeConfirmed", PERSISTENT}, - {"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/common/version.h b/common/version.h index 1f651fb392..6351a5b3ff 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.9.8" +#define COMMA_VERSION "0.9.9" diff --git a/conftest.py b/conftest.py index a14a33c37d..9eda5d612a 100644 --- a/conftest.py +++ b/conftest.py @@ -77,8 +77,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 762799b6b2..a3b1fe6351 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -58,11 +58,11 @@ A supported vehicle is one that just works when you install a comma device. All |Genesis|G90 2017-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|GV60 (Advanced Trim) 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|GV60 (Performance Trim) 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|GV70 Electrified (Australia Only) 2022[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|GV70 Electrified (with HDA II) 2023-24[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV80 2023[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV80 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -132,8 +132,8 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Carnival 2022-24[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Carnival (China only) 2023[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Carnival 2022-24[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Carnival (China only) 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|EV6 (Southeast Asia only) 2022-24[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|EV6 (with HDA II) 2022-24[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -162,9 +162,9 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento 2021-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento Hybrid 2021-23[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento Plug-in Hybrid 2022-23[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento 2021-23[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento Hybrid 2021-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento Plug-in Hybrid 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sportage 2023-24[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sportage Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -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/msgq_repo b/msgq_repo index 095f1e2361..ad9020c430 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 095f1e23613d47031a9b23e3f9ed5dd3fe01a06e +Subproject commit ad9020c430362d17c0edf97747d344389234be4d diff --git a/opendbc_repo b/opendbc_repo index da47550d30..e98cbe9f08 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit da47550d30271cb6782ffa96891ee21dfc82b236 +Subproject commit e98cbe9f089c131ffd4b784c6af9b45752d1f51d diff --git a/panda b/panda index 0924df1e8e..1d5b89956b 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 0924df1e8e83e31e9f9fde14e70f0777fc149a65 +Subproject commit 1d5b89956b32bbda2940724ce70c5166e44668c5 diff --git a/pyproject.toml b/pyproject.toml index e554a2f29d..58e714e3a6 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", @@ -215,7 +216,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 8b26dc74ab..735ef5e043 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -74,7 +74,8 @@ find . -name '*.pyc' -delete find . -name 'moc_*' -delete find . -name '__pycache__' -delete rm -rf .sconsign.dblite Jenkinsfile release/ -rm selfdrive/modeld/models/supercombo.onnx +rm selfdrive/modeld/models/driving_vision.onnx +rm selfdrive/modeld/models/driving_policy.onnx find third_party/ -name '*x86*' -exec rm -r {} + find third_party/ -name '*Darwin*' -exec rm -r {} + @@ -91,14 +92,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 697d0273a5..b825808acb 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_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 9a046ddee6..ca96884d7c 100644 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -80,6 +80,8 @@ class TestCarInterfaces: CC = car.CarControl.new_message(**cc_msg) CC.enabled = True + CC.latActive = True + CC.longActive = True CC = CC.as_reader() for _ in range(10): car_interface.update([]) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 90d073dd29..7f0cf4a979 100644 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -28,6 +28,7 @@ from openpilot.tools.lib.logreader import LogReader, LogsUnavailable, openpilotc from openpilot.tools.lib.route import SegmentName SafetyModel = car.CarParams.SafetyModel +SteerControlType = structs.CarParams.SteerControlType NUM_JOBS = int(os.environ.get("NUM_JOBS", "1")) JOB_ID = int(os.environ.get("JOB_ID", "0")) @@ -182,7 +183,7 @@ class TestCarModelBase(unittest.TestCase): # make sure car params are within a valid range self.assertGreater(self.CP.mass, 1) - if self.CP.steerControlType != structs.CarParams.SteerControlType.angle: + if self.CP.steerControlType != SteerControlType.angle: tuning = self.CP.lateralTuning.which() if tuning == 'pid': self.assertTrue(len(self.CP.lateralTuning.pid.kpV)) @@ -265,6 +266,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") @@ -319,6 +323,8 @@ class TestCarModelBase(unittest.TestCase): msg_strategy = st.binary(min_size=size, max_size=size) msgs = data.draw(st.lists(msg_strategy, min_size=20)) + vehicle_speed_seen = self.CP.steerControlType == SteerControlType.angle and not self.CP.notCar + for dat in msgs: # due to panda updating state selectively, only edges are expected to match # TODO: warm up CarState with real CAN messages to check edge of both sources @@ -327,6 +333,8 @@ class TestCarModelBase(unittest.TestCase): prev_panda_brake = self.safety.get_brake_pressed_prev() prev_panda_regen_braking = self.safety.get_regen_braking_prev() prev_panda_vehicle_moving = self.safety.get_vehicle_moving() + prev_panda_vehicle_speed_min = self.safety.get_vehicle_speed_min() + prev_panda_vehicle_speed_max = self.safety.get_vehicle_speed_max() prev_panda_cruise_engaged = self.safety.get_cruise_engaged_prev() prev_panda_acc_main_on = self.safety.get_acc_main_on() @@ -354,6 +362,16 @@ class TestCarModelBase(unittest.TestCase): if self.safety.get_vehicle_moving() != prev_panda_vehicle_moving: self.assertEqual(not CS.standstill, self.safety.get_vehicle_moving()) + # check vehicle speed if angle control car or available + if self.safety.get_vehicle_speed_min() > 0 or self.safety.get_vehicle_speed_max() > 0: + vehicle_speed_seen = True + + if vehicle_speed_seen and (self.safety.get_vehicle_speed_min() != prev_panda_vehicle_speed_min or + self.safety.get_vehicle_speed_max() != prev_panda_vehicle_speed_max): + v_ego_raw = CS.vEgoRaw / self.CP.wheelSpeedFactor + self.assertFalse(v_ego_raw > (self.safety.get_vehicle_speed_max() + 1e-3) or + v_ego_raw < (self.safety.get_vehicle_speed_min() - 1e-3)) + if not (self.CP.brand == "honda" and not (self.CP.flags & HondaFlags.BOSCH)): if self.safety.get_cruise_engaged_prev() != prev_panda_cruise_engaged: self.assertEqual(CS.cruiseState.enabled, self.safety.get_cruise_engaged_prev()) @@ -379,6 +397,7 @@ class TestCarModelBase(unittest.TestCase): controls_allowed_prev = False CS_prev = car.CarState.new_message() checks = defaultdict(int) + vehicle_speed_seen = self.CP.steerControlType == SteerControlType.angle and not self.CP.notCar for idx, can in enumerate(self.can_msgs): CS = self.CI.update(can_capnp_to_list((can.as_builder().to_bytes(), ))).as_reader() for msg in filter(lambda m: m.src in range(64), can.can): @@ -399,6 +418,15 @@ class TestCarModelBase(unittest.TestCase): checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev() checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving() + # check vehicle speed if angle control car or available + if self.safety.get_vehicle_speed_min() > 0 or self.safety.get_vehicle_speed_max() > 0: + vehicle_speed_seen = True + + if vehicle_speed_seen: + v_ego_raw = CS.vEgoRaw / self.CP.wheelSpeedFactor + checks['vEgoRaw'] += (v_ego_raw > (self.safety.get_vehicle_speed_max() + 1e-3) or + v_ego_raw < (self.safety.get_vehicle_speed_min() - 1e-3)) + # TODO: remove this exception once this mismatch is resolved brake_pressed = CS.brakePressed if CS.brakePressed and not self.safety.get_brake_pressed_prev(): diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index c215a42a6d..dcc1c74794 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -10,22 +10,22 @@ from openpilot.common.realtime import config_realtime_process, Priority, Ratekee from openpilot.common.swaglog import cloudlog from opendbc.car.car_helpers import get_car_interface +from opendbc.car.vehicle_model import VehicleModel from openpilot.selfdrive.controls.lib.drive_helpers import clip_curvature from openpilot.selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.longcontrol import LongControl -from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose - State = log.SelfdriveState.OpenpilotState LaneChangeState = log.LaneChangeState LaneChangeDirection = log.LaneChangeDirection ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys()) + class Controls: def __init__(self) -> None: self.params = Params() @@ -40,11 +40,11 @@ class Controls: 'driverMonitoringState', 'onroadEvents', 'driverAssistance'], poll='selfdriveState') self.pm = messaging.PubMaster(['carControl', 'controlsState']) - self.steer_limited = False + self.steer_limited_by_controls = False self.desired_curvature = 0.0 self.pose_calibrator = PoseCalibrator() - self.calibrated_pose: Pose|None = None + self.calibrated_pose: Pose | None = None self.LoC = LongControl(self.CP) self.VM = VehicleModel(self.CP) @@ -109,12 +109,12 @@ 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 - actuators.steer = float(steer) + 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 for p in ACTUATOR_FIELDS: @@ -161,10 +161,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.steer - CO.actuatorsOutput.steer) > 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 @@ -180,7 +180,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) @@ -212,6 +212,7 @@ class Controls: self.publish(CC, lac_log) rk.monitor_time() + def main(): config_realtime_process(4, Priority.CTRL_HIGH) controls = Controls() 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 d66e5be28d..3aef57baca 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -3,9 +3,9 @@ import numpy as np from cereal import log from opendbc.car.interfaces import LatControlInputs +from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from openpilot.selfdrive.controls.lib.latcontrol import LatControl from openpilot.common.pid import PIDController -from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY # At higher speeds (25+mph) we can assume: # Lateral acceleration achieved by a specific car correlates to @@ -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 ba4bd0faec..564c93be0d 100644 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -5,10 +5,10 @@ from opendbc.car.car_helpers import interfaces from opendbc.car.honda.values import CAR as HONDA from opendbc.car.toyota.values import CAR as TOYOTA from opendbc.car.nissan.values import CAR as NISSAN +from opendbc.car.vehicle_model import VehicleModel from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle -from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel from openpilot.selfdrive.locationd.helpers import Pose from openpilot.common.mock.generators import generate_livePose @@ -33,7 +33,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/controls/lib/tests/test_vehicle_model.py b/selfdrive/controls/lib/tests/test_vehicle_model.py deleted file mode 100644 index d15519a866..0000000000 --- a/selfdrive/controls/lib/tests/test_vehicle_model.py +++ /dev/null @@ -1,67 +0,0 @@ -import pytest -import math - -import numpy as np - -from opendbc.car.honda.interface import CarInterface -from opendbc.car.honda.values import CAR -from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices - - -class TestVehicleModel: - def setup_method(self): - CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC) - self.VM = VehicleModel(CP) - - def test_round_trip_yaw_rate(self): - # TODO: fix VM to work at zero speed - for u in np.linspace(1, 30, num=10): - for roll in np.linspace(math.radians(-20), math.radians(20), num=11): - for sa in np.linspace(math.radians(-20), math.radians(20), num=11): - yr = self.VM.yaw_rate(sa, u, roll) - new_sa = self.VM.get_steer_from_yaw_rate(yr, u, roll) - - assert sa == pytest.approx(new_sa) - - def test_dyn_ss_sol_against_yaw_rate(self): - """Verify that the yaw_rate helper function matches the results - from the state space model.""" - - for roll in np.linspace(math.radians(-20), math.radians(20), num=11): - for u in np.linspace(1, 30, num=10): - for sa in np.linspace(math.radians(-20), math.radians(20), num=11): - - # Compute yaw rate based on state space model - _, yr1 = dyn_ss_sol(sa, u, roll, self.VM) - - # Compute yaw rate using direct computations - yr2 = self.VM.yaw_rate(sa, u, roll) - assert float(yr1[0]) == pytest.approx(yr2) - - def test_syn_ss_sol_simulate(self): - """Verifies that dyn_ss_sol matches a simulation""" - - for roll in np.linspace(math.radians(-20), math.radians(20), num=11): - for u in np.linspace(1, 30, num=10): - A, B = create_dyn_state_matrices(u, self.VM) - - # Convert to discrete time system - dt = 0.01 - top = np.hstack((A, B)) - full = np.vstack((top, np.zeros_like(top))) * dt - Md = sum([np.linalg.matrix_power(full, k) / math.factorial(k) for k in range(25)]) - Ad = Md[:A.shape[0], :A.shape[1]] - Bd = Md[:A.shape[0], A.shape[1]:] - - for sa in np.linspace(math.radians(-20), math.radians(20), num=11): - inp = np.array([[sa], [roll]]) - - # Simulate for 1 second - x1 = np.zeros((2, 1)) - for _ in range(100): - x1 = Ad @ x1 + Bd @ inp - - # Compute steady state solution directly - x2 = dyn_ss_sol(sa, u, roll, self.VM) - - np.testing.assert_almost_equal(x1, x2, decimal=3) diff --git a/selfdrive/controls/lib/vehicle_model.py b/selfdrive/controls/lib/vehicle_model.py deleted file mode 100755 index b6f50b4ba8..0000000000 --- a/selfdrive/controls/lib/vehicle_model.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/env python3 -""" -Dynamic bicycle model from "The Science of Vehicle Dynamics (2014), M. Guiggiani" - -The state is x = [v, r]^T -with v lateral speed [m/s], and r rotational speed [rad/s] - -The input u is the steering angle [rad], and roll [rad] - -The system is defined by -x_dot = A*x + B*u - -A depends on longitudinal speed, u [m/s], and vehicle parameters CP -""" - -import numpy as np -from numpy.linalg import solve - -from cereal import car - -ACCELERATION_DUE_TO_GRAVITY = 9.8 - - -class VehicleModel: - def __init__(self, CP: car.CarParams): - """ - Args: - CP: Car Parameters - """ - # for math readability, convert long names car params into short names - self.m: float = CP.mass - self.j: float = CP.rotationalInertia - self.l: float = CP.wheelbase - self.aF: float = CP.centerToFront - self.aR: float = CP.wheelbase - CP.centerToFront - self.chi: float = CP.steerRatioRear - - self.cF_orig: float = CP.tireStiffnessFront - self.cR_orig: float = CP.tireStiffnessRear - self.update_params(1.0, CP.steerRatio) - - def update_params(self, stiffness_factor: float, steer_ratio: float) -> None: - """Update the vehicle model with a new stiffness factor and steer ratio""" - self.cF: float = stiffness_factor * self.cF_orig - self.cR: float = stiffness_factor * self.cR_orig - self.sR: float = steer_ratio - - def steady_state_sol(self, sa: float, u: float, roll: float) -> np.ndarray: - """Returns the steady state solution. - - If the speed is too low we can't use the dynamic model (tire slip is undefined), - we then have to use the kinematic model - - Args: - sa: Steering wheel angle [rad] - u: Speed [m/s] - roll: Road Roll [rad] - - Returns: - 2x1 matrix with steady state solution (lateral speed, rotational speed) - """ - if u > 0.1: - return dyn_ss_sol(sa, u, roll, self) - else: - return kin_ss_sol(sa, u, self) - - def calc_curvature(self, sa: float, u: float, roll: float) -> float: - """Returns the curvature. Multiplied by the speed this will give the yaw rate. - - Args: - sa: Steering wheel angle [rad] - u: Speed [m/s] - roll: Road Roll [rad] - - Returns: - Curvature factor [1/m] - """ - return (self.curvature_factor(u) * sa / self.sR) + self.roll_compensation(roll, u) - - def curvature_factor(self, u: float) -> float: - """Returns the curvature factor. - Multiplied by wheel angle (not steering wheel angle) this will give the curvature. - - Args: - u: Speed [m/s] - - Returns: - Curvature factor [1/m] - """ - sf = calc_slip_factor(self) - return (1. - self.chi) / (1. - sf * u**2) / self.l - - def get_steer_from_curvature(self, curv: float, u: float, roll: float) -> float: - """Calculates the required steering wheel angle for a given curvature - - Args: - curv: Desired curvature [1/m] - u: Speed [m/s] - roll: Road Roll [rad] - - Returns: - Steering wheel angle [rad] - """ - - return (curv - self.roll_compensation(roll, u)) * self.sR * 1.0 / self.curvature_factor(u) - - def roll_compensation(self, roll: float, u: float) -> float: - """Calculates the roll-compensation to curvature - - Args: - roll: Road Roll [rad] - u: Speed [m/s] - - Returns: - Roll compensation curvature [rad] - """ - sf = calc_slip_factor(self) - - if abs(sf) < 1e-6: - return 0 - else: - return (ACCELERATION_DUE_TO_GRAVITY * roll) / ((1 / sf) - u**2) - - def get_steer_from_yaw_rate(self, yaw_rate: float, u: float, roll: float) -> float: - """Calculates the required steering wheel angle for a given yaw_rate - - Args: - yaw_rate: Desired yaw rate [rad/s] - u: Speed [m/s] - roll: Road Roll [rad] - - Returns: - Steering wheel angle [rad] - """ - curv = yaw_rate / u - return self.get_steer_from_curvature(curv, u, roll) - - def yaw_rate(self, sa: float, u: float, roll: float) -> float: - """Calculate yaw rate - - Args: - sa: Steering wheel angle [rad] - u: Speed [m/s] - roll: Road Roll [rad] - - Returns: - Yaw rate [rad/s] - """ - return self.calc_curvature(sa, u, roll) * u - - -def kin_ss_sol(sa: float, u: float, VM: VehicleModel) -> np.ndarray: - """Calculate the steady state solution at low speeds - At low speeds the tire slip is undefined, so a kinematic - model is used. - - Args: - sa: Steering angle [rad] - u: Speed [m/s] - VM: Vehicle model - - Returns: - 2x1 matrix with steady state solution - """ - K = np.zeros((2, 1)) - K[0, 0] = VM.aR / VM.sR / VM.l * u - K[1, 0] = 1. / VM.sR / VM.l * u - return K * sa - - -def create_dyn_state_matrices(u: float, VM: VehicleModel) -> tuple[np.ndarray, np.ndarray]: - """Returns the A and B matrix for the dynamics system - - Args: - u: Vehicle speed [m/s] - VM: Vehicle model - - Returns: - A tuple with the 2x2 A matrix, and 2x2 B matrix - - Parameters in the vehicle model: - cF: Tire stiffness Front [N/rad] - cR: Tire stiffness Front [N/rad] - aF: Distance from CG to front wheels [m] - aR: Distance from CG to rear wheels [m] - m: Mass [kg] - j: Rotational inertia [kg m^2] - sR: Steering ratio [-] - chi: Steer ratio rear [-] - """ - A = np.zeros((2, 2)) - B = np.zeros((2, 2)) - A[0, 0] = - (VM.cF + VM.cR) / (VM.m * u) - A[0, 1] = - (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.m * u) - u - A[1, 0] = - (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.j * u) - A[1, 1] = - (VM.cF * VM.aF**2 + VM.cR * VM.aR**2) / (VM.j * u) - - # Steering input - B[0, 0] = (VM.cF + VM.chi * VM.cR) / VM.m / VM.sR - B[1, 0] = (VM.cF * VM.aF - VM.chi * VM.cR * VM.aR) / VM.j / VM.sR - - # Roll input - B[0, 1] = -ACCELERATION_DUE_TO_GRAVITY - - return A, B - - -def dyn_ss_sol(sa: float, u: float, roll: float, VM: VehicleModel) -> np.ndarray: - """Calculate the steady state solution when x_dot = 0, - Ax + Bu = 0 => x = -A^{-1} B u - - Args: - sa: Steering angle [rad] - u: Speed [m/s] - roll: Road Roll [rad] - VM: Vehicle model - - Returns: - 2x1 matrix with steady state solution - """ - A, B = create_dyn_state_matrices(u, VM) - inp = np.array([[sa], [roll]]) - return -solve(A, B) @ inp # type: ignore - - -def calc_slip_factor(VM: VehicleModel) -> float: - """The slip factor is a measure of how the curvature changes with speed - it's positive for Oversteering vehicle, negative (usual case) otherwise. - """ - return VM.m * (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.l**2 * VM.cF * VM.cR) diff --git a/selfdrive/debug/debug_fw_fingerprinting_offline.py b/selfdrive/debug/debug_fw_fingerprinting_offline.py index 50a3751aa6..d841e91053 100755 --- a/selfdrive/debug/debug_fw_fingerprinting_offline.py +++ b/selfdrive/debug/debug_fw_fingerprinting_offline.py @@ -6,7 +6,7 @@ from openpilot.tools.lib.live_logreader import live_logreader from openpilot.tools.lib.logreader import LogReader, ReadMode -def main(route: str | None, addrs: list[int]): +def main(route: str | None, addrs: list[int], rxoffset: int | None): """ TODO: - highlight TX vs RX clearly @@ -23,7 +23,7 @@ def main(route: str | None, addrs: list[int]): prev_mono_time = 0 # include rx addresses - addrs = addrs + [uds.get_rx_addr_for_tx_addr(addr) for addr in addrs] + addrs = addrs + [uds.get_rx_addr_for_tx_addr(addr, rxoffset) for addr in addrs] for msg in lr: if msg.which() == 'can': @@ -44,7 +44,9 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description='View back and forth ISO-TP communication between various ECUs given an address') parser.add_argument('route', nargs='?', help='Route name, live if not specified') parser.add_argument('--addrs', nargs='*', default=[], help='List of tx address to view (0x7e0 for engine)') + parser.add_argument('--rxoffset', default='') args = parser.parse_args() addrs = [int(addr, base=16) if addr.startswith('0x') else int(addr) for addr in args.addrs] - main(args.route, addrs) + rxoffset = int(args.rxoffset, base=16) if args.rxoffset else None + main(args.route, addrs, rxoffset) diff --git a/selfdrive/debug/max_lat_accel.py b/selfdrive/debug/max_lat_accel.py new file mode 100755 index 0000000000..2369d99d86 --- /dev/null +++ b/selfdrive/debug/max_lat_accel.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +import argparse +import numpy as np +import matplotlib.pyplot as plt +from typing import NamedTuple +from openpilot.tools.lib.logreader import LogReader +from openpilot.selfdrive.locationd.models.pose_kf import EARTH_G + +RLOG_MIN_LAT_ACTIVE = 50 +RLOG_MIN_STEERING_UNPRESSED = 50 +RLOG_MIN_REQUESTING_MAX = 25 # sample many times after reaching max torque + +QLOG_DECIMATION = 10 + + +class Event(NamedTuple): + lateral_accel: float + speed: float + roll: float + timestamp: float # relative to start of route (s) + + +def find_events(lr: LogReader, qlog: bool = False) -> list[Event]: + min_lat_active = RLOG_MIN_LAT_ACTIVE // QLOG_DECIMATION if qlog else RLOG_MIN_LAT_ACTIVE + min_steering_unpressed = RLOG_MIN_STEERING_UNPRESSED // QLOG_DECIMATION if qlog else RLOG_MIN_STEERING_UNPRESSED + min_requesting_max = RLOG_MIN_REQUESTING_MAX // QLOG_DECIMATION if qlog else RLOG_MIN_REQUESTING_MAX + + events = [] + + start_ts = 0 + + # state tracking + steering_unpressed = 0 # frames + requesting_max = 0 # frames + lat_active = 0 # frames + + # current state + curvature = 0 + v_ego = 0 + roll = 0 + + for msg in lr: + if msg.which() == 'carControl': + if start_ts == 0: + start_ts = msg.logMonoTime + + lat_active = lat_active + 1 if msg.carControl.latActive else 0 + + elif msg.which() == 'carOutput': + # if we test with driver torque safety, max torque can be slightly noisy + requesting_max = requesting_max + 1 if abs(msg.carOutput.actuatorsOutput.torque) > 0.95 else 0 + + elif msg.which() == 'carState': + steering_unpressed = steering_unpressed + 1 if not msg.carState.steeringPressed else 0 + v_ego = msg.carState.vEgo + + elif msg.which() == 'controlsState': + curvature = msg.controlsState.curvature + + elif msg.which() == 'liveParameters': + roll = msg.liveParameters.roll + + if lat_active > min_lat_active and steering_unpressed > min_steering_unpressed and requesting_max > min_requesting_max: + # TODO: record max lat accel at the end of the event, need to use the past lat accel as overriding can happen before we detect it + requesting_max = 0 + + current_lateral_accel = curvature * v_ego ** 2 - roll * EARTH_G + events.append(Event(current_lateral_accel, v_ego, roll, round((msg.logMonoTime - start_ts) * 1e-9, 2))) + print(events[-1]) + + return events + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Find max lateral acceleration events", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument("route") + args = parser.parse_args() + + lr = LogReader(args.route, sort_by_time=True) + qlog = args.route.endswith('/q') + if qlog: + print('WARNING: Treating route as qlog!') + + print('Finding events...') + events = find_events(lr, qlog=qlog) + + print() + print(f'Found {len(events)} events') + + perc_left_accel = -np.percentile([-ev.lateral_accel for ev in events if ev.lateral_accel < 0], 90) + perc_right_accel = np.percentile([ev.lateral_accel for ev in events if ev.lateral_accel > 0], 90) + + CP = lr.first('carParams') + + plt.ion() + plt.clf() + plt.suptitle(f'{CP.carFingerprint} - Max lateral acceleration events') + plt.title(args.route) + plt.scatter([ev.speed for ev in events], [ev.lateral_accel for ev in events], label='max lateral accel events') + + plt.plot([0, 35], [3, 3], c='r', label='ISO 11270 - 3 m/s^2') + plt.plot([0, 35], [-3, -3], c='r') + + plt.plot([0, 35], [perc_left_accel, perc_left_accel], c='g', linestyle='--', label='90th percentile left lateral accel') + plt.plot([0, 35], [perc_right_accel, perc_right_accel], c='#ff7f0e', linestyle='--', label='90th percentile right lateral accel') + plt.text(0.4, float(perc_left_accel + 0.4), f'{perc_left_accel:.2f} m/s^2', verticalalignment='center', fontsize=12) + plt.text(0.4, float(perc_right_accel - 0.4), f'{perc_right_accel:.2f} m/s^2', verticalalignment='center', fontsize=12) + + plt.xlim(0, 35) + plt.ylim(-5, 5) + plt.xlabel('speed (m/s)') + plt.ylabel('lateral acceleration (m/s^2)') + plt.legend() + plt.show(block=True) 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/models/car_kf.py b/selfdrive/locationd/models/car_kf.py index 87a93cdd0c..5f40f19e46 100755 --- a/selfdrive/locationd/models/car_kf.py +++ b/selfdrive/locationd/models/car_kf.py @@ -5,7 +5,7 @@ from typing import Any import numpy as np -from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY +from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from openpilot.selfdrive.locationd.models.constants import ObservationKind from openpilot.common.swaglog import cloudlog diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index da2f3f20bb..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, @@ -224,17 +225,21 @@ def main(): liveParameters.posenetValid = True liveParameters.sensorValid = sensors_valid liveParameters.steerRatio = float(x[States.STEER_RATIO].item()) + liveParameters.steerRatioValid = min_sr <= liveParameters.steerRatio <= max_sr liveParameters.stiffnessFactor = float(x[States.STIFFNESS].item()) + liveParameters.stiffnessFactorValid = 0.2 <= liveParameters.stiffnessFactor <= 5.0 liveParameters.roll = float(roll) liveParameters.angleOffsetAverageDeg = float(angle_offset_average) + liveParameters.angleOffsetAverageValid = bool(avg_offset_valid) liveParameters.angleOffsetDeg = float(angle_offset) + liveParameters.angleOffsetValid = bool(total_offset_valid) liveParameters.valid = all(( - avg_offset_valid, - total_offset_valid, + liveParameters.angleOffsetAverageValid, + liveParameters.angleOffsetValid , roll_valid, roll_std < ROLL_STD_MAX, - 0.2 <= liveParameters.stiffnessFactor <= 5.0, - min_sr <= liveParameters.steerRatio <= max_sr, + liveParameters.stiffnessFactorValid, + liveParameters.steerRatioValid, )) liveParameters.steerRatioStd = float(P[States.STEER_RATIO].item()) liveParameters.stiffnessFactorStd = float(P[States.STIFFNESS].item()) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 986c5349f5..590b6cc6f7 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -4,11 +4,11 @@ from collections import deque, defaultdict import cereal.messaging as messaging from cereal import car, log +from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from openpilot.common.params import Params from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, PoseCalibrator, Pose HISTORY = 5 # secs @@ -167,7 +167,7 @@ class TorqueEstimator(ParameterEstimator): self.raw_points["lat_active"].append(msg.latActive) elif which == "carOutput": self.raw_points["carOutput_t"].append(t + self.lag) - self.raw_points["steer_torque"].append(-msg.actuatorsOutput.steer) + self.raw_points["steer_torque"].append(-msg.actuatorsOutput.torque) elif which == "carState": self.raw_points["carState_t"].append(t + self.lag) # TODO: check if high aEgo affects resulting lateral accel 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/SConscript b/selfdrive/modeld/SConscript index 6caac3bce3..cecebfa18b 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -32,10 +32,11 @@ lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LI tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x] # Get model metadata -fn = File("models/supercombo").abspath -script_files = [File(Dir("#selfdrive/modeld").File("get_model_metadata.py").abspath)] -cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' -lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) +for model_name in ['driving_vision', 'driving_policy']: + fn = File(f"models/{model_name}").abspath + script_files = [File(Dir("#selfdrive/modeld").File("get_model_metadata.py").abspath)] + cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' + lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) # Compile tinygrad model pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' @@ -46,7 +47,7 @@ elif arch == 'Darwin': else: device_string = 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0' -for model_name in ['supercombo', 'dmonitoring_model']: +for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath cmd = f'{pythonpath_string} {device_string} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' lenv.Command(fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, cmd) diff --git a/selfdrive/modeld/constants.py b/selfdrive/modeld/constants.py index cf5157591e..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 = 99 + 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/get_model_metadata.py b/selfdrive/modeld/get_model_metadata.py index 0f1fd2a98b..2001d23d75 100755 --- a/selfdrive/modeld/get_model_metadata.py +++ b/selfdrive/modeld/get_model_metadata.py @@ -24,8 +24,7 @@ if __name__ == "__main__": assert output_slices is not None, 'output_slices not found in metadata' metadata = { - 'policy_model': get_metadata_value_by_name(model, 'policy_model'), - 'vision_model': get_metadata_value_by_name(model, 'vision_model'), + 'model_checkpoint': get_metadata_value_by_name(model, 'model_checkpoint'), 'output_slices': pickle.loads(codecs.decode(output_slices.encode(), "base64")), 'input_shapes': dict([get_name_and_shape(x) for x in model.graph.input]), 'output_shapes': dict([get_name_and_shape(x) for x in model.graph.output]) 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 7a3c7288b0..1e557942fe 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -35,8 +35,10 @@ from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, PROCESS_NAME = "selfdrive.modeld.modeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') -MODEL_PKL_PATH = Path(__file__).parent / 'models/supercombo_tinygrad.pkl' -METADATA_PATH = Path(__file__).parent / 'models/supercombo_metadata.pkl' +VISION_PKL_PATH = Path(__file__).parent / 'models/driving_vision_tinygrad.pkl' +POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl' +VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl' +POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl' class FrameMeta: frame_id: int = 0 @@ -54,35 +56,53 @@ 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) - # img buffers are managed in openCL transform code + 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+1), 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+1), 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(METADATA_PATH, 'rb') as f: - model_metadata = pickle.load(f) - self.input_shapes = model_metadata['input_shapes'] + with open(VISION_METADATA_PATH, 'rb') as f: + vision_metadata = pickle.load(f) + self.vision_input_shapes = vision_metadata['input_shapes'] + self.vision_output_slices = vision_metadata['output_slices'] + vision_output_size = vision_metadata['output_shapes']['outputs'][1] - self.output_slices = model_metadata['output_slices'] - net_output_size = model_metadata['output_shapes']['outputs'][1] - self.output = np.zeros(net_output_size, dtype=np.float32) + with open(POLICY_METADATA_PATH, 'rb') as f: + policy_metadata = pickle.load(f) + self.policy_input_shapes = policy_metadata['input_shapes'] + self.policy_output_slices = policy_metadata['output_slices'] + policy_output_size = policy_metadata['output_shapes']['outputs'][1] + + # img buffers are managed in openCL transform code + self.vision_inputs: dict[str, Tensor] = {} + self.vision_output = np.zeros(vision_output_size, dtype=np.float32) + self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} + self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() - self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} - with open(MODEL_PKL_PATH, "rb") as f: - self.model_run = pickle.load(f) + with open(VISION_PKL_PATH, "rb") as f: + self.vision_run = pickle.load(f) - def slice_outputs(self, model_outputs: np.ndarray) -> dict[str, np.ndarray]: - parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in self.output_slices.items()} - if SEND_RAW_PRED: - parsed_model_outputs['raw_pred'] = model_outputs.copy() + with open(POLICY_PKL_PATH, "rb") as f: + self.policy_run = pickle.load(f) + + def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: + parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} return parsed_model_outputs def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_wide: np.ndarray, @@ -92,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'] @@ -103,29 +124,36 @@ class ModelState: if TICI: # The imgs tensors are backed by opencl memory, only need init once for key in imgs_cl: - if key not in self.tensor_inputs: - self.tensor_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.input_shapes[key], dtype=dtypes.uint8) + if key not in self.vision_inputs: + self.vision_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.vision_input_shapes[key], dtype=dtypes.uint8) else: for key in imgs_cl: - self.numpy_inputs[key] = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.input_shapes[key]) - self.tensor_inputs[key] = Tensor(self.numpy_inputs[key], dtype=dtypes.uint8).realize() - + frame_input = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.vision_input_shapes[key]) + self.vision_inputs[key] = Tensor(frame_input, dtype=dtypes.uint8).realize() if prepare_only: return None - self.output = self.model_run(**self.tensor_inputs).numpy().flatten() + 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)) - outputs = self.parser.parse_outputs(self.slice_outputs(self.output)) - - self.numpy_inputs['features_buffer'][0,:-1] = self.numpy_inputs['features_buffer'][0,1:] - self.numpy_inputs['features_buffer'][0,-1] = outputs['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,:] = outputs['desired_curvature'][0, :] - return outputs + 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: + combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) + + return combined_outputs_dict def main(demo=False): diff --git a/selfdrive/modeld/models/README.md b/selfdrive/modeld/models/README.md index 9e11ca8255..255f28d80e 100644 --- a/selfdrive/modeld/models/README.md +++ b/selfdrive/modeld/models/README.md @@ -1,8 +1,8 @@ ## Neural networks in openpilot To view the architecture of the ONNX networks, you can use [netron](https://netron.app/) -## Supercombo -### Supercombo input format (Full size: 799906 x float32) +## Driving Model (vision model + temporal policy model) +### Vision inputs (Full size: 799906 x float32) * **image stream** * Two consecutive images (256 * 512 * 3 in RGB) recorded at 20 Hz : 393216 = 2 * 6 * 128 * 256 * Each 256 * 512 image is represented in YUV420 with 6 channels : 6 * 128 * 256 @@ -15,16 +15,21 @@ To view the architecture of the ONNX networks, you can use [netron](https://netr * Channels 0,1,2,3 represent the full-res Y channel and are represented in numpy as Y[::2, ::2], Y[::2, 1::2], Y[1::2, ::2], and Y[1::2, 1::2] * Channel 4 represents the half-res U channel * Channel 5 represents the half-res V channel +### Policy inputs * **desire** * one-hot encoded buffer to command model to execute certain actions, bit needs to be sent for the past 5 seconds (at 20FPS) : 100 * 8 * **traffic convention** * one-hot encoded vector to tell model whether traffic is right-hand or left-hand traffic : 2 +* **lateral control params** + * speed and steering delay for predicting the desired curvature: 2 +* **previous desired curvatures** + * vector of previously predicted desired curvatures: 100 * 1 * **feature buffer** - * A buffer of intermediate features that gets appended to the current feature to form a 5 seconds temporal context (at 20FPS) : 99 * 512 + * a buffer of intermediate features including the current feature to form a 5 seconds temporal context (at 20FPS) : 100 * 512 -### Supercombo output format (Full size: XXX x float32) -Read [here](https://github.com/commaai/openpilot/blob/90af436a121164a51da9fa48d093c29f738adf6a/selfdrive/modeld/models/driving.h#L236) for more. +### Driving Model output format (Full size: XXX x float32) +Refer to **slice_outputs** and **parse_vision_outputs/parse_policy_outputs** in modeld. ## Driver Monitoring Model 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 new file mode 100644 index 0000000000..3601bbb5da --- /dev/null +++ b/selfdrive/modeld/models/driving_policy.onnx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98f0121ccb6f850077b04cc91bd33d370fc6cbdc2bd35f1ab55628a15a813f36 +size 15966721 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx new file mode 100644 index 0000000000..aee6d8f1bf --- /dev/null +++ b/selfdrive/modeld/models/driving_vision.onnx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:897f80d0388250f99bba69b6a8434560cc0fd83157cbeb0bc134c67fe4e64624 +size 34882971 diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx deleted file mode 100644 index 4c9f795574..0000000000 --- a/selfdrive/modeld/models/supercombo.onnx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d21daa542227ecc5972da45df4e26f018ba113c0461f270e367d57e3ad89221a -size 51461700 diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py index 9b162efe89..810c44ccb9 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -84,23 +84,32 @@ class Parser: outs[name] = pred_mu_final.reshape(final_shape) outs[name + '_stds'] = pred_std_final.reshape(final_shape) - def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: + def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: + self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) + self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) + self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) + self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) + self.parse_binary_crossentropy('meta', outs) + return outs + + def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION, out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH)) self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) - self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) - self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) - self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION, out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH)) if 'lat_planner_solution' in outs: self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_WIDTH)) if 'desired_curvature' in outs: self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,)) - for k in ['lead_prob', 'lane_lines_prob', 'meta']: + for k in ['lead_prob', 'lane_lines_prob']: self.parse_binary_crossentropy(k, outs) self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) - self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) + return outs + + def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: + outs = self.parse_vision_outputs(outs) + outs = self.parse_policy_outputs(outs) return outs 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/selfdrived/events.py b/selfdrive/selfdrived/events.py index 9da844bce4..d89418e177 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -292,6 +292,24 @@ def calibration_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging return NormalPermanentAlert("Calibration Invalid", angles) +def paramsd_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: + if not sm['liveParameters'].angleOffsetValid: + angle_offset_deg = sm['liveParameters'].angleOffsetDeg + title = "Steering misalignment detected" + text = f"Angle offset too high (Offset: {angle_offset_deg:.1f}°)" + elif not sm['liveParameters'].steerRatioValid: + steer_ratio = sm['liveParameters'].steerRatio + title = "Steer ratio mismatch" + text = f"Steering rack geometry may be off (Ratio: {steer_ratio:.1f})" + elif not sm['liveParameters'].stiffnessFactorValid: + stiffness_factor = sm['liveParameters'].stiffnessFactor + title = "Abnormal tire stiffness" + text = f"Check tires, pressure, or alignment (Factor: {stiffness_factor:.1f})" + else: + return NoEntryAlert("paramsd Temporary Error") + + return NoEntryAlert(alert_text_1=title, alert_text_2=text) + def overheat_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: cpu = max(sm['deviceState'].cpuTempC, default=0.) gpu = max(sm['deviceState'].gpuTempC, default=0.) @@ -321,7 +339,7 @@ def wrong_car_mode_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubM def joystick_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: gb = sm['carControl'].actuators.accel / 4. - steer = sm['carControl'].actuators.steer + steer = sm['carControl'].actuators.torque vals = f"Gas: {round(gb * 100.)}%, Steer: {round(steer * 100.)}%" return NormalPermanentAlert("Joystick Mode", vals) @@ -604,7 +622,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { # This alert is thrown when any of these values exceed a sanity check. This can be caused by # bad alignment or bad sensor data. If this happens consistently consider creating an issue on GitHub EventName.paramsdTemporaryError: { - ET.NO_ENTRY: NoEntryAlert("paramsd Temporary Error"), + ET.NO_ENTRY: paramsd_invalid_alert, ET.SOFT_DISABLE: soft_disable_alert("paramsd Temporary Error"), }, diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 9d30090b8b..8b7db19c9a 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -26,6 +26,7 @@ from openpilot.system.version import get_build_metadata 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 @@ -258,7 +259,7 @@ class SelfdriveD: 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: @@ -327,11 +328,11 @@ class SelfdriveD: 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 1135aeb874..34ca98fdc2 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -568,7 +568,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 1e643c695d..54df53fd91 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -fab469a6d651df3e17ac018f41b8d2a806d6146a +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 0fff34917f..927f54c0c3 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -42,22 +42,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 1695e60cd5..8fa4b55ea7 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -29,7 +29,7 @@ qt_libs = [widgets, qt_util] + base_libs qt_src = ["main.cc", "ui.cc", "qt/sidebar.cc", "qt/body.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", "qt/offroad/software_settings.cc", "qt/offroad/developer_panel.cc", "qt/offroad/onboarding.cc", - "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc", + "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc", "qt/offroad/firehose.cc", "qt/onroad/onroad_home.cc", "qt/onroad/annotated_camera.cc", "qt/onroad/model.cc", "qt/onroad/buttons.cc", "qt/onroad/alerts.cc", "qt/onroad/driver_monitoring.cc", "qt/onroad/hud.cc"] @@ -40,12 +40,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 new file mode 100644 index 0000000000..ab1d0198c7 --- /dev/null +++ b/selfdrive/ui/qt/offroad/firehose.cc @@ -0,0 +1,111 @@ +#include "selfdrive/ui/qt/offroad/firehose.h" +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/offroad/settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent) { + layout = new QVBoxLayout(this); + layout->setContentsMargins(40, 40, 40, 40); + layout->setSpacing(20); + + // header + QLabel *title = new QLabel(tr("🔥 Firehose Mode 🔥")); + title->setStyleSheet("font-size: 100px; font-weight: 500; font-family: 'Noto Color Emoji';"); + layout->addWidget(title, 0, Qt::AlignCenter); + + // Create a container for the content + QFrame *content = new QFrame(); + content->setStyleSheet("background-color: #292929; border-radius: 15px; padding: 20px;"); + QVBoxLayout *content_layout = new QVBoxLayout(content); + 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, which means better Experimental Mode.")); + description->setStyleSheet("font-size: 45px; padding-bottom: 20px;"); + description->setWordWrap(true); + content_layout->addWidget(description); + + // Add a separator + QFrame *line = new QFrame(); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + line->setStyleSheet("background-color: #444444; margin-top: 5px; margin-bottom: 5px;"); + content_layout->addWidget(line); + + toggle_label = new QLabel(tr("Firehose Mode: ACTIVE")); + toggle_label->setStyleSheet("font-size: 60px; font-weight: bold; color: white;"); + content_layout->addWidget(toggle_label); + + // 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(); + line2->setFrameShape(QFrame::HLine); + line2->setFrameShadow(QFrame::Sunken); + line2->setStyleSheet("background-color: #444444; margin-top: 10px; margin-bottom: 10px;"); + content_layout->addWidget(line2); + + // Detailed instructions at the bottom + detailed_instructions = new QLabel(tr( + "For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.
" + "
" + "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; 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 new file mode 100644 index 0000000000..9082cb0f99 --- /dev/null +++ b/selfdrive/ui/qt/offroad/firehose.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include "selfdrive/ui/qt/request_repeater.h" + +// Forward declarations +class SettingsWindow; + +class FirehosePanel : public QWidget { + Q_OBJECT +public: + explicit FirehosePanel(SettingsWindow *parent); + +private: + QVBoxLayout *layout; + + QLabel *detailed_instructions; + 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 a1b6895ba0..db229c5fa6 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/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index f68379242e..4d0516bda1 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -14,6 +14,7 @@ #include "selfdrive/ui/qt/widgets/prime.h" #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/offroad/developer_panel.h" +#include "selfdrive/ui/qt/offroad/firehose.h" TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // param, title, desc, icon @@ -36,20 +37,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("When enabled, pressing the accelerator pedal will disengage openpilot."), "../assets/offroad/icon_disengage_on_accelerator.svg", }, - { - "FirehoseMode", - tr("FIREHOSE Mode"), - tr("Enable FIREHOSE Mode to get your driving data in the training set.

" - "Follow these steps to get your device ready:
" - " 1. Bring your device inside and connect to a good USB-C adapter
" - " 2. Connect to Wi-Fi
" - " 3. Enable this toggle
" - " 4. Leave it connected for at least 30 minutes
" - "
" - "This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness." - ""), - "../assets/offroad/icon_warning.png", - }, { "IsLdwEnabled", tr("Enable Lane Departure Warnings"), @@ -334,11 +321,26 @@ void SettingsWindow::showEvent(QShowEvent *event) { } void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { + if (!param.isEmpty()) { + // Check if param ends with "Panel" to determine if it's a panel name + if (param.endsWith("Panel")) { + QString panelName = param; + panelName.chop(5); // Remove "Panel" suffix + + // Find the panel by name + for (int i = 0; i < nav_btns->buttons().size(); i++) { + if (nav_btns->buttons()[i]->text() == tr(panelName.toStdString().c_str())) { + index = i; + break; + } + } + } else { + emit expandToggleDescription(param); + } + } + panel_widget->setCurrentIndex(index); nav_btns->buttons()[index]->setChecked(true); - if (!param.isEmpty()) { - emit expandToggleDescription(param); - } } SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { @@ -383,6 +385,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { {tr("Network"), networking}, {tr("Toggles"), toggles}, {tr("Software"), new SoftwarePanel(this)}, + {tr("Firehose"), new FirehosePanel(this)}, {tr("Developer"), new DeveloperPanel(this)}, }; diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 68ba0d1898..b8277e0ae9 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -98,3 +98,6 @@ private: Params params; ParamWatcher *fs_watch; }; + +// Forward declaration +class FirehosePanel; 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 0b1220a27a..b2ac379dbe 100644 --- a/selfdrive/ui/qt/onroad/hud.h +++ b/selfdrive/ui/qt/onroad/hud.h @@ -19,6 +19,7 @@ private: 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/qt/widgets/wifi.cc b/selfdrive/ui/qt/widgets/wifi.cc index 9c5289a22d..d7eb8beeb3 100644 --- a/selfdrive/ui/qt/widgets/wifi.cc +++ b/selfdrive/ui/qt/widgets/wifi.cc @@ -6,80 +6,35 @@ #include WiFiPromptWidget::WiFiPromptWidget(QWidget *parent) : QFrame(parent) { - stack = new QStackedLayout(this); + // Setup Firehose Mode + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(56, 40, 56, 40); + main_layout->setSpacing(42); + + QLabel *title = new QLabel(tr("🔥 Firehose Mode 🔥")); + title->setStyleSheet("font-size: 64px; font-weight: 500;"); + main_layout->addWidget(title); - // Setup Wi-Fi - QFrame *setup = new QFrame; - QVBoxLayout *setup_layout = new QVBoxLayout(setup); - setup_layout->setContentsMargins(56, 40, 56, 40); - setup_layout->setSpacing(20); - { - QHBoxLayout *title_layout = new QHBoxLayout; - title_layout->setSpacing(32); - { - QLabel *icon = new QLabel; - QPixmap pixmap("../assets/offroad/icon_wifi_strength_full.svg"); - icon->setPixmap(pixmap.scaledToWidth(80, Qt::SmoothTransformation)); - title_layout->addWidget(icon); + QLabel *desc = new QLabel(tr("Maximize your training data uploads to improve openpilot's driving models.")); + desc->setStyleSheet("font-size: 40px; font-weight: 400;"); + desc->setWordWrap(true); + main_layout->addWidget(desc); - QLabel *title = new QLabel(tr("Setup Wi-Fi")); - title->setStyleSheet("font-size: 64px; font-weight: 600;"); - title_layout->addWidget(title); - title_layout->addStretch(); + QPushButton *settings_btn = new QPushButton(tr("Open")); + connect(settings_btn, &QPushButton::clicked, [=]() { emit openSettings(1, "FirehosePanel"); }); + settings_btn->setStyleSheet(R"( + QPushButton { + font-size: 48px; + font-weight: 500; + border-radius: 10px; + background-color: #465BEA; + padding: 32px; } - setup_layout->addLayout(title_layout); - - QLabel *desc = new QLabel(tr("Connect to Wi-Fi to upload driving data and help improve openpilot")); - desc->setStyleSheet("font-size: 40px; font-weight: 400;"); - desc->setWordWrap(true); - setup_layout->addWidget(desc); - - QPushButton *settings_btn = new QPushButton(tr("Open Settings")); - connect(settings_btn, &QPushButton::clicked, [=]() { emit openSettings(1); }); - settings_btn->setStyleSheet(R"( - QPushButton { - font-size: 48px; - font-weight: 500; - border-radius: 10px; - background-color: #465BEA; - padding: 32px; - } - QPushButton:pressed { - background-color: #3049F4; - } - )"); - setup_layout->addWidget(settings_btn); - } - stack->addWidget(setup); - - // Uploading data - QWidget *uploading = new QWidget; - QVBoxLayout *uploading_layout = new QVBoxLayout(uploading); - uploading_layout->setContentsMargins(64, 56, 64, 56); - uploading_layout->setSpacing(36); - { - QHBoxLayout *title_layout = new QHBoxLayout; - { - QLabel *title = new QLabel(tr("Ready to upload")); - title->setStyleSheet("font-size: 64px; font-weight: 600;"); - title->setWordWrap(true); - title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); - title_layout->addWidget(title); - title_layout->addStretch(); - - QLabel *icon = new QLabel; - QPixmap pixmap("../assets/offroad/icon_wifi_uploading.svg"); - icon->setPixmap(pixmap.scaledToWidth(120, Qt::SmoothTransformation)); - title_layout->addWidget(icon); + QPushButton:pressed { + background-color: #3049F4; } - uploading_layout->addLayout(title_layout); - - QLabel *desc = new QLabel(tr("Training data will be pulled periodically while your device is on Wi-Fi")); - desc->setStyleSheet("font-size: 48px; font-weight: 400;"); - desc->setWordWrap(true); - uploading_layout->addWidget(desc); - } - stack->addWidget(uploading); + )"); + main_layout->addWidget(settings_btn); setStyleSheet(R"( WiFiPromptWidget { @@ -87,17 +42,4 @@ WiFiPromptWidget::WiFiPromptWidget(QWidget *parent) : QFrame(parent) { border-radius: 10px; } )"); - - QObject::connect(uiState(), &UIState::uiUpdate, this, &WiFiPromptWidget::updateState); -} - -void WiFiPromptWidget::updateState(const UIState &s) { - if (!isVisible()) return; - - auto &sm = *(s.sm); - - auto network_type = sm["deviceState"].getDeviceState().getNetworkType(); - auto uploading = network_type == cereal::DeviceState::NetworkType::WIFI || - network_type == cereal::DeviceState::NetworkType::ETHERNET; - stack->setCurrentIndex(uploading ? 1 : 0); -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/widgets/wifi.h b/selfdrive/ui/qt/widgets/wifi.h index 60c865f2b8..3e68a15b7b 100644 --- a/selfdrive/ui/qt/widgets/wifi.h +++ b/selfdrive/ui/qt/widgets/wifi.h @@ -1,11 +1,8 @@ #pragma once #include -#include #include -#include "selfdrive/ui/ui.h" - class WiFiPromptWidget : public QFrame { Q_OBJECT @@ -14,10 +11,4 @@ public: signals: void openSettings(int index = 0, const QString ¶m = ""); - -public slots: - void updateState(const UIState &s); - -protected: - QStackedLayout *stack; }; 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/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py index fd2df29d8c..35323cf000 100755 --- a/selfdrive/ui/tests/test_ui/run.py +++ b/selfdrive/ui/tests/test_ui/run.py @@ -1,4 +1,4 @@ -from collections import namedtuple +#!/usr/bin/env python3 import capnp import pathlib import shutil @@ -8,6 +8,7 @@ import pywinctl import pyautogui import pickle import time +from collections import namedtuple from cereal import car, log from msgq.visionipc import VisionIpcServer, VisionStreamType @@ -42,21 +43,24 @@ def setup_settings_device(click, pm: PubMaster): def setup_settings_toggles(click, pm: PubMaster): setup_settings_device(click, pm) - click(278, 650) + click(278, 600) time.sleep(UI_DELAY) def setup_settings_software(click, pm: PubMaster): setup_settings_device(click, pm) - click(278, 800) + click(278, 720) time.sleep(UI_DELAY) +def setup_settings_firehose(click, pm: PubMaster): + click(1780, 730) + def setup_settings_developer(click, pm: PubMaster): CP = car.CarParams() CP.experimentalLongitudinalAvailable = True Params().put("CarParamsPersistent", CP.to_bytes()) setup_settings_device(click, pm) - click(278, 960) + click(278, 970) time.sleep(UI_DELAY) def setup_onroad(click, pm: PubMaster): @@ -191,6 +195,7 @@ CASES = { "settings_device": setup_settings_device, "settings_toggles": setup_settings_toggles, "settings_software": setup_settings_software, + "settings_firehose": setup_settings_firehose, "settings_developer": setup_settings_developer, "onroad": setup_onroad, "onroad_disengaged": setup_onroad_disengaged, diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index c0da67eef6..6471159616 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -305,6 +305,39 @@ تشغيل وضع الراحة + + FirehosePanel + + 🔥 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, which means better Experimental Mode. + + + + Firehose Mode: ACTIVE + + + + 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 + + + HudRenderer @@ -657,6 +690,10 @@ This may take up to a minute. Developer المطور + + Firehose + + Setup @@ -966,22 +1003,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 @@ -1093,14 +1130,6 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. تمكين مراقبة السائق حتى عندما لا يكون نظام OpenPilot مُفعّلاً. - - FIREHOSE Mode - الوضع FIREHOSE - - - Enable <b>FIREHOSE Mode</b> to get your driving data in the training set.<br><br>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 this toggle<br> 4. Leave it connected for at least 30 minutes<br><br>This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness. - قم بتمكين <b>وضع FIREHOSE</b> للحصول على بيانات القيادة الخاصة بك في مجموعة التدريب.<br><br>اتبع الخطوات التالية لتجهيز جهازك:<br> 1. أحضر جهازك إلى الداخل وقم بتوصيله بمحول USB-C جيد<br> 2. اتصل بشبكة Wi-Fi<br> 3. قم بتمكين هذا التبديل<br> 4. اتركه متصلاً لمدة 30 دقيقة على الأقل<br><br>يتم إيقاف تشغيل هذا التبديل بمجرد إعادة تشغيل جهازك. كرر ذلك مرة واحدة في الأسبوع لتحقيق أقصى قدر من الفعالية. - Updater @@ -1140,24 +1169,16 @@ This may take up to a minute. WiFiPromptWidget - Setup Wi-Fi - إعداد شبكة الواي فاي + Open + - Connect to Wi-Fi to upload driving data and help improve openpilot - الاتصال بشبكة الواي فاي لتحميل بيانات القيادة والمساهمة في تحسين openpilot + Maximize your training data uploads to improve openpilot's driving models. + - Open Settings - فتح الإعدادات - - - Ready to upload - جاهز للتحميل - - - Training data will be pulled periodically while your device is on Wi-Fi - سيتم سحب بيانات التدريب دورياً عندما يكون جهازك متصل بشبكة واي فاي + <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> + diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 06543db382..bdda0484d4 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -305,6 +305,39 @@ ENTSPANNTER MODUS AN + + FirehosePanel + + 🔥 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, which means better Experimental Mode. + + + + Firehose Mode: ACTIVE + + + + 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 + + + HudRenderer @@ -639,6 +672,10 @@ This may take up to a minute. Developer + + Firehose + + Setup @@ -950,22 +987,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 @@ -1077,14 +1114,6 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. - - FIREHOSE Mode - - - - Enable <b>FIREHOSE Mode</b> to get your driving data in the training set.<br><br>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 this toggle<br> 4. Leave it connected for at least 30 minutes<br><br>This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness. - - Updater @@ -1124,23 +1153,15 @@ This may take up to a minute. WiFiPromptWidget - Setup Wi-Fi + Open - Connect to Wi-Fi to upload driving data and help improve openpilot + Maximize your training data uploads to improve openpilot's driving models. - Open Settings - - - - Ready to upload - - - - Training data will be pulled periodically while your device is on Wi-Fi + <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts index a0e5dc222a..8168e126e3 100644 --- a/selfdrive/ui/translations/main_es.ts +++ b/selfdrive/ui/translations/main_es.ts @@ -305,6 +305,39 @@ MODO CHILL + + FirehosePanel + + 🔥 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, which means better Experimental Mode. + + + + Firehose Mode: ACTIVE + + + + 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 + + + HudRenderer @@ -641,6 +674,10 @@ Esto puede tardar un minuto. Developer Desarrollador + + Firehose + + Setup @@ -950,22 +987,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 @@ -1077,14 +1114,6 @@ Esto puede tardar un minuto. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. Activar el control longitudinal (fase experimental) para permitir el modo Experimental. - - FIREHOSE Mode - - - - Enable <b>FIREHOSE Mode</b> to get your driving data in the training set.<br><br>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 this toggle<br> 4. Leave it connected for at least 30 minutes<br><br>This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness. - - Updater @@ -1124,24 +1153,16 @@ Esto puede tardar un minuto. WiFiPromptWidget - Setup Wi-Fi - Configurar Wi-Fi + Open + - Connect to Wi-Fi to upload driving data and help improve openpilot - Conectarse al Wi-Fi para subir los datos de conducción y mejorar openpilot + Maximize your training data uploads to improve openpilot's driving models. + - Open Settings - Abrir Configuraciones - - - Ready to upload - Listo para subir - - - Training data will be pulled periodically while your device is on Wi-Fi - Los datos de entrenamiento se extraerán periódicamente mientras tu dispositivo esté conectado a Wi-Fi + <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> + diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 69eb2918c0..3fe002e03d 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -305,6 +305,39 @@ MODE DÉTENTE ACTIVÉ + + FirehosePanel + + 🔥 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, which means better Experimental Mode. + + + + Firehose Mode: ACTIVE + + + + 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 + + + HudRenderer @@ -641,6 +674,10 @@ Cela peut prendre jusqu'à une minute. Developer Dév. + + Firehose + + Setup @@ -950,22 +987,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 @@ -1077,14 +1114,6 @@ Cela peut prendre jusqu'à une minute. Enable driver monitoring even when openpilot is not engaged. Activer la surveillance conducteur lorsque openpilot n'est pas actif. - - FIREHOSE Mode - - - - Enable <b>FIREHOSE Mode</b> to get your driving data in the training set.<br><br>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 this toggle<br> 4. Leave it connected for at least 30 minutes<br><br>This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness. - - Updater @@ -1124,24 +1153,16 @@ Cela peut prendre jusqu'à une minute. WiFiPromptWidget - Setup Wi-Fi - Configurer Wi-Fi + Open + - Connect to Wi-Fi to upload driving data and help improve openpilot - Connectez-vous au Wi-Fi pour publier les données de conduite et aider à améliorer openpilot + Maximize your training data uploads to improve openpilot's driving models. + - Open Settings - Ouvrir les paramètres - - - Ready to upload - Prêt à uploader - - - Training data will be pulled periodically while your device is on Wi-Fi - Les données d'entraînement seront envoyées périodiquement lorsque votre appareil est connecté au réseau Wi-Fi + <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> + diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index e09d8be80f..d242875937 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -305,6 +305,39 @@ CHILLモード + + FirehosePanel + + 🔥 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, which means better Experimental Mode. + + + + Firehose Mode: ACTIVE + + + + 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 + + + HudRenderer @@ -637,6 +670,10 @@ This may take up to a minute. Developer 開発 + + Firehose + データ学習 + Setup @@ -946,22 +983,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 @@ -1073,14 +1110,6 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. openpilotが作動していない場合でも運転者モニタリングを有効にする。 - - FIREHOSE Mode - FIREHOSEモード - - - Enable <b>FIREHOSE Mode</b> to get your driving data in the training set.<br><br>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 this toggle<br> 4. Leave it connected for at least 30 minutes<br><br>This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness. - <b>FIREHOSEモード</b>を有効にすると、あなたの運転が公式のトレーニングデータに追加されます。<br><br>次の手順でデバイスを準備してください:<br> 1. デバイスを屋内に持ち込み、適切なUSB-Cアダプターに接続する<br> 2. Wi-Fiに接続する<br> 3. このスイッチを有効にする<br> 4. 少なくとも30分間接続したままにする<br><br>このスイッチはデバイスを再起動すると無効になります。効果を最大化するためには毎週実行するのが望ましいです。 - Updater @@ -1120,24 +1149,16 @@ This may take up to a minute. WiFiPromptWidget - Setup Wi-Fi - Wi-Fi設定 + Open + 開く - Connect to Wi-Fi to upload driving data and help improve openpilot - 走行データをアップロードしてopenpilotの改善に役立てるためにWi-Fi接続してください + Maximize your training data uploads to improve openpilot's driving models. + openpilotの運転モデルを改善するために、大容量の学習データをアップロードして下さい。 - Open Settings - 設定を開く - - - Ready to upload - アップロード準備完了 - - - Training data will be pulled periodically while your device is on Wi-Fi - デバイスがWi-Fiに接続中は、トレーニングデータが定期的に送信されます + <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 a35cafd646..6c6c73da16 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를 참조하세요. @@ -305,6 +305,39 @@ 안정 모드 사용 + + FirehosePanel + + 🔥 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, which means better Experimental Mode. + + + + Firehose Mode: ACTIVE + + + + 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 + + + HudRenderer @@ -637,6 +670,10 @@ This may take up to a minute. Developer 개발자 + + Firehose + 파이어호스 + Setup @@ -946,22 +983,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 @@ -1073,14 +1110,6 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. Openpilot이 활성화되지 않은 경우에도 드라이버 모니터링을 활성화합니다. - - FIREHOSE Mode - - - - Enable <b>FIREHOSE Mode</b> to get your driving data in the training set.<br><br>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 this toggle<br> 4. Leave it connected for at least 30 minutes<br><br>This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness. - - Updater @@ -1120,24 +1149,16 @@ This may take up to a minute. WiFiPromptWidget - Setup Wi-Fi - Wi-Fi 설정 + Open + 열기 - Connect to Wi-Fi to upload driving data and help improve openpilot - Wi-Fi에 연결하여 주행 데이터를 업로드하고 openpilot 개선에 기여하세요 + Maximize your training data uploads to improve openpilot's driving models. + 훈련 데이터 업로드를 최대화하여 오픈파일럿의 주행 모델을 개선하세요. - Open Settings - 설정 열기 - - - Ready to upload - 업로드 준비 완료 - - - Training data will be pulled periodically while your device is on Wi-Fi - 기기가 Wi-Fi에 연결되어 있는 동안 트레이닝 데이터를 주기적으로 전송합니다 + <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 5400ffdf06..16edf605ad 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -305,6 +305,39 @@ MODO CHILL ON + + FirehosePanel + + 🔥 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, which means better Experimental Mode. + + + + Firehose Mode: ACTIVE + + + + 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 + + + HudRenderer @@ -641,6 +674,10 @@ Isso pode levar até um minuto. Developer Desenvdor + + Firehose + Firehose + Setup @@ -950,22 +987,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 @@ -1077,14 +1114,6 @@ Isso pode levar até um minuto. Enable driver monitoring even when openpilot is not engaged. Habilite o monitoramento do motorista mesmo quando o openpilot não estiver acionado. - - FIREHOSE Mode - Modo FIREHOSE - - - Enable <b>FIREHOSE Mode</b> to get your driving data in the training set.<br><br>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 this toggle<br> 4. Leave it connected for at least 30 minutes<br><br>This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness. - Habilite o <b>Modo FIREHOSE</b> para obter seus dados de direção no conjunto de treinamento.<br><br>Siga estas etapas para preparar seu dispositivo:<br> 1. Leve seu dispositivo para dentro e conecte-o a um bom adaptador USB-C<br> 2. Conecte-se ao Wi-Fi<br> 3. Habilite este toggle<br> 4. Deixe-o conectado por pelo menos 30 minutos.<br><br>Este botão desativa após reiniciar o dispositivo. Repita uma vez por semana para obter a máxima eficácia. - Updater @@ -1124,24 +1153,16 @@ Isso pode levar até um minuto. WiFiPromptWidget - Setup Wi-Fi - Configurar Wi-Fi + Open + Abrir - Connect to Wi-Fi to upload driving data and help improve openpilot - Conecte se ao Wi-Fi para realizar upload de dados de condução e ajudar a melhorar o openpilot + 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. - Open Settings - Abrir Configurações - - - Ready to upload - Pronto para upload - - - Training data will be pulled periodically while your device is on Wi-Fi - Os dados de treinamento serão extraídos periodicamente enquanto o dispositivo estiver no Wi-Fi + <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 0bc733997d..fc5e690ce0 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -305,6 +305,39 @@ คุณกำลังใช้โหมดชิล + + FirehosePanel + + 🔥 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, which means better Experimental Mode. + + + + Firehose Mode: ACTIVE + + + + 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 + + + HudRenderer @@ -637,6 +670,10 @@ This may take up to a minute. Developer + + Firehose + + Setup @@ -946,22 +983,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 @@ -1073,14 +1110,6 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. - - FIREHOSE Mode - - - - Enable <b>FIREHOSE Mode</b> to get your driving data in the training set.<br><br>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 this toggle<br> 4. Leave it connected for at least 30 minutes<br><br>This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness. - - Updater @@ -1120,24 +1149,16 @@ This may take up to a minute. WiFiPromptWidget - Setup Wi-Fi - ตั้งค่า Wi-Fi + Open + - Connect to Wi-Fi to upload driving data and help improve openpilot - เชื่อมต่อกับ Wi-Fi เพื่ออัปโหลดข้อมูลการขับขี่และช่วยปรับปรุง openpilot + Maximize your training data uploads to improve openpilot's driving models. + - Open Settings - เปิดการตั้งค่า - - - Ready to upload - พร้อมจะอัปโหลด - - - Training data will be pulled periodically while your device is on Wi-Fi - ข้อมูลการฝึกฝนจะถูกดึงเป็นระยะระหว่างที่อุปกรณ์ของคุณเชื่อมต่อกับ Wi-Fi + <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> + diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index 0bc5c054c7..8484f23160 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -305,6 +305,39 @@ + + FirehosePanel + + 🔥 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, which means better Experimental Mode. + + + + Firehose Mode: ACTIVE + + + + 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 + + + HudRenderer @@ -635,6 +668,10 @@ This may take up to a minute. Developer + + Firehose + + Setup @@ -944,22 +981,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 @@ -1071,14 +1108,6 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. - - FIREHOSE Mode - - - - Enable <b>FIREHOSE Mode</b> to get your driving data in the training set.<br><br>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 this toggle<br> 4. Leave it connected for at least 30 minutes<br><br>This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness. - - Updater @@ -1118,23 +1147,15 @@ This may take up to a minute. WiFiPromptWidget - Setup Wi-Fi + Open - Connect to Wi-Fi to upload driving data and help improve openpilot + Maximize your training data uploads to improve openpilot's driving models. - Open Settings - - - - Ready to upload - - - - Training data will be pulled periodically while your device is on Wi-Fi + <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index b241b8a034..69a2c0c594 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -305,6 +305,39 @@ 轻松模式运行 + + FirehosePanel + + 🔥 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, which means better Experimental Mode. + + + + Firehose Mode: ACTIVE + + + + 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 + + + HudRenderer @@ -637,6 +670,10 @@ This may take up to a minute. Developer 开发人员 + + Firehose + 训练上传 + Setup @@ -946,22 +983,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 @@ -1073,14 +1110,6 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. 即使在openpilot未激活时也启用驾驶员监控。 - - FIREHOSE Mode - FIREHOSE 模式 - - - Enable <b>FIREHOSE Mode</b> to get your driving data in the training set.<br><br>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 this toggle<br> 4. Leave it connected for at least 30 minutes<br><br>This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness. - 启用 <b>FIREHOSE 模式</b> 以将您的驾驶数据纳入训练集。<br><br>按照以下步骤准备您的设备:<br> 1. 将设备带入室内并连接到良好的 USB-C 适配器<br> 2. 连接到 Wi-Fi<br> 3. 启用此开关<br> 4. 保持连接至少 30 分钟<br><br>此开关在您重新启动设备后会关闭。每周重复一次以达到最佳效果。 - Updater @@ -1120,24 +1149,16 @@ This may take up to a minute. WiFiPromptWidget - Setup Wi-Fi - 设置 Wi-Fi 连接 + Open + 开启 - Connect to Wi-Fi to upload driving data and help improve openpilot - 请连接至 Wi-Fi 上传驾驶数据以协助改进openpilot + Maximize your training data uploads to improve openpilot's driving models. + 最大化您的训练数据上传,以改善 openpilot 的驾驶模型。 - Open Settings - 打开设置 - - - Ready to upload - 准备好上传 - - - Training data will be pulled periodically while your device is on Wi-Fi - 训练数据将定期通过 Wi-Fi 上载 + <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 b205b23e0a..6d50bf92b8 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -305,6 +305,39 @@ 輕鬆模式 ON + + FirehosePanel + + 🔥 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, which means better Experimental Mode. + + + + Firehose Mode: ACTIVE + + + + 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 + + + HudRenderer @@ -637,6 +670,10 @@ This may take up to a minute. Developer 開發人員 + + Firehose + 訓練上傳 + Setup @@ -946,22 +983,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 @@ -1073,14 +1110,6 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. 即使在openpilot未激活時也啟用駕駛監控。 - - FIREHOSE Mode - FIREHOSE 模式 - - - Enable <b>FIREHOSE Mode</b> to get your driving data in the training set.<br><br>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 this toggle<br> 4. Leave it connected for at least 30 minutes<br><br>This toggle turns off once you restart your device. Repeat once a week for maximum effectiveness. - 啟用<b>Firehose 模式</b>,將您的駕駛數據加入訓練集。<br><br>請按照以下步驟準備您的裝置:<br> 1. 將裝置帶到室內並連接到穩定的 USB-C 充電器<br> 2. 連接 Wi-Fi<br> 3. 開啟此切換開關<br> 4. 保持連接至少 30 分鐘<br><br>此切換開關在裝置重新啟動後會自動關閉。為確保最佳效果,請每週重複一次。 - Updater @@ -1120,24 +1149,16 @@ This may take up to a minute. WiFiPromptWidget - Setup Wi-Fi - 設置 Wi-Fi 連接 + Open + 開啟 - Connect to Wi-Fi to upload driving data and help improve openpilot - 請連接至 Wi-Fi 傳駕駛數據以協助改進 openpilot + Maximize your training data uploads to improve openpilot's driving models. + 最大化您的訓練數據上傳,以改善 openpilot 的駕駛模型。 - Open Settings - 開啟設置 - - - Ready to upload - 準備好上傳 - - - Training data will be pulled periodically while your device is on Wi-Fi - 訓練數據將定期經過 Wi-Fi 上傳 + <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 27440e84a6..b36bdb103d 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -508,10 +508,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/SConscript b/system/camerad/SConscript index 0d936176b9..fe5cf87b78 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'messaging', 'common', 'gpucommon', 'visionipc') -libs = ['pthread', common, 'OpenCL', messaging, visionipc, gpucommon] +libs = [common, 'OpenCL', messaging, visionipc, gpucommon] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 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 2ab7bc568a..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,65 +895,27 @@ 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; - 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 - 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 { + sync_objs_ife[i] = sync_create.sync_obj; } - sync_objs_ife[i] = sync_create.sync_obj; if (icp_dev_handle > 0) { ret = do_cam_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 { + sync_objs_bps[i] = sync_create.sync_obj; } - sync_objs_bps[i] = sync_create.sync_obj; } // schedule request with camera request manager @@ -976,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) { @@ -1011,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)); @@ -1020,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; @@ -1117,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; @@ -1140,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++) { @@ -1292,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); @@ -1307,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); } @@ -1336,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); } @@ -1363,85 +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 (!enabled) return false; + /* + Handles camera SOF event. Returns true if the frame is valid for publishing. + */ - // ID from the qcom camera request manager - uint64_t request_id = event_data->u.frame_msg.request_id; + 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); - // raw as opposed to our re-indexed frame ID - uint64_t frame_id_raw = event_data->u.frame_msg.frame_id; + if (stress_test("skipping SOF event")) return false; - 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; - } + if (!validateEvent(request_id, frame_id_raw)) { + return false; } - 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; + + // 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; + } + + 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}; @@ -1455,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 9c2026058a..21f1970326 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -10,6 +10,7 @@ #include "media/cam_req_mgr.h" #include "common/util.h" +#include "common/swaglog.h" #include "system/camerad/cameras/hw.h" #include "system/camerad/cameras/camera_common.h" #include "system/camerad/sensors/sensor.h" @@ -21,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) @@ -30,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); @@ -116,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); @@ -127,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(); @@ -189,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; @@ -205,4 +200,14 @@ private: }; inline static std::map camera_sync_data; inline static bool first_frame_synced = false; + + // a mode for stressing edge cases: realignment, sync failures, etc. + 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 (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 cf4013eb4a..ab76985972 100644 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -1,81 +1,98 @@ -import pytest +import os import time +import pytest import numpy as np -from collections import defaultdict import cereal.messaging as messaging -from cereal import log from cereal.services import SERVICE_LIST -from openpilot.common.retry import retry from openpilot.system.manager.process_config import managed_processes +from openpilot.tools.lib.log_time_series import msgs_to_time_series TEST_TIMESPAN = 10 -LAG_FRAME_TOLERANCE = {log.FrameData.ImageSensor.ar0231: 0.5, # ARs use synced pulses for frame starts - log.FrameData.ImageSensor.ox03c10: 1.1, # OXs react to out-of-sync at next frame - log.FrameData.ImageSensor.os04c10: 1.1} -FRAME_DELTA_TOLERANCE = {log.FrameData.ImageSensor.ar0231: 1.0, - log.FrameData.ImageSensor.ox03c10: 1.0, - log.FrameData.ImageSensor.os04c10: 1.0} - CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') -@retry(attempts=3, delay=5.0) -def setup_camerad(cls): - # run camerad and record logs - managed_processes['camerad'].start() - time.sleep(3) - socks = {c: messaging.sub_sock(c, conflate=False, timeout=100) for c in CAMERAS} - cls.logs = defaultdict(list) - start_time = time.monotonic() - while time.monotonic()- start_time < TEST_TIMESPAN: - for cam, s in socks.items(): - cls.logs[cam] += messaging.drain_sock(s) - time.sleep(0.2) - managed_processes['camerad'].stop() +def run_and_log(procs, services, duration): + logs = [] - cls.log_by_frame_id = defaultdict(list) - cls.sensor_type = None - for cam, msgs in cls.logs.items(): - if cls.sensor_type is None: - cls.sensor_type = getattr(msgs[0], msgs[0].which()).sensor.raw + try: + for p in procs: + managed_processes[p].start() + socks = [messaging.sub_sock(s, conflate=False, timeout=100) for s in services] + + start_time = time.monotonic() + while time.monotonic() - start_time < duration: + for s in socks: + logs.extend(messaging.drain_sock(s)) + for p in procs: + assert managed_processes[p].proc.is_alive() + finally: + for p in procs: + managed_processes[p].stop() + + return logs + +@pytest.fixture(scope="module") +def logs(): + logs = run_and_log(["camerad", ], CAMERAS, TEST_TIMESPAN) + ts = msgs_to_time_series(logs) + + for cam in CAMERAS: expected_frames = SERVICE_LIST[cam].frequency * TEST_TIMESPAN - assert expected_frames*0.95 < len(msgs) < expected_frames*1.05, f"unexpected frame count {cam}: {expected_frames=}, got {len(msgs)}" + cnt = len(ts[cam]['t']) + assert expected_frames*0.8 < cnt < expected_frames*1.2, f"unexpected frame count {cam}: {expected_frames=}, got {cnt}" - dts = np.abs(np.diff([getattr(m, m.which()).timestampSof/1e6 for m in msgs]) - 1000/SERVICE_LIST[cam].frequency) - assert (dts < FRAME_DELTA_TOLERANCE[cls.sensor_type]).all(), f"{cam} dts(ms) out of spec: max diff {dts.max()}, 99 percentile {np.percentile(dts, 99)}" - - for m in msgs: - cls.log_by_frame_id[getattr(m, m.which()).frameId].append(m) - - # strip beginning and end - for _ in range(3): - mn, mx = min(cls.log_by_frame_id.keys()), max(cls.log_by_frame_id.keys()) - del cls.log_by_frame_id[mn] - del cls.log_by_frame_id[mx] + dts = np.abs(np.diff([ts[cam]['timestampSof']/1e6]) - 1000/SERVICE_LIST[cam].frequency) + assert (dts < 1.0).all(), f"{cam} dts(ms) out of spec: max diff {dts.max()}, 99 percentile {np.percentile(dts, 99)}" + return ts @pytest.mark.tici class TestCamerad: - @classmethod - def setup_class(cls): - setup_camerad(cls) + def test_frame_skips(self, logs): + for c in CAMERAS: + assert set(np.diff(logs[c]['frameId'])) == {1, }, f"{c} has frame skips" - def test_frame_skips(self): - skips = {} - frame_ids = self.log_by_frame_id.keys() - for frame_id in range(min(frame_ids), max(frame_ids)): - seen_cams = [msg.which() for msg in self.log_by_frame_id[frame_id]] - skip_cams = set(CAMERAS) - set(seen_cams) - if len(skip_cams): - skips[frame_id] = skip_cams - assert len(skips) == 0, f"Found frame skips, missing cameras for the following frames: {skips}" + def test_frame_sync(self, logs): + n = range(len(logs['roadCameraState']['t'][:-10])) - def test_frame_sync(self): - frame_times = {frame_id: [getattr(m, m.which()).timestampSof for m in msgs] for frame_id, msgs in self.log_by_frame_id.items()} - diffs = {frame_id: (max(ts) - min(ts))/1e6 for frame_id, ts in frame_times.items()} + frame_ids = {i: [logs[cam]['frameId'][i] for cam in CAMERAS] for i in n} + assert all(len(set(v)) == 1 for v in frame_ids.values()), "frame IDs not aligned" - def get_desc(fid, diff): - cam_times = [(m.which(), getattr(m, m.which()).timestampSof/1e6) for m in self.log_by_frame_id[fid]] - return (diff, cam_times) - laggy_frames = {k: get_desc(k, v) for k, v in diffs.items() if v > LAG_FRAME_TOLERANCE[self.sensor_type]} + frame_times = {i: [logs[cam]['timestampSof'][i] for cam in CAMERAS] for i in n} + diffs = {i: (max(ts) - min(ts))/1e6 for i, ts in frame_times.items()} + + 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=}" + + 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 00e7950ba2..179ef54a9b 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -76,6 +76,7 @@ public: } int value = util::map_val(std::clamp(percent, 0, 100), 0, 100, 0, 255); + std::ofstream("/sys/class/leds/led:switch_2/brightness") << 0 << "\n"; std::ofstream("/sys/class/leds/led:torch_2/brightness") << value << "\n"; std::ofstream("/sys/class/leds/led:switch_2/brightness") << value << "\n"; } @@ -108,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 120390ce5f..96315eeb29 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -75,10 +75,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"], only_onroad), + # 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"], only_onroad), + 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 2fd82f61e0..70266e9f94 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 2fd82f61e0378485762d5cd0a6fd376fc8fc356c +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/README.md b/tools/cabana/README.md index 1d9a2a2ccc..0b7c5bf3ee 100644 --- a/tools/cabana/README.md +++ b/tools/cabana/README.md @@ -14,11 +14,11 @@ Options: --demo use a demo route instead of providing your own --qcam load qcamera --ecam load wide road camera - --stream read can messages from live streaming + --msgq read can messages from msgq --panda read can messages from panda --panda-serial read can messages from panda with given serial --socketcan read can messages from given SocketCAN device - --zmq the ip address on which to receive zmq + --zmq read can messages from zmq at the specified ip-address messages --data_dir local directory with routes --no-vipc do not output video @@ -68,7 +68,7 @@ cd /data/openpilot/cereal/messaging/ Then Run Cabana with the device's IP address: ```shell -cabana --stream --zmq +cabana --zmq ``` Replace <ipaddress> with your comma device's IP address. diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index cd7a0be0db..d140a323e1 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -26,13 +26,13 @@ int main(int argc, char *argv[]) { cmd_parser.addOption({"qcam", "load qcamera"}); cmd_parser.addOption({"ecam", "load wide road camera"}); cmd_parser.addOption({"dcam", "load driver camera"}); - cmd_parser.addOption({"stream", "read can messages from live streaming"}); + cmd_parser.addOption({"msgq", "read can messages from the msgq"}); cmd_parser.addOption({"panda", "read can messages from panda"}); cmd_parser.addOption({"panda-serial", "read can messages from panda with given serial", "panda-serial"}); if (SocketCanStream::available()) { cmd_parser.addOption({"socketcan", "read can messages from given SocketCAN device", "socketcan"}); } - cmd_parser.addOption({"zmq", "the ip address on which to receive zmq messages", "zmq"}); + cmd_parser.addOption({"zmq", "read can messages from zmq at the specified ip-address", "ip-address"}); cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"}); cmd_parser.addOption({"no-vipc", "do not output video"}); cmd_parser.addOption({"dbc", "dbc file to open", "dbc"}); @@ -40,7 +40,9 @@ int main(int argc, char *argv[]) { AbstractStream *stream = nullptr; - if (cmd_parser.isSet("stream")) { + if (cmd_parser.isSet("msgq")) { + stream = new DeviceStream(&app); + } else if (cmd_parser.isSet("zmq")) { stream = new DeviceStream(&app, cmd_parser.value("zmq")); } else if (cmd_parser.isSet("panda") || cmd_parser.isSet("panda-serial")) { try { 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/cabana/streams/socketcanstream.cc b/tools/cabana/streams/socketcanstream.cc index 1346a496bb..e4801df9c0 100644 --- a/tools/cabana/streams/socketcanstream.cc +++ b/tools/cabana/streams/socketcanstream.cc @@ -27,6 +27,7 @@ bool SocketCanStream::connect() { // These are expected and can be ignored, we don't need the advanced features of libsocketcan QString errorString; device.reset(QCanBus::instance()->createDevice("socketcan", config.device, &errorString)); + device->setConfigurationParameter(QCanBusDevice::CanFdKey, true); if (!device) { qDebug() << "Failed to create SocketCAN device" << errorString; 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/joystick/joystickd.py b/tools/joystick/joystickd.py index f76466dfce..b775f18f70 100755 --- a/tools/joystick/joystickd.py +++ b/tools/joystick/joystickd.py @@ -4,10 +4,10 @@ import math import numpy as np from cereal import messaging, car +from opendbc.car.vehicle_model import VehicleModel from openpilot.common.realtime import DT_CTRL, Ratekeeper from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel LongCtrlState = car.CarControl.Actuators.LongControlState MAX_LAT_ACCEL = 2.5 @@ -53,8 +53,8 @@ def joystickd_thread(): max_curvature = MAX_LAT_ACCEL / max(sm['carState'].vEgo ** 2, 5) max_angle = math.degrees(VM.get_steer_from_curvature(max_curvature, sm['carState'].vEgo, sm['liveParameters'].roll)) - actuators.steer = float(np.clip(joystick_axes[1], -1, 1)) - actuators.steeringAngleDeg, actuators.curvature = actuators.steer * max_angle, actuators.steer * -max_curvature + actuators.torque = float(np.clip(joystick_axes[1], -1, 1)) + actuators.steeringAngleDeg, actuators.curvature = actuators.torque * max_angle, actuators.torque * -max_curvature pm.send('carControl', cc_msg) diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 925b0bdabe..f66782d6f3 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -12,6 +12,8 @@ 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 @@ -54,7 +56,7 @@ def get_plotjuggler_version(): return tuple(map(int, version.split("."))) -def start_juggler(fn=None, dbc=None, layout=None, route_or_segment_name=None): +def start_juggler(fn=None, dbc=None, layout=None, route_or_segment_name=None, platform=None): env = os.environ.copy() env["BASEDIR"] = BASEDIR env["PATH"] = f"{INSTALL_DIR}:{os.getenv('PATH', '')}" @@ -67,38 +69,37 @@ def start_juggler(fn=None, dbc=None, layout=None, route_or_segment_name=None): if layout is not None: extra_args += f" -l {layout}" if route_or_segment_name is not None: - extra_args += f" --window_title \"{route_or_segment_name}\"" + extra_args += f" --window_title \"{route_or_segment_name}{f' ({platform})' if platform is not None else ''}\"" cmd = f'{PLOTJUGGLER_BIN} --buffer_size {MAX_STREAMING_BUFFER_SIZE} --plugin_folders {INSTALL_DIR}{extra_args}' subprocess.call(cmd, shell=True, env=env, cwd=juggle_dir) def process(can, lr): - return [d for d in lr if can or d.which() not in ['can', 'sendcan']] + 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): - sr = LogReader(route_or_segment_name, default_mode=ReadMode.AUTO_INTERACTIVE) + lr = LogReader(route_or_segment_name, default_mode=ReadMode.AUTO_INTERACTIVE) - all_data = sr.run_across_segments(24, partial(process, can)) + all_data = lr.run_across_segments(24, partial(process, can)) if should_migrate: all_data = migrate_all(all_data) # Infer DBC name from logs + platform = None if dbc is None: - for cp in [m for m in all_data if m.which() == 'carParams']: - try: - DBC = __import__(f"opendbc.car.{cp.carParams.brand}.values", fromlist=['DBC']).DBC - fingerprint = cp.carParams.carFingerprint - dbc = DBC[MIGRATION.get(fingerprint, fingerprint)]['pt'] - except Exception: - pass - break + try: + CP = lr.first('carParams') + platform = MIGRATION.get(CP.carFingerprint, CP.carFingerprint) + dbc = generate_dbc_dict()[platform] + except Exception: + 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) del all_data - start_juggler(tmp.name, dbc, layout, route_or_segment_name) + start_juggler(tmp.name, dbc, layout, route_or_segment_name, platform) if __name__ == "__main__": 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 03f3a0c37c..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.steer * ANGLE_SCALE + 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 0b8916343e..a2f437819f 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -47,9 +47,9 @@ class SteeringAccuracyTool: v_ego = sm['carState'].vEgo active = sm['controlsState'].active - steer = sm['carOutput'].actuatorsOutput.steer + steer = sm['carOutput'].actuatorsOutput.torque standstill = sm['carState'].standstill - steer_limited = abs(sm['carControl'].actuators.steer - sm['carControl'].actuatorsOutput.steer) > 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 d594c2c8fd..8da47bf004 100644 --- a/uv.lock +++ b/uv.lock @@ -1,21 +1,22 @@ version = 1 +revision = 1 requires-python = ">=3.11, <3.13" resolution-markers = [ - "python_full_version < '3.12' and platform_system == 'Darwin'", - "python_full_version < '3.12' and platform_machine == 'aarch64' and platform_system == 'Linux'", - "(python_full_version < '3.12' and platform_machine != 'aarch64' and platform_system != 'Darwin') or (python_full_version < '3.12' and platform_system != 'Darwin' and platform_system != 'Linux')", - "python_full_version >= '3.12' and platform_system == 'Darwin'", - "python_full_version >= '3.12' and platform_machine == 'aarch64' and platform_system == 'Linux'", - "(python_full_version >= '3.12' and platform_machine != 'aarch64' and platform_system != 'Darwin') or (python_full_version >= '3.12' and platform_system != 'Darwin' and platform_system != 'Linux')", + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", ] [[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]] @@ -297,7 +298,7 @@ name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ @@ -627,17 +628,17 @@ wheels = [ [[package]] name = "gymnasium" -version = "1.0.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "farama-notifications", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "numpy", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, + { name = "cloudpickle" }, + { name = "farama-notifications" }, + { name = "numpy" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/12/1047b8fdbfcdce74022048d916e844ad7e6e1114d81d26a7aed657e3a76d/gymnasium-1.0.0.tar.gz", hash = "sha256:9d2b66f30c1b34fe3c2ce7fae65ecf365d0e9982d2b3d860235e773328a3b403", size = 821389 } +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/de/85/f5039ce2df5f0789ff1240f08a59f3e8c92e4c5f99543b7aad7388532f7c/gymnasium-1.0.0-py3-none-any.whl", hash = "sha256:b6f40e1e24c5bd419361e1a5b86a9117d2499baecc3a660d44dfff4c465393ad", size = 958120 }, + { url = "https://files.pythonhosted.org/packages/f9/68/2bdc7b46b5f543dd865575f9d19716866bdb76e50dd33b71ed1a3dd8bb42/gymnasium-1.1.1-py3-none-any.whl", hash = "sha256:9c167ec0a2b388666e37f63b2849cd2552f7f5b71938574c637bb36487eb928a", size = 965410 }, ] [[package]] @@ -700,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]] @@ -759,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]] @@ -901,7 +902,7 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.10.0" +version = "3.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, @@ -914,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]] @@ -944,22 +945,22 @@ name = "metadrive-simulator" version = "0.4.2.4" source = { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" } dependencies = [ - { name = "filelock", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "gymnasium", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "lxml", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "matplotlib", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "numpy", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "opencv-python-headless", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "panda3d", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "panda3d-gltf", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "pillow", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "progressbar", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "psutil", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "pygments", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "requests", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "shapely", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "tqdm", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "yapf", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, + { name = "filelock" }, + { name = "gymnasium" }, + { name = "lxml" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "panda3d" }, + { name = "panda3d-gltf" }, + { name = "pillow" }, + { name = "progressbar" }, + { name = "psutil" }, + { name = "pygments" }, + { name = "requests" }, + { name = "shapely" }, + { name = "tqdm" }, + { name = "yapf" }, ] wheels = [ { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:fbf0ea9be67e65cd45d38ff930e3d49f705dd76c9ddbd1e1482e3f87b61efcef" }, @@ -991,6 +992,7 @@ requires-dist = [ { name = "yapf" }, { name = "zmq", marker = "extra == 'ros'" }, ] +provides-extras = ["cuda", "gym", "ros"] [[package]] name = "mkdocs" @@ -998,7 +1000,7 @@ version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "ghp-import" }, { name = "jinja2" }, { name = "markdown" }, @@ -1036,8 +1038,8 @@ version = "0.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyperclip" }, - { name = "python3-xlib", marker = "platform_system == 'Linux'" }, - { name = "rubicon-objc", marker = "platform_system == 'Darwin'" }, + { name = "python3-xlib", marker = "sys_platform == 'linux'" }, + { name = "rubicon-objc", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/fa/b2ba8229b9381e8f6381c1dcae6f4159a7f72349e414ed19cfbbd1817173/MouseInfo-0.1.3.tar.gz", hash = "sha256:2c62fb8885062b8e520a3cce0a297c657adcc08c60952eb05bc8256ef6f7f6e7", size = 10850 } @@ -1210,7 +1212,7 @@ name = "opencv-python-headless" version = "4.11.0.86" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929 } wheels = [ @@ -1255,7 +1257,7 @@ dependencies = [ { name = "setuptools" }, { name = "smbus2" }, { name = "sounddevice" }, - { name = "spidev", marker = "platform_system == 'Linux'" }, + { name = "spidev", marker = "sys_platform == 'linux'" }, { name = "sympy" }, { name = "tqdm" }, { name = "websocket-client" }, @@ -1373,7 +1375,7 @@ requires-dist = [ { name = "setuptools" }, { name = "smbus2" }, { name = "sounddevice" }, - { name = "spidev", marker = "platform_system == 'Linux'" }, + { name = "spidev", marker = "sys_platform == 'linux'" }, { name = "sympy" }, { name = "tabulate", marker = "extra == 'dev'" }, { name = "tqdm" }, @@ -1382,6 +1384,7 @@ requires-dist = [ { name = "websocket-client" }, { name = "zstandard" }, ] +provides-extras = ["docs", "testing", "dev", "tools"] [[package]] name = "packaging" @@ -1415,8 +1418,8 @@ name = "panda3d-gltf" version = "0.13" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "panda3d-simplepbr", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, + { name = "panda3d" }, + { name = "panda3d-simplepbr" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/7f/9f18fc3fa843a080acb891af6bcc12262e7bdf1d194a530f7042bebfc81f/panda3d-gltf-0.13.tar.gz", hash = "sha256:d06d373bdd91cf530909b669f43080e599463bbf6d3ef00c3558bad6c6b19675", size = 25573 } wheels = [ @@ -1428,8 +1431,8 @@ name = "panda3d-simplepbr" version = "0.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, + { name = "panda3d" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/af/505608eef09d7f9b822e69dc7631cd14102650b8fe1b6f60d9562d2788d9/panda3d-simplepbr-0.12.0.tar.gz", hash = "sha256:c71d490afeeb3a90455dcfde1d30c41f321a38742a97d18834e5c31016331ed5", size = 1929980 } wheels = [ @@ -1507,7 +1510,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -1575,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]] @@ -1644,10 +1647,10 @@ dependencies = [ { name = "mouseinfo" }, { name = "pygetwindow" }, { name = "pymsgbox" }, - { name = "pyobjc-core", marker = "platform_system == 'Darwin'" }, - { name = "pyobjc-framework-quartz", marker = "platform_system == 'Darwin'" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, { name = "pyscreeze" }, - { name = "python3-xlib", marker = "platform_system == 'Linux'" }, + { name = "python3-xlib", marker = "sys_platform == 'linux'" }, { name = "pytweening" }, ] sdist = { url = "https://files.pythonhosted.org/packages/65/ff/cdae0a8c2118a0de74b6cf4cbcdcaf8fd25857e6c3f205ce4b1794b27814/PyAutoGUI-0.9.54.tar.gz", hash = "sha256:dd1d29e8fd118941cb193f74df57e5c6ff8e9253b99c7b04f39cfc69f3ae04b2", size = 61236 } @@ -1713,8 +1716,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/1b/d0b013bf7d1af7cf0a6a4fce13f5fe5813ab225313755367b36e714a63f8/pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93", size = 2254397 }, { url = "https://files.pythonhosted.org/packages/14/71/4cbd3870d3e926c34706f705d6793159ac49d9a213e3ababcdade5864663/pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764", size = 1775641 }, { url = "https://files.pythonhosted.org/packages/43/1d/81d59d228381576b92ecede5cd7239762c14001a828bdba30d64896e9778/pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53", size = 1812863 }, - { url = "https://files.pythonhosted.org/packages/25/b3/09ff7072e6d96c9939c24cf51d3c389d7c345bf675420355c22402f71b68/pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca", size = 1691593 }, - { url = "https://files.pythonhosted.org/packages/a8/91/38e43628148f68ba9b68dedbc323cf409e537fd11264031961fd7c744034/pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd", size = 1765997 }, ] [[package]] @@ -4201,9 +4202,9 @@ name = "pyopencl" version = "2025.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "platformdirs", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "pytools", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, + { name = "numpy" }, + { name = "platformdirs" }, + { name = "pytools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/88/0ac460d3e2def08b2ad6345db6a13613815f616bbbd60c6f4bdf774f4c41/pyopencl-2025.1.tar.gz", hash = "sha256:0116736d7f7920f87b8db4b66a03f27b1d930d2e37ddd14518407cc22dd24779", size = 422510 } wheels = [ @@ -4278,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'" }, @@ -4286,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]] @@ -4437,9 +4438,9 @@ name = "pytools" version = "2024.1.10" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "siphash24", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, + { name = "platformdirs" }, + { name = "siphash24" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ee/0f/56e109c0307f831b5d598ad73976aaaa84b4d0e98da29a642e797eaa940c/pytools-2024.1.10.tar.gz", hash = "sha256:9af6f4b045212c49be32bb31fe19606c478ee4b09631886d05a32459f4ce0a12", size = 81741 } wheels = [ @@ -4677,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]] @@ -4756,11 +4757,11 @@ wheels = [ [[package]] name = "setuptools" -version = "75.8.0" +version = "75.8.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/ec/089608b791d210aec4e7f97488e67ab0d33add3efccb83a056cbafe3a2a6/setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6", size = 1343222 } +sdist = { url = "https://files.pythonhosted.org/packages/d1/53/43d99d7687e8cdef5ab5f9ec5eaf2c0423c2b35133a2b7e7bc276fc32b21/setuptools-75.8.2.tar.gz", hash = "sha256:4880473a969e5f23f2a2be3646b2dfd84af9028716d398e46192f84bc36900d2", size = 1344083 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/8a/b9dc7678803429e4a3bc9ba462fa3dd9066824d3c607490235c6a796be5a/setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3", size = 1228782 }, + { url = "https://files.pythonhosted.org/packages/a9/38/7d7362e031bd6dc121e5081d8cb6aa6f6fedf2b67bf889962134c6da4705/setuptools-75.8.2-py3-none-any.whl", hash = "sha256:558e47c15f1811c1fa7adbd0096669bf76c1d3f433f58324df69f3f5ecac4e8f", size = 1229385 }, ] [[package]] @@ -4768,7 +4769,7 @@ name = "shapely" version = "2.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/21/c0/a911d1fd765d07a2b6769ce155219a281bfbe311584ebe97340d75c5bdb1/shapely-2.0.7.tar.gz", hash = "sha256:28fe2997aab9a9dc026dc6a355d04e85841546b2a5d232ed953e3321ab958ee5", size = 283413 } wheels = [ @@ -4909,7 +4910,7 @@ name = "tqdm" version = "4.67.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } wheels = [ @@ -4918,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]] @@ -4993,7 +4994,7 @@ name = "yapf" version = "0.43.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs", marker = "platform_machine != 'aarch64' or platform_system != 'Linux'" }, + { name = "platformdirs" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907 } wheels = [